0%

Request Bodies and a JSON API · practice

Create, Replace, Delete

The table from the last lesson says what each write does and answers. Your server does none of it yet. Run the helper’s write scenario from the Shell tab:

python pl_http.py writes

It sends POST /books with a new book, then GET, PUT, DELETE, and GET again on /books/3, the id the new book should get. Today the POST gets 405, because /books allows only GET and HEAD, and the four requests for /books/3 get 404, because no book 3 was ever created. By the end of this lesson the same run shows the book appearing, changing, and disappearing.

In this lesson every body is a valid book: a with a title and author, labeled application/json. Lesson 5 adds the checks for everything else.

201 and location

A POST /books that stores a book answers 201 Created. Its body is the book as stored, id included, and a location header says where the new book lives. Here is the reply, with the Server and Date lines left out:

HTTP/1.0 201 Created
content-type: application/json
content-length: 58
location: /books/3

{"id": 3, "title": "Piranesi", "author": "Susanna Clarke"}

A 200 would also say the request succeeded; 201 says more, that a new resource now exists, and location says where. location: /books/3 is a path, not a full . allows that: a client resolves a relative Location against the URL it requested (RFC 9110, section 10.2.2), the same way a browser resolves a link on a page. Chapter 6 writes that resolution for your client. The id in the path is the one the server chose, so the client learns it from the reply without parsing the body.

The id comes from state["next_id"]. Read it, store the book under it, and add 1, so the next POST gets the next id. An id in the body is ignored: the server picks ids for POST.

After POST /books creates book 41, what should location say?

204 means no body

A DELETE that removes a book answers 204 No Content: success, with nothing to send. A 204 has no body by definition, so it also carries no content-length (RFC 9110, section 8.6, forbids one on a 204). That rules out json_response(204, ...), which adds both. Build this response by hand:

Try it

On the wire it is a status line, the Server and Date lines http.server adds, and the blank line. Nothing follows it.

Clients handle a 204 differently. http.client knows a 204 has no body and reads nothing after it. The fetch in your wire.py, written in Chapter 1, knows only HEAD as bodiless, so on a 204 with no content-length it raises , the same refusal it gives any reply without a length. If your handle did return a body on a 204, http.server would send it anyway, and pl_http.py would show the stray bytes on a line of their own under the reply, such as (5 bytes followed the reply: b'stray').

Which reply to DELETE /books/41 is right for this server?

Replace means all of it

PUT /books/7 replaces book 7 with what the body says. The new book is built from the body’s title and author and the id from the URL, 7, never from the body. Build a new rather than updating the old one: PUT replaces, so nothing of the old book survives except its id. The reply is 200 OK with the stored book.

A PUT or DELETE to an id with no book gets 404, which your handle already answers before it looks at the . Keep it that way: the write are called only for a book that exists.

Your task

In reading_server.py, the body is already read and, in this lesson, valid. Use the supplied json_body(request) to decode it.

  1. create_book(request, state): store {"id": ..., "title": ..., "author": ...} under state["next_id"], add 1 to next_id, and return json_response(201, book) with a location header added: /books/ followed by the id.

  2. replace_book(request, state, book_id): store a new book with book_id and the body’s title and author; return json_response(200, book).

  3. delete_book(state, book_id): remove the book and return the 204 dict above.

  4. In handle, call create_book for POST on /books, and replace_book and delete_book for PUT and DELETE on /books/{id}. Change the allow values to GET, HEAD, POST for the collection and GET, HEAD, PUT, DELETE for an item.

Run python pl_http.py writes again. The POST now gets HTTP/1.0 201 Created with location: /books/3, the GET reads the new book, the PUT gets 200 OK with the new title, the DELETE gets HTTP/1.0 204 No Content followed by nothing but the blank line, and the last GET gets 404: the book is gone.

Then press Check my work. Next, the server stops trusting bodies: each kind of bad request gets its own status.

Task

Make the three writes work, for bodies that are valid books.

In reading_server.py, decode the body with the supplied json_body(request) and write:

  1. create_book(request, state): store {"id": ..., "title": ..., "author": ...} under state["next_id"], using the body’s title and author, then add 1 to state["next_id"]. Return json_response(201, book) with a location header added whose is /books/ followed by the new id.

  2. replace_book(request, state, book_id): store a new book with id book_id and the body’s title and author, and return json_response(200, book). The id always comes from the , never from the body.

  3. delete_book(state, book_id): remove the book and return {"status": 204, "headers": {}, "body": b""}: no body and no content-length.

  4. In handle: POST on /books calls create_book; PUT and DELETE on /books/{id} call replace_book and delete_book once the book is known to exist (an unknown id still gets 404). The 405 replies GET, HEAD, POST on /books and GET, HEAD, PUT, DELETE on /books/{id}.

In this lesson every request body is a with title and author, labeled application/json. Lesson 5 handles the rest.

In the , python pl_http.py writes creates, reads, replaces, and deletes a book through your server.

Then press Check my work. It calls handle with a state and books you have not seen: two POSTs, a PUT sent twice, a DELETE, writes to ids that hold no book, reads after the writes, and each path does not take. Bodies carry an id that must be ignored. It checks statuses, location, the stored state, next_id, and that a 204 has neither a body nor a content-length. Then it sends a POST and a DELETE through the adapter in memory, with your read_body, and reads the bytes. Nothing from your terminal is graded.