DimaSOS

Сеть и 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