#!/usr/bin/env python3 """Observe curl exit codes on a disposable, loopback-only HTTP server.""" import argparse import json import platform import subprocess import tempfile import threading from datetime import datetime, timezone from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path class Handler(BaseHTTPRequestHandler): def log_message(self, *args): pass def do_GET(self): status, body = (200, b'release-v2') if self.path == '/ok' else (404, b'not-found') self.send_response(status) self.send_header('Content-Length', str(len(body))) self.end_headers() self.wfile.write(body) def main(): parser = argparse.ArgumentParser() parser.add_argument('--output', required=True, type=Path) output = parser.parse_args().output if output.exists(): raise SystemExit('Existing evidence will not be overwritten.') server = ThreadingHTTPServer(('127.0.0.1', 0), Handler) worker = threading.Thread(target=server.serve_forever, kwargs={'poll_interval': .05}, daemon=True) worker.start() observations = [] try: with tempfile.TemporaryDirectory(prefix='punchblog-curl-exit-') as directory: for fail in (False, True): for path in ('/ok', '/missing'): target = Path(directory) / ('body-' + str(len(observations))) command = ['curl', '--disable', '--noproxy', '*', '-sS', '--connect-timeout', '2', '--max-time', '3', '-o', str(target), '-w', '%{http_code}'] if fail: command.append('--fail') result = subprocess.run(command + [f'http://127.0.0.1:{server.server_port}{path}'], capture_output=True, text=True, timeout=5) observations.append({'path': path, 'fail_option': fail, 'http_status': int(result.stdout), 'curl_exit': result.returncode, 'body': target.read_text() if target.exists() else '', 'stderr': result.stderr.strip()}) finally: server.shutdown() server.server_close() worker.join(timeout=3) assert [(x['http_status'], x['curl_exit']) for x in observations] == [(200, 0), (404, 0), (200, 0), (404, 22)] version = subprocess.run(['curl', '--disable', '--version'], capture_output=True, text=True, check=True).stdout.splitlines()[0] record = {'schema_version': 1, 'observed_at': datetime.now(timezone.utc).isoformat(), 'environment': {'python': platform.python_version(), 'os': platform.system(), 'curl': version, 'binding': '127.0.0.1:자동 배정 포트'}, 'method': '같은 합성 HTTP 서버의 200·404에 curl을 --fail 유무로 네 번 실행했습니다. 각 요청에 새 출력 파일을 사용했습니다.', 'limits': '운영 서버·Nginx·CDN·TLS·네트워크 장애를 시험하지 않았습니다. --fail이 모든 인증 오류를 감지한다는 검증도 아닙니다.', 'observations': observations, 'tables': {'exits': {'headers': ['요청', '--fail', 'HTTP 상태', 'curl 종료 코드', '저장 본문'], 'rows': [[r['path'], '사용' if r['fail_option'] else '없음', r['http_status'], r['curl_exit'], r['body'] or '없음'] for r in observations]}}} output.parent.mkdir(parents=True, exist_ok=True) with output.open('x', encoding='utf-8') as stream: json.dump(record, stream, ensure_ascii=False, indent=2) stream.write('\n') print(json.dumps({'cases': len(observations), 'checks': 'passed', 'output': str(output)})) if __name__ == '__main__': main()