构建同步中继服务器 — 不知道就说不知道
한국어 원문으로 표시합니다.
목표
LH-STD 전문을 받아 계정계(HTTP JSON)를 부르고, 결과를 표준 응답코드로 바꿔 돌려주는 동기 중계 서버를 만든다. 읽기 타임아웃(결과 모름)과 연결 실패(미전송 확정)를 구분한다.
왜 중요한가
채널은 대상 시스템의 규약을 몰라도 되고, 표준 응답코드 하나로 판단해야 한다. 그 번역을 중계 계층이 맡는다. 그리고 중계는 실패 지점을 하나 늘리므로, 어디서 멈췄는지에 따라 '다시 보내도 되는가' 가 갈린다. 이 구분을 틀리면 이중 이체가 난다.
단계
- 계정계 픽스처를 띄운다:
nohup python3 /opt/lab/fixtures/eaimw/partner.py core --log /root/eaimw/sync/core.log > /root/eaimw/sync/core.out 2>&1 &(포트 9201). curl 로 정상 이체 한 건(wdBankLHB,wdAcct11002003004005,amount정수 등 —partner.py머리말의 API 설명 참고)을 보내고 응답 본문을 그대로/root/eaimw/sync/core-ok.json에 저장한다. - 정의서
/opt/lab/fixtures/eaimw/header/SPEC.md4절과 계정계 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아홉 개. /root/eaimw/sync/relay.py뼈대:python3 relay.py --port <P> --core <계정계URL> --timeout <초>로 TCP 에서 전문을 받는다. 길이 4바이트를 정확히 읽고 그만큼 다시 읽는다(조각나서 와도). 형식 오류는 E102, 거래코드가 BKTR0001 이 아니면 E101 로 답한다(같은 GUID, 구분 R, 기관 맞바꿈). BKTR0001 은 아직 계정계에 붙이지 않았으므로 E902 로 답한다.- BKTR0001 을 계정계에 중계한다. 본문을
lhconv로 JSON 으로 바꾸고guid를 넣어POST /v1/transfers,X-GUID헤더에도 GUID. 200 이면 0000 과 응답 본문(BKTR0001.rsp.layout, 45바이트)을 돌려준다. 계정계는 정확히 한 번 부른다. - 업무 오류를 바꾼다: 422 의 result 에 따라 B201·B202·B203, 500 은 E500. 오류 응답은 본문이 없다.
- 응답을 기다리다
--timeout을 넘기면 E901 로 답한다. 계정계를 끝까지 기다리지 않는다. - 계정계에 연결하지 못하면(연결 거부·연결 시간 초과) E902 로 답한다. 6단계의 E901 과 구분한다.
- 연결마다 따로 처리한다. 느린 요청 하나가 처리되는 동안 다른 요청들이 기다리지 않아야 한다.
참고
- 공통 라이브러리:
import sys; sys.path.insert(0, "/opt/lab/fixtures/eaimw/lib"); import lhstd, lhconv—lhstd.read_frame(sock)·parse·reply(req, code, body),lhconv.load_layout·load_codemap·fixed_to_json·json_to_fixed. - 장애 재현 스위치: 본문 메모가
SLOW로 시작하면 계정계가--delay초 뒤에 처리하고(처리는 한다),FAIL로 시작하면 500. - 계정계 통계:
curl -s localhost:9201/_stats(호출 수·GUID 별 횟수), 초기화curl -s -XPOST localhost:9201/_reset. - 예외 구분: 연결 단계 문제는
urllib.error.URLError, 응답 대기 시간 초과는TimeoutError, HTTP 오류 상태는urllib.error.HTTPError(URLError 의 하위 클래스라 먼저 잡는다). - 흔한 실수:
except Exception하나로 전부 같은 코드로 바꾸는 것. E901 과 E902 가 뭉개진다.
상대 시스템을 직접 불러 본다
계정계 픽스처를 9201 에 띄우고 정상 이체 한 건의 응답 본문을 /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 아홉 개를 표준 응답코드로 옮긴다.
정의서 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)를 직접 읽어 만드세요.
정상 이체를 계정계에 한 번 중계한다
BKTR0001 을 JSON 으로 바꿔 계정계를 한 번 부르고, 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 를 잡으세요.
느린 한 건이 줄을 세우지 않게
연결마다 따로 처리해, 느린 요청 하나가 처리되는 동안 다른 요청이 기다리지 않게 한다.
socketserver.TCPServer 는 한 번에 한 연결만 처리합니다. 연결마다 스레드를 띄우는 서버 클래스가 표준 라이브러리에 있습니다.