포스트

[Python 100일 챌린지] Day 52 - HTTP 메서드와 헤더

[Python 100일 챌린지] Day 52 - HTTP 메서드와 헤더

requests.post(url, json=data) → 서버에 데이터 전송! requests.delete(url) → 삭제! 😊

GET으로 읽고, POST로 생성하고, PUT으로 수정하고, DELETE로 삭제! 웹 서비스의 CRUD 작업을 모두 HTTP 메서드로 처리합니다!

(35-45분 완독 ⭐⭐⭐)

🎯 오늘의 학습 목표

📚 사전 지식


🎯 오늘의 학습 목표 1: HTTP 메서드의 종류 이해하기

1.1 HTTP 메서드 개요

HTTP 메서드는 서버에 요청하는 작업의 종류를 나타냅니다:

메서드 용도 설명
GET 조회 리소스 읽기 (안전, 멱등)
POST 생성 새 리소스 생성
PUT 전체 수정 리소스 완전 교체 (멱등)
PATCH 부분 수정 리소스 일부만 수정
DELETE 삭제 리소스 삭제 (멱등)
HEAD 헤더만 GET과 동일하지만 본문 없음
OPTIONS 허용 메서드 서버가 지원하는 메서드 확인

멱등성(Idempotent): 같은 요청을 여러 번 해도 결과가 동일

1.2 CRUD와 HTTP 메서드 매핑

1
2
3
4
5
6
7
# CRUD 작업과 HTTP 메서드
actions = {
    'Create': 'POST',    # 생성
    'Read': 'GET',       # 조회
    'Update': 'PUT/PATCH',  # 수정
    'Delete': 'DELETE'   # 삭제
}

🎯 오늘의 학습 목표 2: PUT과 PATCH 요청 사용하기

2.1 PUT - 전체 수정

리소스를 완전히 교체합니다. 기존 데이터는 모두 사라지고 새 데이터로 대체됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests

# 사용자 정보 전체 수정
user_data = {
    'name': 'Alice',
    'email': 'alice@example.com',
    'age': 26,
    'city': 'Seoul'
}

response = requests.put(
    'https://jsonplaceholder.typicode.com/users/1',
    json=user_data
)

print(response.status_code)  # 200
print(response.json())

2.2 PATCH - 부분 수정

리소스의 일부만 수정합니다. 지정한 필드만 변경됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
# 이메일만 수정
partial_data = {
    'email': 'newemail@example.com'
}

response = requests.patch(
    'https://jsonplaceholder.typicode.com/users/1',
    json=partial_data
)

print(response.status_code)  # 200
print(response.json())

2.3 PUT vs PATCH 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests

# 초기 사용자 데이터
user = {
    'name': 'Bob',
    'email': 'bob@example.com',
    'age': 30
}

# PUT: 전체 교체 (age 필드가 사라짐!)
put_data = {
    'name': 'Bob',
    'email': 'newemail@example.com'
}
# 결과: {name: 'Bob', email: 'newemail@example.com'}

# PATCH: 부분 수정 (age 필드 유지)
patch_data = {
    'email': 'newemail@example.com'
}
# 결과: {name: 'Bob', email: 'newemail@example.com', age: 30}

2.4 실전 예제: 프로필 업데이트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def update_user_profile(user_id, updates):
    """사용자 프로필 부분 수정"""
    url = f'https://api.example.com/users/{user_id}'

    try:
        response = requests.patch(url, json=updates)
        response.raise_for_status()

        print("✅ 프로필 업데이트 성공")
        return response.json()

    except requests.exceptions.HTTPError as e:
        print(f"❌ 업데이트 실패: {e}")
        return None

# 사용
# updates = {'bio': '파이썬 개발자', 'location': '서울'}
# update_user_profile(123, updates)

🎯 오늘의 학습 목표 3: DELETE 요청으로 리소스 삭제하기

3.1 기본 DELETE

1
2
3
4
5
6
7
8
9
10
11
import requests

# 사용자 삭제
response = requests.delete('https://jsonplaceholder.typicode.com/users/1')

if response.status_code == 200:
    print("✅ 삭제 성공 (200)")
elif response.status_code == 204:
    print("✅ 삭제 성공 (204 No Content)")
elif response.status_code == 404:
    print("❌ 리소스 없음")

3.2 안전한 삭제 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def delete_resource(url, resource_id):
    """안전한 리소스 삭제"""
    delete_url = f"{url}/{resource_id}"

    try:
        # 1. 먼저 존재 확인
        check = requests.get(delete_url)
        if check.status_code == 404:
            print("⚠️ 삭제할 리소스가 없습니다")
            return False

        # 2. 삭제 요청
        response = requests.delete(delete_url)
        response.raise_for_status()

        if response.status_code in [200, 204]:
            print("✅ 삭제 완료")
            return True

    except requests.exceptions.RequestException as e:
        print(f"❌ 오류: {e}")
        return False

# 사용
# delete_resource('https://api.example.com/posts', 123)

3.3 일괄 삭제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def batch_delete(base_url, resource_ids):
    """여러 리소스 일괄 삭제"""
    results = {'success': [], 'failed': []}

    for rid in resource_ids:
        try:
            response = requests.delete(f"{base_url}/{rid}")

            if response.status_code in [200, 204]:
                results['success'].append(rid)
            else:
                results['failed'].append(rid)

        except Exception as e:
            results['failed'].append(rid)
            print(f"{rid} 삭제 실패: {e}")

    print(f"✅ 성공: {len(results['success'])}, ❌ 실패: {len(results['failed'])}")
    return results

# 사용
# ids = [1, 2, 3, 4, 5]
# batch_delete('https://api.example.com/posts', ids)

🎯 오늘의 학습 목표 4: HTTP 상태 코드 이해하기

4.1 주요 상태 코드

2xx - 성공

1
2
3
4
5
6
7
8
9
# 200 OK: 요청 성공
# 201 Created: 생성 성공 (POST)
# 204 No Content: 성공, 응답 본문 없음 (DELETE)

response = requests.post(url, json=data)
if response.status_code == 201:
    print("✅ 리소스 생성됨")
    location = response.headers.get('Location')
    print(f"새 리소스 위치: {location}")

3xx - 리다이렉션

1
2
3
4
5
6
7
# 301 Moved Permanently: 영구 이동
# 302 Found: 임시 이동
# 304 Not Modified: 캐시 사용

# requests는 자동으로 리다이렉트 따라감
response = requests.get('http://github.com')
print(response.url)  # https://github.com (리다이렉트됨)

4xx - 클라이언트 오류

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 400 Bad Request: 잘못된 요청
# 401 Unauthorized: 인증 필요
# 403 Forbidden: 권한 없음
# 404 Not Found: 리소스 없음
# 429 Too Many Requests: 요청 초과

response = requests.get(url)

if response.status_code == 401:
    print("❌ 로그인이 필요합니다")
elif response.status_code == 403:
    print("❌ 접근 권한이 없습니다")
elif response.status_code == 404:
    print("❌ 페이지를 찾을 수 없습니다")
elif response.status_code == 429:
    print("⚠️ 요청이 너무 많습니다. 잠시 후 다시 시도하세요")

5xx - 서버 오류

1
2
3
4
5
6
7
8
9
10
# 500 Internal Server Error: 서버 내부 오류
# 502 Bad Gateway: 게이트웨이 오류
# 503 Service Unavailable: 서비스 이용 불가
# 504 Gateway Timeout: 게이트웨이 시간 초과

response = requests.get(url)

if response.status_code >= 500:
    print(f"❌ 서버 오류 ({response.status_code})")
    print("잠시 후 다시 시도하세요")

4.2 상태 코드 처리 헬퍼 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def handle_response(response):
    """HTTP 상태 코드별 처리"""
    status = response.status_code

    if 200 <= status < 300:
        return {'success': True, 'data': response.json()}

    elif status == 400:
        return {'success': False, 'error': '잘못된 요청입니다'}

    elif status == 401:
        return {'success': False, 'error': '인증이 필요합니다'}

    elif status == 403:
        return {'success': False, 'error': '권한이 없습니다'}

    elif status == 404:
        return {'success': False, 'error': '리소스를 찾을 수 없습니다'}

    elif status == 429:
        retry_after = response.headers.get('Retry-After', '60')
        return {'success': False, 'error': f'{retry_after}초 후 재시도하세요'}

    elif status >= 500:
        return {'success': False, 'error': '서버 오류입니다'}

    else:
        return {'success': False, 'error': f'알 수 없는 오류 ({status})'}

# 사용
response = requests.get(url)
result = handle_response(response)

if result['success']:
    print(result['data'])
else:
    print(f"오류: {result['error']}")

💡 실전 팁 & 주의사항

✅ HTTP 메서드 베스트 프랙티스

  1. 적절한 메서드 선택
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # ✅ 좋은 예
    requests.get(url)      # 데이터 조회
    requests.post(url)     # 새 데이터 생성
    requests.patch(url)    # 일부 수정
    requests.delete(url)   # 삭제
    
    # ❌ 나쁜 예
    requests.post(url)     # 조회에 POST 사용 (X)
    requests.get(url)      # 삭제에 GET 사용 (X)
    
  2. 멱등성 이해
    1
    2
    3
    4
    5
    6
    7
    
    # PUT, DELETE는 멱등성 보장
    for i in range(3):
        requests.put(url, json=data)  # 3번 실행해도 결과 동일
    
    # POST는 멱등성 없음
    for i in range(3):
        requests.post(url, json=data)  # 3개의 리소스 생성됨!
    
  3. 상태 코드 확인
    1
    2
    3
    4
    5
    6
    7
    
    # ✅ 항상 상태 코드 확인
    response = requests.delete(url)
    if response.status_code in [200, 204]:
        print("삭제 성공")
    
    # 또는 예외 발생
    response.raise_for_status()
    

⚠️ 주의사항

  • DELETE는 복구 불가: 삭제 전에 확인 필수
  • PUT은 전체 교체: 일부 수정은 PATCH 사용
  • 상태 코드 204: 본문이 없으므로 .json() 호출 불가

🧪 연습 문제

문제 1: TODO API 클라이언트

목표: 완전한 CRUD 기능을 가진 TODO API 클라이언트를 작성하세요.

요구사항:

  • 할 일 생성 (POST)
  • 할 일 조회 (GET)
  • 할 일 수정 (PATCH)
  • 할 일 삭제 (DELETE)
  • 적절한 오류 처리
해답 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import requests

class TodoClient:
    """TODO API 클라이언트"""
    def __init__(self, base_url='https://jsonplaceholder.typicode.com'):
        self.base_url = base_url

    def create_todo(self, title, completed=False):
        """할 일 생성"""
        data = {'title': title, 'completed': completed}
        response = requests.post(f'{self.base_url}/todos', json=data)

        if response.status_code == 201:
            print(f"✅ 할 일 생성: {title}")
            return response.json()
        else:
            print(f"❌ 생성 실패: {response.status_code}")
            return None

    def get_todo(self, todo_id):
        """할 일 조회"""
        response = requests.get(f'{self.base_url}/todos/{todo_id}')

        if response.status_code == 200:
            return response.json()
        elif response.status_code == 404:
            print(f"❌ TODO {todo_id}를 찾을 수 없습니다")
            return None

    def update_todo(self, todo_id, **updates):
        """할 일 수정"""
        response = requests.patch(
            f'{self.base_url}/todos/{todo_id}',
            json=updates
        )

        if response.status_code == 200:
            print(f"✅ TODO {todo_id} 업데이트 완료")
            return response.json()
        else:
            print(f"❌ 업데이트 실패: {response.status_code}")
            return None

    def delete_todo(self, todo_id):
        """할 일 삭제"""
        response = requests.delete(f'{self.base_url}/todos/{todo_id}')

        if response.status_code in [200, 204]:
            print(f"✅ TODO {todo_id} 삭제 완료")
            return True
        else:
            print(f"❌ 삭제 실패: {response.status_code}")
            return False

# 테스트
client = TodoClient()

# 생성
new_todo = client.create_todo('파이썬 공부하기')

# 조회
# todo = client.get_todo(1)
# print(todo)

# 수정
# client.update_todo(1, completed=True)

# 삭제
# client.delete_todo(1)

문제 2: HTTP 메서드 테스터

목표: 다양한 HTTP 메서드를 테스트하고 응답을 비교하는 도구를 만드세요.

해답 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import requests

def test_http_methods(url):
    """HTTP 메서드 테스트"""
    methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']

    results = {}
    test_data = {'title': 'Test', 'body': 'Test body'}

    for method in methods:
        try:
            if method in ['POST', 'PUT', 'PATCH']:
                response = requests.request(method, url, json=test_data)
            else:
                response = requests.request(method, url)

            results[method] = {
                'status': response.status_code,
                'allowed': response.status_code != 405
            }

        except Exception as e:
            results[method] = {'status': 'Error', 'error': str(e)}

    # 결과 출력
    print(f"\n🧪 HTTP 메서드 테스트 결과: {url}")
    print("-" * 50)

    for method, result in results.items():
        status = result.get('status')
        allowed = "" if result.get('allowed') else ""
        print(f"{method:8} | 상태: {status:3} | {allowed}")

# 테스트
# test_http_methods('https://jsonplaceholder.typicode.com/posts/1')

📝 오늘 배운 내용 정리

개념 설명 예시
PUT 리소스 전체 교체 requests.put(url, json=data)
PATCH 리소스 부분 수정 requests.patch(url, json=updates)
DELETE 리소스 삭제 requests.delete(url)
상태 코드 요청 결과 표시 200, 201, 404, 500
멱등성 같은 요청 반복 시 동일 결과 PUT, DELETE는 멱등

핵심 코드 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

# PUT - 전체 수정
data = {'name': 'Alice', 'email': 'alice@example.com'}
response = requests.put(url, json=data)

# PATCH - 부분 수정
updates = {'email': 'newemail@example.com'}
response = requests.patch(url, json=updates)

# DELETE - 삭제
response = requests.delete(url)

# 상태 코드 확인
if response.status_code == 200:
    print("성공")
elif response.status_code == 404:
    print("없음")

🔗 관련 자료


📚 이전 학습

Day 51: requests 라이브러리 기초 ⭐⭐⭐

어제는 requests로 GET/POST 요청을 보내고 응답을 처리하는 방법을 배웠습니다!

📚 다음 학습

Day 53: BeautifulSoup 기초 ⭐⭐⭐

내일은 BeautifulSoup으로 HTML을 파싱하고 웹 스크래핑을 시작합니다!


“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀

Day 52/100 Phase 6: 웹 스크래핑과 API #100DaysOfPython

이제와서 시작하는 Python 마스터하기 - Day 52 완료! 🎉

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.