Сеть и API
Инструменты для проверки API руками и разбора того, почему запрос не доходит.
#curl
curl -sS https://api.example.com/users
curl -i https://api.example.com/users # с заголовками ответа
curl -I https://api.example.com/users # только заголовки (HEAD)
curl -v https://api.example.com/users # весь диалог, включая TLS
-s убирает прогресс-бар, -S оставляет ошибки. В скриптах всегда -sS, иначе молчаливые падения.
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"name":"Тест","email":"t@example.com"}'
POST с JSON. Одинарные кавычки вокруг тела — чтобы shell не трогал двойные внутри.
curl -sS -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
--data @payload.json
Тело из файла. Для больших запросов удобнее и не ломается на кавычках.
curl -w '\nhttp=%{http_code} time=%{time_total}s dns=%{time_namelookup}s tls=%{time_appconnect}s\n' \
-o /dev/null -sS https://api.example.com/health
Тайминги по фазам. Так отделяется медленный DNS от медленного TLS и от медленного сервера.
Полезные переменные: %{http_code}, %{time_total}, %{time_namelookup}, %{time_connect}, %{time_appconnect}, %{size_download}, %{redirect_url}.
curl -sS --retry 5 --retry-delay 2 --retry-connrefused \
--max-time 30 --connect-timeout 5 https://api.example.com/health
Таймауты и повторы. В CI без --max-time зависший запрос съедает весь таймаут джобы.
curl -sS -c cookies.txt -b cookies.txt https://example.com/login
curl -sS -L https://example.com # следовать редиректам
curl -sS --resolve api.example.com:443:1.2.3.4 https://api.example.com/
curl -sS -x http://127.0.0.1:8888 https://api.example.com/
--resolve — проверить конкретный сервер за балансировщиком, не меняя /etc/hosts.
curl -sS -F "file=@report.pdf" -F "title=Отчёт" https://api.example.com/upload
curl -sS -T local.bin https://api.example.com/blob/1 # PUT
Multipart и загрузка через PUT.
curl -sS -k https://self-signed.local/ # игнорировать сертификат
curl -sS --cacert ca.pem https://internal.local/
-k только для отладки. В скриптах, которые куда-то ходят с секретами, он недопустим.
#jq
curl -sS https://api.example.com/users | jq .
curl -sS https://api.example.com/users | jq -r '.[].email'
jq '.data.items | length' resp.json
-r отдаёт строки без кавычек — то, что нужно для подстановки в shell.
jq '.items[] | select(.status == "active") | .id' resp.json
jq '.items | map(select(.price > 100)) | length' resp.json
jq '.items | sort_by(.created_at) | reverse | .[0:5]' resp.json
Фильтрация, сортировка, срезы.
jq '{id: .user.id, name: .user.full_name}' resp.json
jq '.items | map({id, name})' resp.json
jq -s 'add' part1.json part2.json
Пересборка структуры. -s читает несколько документов как массив.
jq -e '.status == "ok"' resp.json > /dev/null && echo OK || echo FAIL
-e задаёт код возврата по результату — так jq становится проверкой в CI.
Без -e jq вернёт 0 даже когда выражение дало false или null.
jq --arg id "42" '.items[] | select(.id == $id)' resp.json
jq -n --arg name "Тест" '{name: $name, active: true}'
--arg вместо склейки строк — не ломается на кавычках и спецсимволах. -n строит JSON с нуля.
jq 'paths(scalars) | join(".")' resp.json | sort -u | head -30
Все пути в документе. Быстрый способ понять схему незнакомого ответа.
#Порты и соединения
ss -tlnp # что слушает TCP
ss -tnp state established | head
ss -s # сводка
ss -tn '( dport = :443 )' | head
ss заменил netstat. -p показывает процесс, требует root.
ss -tlnp | grep :8443
lsof -i :8443
fuser -k 8443/tcp # убить того, кто держит порт
Разбор Address already in use.
nc -zv host 443
nc -zv host 20-25
timeout 3 bash -c 'cat < /dev/null > /dev/tcp/host/443' && echo open
Проверка доступности порта. Последний вариант работает без установленного netcat.
ip addr
ip route
ip -br a # компактно
mtr -r -c 10 example.com
traceroute example.com
mtr лучше traceroute: показывает потери по каждому хопу за много проб.
iptables -L -n -v --line-numbers
ufw status verbose
nft list ruleset | head -40
Правила фильтрации. Если пакеты «пропадают» — смотреть здесь до того, как винить приложение.
#DNS
dig +short example.com
dig +short example.com A
dig example.com CNAME +short
dig example.com MX +short
dig +trace example.com | tail -20
+trace проходит цепочку от корневых серверов — показывает, где именно ломается делегирование.
dig @8.8.8.8 example.com +short
dig @1.1.1.1 example.com +short
dig -x 93.184.216.34 +short # обратная запись
Сравнение ответов разных резолверов — так ловится устаревший кеш у провайдера.
getent hosts example.com
resolvectl query example.com
resolvectl status | head -20
resolvectl flush-caches
getent использует системный резолвер (/etc/hosts тоже), в отличие от dig, который идёт прямо в DNS.
dig +short _acme-challenge.example.com TXT
dig +short example.com CAA
Проверка при выпуске сертификатов.
#TLS и сертификаты
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
Кто выдал, кому и до какого числа. -servername обязателен для SNI — без него получите сертификат дефолтного хоста.
openssl s_client -connect example.com:443 -servername example.com -showcerts < /dev/null
openssl x509 -in cert.pem -noout -text | head -30
openssl x509 -in cert.pem -noout -ext subjectAltName
Полная цепочка и SAN. Ошибка NET::ERR_CERT_COMMON_NAME_INVALID почти всегда про отсутствующий SAN.
openssl s_client -connect example.com:443 -tls1_2 < /dev/null
openssl s_client -connect example.com:443 -tls1_3 < /dev/null
openssl s_client -connect example.com:443 -alpn h2 < /dev/null | grep ALPN
Какие версии и протоколы поддерживает сервер.
# совпадают ли ключ и сертификат
openssl x509 -noout -modulus -in cert.pem | openssl md5
openssl rsa -noout -modulus -in key.pem | openssl md5
Два одинаковых хеша — пара валидна. Несовпадение — типовая причина, по которой nginx не стартует после обновления сертификата.
certbot certificates
certbot renew --dry-run
openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/cert.pem
Состояние Let's Encrypt и проверка продления без реального выпуска.
#Перехват трафика
tcpdump -i any -n port 443 -c 20
tcpdump -i any -n host 1.2.3.4 and port 8443
tcpdump -i any -n -A port 8080 | head -60
-n не резолвит имена (быстрее и без лишних DNS-запросов), -A печатает payload как текст — годится только для нешифрованного.
tcpdump -i any -n -s 0 -w capture.pcap port 443
# потом открыть в Wireshark
tcpdump -r capture.pcap -n | head -40
Запись в файл для разбора в Wireshark. -s 0 — полные пакеты.
mitmproxy -p 8888
mitmdump -p 8888 -w flows.mitm
# на устройстве: adb shell settings put global http_proxy 10.0.2.2:8888
Разбор HTTPS-трафика приложения. Требует установки CA на устройство.
Начиная с Android 7 приложение обязано явно доверять пользовательским CA через network_security_config, иначе перехват не сработает даже с установленным сертификатом.
#Проверка API в скриптах
#!/usr/bin/env bash
set -euo pipefail
BASE="${BASE:-https://api.example.com}"
TOKEN="${TOKEN:?нужен TOKEN}"
check() {
local name="$1" method="$2" path="$3" expect="$4"
local code
code=$(curl -sS -o /tmp/resp.json -w '%{http_code}' \
-X "$method" "$BASE$path" \
-H "Authorization: Bearer $TOKEN" \
--max-time 15)
if [ "$code" = "$expect" ]; then
echo " OK $name ($code)"
else
echo " FAIL $name: ожидали $expect, получили $code"
jq . /tmp/resp.json 2>/dev/null | head -10
return 1
fi
}
check "список пользователей" GET /users 200
check "несуществующий" GET /users/0 404
check "без авторизации" GET /admin 403
Каркас смоук-проверки API на bash. ${TOKEN:?} падает с внятным сообщением, если переменная не задана.
# схему ответа проверить без питона
curl -sS "$BASE/users" | jq -e '
(type == "array") and
(length > 0) and
(all(.[]; has("id") and has("email")))
' > /dev/null && echo "схема ок"
Контрактная проверка одним выражением jq: тип, непустота, обязательные поля у всех элементов.
import httpx, pytest
BASE = "https://api.example.com"
@pytest.fixture
def client():
with httpx.Client(base_url=BASE, timeout=15) as c:
yield c
def test_users_schema(client):
r = client.get("/users", headers={"Authorization": f"Bearer {TOKEN}"})
assert r.status_code == 200
data = r.json()
assert isinstance(data, list) and data
assert {"id", "email"} <= data[0].keys()
То же на Python, когда проверок становится больше десятка и bash перестаёт быть читаемым.
| Симптом | Что проверить |
|---|---|
Connection refused | порт не слушается: ss -tlnp |
Connection timed out | фильтрация: firewall, security group |
| Имя не резолвится | dig +trace, resolvectl status |
| Сертификат «не тот» | нет -servername, отдаётся дефолтный хост |
CERT_COMMON_NAME_INVALID | домена нет в SAN сертификата |
| nginx не стартует после обновления cert | ключ и сертификат не пара — сверить modulus |
| Скрипт «молча» проходит | нет -sS у curl или нет -e у jq |
| Перехват HTTPS не работает на Android | нужен network_security_config |