개요
Python에서 HTTP 요청을 처리하는 방법은 다양합니다. 표준 라이브러리부터 서드파티 라이브러리까지 여러 옵션이 있으며, 각각의 장단점이 존재합니다. 본 문서에서는 대표적인 HTTP 클라이언트 라이브러리들을 살펴보고, 동기 및 비동기 방식의 요청 처리를 비교합니다.
주요 HTTP 클라이언트 라이브러리
- http.client: Python 표준 라이브러리
- urllib: Python 표준 라이브러리 (urllib.request)
- requests: 가장 널리 사용되는 동기 요청 라이브러리
- aiohttp: 비동기 HTTP 클라이언트
- httpx: 동기/비동기 모두 지원하는 moderna 라이브러리
테스트 환경 구성
필수 조건
Python 3.7 이상
필요 패키지
aiohttp==3.8.5
aiosignal==1.3.1
anyio==3.7.1
async-timeout==4.0.3
asynctest==0.13.0
attrs==23.1.0
certifi==2023.7.22
charset-normalizer==3.2.0
exceptiongroup==1.1.3
frozenlist==1.3.3
h11==0.14.0
httpcore==0.17.3
httpx==0.24.1
idna==3.4
importlib-metadata==6.7.0
multidict==6.0.4
requests==2.31.0
sniffio==1.3.0
typing_extensions==4.7.1
urllib3==2.0.4
yarl==1.9.2
zipp==3.15.0
테스트용 서버 구현
HTTP 클라이언트 테스트를 위해 Flask 기반의 간단한 테스트 서버를 구현합니다.
# -*- coding:utf-8 -*-
'''
pip install flask
'''
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route("/", methods=["POST", "GET"])
def handle_request():
return jsonify({
"code": 0,
"msg": "ok",
"data": {
"args": request.args,
"form": request.form,
"json": request.get_json(),
}
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=6666, debug=False)
동기 HTTP 클라이언트 구현
http.client를 이용한 요청
Python 표준 라이브러리인 http.client 모듈을 사용한 기본적인 HTTP 요청 처리입니다.
import time
from functools import wraps
def measure_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
results = func(*args, **kwargs)
elapsed = round((time.time() - start), 4) * 1000
success_count = len([r for r in results if r == 200])
print(f"Func:{func.__name__};Total:{len(results)};Success:{success_count};Time:{elapsed}ms")
return wrapper
@measure_time
def test_http_client(request_count=1):
"""
표준 http.client 모듈을 사용한 HTTP 요청
"""
import http.client
import json
response_codes = []
connection = http.client.HTTPConnection(host="localhost", port=6666)
for i in range(request_count):
# 쿼리 파라미터 전송
connection.request(
method="GET",
url="/?id=test" + str(i),
body=None,
headers={}
)
response = connection.getresponse()
response_codes.append(response.status)
connection.close()
return response_codes
urllib를 이용한 요청
@measure_time
def test_urllib_request(request_count=1):
"""
urllib.request 모듈을 사용한 HTTP 요청
"""
import urllib.request
import json
response_codes = []
for i in range(request_count):
# 쿼리 파라미터가 포함된 URL 요청
request_obj = urllib.request.Request(
url="http://localhost:6666?id=test" + str(i),
data=None,
headers={},
method="GET"
)
with urllib.request.urlopen(request_obj) as response:
response_codes.append(response.status)
return response_codes
requests 라이브러리를 이용한 요청
requests는 가장 직관적이고 사용하기 쉬운 API를 제공합니다.
# 설치 방법
pip install requests -i http://mirrors.cloud.tencent.com/pypi/simple --trust
@measure_time
def test_requests_library(request_count=1):
"""
requests 라이브러리를 사용한 동기 HTTP 요청
"""
import requests
response_codes = []
session = requests.session()
for i in range(request_count):
# GET 요청 with 쿼리 파라미터
response = session.get(
url="http://localhost:6666",
params={"id": "test" + str(i)}
)
response_codes.append(response.status_code)
return response_codes
httpx 동기 모드
@measure_time
def test_httpx_sync(request_count=1):
"""
httpx 라이브러리의 동기 모드 사용
"""
import httpx
response_codes = []
with httpx.Client(timeout=3.0) as client:
for i in range(request_count):
# 쿼리 파라미터 포함 GET 요청
response = client.get(
url="http://localhost:6666",
params={"id": "test" + str(i)}
)
response_codes.append(response.status_code)
return response_codes
비동기 HTTP 클라이언트 구현
aiohttp를 이용한 비동기 요청
pip install aiohttp
async def fetch_with_aiohttp(request_count=1):
"""
aiohttp를 사용한 비동기 HTTP 요청
"""
import aiohttp
import time
response_codes = []
start_time = time.time()
async with aiohttp.ClientSession() as session:
for i in range(request_count):
async with session.get(
url="http://localhost:6666",
params={"id": "test" + str(i)}
) as response:
await response.json()
response_codes.append(response.status)
elapsed = round((time.time() - start_time), 4) * 1000
success = len([r for r in response_codes if r == 200])
print(f"Func:fetch_with_aiohttp;Total:{len(response_codes)};Success:{success};Time:{elapsed}ms")
import asyncio
asyncio.run(fetch_with_aiohttp(1))
httpx 비동기 모드
async def single_request_task(client, index):
"""
개별 비동기 요청 태스크
"""
response = await client.get(
url="http://localhost:6666",
params={"id": "test" + str(index)}
)
return response.status_code
async def fetch_with_httpx_async(request_count=1):
"""
httpx의 AsyncClient를 사용한 비동기 요청
"""
import httpx
import asyncio
import time
start_time = time.time()
async with httpx.AsyncClient(timeout=30.0) as client:
tasks = [single_request_task(client, i) for i in range(request_count)]
response_codes = await asyncio.gather(*tasks)
elapsed = round((time.time() - start_time), 4) * 1000
success = len([r for r in response_codes if r == 200])
print(f"Func:fetch_with_httpx_async;Total:{len(response_codes)};Success:{success};Time:{elapsed}ms")
import asyncio
asyncio.run(fetch_with_httpx_async(10))
라이브러리 비교 요약
| 라이브러리 | 동기 | 비동기 | 특징 |
|---|---|---|---|
| http.client | ✓ | ✗ | 표준 라이브러리, 낮은 수준의 제어 |
| urllib | ✓ | ✗ | 표준 라이브러리, 다양한 프로토콜 지원 |
| requests | ✓ | ✗ | 직관적인 API, 높은 생산성 |
| aiohttp | ✗ | ✓ | 비동기 전용, 높은 성능 |
| httpx | ✓ | ✓ | 현대적인 API, Bothway 지원 |
요청 성능은 테스트 환경과 네트워크 조건에 따라 달라질 수 있으며, 대량의并发 요청 처리 시 비동기 라이브러리(aiohttp, httpx-async)가明显한 성능 이점을 제공합니다.