Как обрабатывать различные HTTP коды состояния в запросах Python

PythonBeginner
Практиковаться сейчас

Введение

Этот учебник проведет вас через обработку различных HTTP-кодов состояния в Python requests. HTTP-коды состояния необходимы для понимания того, была ли веб-заявка успешной или неудачной, а также для правильной реакции на различные сценарии. К концу этой лабораторной работы вы узнаете, как реализовать надежную обработку ошибок в ваших Python-приложениях, взаимодействующих с веб-сервисами.

Вы начнете с понимания HTTP-кодов состояния, а затем постепенно будете развивать свои навыки, реализуя базовые и продвинутые методы обработки ошибок. Эти знания критически важны для разработки надежных Python-веб-приложений, которые корректно обрабатывают различные ответы сервера.

Понимание HTTP-кодов состояния и настройка

Что такое HTTP-коды состояния?

HTTP-коды состояния — это трехзначные числа, возвращаемые сервером в ответ на запрос клиента. Они указывают, был ли конкретный HTTP-запрос успешно завершен. Эти коды сгруппированы в пять категорий:

  • 1xx (Информационные): Запрос получен, и процесс продолжается
  • 2xx (Успех): Запрос успешно получен, понят и принят
  • 3xx (Перенаправление): Необходимо предпринять дальнейшие действия для завершения запроса
  • 4xx (Ошибка клиента): Запрос содержит плохой синтаксис или не может быть выполнен
  • 5xx (Ошибка сервера): Сервер не смог выполнить, казалось бы, допустимый запрос

Общие коды состояния, с которыми вы столкнетесь, включают:

Код состояния Название Описание
200 OK Запрос выполнен успешно
201 Created Запрос выполнен успешно, ресурс создан
400 Bad Request Сервер не может обработать запрос
401 Unauthorized Требуется аутентификация
404 Not Found Запрошенный ресурс не найден
500 Internal Server Error На сервере произошла непредвиденная ошибка

Настройка вашей среды Python

Прежде чем мы начнем обрабатывать HTTP-коды состояния, давайте установим библиотеку Python requests и создадим простой тестовый скрипт.

  1. Откройте терминал в среде LabEx и запустите:
pip install requests
  1. Создайте новую директорию для файлов нашего проекта:
mkdir -p ~/project/http_status_lab
cd ~/project/http_status_lab
  1. Используя WebIDE, создайте новый файл с именем test_request.py в директории http_status_lab со следующим базовым кодом:
import requests

def make_request(url):
    """Make a basic GET request to the specified URL."""
    response = requests.get(url)
    print(f"Status Code: {response.status_code}")
    print(f"Response: {response.text[:100]}...")  ## Print first 100 characters
    return response

## Test with a working URL
response = make_request("https://httpbin.org/status/200")
print(f"Request was successful: {response.ok}")
  1. Запустите тестовый скрипт, чтобы увидеть его в действии:
python test_request.py

Вы должны увидеть вывод, похожий на этот:

Status Code: 200
Response: ...
Request was successful: True

Это подтверждает, что вы успешно настроили среду Python и можете делать базовые HTTP-запросы. В следующих шагах вы узнаете, как обрабатывать различные HTTP-коды состояния и реализовывать более продвинутые методы обработки ошибок.

Обработка базовых HTTP-кодов состояния

Теперь, когда вы понимаете, что такое HTTP-коды состояния, и настроили свою среду, давайте реализуем базовую обработку ошибок для распространенных кодов состояния.

Создание инструмента для тестирования кодов состояния

Во-первых, давайте создадим инструмент, который позволит нам тестировать различные HTTP-коды состояния. Веб-сайт httpbin.org предоставляет конечные точки, которые возвращают определенные коды состояния, что идеально подходит для нашего тестирования.

Создайте новый файл с именем status_code_tester.py в вашей директории http_status_lab со следующим кодом:

import requests
import sys

def test_status_code(status_code):
    """Test a specific HTTP status code using httpbin.org."""
    url = f"https://httpbin.org/status/{status_code}"
    try:
        response = requests.get(url)
        print(f"Status Code: {response.status_code}")
        print(f"Response OK: {response.ok}")
        return response
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
        return None

if __name__ == "__main__":
    ## Get status code from command line or use default
    status_code = sys.argv[1] if len(sys.argv) > 1 else "200"
    test_status_code(status_code)

Этот скрипт делает запрос к httpbin.org с определенным кодом состояния и выводит результат. Вы можете запустить его с аргументом командной строки, чтобы протестировать разные коды состояния.

Тестирование различных кодов состояния

Давайте протестируем наш скрипт с разными кодами состояния, чтобы увидеть, как библиотека requests их обрабатывает:

  1. Протестируйте успешный запрос (200 OK):
python status_code_tester.py 200

Ожидаемый вывод:

Status Code: 200
Response OK: True
  1. Протестируйте ошибку клиента (404 Not Found):
python status_code_tester.py 404

Ожидаемый вывод:

Status Code: 404
Response OK: False
  1. Протестируйте ошибку сервера (500 Internal Server Error):
python status_code_tester.py 500

Ожидаемый вывод:

Status Code: 500
Response OK: False

Обработка базовых категорий кодов состояния

Теперь давайте создадим более сложный скрипт, который надлежащим образом обрабатывает различные категории кодов состояния. Создайте новый файл с именем basic_handler.py в вашей директории http_status_lab:

import requests

def handle_response(url):
    """Handle different HTTP status codes with basic error handling."""
    try:
        response = requests.get(url)

        ## Check status code category
        if 200 <= response.status_code < 300:
            print(f"Success! Status code: {response.status_code}")
            return response
        elif 300 <= response.status_code < 400:
            print(f"Redirection! Status code: {response.status_code}")
            ## For redirection, you might want to follow the redirect
            return response
        elif 400 <= response.status_code < 500:
            print(f"Client error! Status code: {response.status_code}")
            ## Handle client errors
            return response
        elif 500 <= response.status_code < 600:
            print(f"Server error! Status code: {response.status_code}")
            ## Handle server errors
            return response
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
        return None

## Test with different status codes
print("Testing 200 OK:")
handle_response("https://httpbin.org/status/200")

print("\nTesting 404 Not Found:")
handle_response("https://httpbin.org/status/404")

print("\nTesting 500 Internal Server Error:")
handle_response("https://httpbin.org/status/500")

Запустите скрипт, чтобы увидеть, как он обрабатывает разные коды состояния:

python basic_handler.py

Ожидаемый вывод:

Testing 200 OK:
Success! Status code: 200

Testing 404 Not Found:
Client error! Status code: 404

Testing 500 Internal Server Error:
Server error! Status code: 500

Этот базовый обработчик группирует коды состояния по категориям и выполняет разные действия в зависимости от категории. Это распространенный шаблон в Python-веб-приложениях, позволяющий вам надлежащим образом обрабатывать различные типы ответов.

Реализация расширенной обработки ошибок

Теперь, когда вы понимаете основы обработки HTTP-кодов состояния, давайте реализуем более продвинутые методы обработки ошибок для ваших приложений Python.

Использование raise_for_status()

Библиотека requests предоставляет удобный метод raise_for_status(), который вызывает исключение для кодов состояния 4xx и 5xx. Это простой, но эффективный способ обработки ошибок в вашем коде.

Создайте новый файл с именем raise_for_status_example.py в вашей директории http_status_lab:

import requests

def fetch_data(url):
    """Fetch data from a URL with error handling using raise_for_status()."""
    try:
        response = requests.get(url)
        ## This will raise an HTTPError if the HTTP request returned an unsuccessful status code
        response.raise_for_status()
        ## If we get here, the request was successful
        print(f"Success! Status code: {response.status_code}")
        return response.json() if 'application/json' in response.headers.get('Content-Type', '') else response.text
    except requests.exceptions.HTTPError as e:
        print(f"HTTP Error: {e}")
        return None
    except requests.exceptions.ConnectionError as e:
        print(f"Connection Error: {e}")
        return None
    except requests.exceptions.Timeout as e:
        print(f"Timeout Error: {e}")
        return None
    except requests.exceptions.RequestException as e:
        print(f"Request Exception: {e}")
        return None

## Test with different URLs
print("Testing successful request:")
data = fetch_data("https://httpbin.org/json")
print(f"Received data type: {type(data)}")

print("\nTesting 404 error:")
data = fetch_data("https://httpbin.org/status/404")
print(f"Received data: {data}")

print("\nTesting 500 error:")
data = fetch_data("https://httpbin.org/status/500")
print(f"Received data: {data}")

Запустите скрипт, чтобы увидеть, как raise_for_status() обрабатывает ошибки:

python raise_for_status_example.py

Ожидаемый вывод покажет, что библиотека автоматически вызывает исключения для кодов состояния, отличных от 2xx, которые ваш код затем может перехватить и обработать соответствующим образом.

Создание пользовательских обработчиков исключений

Для более сложных приложений вам может потребоваться создать пользовательские обработчики исключений, чтобы обеспечить более специфическую обработку ошибок. Давайте создадим более продвинутый пример:

Создайте новый файл с именем custom_exception_handler.py в вашей директории http_status_lab:

import requests
import logging

## Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

## Custom exceptions
class APIError(Exception):
    """Base class for API request exceptions."""
    pass

class ClientError(APIError):
    """Exception raised for client errors (4xx)."""
    pass

class ServerError(APIError):
    """Exception raised for server errors (5xx)."""
    pass

class NotFoundError(ClientError):
    """Exception raised for 404 Not Found errors."""
    pass

class UnauthorizedError(ClientError):
    """Exception raised for 401 Unauthorized errors."""
    pass

def api_request(url, method="get", **kwargs):
    """Make an API request with comprehensive error handling."""
    try:
        ## Make the request using the specified HTTP method
        response = getattr(requests, method.lower())(url, **kwargs)

        ## Log the response status
        logger.info(f"Request to {url} returned status code {response.status_code}")

        ## Handle different status codes
        if 200 <= response.status_code < 300:
            return response
        elif response.status_code == 401:
            raise UnauthorizedError(f"Authentication required: {response.text}")
        elif response.status_code == 404:
            raise NotFoundError(f"Resource not found: {url}")
        elif 400 <= response.status_code < 500:
            raise ClientError(f"Client error {response.status_code}: {response.text}")
        elif 500 <= response.status_code < 600:
            raise ServerError(f"Server error {response.status_code}: {response.text}")
        else:
            raise APIError(f"Unexpected status code {response.status_code}: {response.text}")

    except requests.exceptions.RequestException as e:
        logger.error(f"Request failed: {e}")
        raise APIError(f"Request failed: {e}")

## Test the custom exception handler
def test_api_request(url):
    """Test the api_request function with error handling."""
    try:
        response = api_request(url)
        print(f"Success! Status code: {response.status_code}")
        print(f"Response: {response.text[:100]}...")  ## Print first 100 characters
        return response
    except UnauthorizedError as e:
        print(f"Authentication Error: {e}")
    except NotFoundError as e:
        print(f"Not Found Error: {e}")
    except ClientError as e:
        print(f"Client Error: {e}")
    except ServerError as e:
        print(f"Server Error: {e}")
    except APIError as e:
        print(f"API Error: {e}")
    return None

## Test with different status codes
print("Testing 200 OK:")
test_api_request("https://httpbin.org/status/200")

print("\nTesting 404 Not Found:")
test_api_request("https://httpbin.org/status/404")

print("\nTesting 401 Unauthorized:")
test_api_request("https://httpbin.org/status/401")

print("\nTesting 500 Internal Server Error:")
test_api_request("https://httpbin.org/status/500")

Запустите скрипт, чтобы увидеть, как работает обработка пользовательских исключений:

python custom_exception_handler.py

Этот расширенный пример демонстрирует несколько важных концепций:

  1. Пользовательские классы исключений, которые расширяют базовый класс Exception
  2. Иерархические исключения для разных типов ошибок
  3. Подробное ведение журнала для отладки и мониторинга
  4. Специфическая обработка для разных кодов состояния

Этот подход позволяет вам обрабатывать различные типы ошибок более структурированным и удобным для сопровождения способом, что необходимо для больших приложений.

Создание полноценного клиента Web API

На этом заключительном этапе мы объединим все, что вы узнали, чтобы создать полноценный клиент Web API с надежной обработкой ошибок. Мы создадим клиент для JSONPlaceholder API, который является бесплатным онлайн REST API, который вы можете использовать для тестирования.

Создание простого API-клиента

Давайте создадим файл с именем api_client.py в вашей директории http_status_lab:

import requests
import json
import logging

## Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class APIClient:
    """A simple API client with robust error handling."""

    def __init__(self, base_url):
        """Initialize the API client with a base URL."""
        self.base_url = base_url
        self.session = requests.Session()

    def request(self, endpoint, method="get", params=None, data=None, headers=None):
        """Make a request to the API with error handling."""
        url = f"{self.base_url}{endpoint}"

        ## Default headers for JSON APIs
        if headers is None:
            headers = {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            }

        ## Convert data to JSON if it's a dictionary
        json_data = None
        if data and isinstance(data, dict):
            json_data = data
            data = None

        try:
            ## Log the request
            logger.info(f"Making {method.upper()} request to {url}")

            ## Make the request
            response = self.session.request(
                method=method,
                url=url,
                params=params,
                data=data,
                json=json_data,
                headers=headers
            )

            ## Log the response status
            logger.info(f"Received response with status code {response.status_code}")

            ## Check for HTTP errors
            response.raise_for_status()

            ## Parse JSON response if applicable
            try:
                return response.json()
            except json.JSONDecodeError:
                return response.text

        except requests.exceptions.HTTPError as e:
            status_code = e.response.status_code
            error_message = f"HTTP Error: {status_code}"

            try:
                ## Try to get more details from the response
                error_data = e.response.json()
                error_message = f"{error_message} - {error_data.get('message', str(error_data))}"
            except (json.JSONDecodeError, AttributeError):
                error_message = f"{error_message} - {e.response.text if hasattr(e, 'response') else str(e)}"

            logger.error(error_message)

            ## Re-raise with more context
            if 400 <= status_code < 500:
                logger.error(f"Client error: {error_message}")
                raise Exception(f"Client error (HTTP {status_code}): {error_message}")
            elif 500 <= status_code < 600:
                logger.error(f"Server error: {error_message}")
                raise Exception(f"Server error (HTTP {status_code}): {error_message}")
            else:
                raise

        except requests.exceptions.ConnectionError as e:
            logger.error(f"Connection Error: {e}")
            raise Exception(f"Connection Error: Could not connect to {url}")

        except requests.exceptions.Timeout as e:
            logger.error(f"Timeout Error: {e}")
            raise Exception(f"Timeout Error: Request to {url} timed out")

        except requests.exceptions.RequestException as e:
            logger.error(f"Request Exception: {e}")
            raise Exception(f"Request Error: {str(e)}")

    def get(self, endpoint, params=None, headers=None):
        """Make a GET request to the API."""
        return self.request(endpoint, method="get", params=params, headers=headers)

    def post(self, endpoint, data=None, headers=None):
        """Make a POST request to the API."""
        return self.request(endpoint, method="post", data=data, headers=headers)

    def put(self, endpoint, data=None, headers=None):
        """Make a PUT request to the API."""
        return self.request(endpoint, method="put", data=data, headers=headers)

    def delete(self, endpoint, headers=None):
        """Make a DELETE request to the API."""
        return self.request(endpoint, method="delete", headers=headers)

## Test the API client with JSONPlaceholder
def test_api_client():
    """Test the API client with JSONPlaceholder."""
    client = APIClient("https://jsonplaceholder.typicode.com")

    try:
        ## Get a list of posts
        print("\nGetting posts:")
        posts = client.get("/posts")
        print(f"Retrieved {len(posts)} posts")
        print(f"First post: {posts[0]}")

        ## Get a single post
        print("\nGetting a single post:")
        post = client.get("/posts/1")
        print(f"Retrieved post: {post}")

        ## Get a non-existent post (404)
        print("\nTrying to get a non-existent post:")
        try:
            client.get("/posts/999999")
        except Exception as e:
            print(f"Expected error: {e}")

        ## Create a new post
        print("\nCreating a new post:")
        new_post = client.post("/posts", {
            "title": "My New Post",
            "body": "This is the content of my new post.",
            "userId": 1
        })
        print(f"Created post: {new_post}")

        ## Update a post
        print("\nUpdating a post:")
        updated_post = client.put("/posts/1", {
            "id": 1,
            "title": "Updated Title",
            "body": "Updated content.",
            "userId": 1
        })
        print(f"Updated post: {updated_post}")

        ## Delete a post
        print("\nDeleting a post:")
        delete_response = client.delete("/posts/1")
        print(f"Delete response: {delete_response}")

    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    test_api_client()

Запустите скрипт, чтобы увидеть, как наш полноценный API-клиент обрабатывает различные сценарии:

python api_client.py

Тестирование обработки ошибок

Давайте создадим отдельный файл для тестирования возможностей обработки ошибок нашего API-клиента. Создайте файл с именем test_error_handling.py в вашей директории http_status_lab:

from api_client import APIClient
import time

def test_different_errors():
    """Test different error scenarios with our API client."""

    ## Test with a valid API
    print("\n1. Testing with a valid API:")
    valid_client = APIClient("https://jsonplaceholder.typicode.com")
    try:
        data = valid_client.get("/posts/1")
        print(f"Success! Retrieved data: {data}")
    except Exception as e:
        print(f"Unexpected error: {e}")

    ## Test with a 404 error
    print("\n2. Testing with a non-existent endpoint (404):")
    try:
        valid_client.get("/non_existent_endpoint")
    except Exception as e:
        print(f"Expected error: {e}")

    ## Test with an invalid host
    print("\n3. Testing with an invalid host (Connection Error):")
    invalid_client = APIClient("https://this-does-not-exist-123456789.com")
    try:
        invalid_client.get("/anything")
    except Exception as e:
        print(f"Expected error: {e}")

    ## Test with a timeout
    print("\n4. Testing with a timeout:")
    timeout_client = APIClient("https://httpbin.org")
    try:
        ## httpbin.org/delay/5 will delay the response for 5 seconds
        ## but we'll set the timeout to 2 seconds
        timeout_client.session.request = lambda **kwargs: timeout_client.session.request_original(
            **{**kwargs, 'timeout': 2}
        )
        timeout_client.session.request_original = timeout_client.session.request
        timeout_client.get("/delay/5")
    except Exception as e:
        print(f"Expected error: {e}")

if __name__ == "__main__":
    test_different_errors()

Скрипт выше может выдавать ошибки из-за обработки таймаута, поскольку он пытается изменить метод во время выполнения. Давайте упростим его, чтобы избежать этих проблем:

from api_client import APIClient

def test_different_errors():
    """Test different error scenarios with our API client."""

    ## Test with a valid API
    print("\n1. Testing with a valid API:")
    valid_client = APIClient("https://jsonplaceholder.typicode.com")
    try:
        data = valid_client.get("/posts/1")
        print(f"Success! Retrieved data: {data}")
    except Exception as e:
        print(f"Unexpected error: {e}")

    ## Test with a 404 error
    print("\n2. Testing with a non-existent endpoint (404):")
    try:
        valid_client.get("/non_existent_endpoint")
    except Exception as e:
        print(f"Expected error: {e}")

    ## Test with a server error
    print("\n3. Testing with a server error (500):")
    error_client = APIClient("https://httpbin.org")
    try:
        error_client.get("/status/500")
    except Exception as e:
        print(f"Expected error: {e}")

if __name__ == "__main__":
    test_different_errors()

Запустите скрипт тестирования ошибок:

python test_error_handling.py

Этот скрипт демонстрирует, как наш API-клиент обрабатывает различные сценарии ошибок, обеспечивая надежную основу для реальных приложений.

Основные выводы

Создав этот полноценный API-клиент, вы изучили несколько важных методов:

  1. Создание многоразового класса API-клиента
  2. Реализация комплексной обработки ошибок
  3. Ведение журнала запросов и ответов для отладки
  4. Разбор различных форматов ответов
  5. Обработка различных HTTP-методов (GET, POST, PUT, DELETE)

Эти навыки необходимы для создания надежных приложений Python, которые взаимодействуют с веб-API, гарантируя, что ваш код сможет корректно обрабатывать различные сценарии ошибок и предоставлять пользователям содержательные сообщения об ошибках.

Резюме

В этой лабораторной работе вы узнали, как обрабатывать HTTP-коды состояния в запросах Python, переходя от базовых к продвинутым методам. Теперь у вас есть навыки:

  • Понимать различные категории HTTP-кодов состояния и их значения
  • Реализовывать базовую обработку ошибок с проверкой кодов состояния
  • Использовать raise_for_status() для простой обработки ошибок
  • Создавать пользовательские классы исключений для более специфической обработки ошибок
  • Создавать полноценный API-клиент с комплексной обработкой ошибок

Эти навыки необходимы для разработки надежных приложений Python, взаимодействующих с веб-сервисами. Правильно обрабатывая HTTP-коды состояния и ошибки, вы можете создавать более надежные приложения, которые обеспечивают лучший пользовательский опыт и упрощают обслуживание.

Помните, что при работе с HTTP-запросами в ваших приложениях Python всегда следует учитывать обработку ошибок. Это важный аспект веб-разработки, который отличает профессиональный код от любительских реализаций.