[PY-012][실무] 로컬 HTTP API를 읽는 명령줄 도구 만들기 > IT 기술 공유

본문 바로가기

사이트 내 전체검색

뒤로가기 IT 기술 공유

[PY-012][실무] 로컬 HTTP API를 읽는 명령줄 도구 만들기

페이지 정보

작성자 기술팀장 작성일 26-09-06 06:35 조회 61 댓글 0

본문

[이번 수업]

명령줄에서 HTTP API를 호출하고 JSON을 보여 주는 Python 도구를 만듭니다. 내 컴퓨터에 연습용 API를 띄워 요청·검증·오류 처리를 익힙니다.

[선수지식]

PY-004의 함수, PY-006의 JSON, PY-007의 예외 처리, BACK-002의 HTTP를 알면 좋습니다. 표준 라이브러리만 사용합니다.

[학습목표]

1. 명령줄 옵션으로 주소를 받아 GET 요청을 보낸다.
2. 제한 시간·응답 크기·JSON 구조를 검증한다.
3. 연결·HTTP·데이터 오류를 구분한다.

[핵심개념]

CLI(Command Line Interface)는 터미널에서 옵션으로 쓰는 프로그램입니다. HTTP API는 상태 코드·헤더·본문으로 응답하며 JSON은 본문 형식 중 하나입니다. 응답이 와도 크기, 문자 인코딩, 필요한 필드를 확인해야 합니다.

예제는 `urllib.request`로 요청하고 `argparse`로 `--url`을 읽습니다. 127.0.0.1과 localhost만 허용해 다른 시스템에는 요청하지 않습니다. 성공은 종료 코드 0, 실패는 1로 알려 자동화도 결과를 판단하게 합니다.

[따라하기]

빈 폴더에 sample.json을 저장하세요.
```json
{"service": "demo", "state": "ok"}
```

같은 폴더에 api_status.py를 만듭니다.
```python
import argparse
import json
from urllib.error import HTTPError, URLError
from urllib.parse import urlsplit
from urllib.request import urlopen

ALLOWED = {"127.0.0.1", "localhost"}

def fetch(url: str) -> dict:
    target = urlsplit(url)
    if target.scheme != "http" or target.hostname not in ALLOWED:
        raise ValueError("로컬 HTTP 주소만 허용됩니다")
    try:
        with urlopen(url, timeout=3) as response:
            raw = response.read(10_001)
    except HTTPError as error:
        raise RuntimeError(f"HTTP 오류: {error.code}")
    except URLError:
        raise RuntimeError("API에 연결할 수 없습니다")
    if len(raw) > 10_000:
        raise ValueError("응답이 너무 큽니다")
    try:
        data = json.loads(raw.decode("utf-8"))
    except (UnicodeDecodeError, json.JSONDecodeError):
        raise ValueError("UTF-8 JSON 응답이 아닙니다")
    if not isinstance(data, dict) or data.get("state") not in {"ok", "warning"}:
        raise ValueError("응답 구조가 예상과 다릅니다")
    return data

def main() -> int:
    parser = argparse.ArgumentParser(description="로컬 API 상태 확인")
    parser.add_argument("--url", default="http://127.0.0.1:8000/sample.json")
    try:
        data = fetch(parser.parse_args().url)
        print(f"{data.get('service', 'unknown')}: {data['state']}")
        return 0
    except (ValueError, RuntimeError) as error:
        print(f"오류: {error}")
        return 1

if __name__ == "__main__":
    raise SystemExit(main())
```

첫 터미널에서 macOS·Linux는 `python3 -m http.server 8000 --bind 127.0.0.1`, Windows는 `py -m http.server 8000 --bind 127.0.0.1`을 실행합니다. 둘째 터미널에서 `python3 api_status.py` 또는 `py api_status.py`를 실행하세요. 예상 결과는 `demo: ok`입니다. 서버는 Ctrl+C로 끝냅니다.

[흔한 실수]

필요한 키가 있다고 가정하거나 제한 시간 없이 기다리지 마세요. 모든 예외를 숨기거나 실패에도 종료 코드 0을 내보내면 원인과 결과를 판단하기 어렵습니다.

[보안 주의]

임의 URL을 허용하면 내부 주소 접근 문제가 생길 수 있습니다. 본인 소유의 로컬·격리 환경에서만 실습하세요. `http.server`는 연습용이며 외부 공개나 운영에 쓰지 않습니다. 토큰은 코드·주소·로그에 넣지 않습니다.

[직접 해볼 과제]

state를 warning으로 바꾼 뒤 unknown으로도 바꿔 보세요. 서버를 끈 경우까지 오류 메시지와 종료 코드 1이 나오는지 확인합니다.

[확인문제]

1. JSON을 읽기 전에 응답 크기를 제한하는 이유는 무엇인가요?
2. HTTPError를 URLError보다 먼저 처리한 이유는 무엇인가요?
3. 성공과 실패에 다른 종료 코드를 쓰면 무엇이 좋아지나요?

[다음 학습]

JSTS-012에서 타입을 적용한 작은 서비스로 같은 원칙을 확장합니다.

[공식 참고 자료]

https://docs.python.org/3/library/urllib.request.html
https://docs.python.org/3/library/argparse.html
https://docs.python.org/3/library/json.html
https://docs.python.org/3/library/http.server.html

댓글목록 0

등록된 댓글이 없습니다.

Copyright © 소유하신 도메인. All rights reserved.

사이트 정보

회사명 : 회사명 / 대표 : 대표자명
주소 : OO도 OO시 OO구 OO동 123-45
사업자 등록번호 : 123-45-67890
전화 : 02-123-4567 팩스 : 02-123-4568
통신판매업신고번호 : 제 OO구 - 123호
개인정보관리책임자 : 정보책임자명

PC 버전으로 보기