멱등성 — 두 번 눌러도 한 번만 결제되게 · 같은 키와 같은 요청은 다르다 · 实验
같은 키와 같은 요청은 다르다
목표
키 범위·본문 정규화·재사용 충돌을 분리해 재전송을 판정합니다.
왜 중요한가
두 고객이 우연히 같은 멱등 키를 사용하자 한 고객에게 다른 고객의 응답이 반환됐다. 다른 요청에서는 같은 키로 금액을 바꿨는데도 이전 성공을 돌려줬다. 멱등 키 자체만 비교하면 요청의 의미와 보안 경계를 놓친다. 키는 테넌트와 작업 범위에 묶고 본문의 의미는 별도 지문으로 검사해야 한다.
단계
1. /root/work/idem-fingerprint-lab/service.py에서 valid_key(value)는 영문·숫자·밑줄·하이픈 1~64자만 그대로 반환하고 다른 입력은 ValueError입니다.
처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.
mkdir -p /root/work/idem-fingerprint-labtest -e /root/work/idem-fingerprint-lab/service.py || cp /opt/fixtures/ten_labs/idem-fingerprint-lab/service.py /root/work/idem-fingerprint-lab/service.pycd /root/work/idem-fingerprint-lab2. /root/work/idem-fingerprint-lab/service.py에서 canonical(body)는 dict만 받아 sort_keys=True, separators=(',',':'), ensure_ascii=False, allow_nan=False인 JSON 문자열로 반환합니다. 직렬화 불가능한 값은 ValueError로 통일합니다.
3. /root/work/idem-fingerprint-lab/service.py에서 fingerprint(body)는 canonical(body)의 UTF-8 바이트에 SHA-256을 적용한 64자 hex 문자열입니다.
4. /root/work/idem-fingerprint-lab/service.py에서 scoped_key(tenant, method, path, key)는 tenant와 key를 valid_key로 검증하고, method를 대문자로 바꿉니다. path는 /로 시작하는 문자열이어야 합니다. 네 값을 JSON 배열로 separators=(',',':') 인코딩해 반환합니다.
5. /root/work/idem-fingerprint-lab/service.py에서 classify(record, digest)는 record=None이면 'new', record['fingerprint']==digest면 'replay', 아니면 'conflict'입니다.
6. /root/work/idem-fingerprint-lab/service.py에서 remember(records, key, digest, response)는 새 키에 {fingerprint:digest, response:response의 deepcopy}를 저장합니다. 이미 있으면 ValueError이고 기존 기록은 보존합니다.
7. /root/work/idem-fingerprint-lab/service.py에서 replay(record)는 record['response']의 깊은 사본입니다.
8. /root/work/idem-fingerprint-lab/service.py에서 execute(records, tenant, method, path, key, body, action)는 scope와 지문을 계산합니다. new면 action() 결과를 remember하고 사본 반환, replay면 기존 응답 사본 반환, conflict면 ValueError입니다. action 예외는 전파하고 기록을 남기지 않습니다.
참고
- 인터넷과 패키지 설치 없이 기존 lab-dev 환경에서 수행합니다.
- 각 단계는 45초 채점 예산 안에서 실행됩니다. 실제 sleep이나 네트워크 호출을 추가하지 마세요.
- 채점은 제출 모듈을 새로 불러오고 독립 입력과 임시 DB로 검사합니다. 예상값을 상수로 반환하는 대신 계약을 구현하세요.
- [FastAPI 공식 문서](https://fastapi.tiangolo.com/) · [pytest 공식 문서](https://docs.pytest.org/en/stable/) · [Python sqlite3](https://docs.python.org/3/library/sqlite3.html)
- 한계: 이 실습은 단일 프로세스 메모리 사전으로 키와 요청의 의미를 분리하는 계약을 배운다. 프로세스 장애 후 보존이나 여러 워커의 동시성은 다음 SQLite 실습에서 다룬다. 해시는 암호화가 아니며 JSON 정규화가 모든 언어의 숫자 표현까지 표준화하는 국제 규격이라고 주장하지 않는다.
8个步骤
- 키 문법을 검증한다
- 객체 순서는 접고 배열 순서는 보존한다
- 본문 지문을 계산한다
- 테넌트와 작업으로 키를 구분한다
- 세 가지 판정을 구분한다
- 응답을 저장할 때 복사한다
- 응답을 읽을 때도 복사한다
- 업무 함수를 한 번만 호출한다