fix(sf): User-Agent header for okn-mk searchMap (bypass 403) (#1146)
Co-authored-by: bot-backend <bot-backend@gendsgn.local> Co-committed-by: bot-backend <bot-backend@gendsgn.local>
This commit is contained in:
parent
396db20c48
commit
9ee35bebf0
2 changed files with 58 additions and 3 deletions
|
|
@ -28,6 +28,19 @@ _SEARCHMAP_URL = "https://okn-mk.mkrf.ru/Maps/searchMap"
|
|||
# Таймаут на один HTTP-запрос к ЕГРКН (секунды)
|
||||
_REQUEST_TIMEOUT = 30.0
|
||||
|
||||
# Browser headers -- required: without User-Agent the server returns 403.
|
||||
# Content-Type intentionally omitted: httpx sets it automatically with data=
|
||||
# (application/x-www-form-urlencoded); duplicating it would conflict.
|
||||
_HEADERS: dict[str, str] = {
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
|
||||
),
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"Referer": "https://okn-mk.mkrf.ru/Maps",
|
||||
"Origin": "https://okn-mk.mkrf.ru",
|
||||
}
|
||||
|
||||
|
||||
def _build_form_data(address: str, category_type: int) -> list[tuple[str, str]]:
|
||||
"""Построить form-data в формате jQuery serializeArray для поиска по карте.
|
||||
|
|
@ -88,11 +101,10 @@ def _do_request(
|
|||
) -> list[dict[str, Any]]:
|
||||
"""Выполнить HTTP POST к searchMap. При SSL-ошибке с verify=True повторяет с verify=False."""
|
||||
try:
|
||||
with httpx.Client(timeout=_REQUEST_TIMEOUT, verify=verify) as client:
|
||||
with httpx.Client(timeout=_REQUEST_TIMEOUT, verify=verify, headers=_HEADERS) as client:
|
||||
resp = client.post(
|
||||
_SEARCHMAP_URL,
|
||||
data=form_data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
except httpx.HTTPStatusError as exc:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@ from __future__ import annotations
|
|||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from app.services.scrapers.okn_egrkn_client import _build_form_data, fetch_okn_points
|
||||
from app.services.scrapers.okn_egrkn_client import (
|
||||
_HEADERS,
|
||||
_build_form_data,
|
||||
fetch_okn_points,
|
||||
)
|
||||
|
||||
# ── Вспомогательные функции ───────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -226,6 +230,45 @@ def test_build_form_data_structure() -> None:
|
|||
assert ("data[1][value]", "1") in result
|
||||
|
||||
|
||||
# -- User-Agent header (bypass 403) ------------------------------------------
|
||||
|
||||
|
||||
def test_request_sends_browser_user_agent() -> None:
|
||||
"""httpx.Client is created with browser User-Agent from _HEADERS (bypass 403).
|
||||
|
||||
Without UA, okn-mk.mkrf.ru returns 403.
|
||||
With Chrome/125 UA: 200 response, cob_address=Ekaterinburg = 272 features.
|
||||
Checks:
|
||||
1. _HEADERS constant contains User-Agent with Mozilla string.
|
||||
2. httpx.Client is called with headers=_HEADERS when fetch_okn_points runs.
|
||||
"""
|
||||
assert "User-Agent" in _HEADERS
|
||||
assert "Mozilla" in _HEADERS["User-Agent"]
|
||||
assert "X-Requested-With" in _HEADERS
|
||||
assert "Referer" in _HEADERS
|
||||
assert "Origin" in _HEADERS
|
||||
|
||||
features = [_make_feature("ua-001", 56.83, 60.61)]
|
||||
payload = _make_geojson_response(features)
|
||||
captured_kwargs: list[dict] = []
|
||||
|
||||
def _make_client(**kwargs: Any) -> MagicMock:
|
||||
captured_kwargs.append(dict(kwargs))
|
||||
mc = MagicMock()
|
||||
mc.__enter__ = MagicMock(return_value=mc)
|
||||
mc.__exit__ = MagicMock(return_value=False)
|
||||
mc.post.return_value = _mock_response(payload)
|
||||
return mc
|
||||
|
||||
with patch("app.services.scrapers.okn_egrkn_client.httpx.Client", side_effect=_make_client):
|
||||
fetch_okn_points("Ekaterinburg", 1)
|
||||
|
||||
assert len(captured_kwargs) == 1
|
||||
sent_headers = captured_kwargs[0].get("headers", {})
|
||||
assert "User-Agent" in sent_headers
|
||||
assert "Mozilla" in sent_headers["User-Agent"]
|
||||
|
||||
|
||||
def test_build_form_data_category_type_as_string() -> None:
|
||||
"""category_type конвертируется в строку (httpx data= ожидает str)."""
|
||||
result = _build_form_data("Свердловская", 4)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue