Гузариш ба мундариҷа
Мавзӯи 5 аз 6

Хатоҳо ва такрорҳо

Чаро ба нокомӣ омода мешавем

Сервер метавонад ҷавоб надиҳад, ба ҷойи JSON саҳифаи хизматрасонӣ фиристад ё басомади дархостҳоро маҳдуд кунад. Ин шароити одии шабака аст. Муштарии боэътимод интизориро маҳдуд мекунад, сабабҳои радкуниро фарқ мекунад ва танҳо ҳангоми фоида доштани кӯшиши нав такрор мекунад.

Се сатҳро ҷудо кунед: оё ба сервер пайваст шудем, оё ҳолати HTTP муваффақ аст ва оё муҳтавои ҷавоб мувофиқ аст. Як санҷиши r.ok ҳамаи инро ҳал намекунад: он дар бораи сохтори JSON чизе намегӯяд ва ҳангоми хатои шабака шояд объекти r ҳанӯз вуҷуд надошта бошад.

Мисоли маҳаллии ҷавоби номуваффақ

Блокҳоро бо тартиб дар як раванди Python иҷро кунед. Ин сервер фавран 503 бо HTML медиҳад. Мо махсус хатои равшан месозем, бе интизории сомонаи берунии дастнорас.

import os
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import requests

os.environ["NO_PROXY"] = "127.0.0.1,localhost"

class Handler(BaseHTTPRequestHandler):
    def log_message(self, *args):
        pass

    def do_GET(self):
        data = b'<html>maintenance</html>'
        self.send_response(503)
        self.send_header("Content-Type", "text/html")
        self.send_header("Content-Length", str(len(data)))
        self.end_headers()
        self.wfile.write(data)

server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
base = f"http://127.0.0.1:{server.server_port}"
r = requests.get(base, timeout=(2, 5))
print(r.status_code)

Барориш:

503

Ҷавоб расид, бинобар ин get объектро бомуваффақият баргардонд. Аммо рамз аз дастнорасии муваққатии хизмат хабар медиҳад. Дар timeout=(2, 5) адади аввал интизории пайвастшавӣ, дуюм интизории маълумот ҳангоми хонданро маҳдуд мекунад. Бе timeout китобхона чунин маҳдудият намегузорад. Ин маҳдудияти умумии давомнокӣ нест: ҷараёни маълумот бо таваққуфҳои кӯтоҳ метавонад дер хонда шавад.

Навъҳои истисно

Истисно ҷараёни муқаррарии барномаро қатъ мекунад; try/except имкон медиҳад ҳолати интизориро коркард кунем. Timeout аз гузаштани муҳлати интизорӣ мегӯяд. ConnectionError мушкили пайваст аст, масалан кандашавӣ ё ёфта нашудани сервер. HTTPError пас аз raise_for_status() ва хатои таҳлили JSON ҳангоми json() пайдо мешавад.

print(issubclass(requests.ReadTimeout, requests.Timeout))
print(issubclass(requests.ConnectTimeout, requests.ConnectionError))
print(issubclass(requests.Timeout, requests.RequestException))

Барориш:

True
True
True

Ин истисноҳои шабака ба RequestException тааллуқ доранд. Гурӯҳҳо метавонанд буриш дошта бошанд: ConnectTimeout ҳам Timeout ва ҳам ConnectionError аст. Аз ин рӯ коркардкунандаи махсусро пеш аз умумӣ гузоред: агар аввал RequestException-ро гиред, except Timeout-и баъдӣ дигар иҷро намешавад. ReadTimeout ба Timeout тааллуқ дорад, вале ҳатман ба ConnectionError не.

Пеш аз таҳлили бадан ҳолатро санҷед

raise_for_status() дар бораи 4xx ва 5xx хабар медиҳад ва истинод ба ҷавобро дар истисно нигоҳ медорад. Қисми дуюм алоҳида ҳамон HTML-ро ҳамчун JSON таҳлил кардан мехоҳад. Ин нишон медиҳад, ки тартиби нодурусти санҷиш чӣ гуна сабаби аслии радкуниро пинҳон мекунад.

try:
    r.raise_for_status()
except requests.HTTPError as error:
    print(type(error).__name__, error.response.status_code)

try:
    r.json()
except requests.exceptions.JSONDecodeError:
    print("JSONDecodeError")

Барориш:

HTTPError 503
JSONDecodeError

Дар коди корӣ одатан баъди хатои HTTP таҳлили дуюм намекунанд, агар қоидаи API онро талаб накунад. Баъзе API-ҳо тавсифи сохтордори хатои JSON-ро кафолат медиҳанд; онро алоҳида хондан мумкин аст. Дар версияи requests-и мо хатои JSON ҳамчун requests.exceptions.JSONDecodeError дастрас аст ва ҳамчунин ValueError мебошад.

Баъди истиснои шабака бе шарт ба r.status_code муроҷиат накунед: таъйини r = requests.get(...) шояд анҷом наёфта бошад. Танҳо ҷавоби гирифташуда рамзи ҳолат дорад. Ҳамаи хатоҳоро «интернет нест» нагӯед: 503-и сервер ва хатои коди маҳаллӣ амалҳои гуногун мехоҳанд.

JSON-и дуруст ҳам метавонад номувофиқ бошад

Таҳлили бе истисно танҳо синтаксисро тасдиқ мекунад. Сервер шояд ба ҷойи объект рӯйхат диҳад, майдонро нафиристад ё сифр диҳад. Аввал навъ, баъд мавҷудияти калид, сипас мувофиқати қиматро ба қоидаи барнома санҷед.

import json

for body in ['{"count": 0}', '[]', '{"items": []}']:
    data = json.loads(body)
    print(isinstance(data, dict), isinstance(data, dict) and "count" in data)

Барориш:

True True
False False
True False

Агар санҷиши аввал дурӯғ бошад, and меистад: санҷиши калид танҳо барои луғат иҷро мешавад. Сифр қимати дурусти count аст, ҳарчанд bool(0) ба False баробар мебошад. if data.get("count") сифр ва набудани майдонро фарқ намекунад. Барои мавҷудият "count" in data, барои қимати махсуси набудан is None истифода баред.

Такрор кай лозим аст

Такрор дархости нав баъди нокомии муваққатӣ аст. Барои хондан такрорҳои маҳдуд пас аз 500, 502, 503, 504 ё хатои шабака аксар вақт муносибанд. 400, 401, 403 ва 404 одатан ислоҳи дархост, дастрасӣ ё суроғаро талаб мекунанд. Такрорҳои беохир танҳо сарбориро зиёд ва мушкилро пинҳон мекунанд.

Таваққуфи экспоненсиалӣ мисли 1, 2, 4, 8 сония меафзояд ва ба сервер вақти барқароршавӣ медиҳад. Дар ин ҷо таваққуфҳоро бе интизории воқеӣ ҳисоб мекунем. Дар барнома интизориро time.sleep(delay) ё функсияи интизории додашуда иҷро мекунад.

delays = [2 ** k for k in range(4)]
print(delays)
response = requests.Response()
response.headers["Retry-After"] = "6"
print(int(response.headers.get("Retry-After", "1")))
print(sum(delays[:2]))

Барориш:

[1, 2, 4, 8]
6
3

Барои 429 сервер метавонад Retry-After диҳад. Дар масъалаҳои курс ин шумораи бутуни сонияҳост; дар HTTP-и умумӣ сарлавҳа метавонад сана ҳам дошта бошад. Қиматро аз рӯйи қоидаи API таҳлил кунед. Ҷамъ дар мисол танҳо ду таваққуфи аввалро дар бар мегирад: байни се кӯшиш ду фосила ҳаст, баъди кӯшиши охир интизорӣ лозим нест.

Пеш аз такрори фиристодани маълумот оқибатро ба назар гиред: агар POST сабт сохта бошад, вале ҷавоб гум шавад, такрор метавонад нусхаи дуюм созад. Баъзе API-ҳо калиди идемпотентӣ — шиносаи як амалро дастгирӣ мекунанд, ки ба сервер барои шинохтани такрор ёрӣ медиҳад. Барои хондани бехатар ин мушкил одатан содатар аст, аммо сиёсати такрор ҳамоно бояд маҳдуд бошад.

Сиёсатро аз иҷро ҷудо мекунем

Маҷмӯи рамзҳои такроршаванда қарори равшани барнома аст. Пеш аз навиштани давраи шабака ин қарорро алоҳида санҷидан мумкин аст. Дар мисол 201 такрор намехоҳад, зеро аллакай муваффақият аст; 403 ислоҳи дастрасиро мехоҳад.

retryable = {429, 500, 502, 503, 504}
for status in [201, 403, 503, 429]:
    print(status, status in retryable)

Барориш:

201 False
403 False
503 True
429 True

Дар дохил чӣ рӯй медиҳад

Ҳангоми пайвастшавӣ ва хондан requests маҳдудиятҳоро ба қабати поёнии шабака медиҳад. Ҳангоми гузаштани муҳлат истисно пайдо мешавад; ҳангоми ҷавоби одии HTTP Response бармегардад. raise_for_status ва json() санҷишҳои маҳаллии баъдӣ ҳастанд. Фарохонии нави get кӯшиши ҷудост; китобхона барои шумо давраи беохири интизорӣ намесозад.

Дар муштариҳои калон қоидаи такрорро аксар вақт бо адаптери сессия ва urllib3.Retry танзим мекунанд. Ҳатто он вақт бояд ҳадди кӯшишҳо, методҳои иҷозатдода, таваққуфҳо ва Retry-After-ро фаҳмид. Барои омӯзиш ин қарорҳоро аз худи дархост ҷудо дидан муфид аст.

Пас аз кор муҳити санҷишро мебандем; рақами socket ба -1 табдил меёбад.

server.shutdown()
server.server_close()
print(server.fileno())

Барориш:

-1

Хатоҳои маъмул ва ислоҳи онҳо

  1. Дархост бе timeout. Сервери хомӯш метавонад барномаро беохир нигоҳ дорад. Адад ё ҷуфти адад диҳед ва Timeout-ро коркард кунед.
  2. except RequestException пеш аз except Timeout. Шохаи умумӣ ҳолати махсусро мегирад. Аввал коркардкунандаи махсус, баъд умумиро гузоред.
  3. Фавран r.json() дар саҳифаи хатои HTML. Ба ҷойи ҳолати равшан хатои JSON мегиред. Аввал ҳолати HTTP-ро коркард кунед.
  4. if data.get("count"). Сифр хато ҳамчун набудан фаҳмида мешавад. Навъ ва мавҷудияти калидро ҷудо санҷед.
  5. Такрори ҳар радкунӣ ва таваққуф баъди кӯшиши охир. Вақт сарф мешавад, дархости нодуруст ислоҳ намешавад. Рамзҳо, шумораи кӯшишҳо ва фосилаи байни онҳоро маҳдуд кунед.

Ёддошт

Санҷиш Восита
Муҳлати интизорӣ timeout=5, timeout=(2, 5)
Хатои шабака Timeout, ConnectionError
HTTP-и номуваффақ raise_for_status(), HTTPError
JSON-и нодуруст requests.exceptions.JSONDecodeError
Сохтори лозим isinstance, in, is None
Нокомии муваққатӣ Такрори маҳдуд бо таваққуф
Маҳдудияти басомад 429 ва Retry-After