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 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 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:
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 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 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
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.
create_book(request, state): store{"id": ..., "title": ..., "author": ...}understate["next_id"], add 1 tonext_id, and returnjson_response(201, book)with alocationheader added:/books/followed by the id.replace_book(request, state, book_id): store a new book withbook_idand the body’stitleandauthor; returnjson_response(200, book).delete_book(state, book_id): remove the book and return the 204 dict above.In
handle, callcreate_bookforPOSTon/books, andreplace_bookanddelete_bookforPUTandDELETEon/books/{id}. Change theallowvalues toGET, HEAD, POSTfor the collection andGET, HEAD, PUT, DELETEfor 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:
create_book(request, state): store{"id": ..., "title": ..., "author": ...}understate["next_id"], using the body’stitleandauthor, then add 1 tostate["next_id"]. Returnjson_response(201, book)with alocationheader added whoseis /books/followed by the new id.replace_book(request, state, book_id): store a new book with idbook_idand the body’stitleandauthor, and returnjson_response(200, book). The id always comes from the, never from the body. delete_book(state, book_id): remove the book and return{"status": 204, "headers": {}, "body": b""}: no body and nocontent-length.In
handle:POSTon/bookscallscreate_book;PUTandDELETEon/books/{id}callreplace_bookanddelete_bookonce the book is known to exist (an unknown id still gets 404). The 405 repliesGET, HEAD, POSTon/booksandGET, HEAD, PUT, DELETEon/books/{id}.
In this lesson every request body is a 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 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.