Основы HTTP
Updated Jul 2026
Каждый API-тест, каждый шаг автоматизации браузера и каждая сессия отладки на сетевом уровне зависят от понимания HTTP. Это не теория — это протокол, на котором ваши тесты общаются каждый раз при взаимодействии с веб-приложением.
Методы HTTP
| Метод |
Назначение |
Идемпотентный? |
Безопасный? |
| GET |
Получить ресурс |
Да |
Да |
| POST |
Создать ресурс |
Нет |
Нет |
| PUT |
Полностью заменить ресурс |
Да |
Нет |
| PATCH |
Частичное обновление |
Нет |
Нет |
| DELETE |
Удалить ресурс |
Да |
Нет |
| HEAD |
Как GET, но без тела ответа |
Да |
Да |
| OPTIONS |
Узнать допустимые методы |
Да |
Да |
Что означает идемпотентность для тестирования
Идемпотентный запрос даёт одинаковый результат, независимо от того, отправите вы его один или десять раз. Это важно для логики повторных попыток и надёжности тестов:
PUT /users/123 {"name": "Alice"} — отправьте 10 раз, пользователь по-прежнему называется "Alice"
POST /users {"name": "Alice"} — отправьте 10 раз, вы можете получить 10 пользователей (или 409 Conflict, если email уникален)
DELETE /users/123 — первый вызов удаляет пользователя, последующие возвращают 404 (но состояние то же: пользователь удалён)
Что тестировать для каждого метода
# GET: verify response content and caching
def test_get_user(api):
r = api.get("/users/1")
assert r.status_code == 200
assert r.headers["Content-Type"] == "application/json"
assert "id" in r.json()
# POST: verify creation and response
def test_create_user(api):
r = api.post("/users", json={"name": "Alice", "email": "alice@test.com"})
assert r.status_code == 201
assert "id" in r.json()
# Verify the user actually exists
get_r = api.get(f"/users/{r.json()['id']}")
assert get_r.status_code == 200
# PUT: verify full replacement
def test_update_user(api):
r = api.put("/users/1", json={"name": "Bob", "email": "bob@test.com"})
assert r.status_code == 200
# Verify ALL fields are what you sent (PUT replaces everything)
user = api.get("/users/1").json()
assert user["name"] == "Bob"
# DELETE: verify removal
def test_delete_user(api):
r = api.delete("/users/1")
assert r.status_code == 204
# Verify the user is gone
get_r = api.get("/users/1")
assert get_r.status_code == 404
Коды состояния HTTP
| Диапазон |
Значение |
Типичные коды |
| 2xx |
Успех |
200 OK, 201 Created, 204 No Content |
| 3xx |
Перенаправление |
301 Moved Permanently, 302 Found, 304 Not Modified |
| 4xx |
Ошибка клиента |
400, 401, 403, 404, 409, 422, 429 |
| 5xx |
Ошибка сервера |
500, 502, 503 |
Коды состояния, которые должен знать QA-инженер
| Код |
Значение |
Тестовый сценарий |
| 200 |
OK |
Успешный GET, PUT, PATCH |
| 201 |
Created |
Успешный POST |
| 204 |
No Content |
Успешный DELETE |
| 301 |
Moved Permanently |
Старый URL должен перенаправлять на новый |
| 304 |
Not Modified |
Кеширование: ресурс не изменился с последнего запроса |
| 400 |
Bad Request |
Некорректный JSON, отсутствующие обязательные поля |
| 401 |
Unauthorized |
Отсутствующий или истёкший токен аутентификации |
| 403 |
Forbidden |
Валидный токен, но недостаточно прав |
| 404 |
Not Found |
Несуществующий ресурс |
| 405 |
Method Not Allowed |
POST на эндпоинт, принимающий только GET |
| 409 |
Conflict |
Дублирующее создание (ограничение уникальности) |
| 422 |
Unprocessable Entity |
Валидный JSON, но невалидные данные (например, формат email) |
| 429 |
Too Many Requests |
Превышен лимит запросов |
| 500 |
Internal Server Error |
Необработанное исключение на сервере |
| 502 |
Bad Gateway |
Бэкенд-сервис недоступен |
| 503 |
Service Unavailable |
Сервер перегружен или на обслуживании |
Заголовки HTTP
Заголовки несут метаданные о запросе и ответе. Несколько заголовков критичны для тестирования.
Заголовки запроса для установки
| Заголовок |
Назначение |
Пример |
Content-Type |
Формат тела запроса |
application/json |
Authorization |
Учётные данные аутентификации |
Bearer eyJhbG... |
Accept |
Желаемый формат ответа |
application/json |
User-Agent |
Идентификация клиента |
Пользовательское значение для идентификации тестов |
Заголовки ответа для проверки
| Заголовок |
Что тестировать |
Content-Type |
Соответствует ожидаемому формату (неправильный тип вызывает скрытые сбои) |
Set-Cookie |
Проверка атрибутов HttpOnly, Secure, SameSite |
Access-Control-Allow-Origin |
Корректная конфигурация CORS для разрешённых доменов |
Cache-Control |
Подходящее кеширование для типа ресурса |
X-Request-ID |
Присутствие для трассируемости (полезно при отладке) |
Retry-After |
Присутствие в ответах 429 |
Location |
Присутствие в 201 (указывает на новый ресурс) и при перенаправлениях 3xx |
def test_security_headers(api):
r = api.get("/")
# Security headers should be present
assert "X-Content-Type-Options" in r.headers
assert r.headers["X-Content-Type-Options"] == "nosniff"
assert "X-Frame-Options" in r.headers
assert "Strict-Transport-Security" in r.headers
def test_cors_headers(api):
r = api.options("/api/users", headers={
"Origin": "https://app.example.com",
"Access-Control-Request-Method": "GET"
})
assert r.headers["Access-Control-Allow-Origin"] in [
"https://app.example.com", "*"
]
Цикл запроса/ответа
Client Server
| |
|-- HTTP Request ---------------------->|
| Method: POST |
| URL: /api/users |
| Headers: |
| Content-Type: application/json |
| Authorization: Bearer token |
| Body: {"name": "Alice"} |
| |
|<-- HTTP Response --------------------|
| Status: 201 Created |
| Headers: |
| Content-Type: application/json |
| Location: /api/users/42 |
| Body: {"id": 42, "name": "Alice"} |
Что проверять на каждом уровне
- Код состояния: сервер корректно принял/отклонил запрос?
- Заголовки ответа: присутствуют ли заголовки безопасности? Корректен ли тип содержимого?
- Тело ответа: данные соответствуют ожиданиям? Все ли поля присутствуют?
- Время ответа: укладывается ли ответ в допустимую задержку?
- Побочные эффекты: сервер действительно создал/обновил/удалил ресурс?
Параметры запроса и тело запроса
| Аспект |
Параметры запроса |
Тело запроса |
| Используется с |
GET, DELETE |
POST, PUT, PATCH |
| Видимо в URL |
Да |
Нет |
| Кешируемо |
Да (часть URL) |
Нет |
| Ограничение размера |
~2КБ (ограничение длины URL) |
Без практического ограничения |
| Пример |
GET /users?page=1&limit=10 |
POST /users {"name": "Alice"} |
# Query parameters
r = requests.get(f"{base_url}/users", params={"page": 1, "limit": 10, "sort": "name"})
# Results in: GET /users?page=1&limit=10&sort=name
# Request body
r = requests.post(f"{base_url}/users", json={"name": "Alice", "email": "alice@test.com"})
Типы содержимого
| Content-Type |
Используется для |
Пример |
application/json |
REST API |
{"name": "Alice"} |
application/x-www-form-urlencoded |
HTML-формы |
name=Alice&email=alice@test.com |
multipart/form-data |
Загрузка файлов |
Бинарные данные файла с разделителями |
text/html |
Веб-страницы |
HTML-содержимое |
application/xml |
SOAP API, устаревшие системы |
<user><name>Alice</name></user> |
# Sending form data (not JSON)
r = requests.post(f"{base_url}/login", data={"username": "alice", "password": "pass123"})
# Uploading a file
with open("document.pdf", "rb") as f:
r = requests.post(f"{base_url}/upload", files={"file": f})
Практическое упражнение
- Используйте curl или requests для выполнения запросов GET, POST, PUT и DELETE к публичному API (например, JSONPlaceholder)
- Проверьте заголовки ответа для каждого запроса и верифицируйте Content-Type, коды состояния и заголовки безопасности
- Напишите тест, проверяющий корректную обработку 401: отправьте запрос без аутентификации, с истёкшей аутентификацией и с невалидной аутентификацией
- Напишите тест, проверяющий корректные заголовки CORS для кросс-доменного запроса
Ключевые выводы
- Знайте все методы HTTP и их характеристики идемпотентности
- Тестируйте как успешные коды состояния, так и коды ошибок (особенно 400, 401, 403, 404, 409, 429)
- Проверяйте заголовки ответа, а не только тело — заголовки безопасности, CORS, кеширование
- Понимайте разницу между параметрами запроса (GET) и телом запроса (POST/PUT)
- Несоответствие Content-Type вызывает скрытые сбои — всегда проверяйте