Przejdź do głównej zawartości

Obsługa błędów i ponowień

Dla: Programista

Integracja, która działa miesiącami, trafi na zerwane połączenie, chwilową niedostępność albo limit żądań. Ten przepis to mały klient API, który odróżnia błędy przejściowe od stałych, ponawia tylko te pierwsze, a mutacje ponawia z tym samym Idempotency-Key, żeby ponowienie nie wykonało zapisu drugi raz.

  • Sukces to wyłącznie status HTTP 2xx, brak tablicy errors i obiekt data. Odpowiedź, która nie jest odpowiedzią GraphQL (strona HTML, pusta treść, krótki JSON { code, message }), nigdy nie jest sukcesem, nawet ze statusem 200.
  • Ponawiaj tylko błędy przejściowe: błąd sieci lub przekroczony czas, każdą odpowiedź HTTP 5xx (z treścią JSON albo bez), RATE_LIMITED, INTERNAL i API_IDEMPOTENCY_IN_PROGRESS. Pozostałe błędy zwracaj od razu: ponowienie niczego nie zmieni.
  • Czekaj coraz dłużej: 1, 2, 4, 8 sekund z losowym rozrzutem, najwyżej 30 sekund i pięć prób. Gdy odpowiedź ma nagłówek Retry-After, czekaj dokładnie tyle.
  • Mutacje z kluczem idempotencji: wygeneruj Idempotency-Key raz, zapisz go razem z operacją i wysyłaj ten sam przy każdej próbie. Szczegóły: Zapisy i idempotencja.
  • Loguj requestId z każdego błędu. Po nim znajdziemy żądanie, gdy poprosisz nas o pomoc.

Przykłady pokazują adres zastępczy. Wpisz adres swojej drukarni, żeby go podmienić.

Skrypt wysyła treść żądania z pliku JSON, np. {"query": "...", "variables": {...}}, i wypisuje odpowiedź po sukcesie. Wymaga curl i jq.

prynt-call.sh
#!/usr/bin/env bash
# prynt-call.sh — wysyła żądanie GraphQL z pliku JSON i ponawia błędy przejściowe (curl, jq).
# Użycie: IDEMPOTENCY_KEY=<klucz zapisany razem z operacją> ./prynt-call.sh zadanie.json
# Przy zapytaniu (odczycie) IDEMPOTENCY_KEY możesz pominąć.
set -uo pipefail
endpoint="https://twoja-drukarnia.prynt.pl/api/v1/graphql"
response=$(mktemp) headers=$(mktemp)
trap 'rm -f "$response" "$headers"' EXIT
delay=1
for attempt in 1 2 3 4 5; do
: >"$response"; : >"$headers" # nic z poprzedniej próby
status=$(curl -sS --max-time 30 -o "$response" -D "$headers" -w '%{http_code}' "$endpoint" \
-H "Authorization: Bearer $PRYNT_API_KEY" \
-H "Content-Type: application/json" \
${IDEMPOTENCY_KEY:+-H "Idempotency-Key: $IDEMPOTENCY_KEY"} \
--data-binary @"$1") || status=000 # 000: błąd sieci albo przekroczony czas
retry_after=$(awk 'tolower($1) == "retry-after:" { print $2 + 0 }' "$headers")
# Sukces to wyłącznie status 2xx, brak `errors` i obiekt `data`.
if [[ $status == 2?? ]] &&
jq -e '((.errors // []) | length == 0) and (.data | type == "object")' "$response" >/dev/null 2>&1; then
cat "$response"; exit 0
fi
if jq -e '(.errors // []) | length > 0' "$response" >/dev/null 2>&1; then
reason=$(jq -r '.errors[0].extensions | "\(.code)/\(.subCode // "")"' "$response")
else
# Odpowiedź spoza GraphQL: serwer niedostępny (5xx) albo zły adres.
reason="$(jq -r '.code // empty' "$response" 2>/dev/null || true)/HTTP $status"
fi
if [[ $status != 000 ]] && ((10#$status < 500)); then # 000 (błąd sieci) i 5xx: ponawiaj
case "$reason" in
RATE_LIMITED/* | INTERNAL/* | CONFLICT/API_IDEMPOTENCY_IN_PROGRESS) ;;
*) echo "$reason" >&2; cat "$response" >&2; exit 1 ;; # błąd stały: popraw żądanie
esac
fi
sleep "${retry_after:-$((delay + RANDOM % delay))}"
delay=$((delay * 2 > 30 ? 30 : delay * 2))
done
echo "Brak sukcesu po 5 próbach ($reason)" >&2
exit 1
Okno terminala
IDEMPOTENCY_KEY=$(uuidgen) ./prynt-call.sh mutacja.json
  • Błąd stały ma kategorię w code i szczegóły w subCode oraz params. Co zrobić z każdym z nich, opisuje strona Błędy.
  • Częściowy sukces (dane i błędy naraz) klient zwraca jako błąd z danymi w polu data. Zdecyduj, czy dla Twojej operacji to sukces. Gdy pierwszy błąd ma kod INTERNAL, klient najpierw ponawia żądanie: z Idempotency-Key dostaje wtedy tę samą zapisaną odpowiedź, bez drugiego zapisu.
  • Integracja, która często dostaje RATE_LIMITED, wysyła za dużo żądań. Zobacz Limity.