TT Lab
はじめる
学ぶ 学習パス コース

EAI 中間層をつくる

同期中継サーバーを作る — 分からないことは分からないと答える

TT Labで続きを見る

目標

LH-STD電文を受け取って勘定系(HTTP JSON)を呼び出し、結果を標準応答コードに変換して返す同期中継サーバーを作ります。読み取りタイムアウト(結果不明)と接続失敗(未送信が確定)を区別します。

なぜ重要なのか

チャネルは対象システムの規約を知らなくてよく、標準応答コード1つで判断しなければなりません。その翻訳を中継層が担います。また中継は失敗の地点を1つ増やすので、どこで止まったかによって「再送してよいか」が分かれます。この区別を間違えると、二重振込が起きます。

ステップ

  1. 勘定系フィクスチャを起動してください: nohup python3 /opt/lab/fixtures/eaimw/partner.py core --log /root/eaimw/sync/core.log > /root/eaimw/sync/core.out 2>&1 &(ポート9201)。curlで正常な振込1件(wdBankはLHB、wdAcctは11002003004005、amountは整数など。partner.pyの冒頭にあるAPI説明を参照)を送り、レスポンス本文をそのまま/root/eaimw/sync/core-ok.jsonに保存してください。
  2. インターフェース仕様書/opt/lab/fixtures/eaimw/header/SPEC.mdの4節と勘定系APIを見て、/root/eaimw/sync/rspmap.csvを作成してください。見出し行はsource,rsp_code、sourceはHTTP200・INSUFFICIENT_FUNDS・NO_ACCOUNT・LIMIT_EXCEEDED・HTTP500・READ_TIMEOUT・CONNECT_FAIL・UNKNOWN_TX・BAD_FRAMEの9個です。
  3. /root/eaimw/sync/relay.pyの骨組みを作成してください。python3 relay.py --port <P> --core <계정계URL> --timeout <초>(プレースホルダーは勘定系のURLと秒数です)でTCPから電文を受け取ります。長さの4バイトをちょうど読み、その長さ分だけ読み直します(断片に分かれて届いても)。形式エラーはE102、取引コードがBKTR0001でなければE101で答えます(同じGUID、区分R、機関は入れ替え)。BKTR0001はまだ勘定系につないでいないので、E902で答えます。
  4. BKTR0001を勘定系に中継してください。本文をlhconvでJSONに変換し、guidを入れてPOST /v1/transfersを呼び、X-GUIDヘッダーにもGUIDを載せます。200なら、0000とレスポンス本文(BKTR0001.rsp.layout、45バイト)を返します。勘定系はちょうど1回だけ呼びます。
  5. 業務エラーを変換してください。422のresultに応じてB201・B202・B203、500はE500です。エラーレスポンスには本文がありません。
  6. レスポンスを待って--timeoutを超えたら、E901で答えてください。勘定系を最後まで待ちません。
  7. 勘定系に接続できなければ(接続拒否・接続タイムアウト)、E902で答えてください。ステップ6のE901とは区別します。
  8. 接続ごとに別々に処理してください。遅いリクエスト1件が処理されている間、ほかのリクエストが待たされてはいけません。

参考

相手のシステムを直接呼んでみる

勘定系フィクスチャを9201で起動し、正常な振込1件のレスポンス本文を/root/eaimw/sync/core-ok.jsonに保存してください。

partner.pyの冒頭にAPIの説明があります。curl -s -XPOST -H 'Content-Type: application/json' -d '{...}' localhost:9201/v1/transfersのように呼び出します。guidは小文字16進数32文字、amountは引用符のない整数です。

応答コードの変換表を書く

/root/eaimw/sync/rspmap.csvに、sourceの9個を標準応答コードに対応づけてください。

インターフェース仕様書4節の意味を読んでください。核心はREAD_TIMEOUTとCONNECT_FAILです。片方は「送ったがわからない」、もう片方は「送れなかった」です。

骨組み。最後まで読み、わからなければ拒否する

/root/eaimw/sync/relay.pyが、断片に分かれた電文も長さの分だけ読み切り、形式エラーはE102、未登録の取引はE101で答えるようにしてください。

lhstd.read_frame(sock)が、長さの4バイトを読み、残りをすべて読み切るまでrecvを繰り返します。形式が誤った電文はparseが失敗するので、レスポンスは元の80バイトの位置(4–12が取引コード、12–44がGUID)を直接読んで作ってください。

正常な振込を勘定系に1回だけ中継する

BKTR0001をJSONに変換して勘定系を1回呼び出し、0000と45バイトのレスポンス本文を返してください(X-GUIDヘッダーを含む)。

lhconv.fixed_to_json(REQ, h['BODY'], CODES)で変換し、guidを入れます。urllib.request.Requestにheaders={'X-GUID': ...}、urlopen(req, timeout=ARGS.timeout)を指定します。レスポンス本文はlhconv.json_to_fixed(RSP, res)で作り、lhstd.reply(h, '0000', body)で返します。

業務エラーを標準コードに変換する

422のresultをB201・B202・B203に、500をE500に変換してください。エラーレスポンスは本文なしにします。

urllib.error.HTTPErrorは、ステータスコード(e.code)と本文(e.read())を持っています。URLErrorのサブクラスなので、exceptの順序が重要です。

待ちくたびれたら、わからないと答える

レスポンス待ちが--timeoutを超えたら、E901で答えてください。勘定系を最後まで待たないようにします。

urlopenのtimeoutは、接続とレスポンス待ちの両方にかかります。レスポンスを待つ間のタイムアウトは、包まれていないTimeoutErrorとして上がってきます。勘定系はそのあとも振込を処理することを覚えておいてください。

送ることすらできなかったなら、確実に送っていないと答える

勘定系に接続できなければE902で答えてください(E901と区別します)。

接続段階の失敗(拒否・接続タイムアウト)は、urllib.error.URLErrorに包まれて届きます。HTTPErrorとTimeoutErrorを先に捕捉し、そのあとでURLErrorを捕捉してください。

遅い1件が行列を作らないようにする

接続ごとに別々に処理し、遅いリクエスト1件が処理されている間、ほかのリクエストが待たされないようにしてください。

socketserver.TCPServerは、一度に1つの接続しか処理しません。接続ごとにスレッドを起動するサーバークラスが、標準ライブラリにあります。