0%

Bodies van verzoeken en een JSON-API · oefening

Weiger foute verzoeken naar oorzaak

Je schrijfacties werken voor geldige boeken, en alleen daarvoor. Stuur de foute verzoeken van het hulpprogramma en kijk wat er gebeurt:

python pl_http.py bad-requests

Het eerste verzoek is POST /books met de body title=Dune, gelabeld als text/plain. handle roept create_book aan, die json_body aanroept, die een opwerpt. De antwoordregel in het transcript luidt (no bytes: the server closed the connection without answering), en het logblok bevat een die eindigt op json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0). Als handle een exception opwerpt, drukt http.server de traceback af en sluit hij de verbinding zonder antwoord, dus de client komt niets te weten. pl_http stopt na dat verzoek met verzenden en zegt dat ook.

Een - zou verder komen en mislukken op data["title"]; een boek met een lege titel zou worden opgeslagen. Elk fout verzoek heeft een status nodig die zegt wat er mis is, en geen ervan mag de state veranderen.

Status naar oorzaak

SERVICE_CONTRACT.md somt deze controles op, en de eerste die mislukt, bepaalt het antwoord. Hier staan ze in de volgorde waarin de server ze uitvoert:

ControleMislukt alsStatusWie controleert
framinggeen lengte bij POST of PUT; een misvormde lengte of een te korte body; te lang411; 400; 413read_body
route en zo’n pad bestaat niet, of het pad accepteert deze methode niet404, 405handle
boek-idgeen boek heeft die id404handle
mediatypecontent-type is niet application/json415body_error
syntaxisde bytes zijn geen -JSON400body_error
vorm en geen met niet-lege title en author422book_error

Een verzoek dat door elke rij komt, bereikt de schrijfactie en krijgt 201, 200 of 204. Over de plaats van twee rijen moet iets gezegd worden. Framing komt vóór de route, omdat read_body in de adapter draait voordat handle iets ziet: daarom krijgt een POST /nowhere zonder lengte 411, niet 404. En de boek-id komt vóór de body, dus een PUT naar een boek dat niet bestaat, is een 404, wat de body ook zegt. Volg in het diagram een fout verzoek langs de controles omlaag tot het rechts vertrekt:

De controles lopen van framing omlaag naar vorm en waarden, de eerste die mislukt, bepaalt de status, en alleen een verzoek dat ze allemaal doorstaat, bereikt de schrijfactie.

400 of 422

Een 400 en een 422 kunnen allebei een foute body beantwoorden; het verschil is hoe ver de server kwam. 400 Bad Request zegt dat het verzoek helemaal niet gelezen kon worden: deze bytes laten zich niet als JSON parsen. 422 Unprocessable Content zegt dat de server het prima kon lezen en niet kan doen wat erin staat (RFC 9110, sectie 15.5.21): de JSON is geldig, maar het is een lijst waar een object hoort, of de titel is 7, of de auteur is drie spaties.

Vorm controleren betekent controleren. isinstance(value, dict) vraagt of een waarde een is, en dat is wat een JSON-object wordt; isinstance(value, str) vraagt of het een string is. Een titel die leeg is zodra strip() de spaties weghaalt, is net zo leeg als "".

Wat drukt dit af?

print(isinstance([1, 2], dict), isinstance({"title": 3}["title"], str))

PUT /books/7 (boek 7 bestaat) draagt {"title": "", "author": "Ana Ruiz"}, gelabeld als application/json, met een juiste lengte. Welke status?

Eerst controleren, dan wijzigen

book_error(data) controleert een gedecodeerde waarde: geen dict, een ontbrekende title of author, of een die geen niet-lege string is, elk geval krijgt een 422 waarvan de fout het probleem noemt. Schrijf elke melding voor de persoon die het antwoord leest: author is missing vertelt wat er veranderd moet worden, en bad request niet. Je mag eerst title of eerst author controleren; hoe dan ook is het eerste gevonden probleem het probleem dat wordt gemeld. book_request_error(request) voert de hele bodycontrole uit: eerst body_error, en alleen als die niets vindt, book_error(json_body(request)). Elk van beide geeft None terug of het antwoord dat verstuurd moet worden, zoals read_body en body_error doen, dus handle houdt de eerste fout die hij krijgt:

response = book_request_error(request)
if response is None:
    response = create_book(request, state)

De volgorde binnenin doet er twee keer toe. book_error mag niet draaien op bytes die body_error heeft geweigerd, omdat json_body daarop een exception zou opwerpen. En create_book mag niet draaien voordat elke controle geslaagd is: een boek dat vóór een 422 is opgeslagen, of een next_id die is opgehoogd, zou een wijziging zijn waarvan de client te horen kreeg dat die niet gebeurd is.

PUT /books/99 (zo'n boek bestaat niet) draagt een body gelabeld als text/csv. Welke status, volgens de volgorde van dit contract?

Jouw opdracht

In reading_server.py:

  1. Schrijf book_error(data): geef een 422-antwoord terug, gebouwd met json_error, als data geen dict is, als title of author ontbreekt, of als een van beide geen string is of leeg is na het strippen. Geef anders None terug.

  2. Schrijf book_request_error(request): geef body_error(request) terug als dat niet None is; geef anders book_error(json_body(request)) terug.

  3. Roep in handle book_request_error aan vóór create_book en vóór replace_book, en stuur het antwoord ervan als het er een teruggeeft.

Voer python pl_http.py bad-requests opnieuw uit. Elk verzoek krijgt nu een eigen antwoord: HTTP/1.0 415 Unsupported Media Type voor de text/plain-body, HTTP/1.0 400 Bad Request voor de afgebroken JSON, HTTP/1.0 422 Unprocessable Content voor de lijst, HTTP/1.0 404 Not Found voor de PUT naar boek 99, en HTTP/1.0 411 Length Required voor de POST zonder lengte, die je read_body beantwoordde voordat handle draaide.

Klik daarna op Check my work. Je server weigert nu elke foute schrijfactie met een reden; de volgende les bewijst met je eigen tests dat de goede schrijfacties hun beloften nakomen.

Opdracht

Geef elke foute schrijfactie een eigen status, en verander niets als een verzoek wordt geweigerd.

In reading_server.py:

  1. Schrijf book_error(data). Geef een 422-antwoord terug als data geen is, als het geen title of geen author heeft, of als een van beide geen is of leeg is nadat de spaties zijn weggehaald. Geef anders None terug.

  2. Schrijf book_request_error(request). Geef terug wat body_error(request) teruggeeft als dat niet None is; geef anders book_error(json_body(request)) terug.

  3. Roep in handle book_request_error aan vóór create_book en vóór replace_book. Als die een antwoord teruggeeft, stuur je dat antwoord en roep je de schrijfactie niet aan.

Bouw elk foutantwoord met json_error en een zin die het probleem noemt. Houd de volgorde van handle aan: route en , dan de boek-id, dan de body.

In de stuurt python pl_http.py bad-requests vijf foute verzoeken naar je server.

Klik daarna op Check my work. Die roept book_error aan met die je nog niet hebt gezien: , getallen, null, ontbrekende velden, getallen en lege waarden waar strings horen, en geldige boeken met extra velden. Hij roept book_request_error en handle aan met telkens één oorzaak, op POST /books en op PUT naar een bestaand boek, en controleert voor elk de status en dat de state, inclusief next_id, precies is zoals ervoor. Hij stuurt ook een PUT met een verkeerd label naar een boek dat niet bestaat, en controleert dat geldige schrijfacties nog steeds werken. Niets uit je terminal wordt beoordeeld.