0%

What a Framework Does for You

See FastAPI's Bytes

Every reply your app has sent so far went from to TestClient as Python . No byte of it crossed a , so you have not yet seen what FastAPI’s 404 looks like on the wire. A server program does that work: it accepts connections, reads request bytes, hands each request to the app, and writes the app’s reply as bytes. For FastAPI that program is often uvicorn, and this chapter’s helper can run it for you.

A server that stops

Every server in this course is bounded, and this one is too. pl_http binds a socket to 127.0.0.1 on a the system chooses, as your make_server did, hands that socket to uvicorn, and tells uvicorn to stop after three requests. Then it sends three requests with wire.fetch from wire.py, the Chapter 9 client, and prints the bytes in both directions. uvicorn stops on its own shortly after the third reply and closes its listening socket. If the client finished early, pl_http would tell uvicorn to stop and wait at most 2 seconds for it. No server in this course waits for you to stop it by hand.

Run it in the Shell tab:

python pl_http.py uvicorn

With Lesson 1’s in place, the first exchange is GET /books. As in earlier chapters, the Host line names books.test while the connection goes to 127.0.0.1:

client -> server  b'GET /books HTTP/1.1\r\nHost: books.test\r\n\r\n'  (41 bytes)
server -> client  b'HTTP/1.1 200 OK\r\ndate: ...\r\nserver: uvicorn\r\ncontent-length: 120\r\ncontent-type: application/json\r\n\r\n{"books":[{"id":1,"title":"Kindred","author":"Octavia E. Butler"},{"id":2,'...  (246 bytes)

The date line holds the time of your run, shown here as .... pl_http cuts a long reply short, but wire.fetch read the whole body:

body: b'{"books":[{"id":1,"title":"Kindred","author":"Octavia E. Butler"},{"id":2,"title":"Caf\xc3\xa9 Society","author":"Ana Ruiz"}]}'  (120 bytes)

No space follows a : or a ,. Your server’s json.dumps put a space after each, so its body for the same two books is 132 bytes.

The second request is HEAD /books, and the 405 from Lesson 2 arrives on the wire:

client -> server  b'HEAD /books HTTP/1.1\r\nHost: books.test\r\n\r\n'  (42 bytes)
server -> client  b'HTTP/1.1 405 Method Not Allowed\r\ndate: ...\r\nserver: uvicorn\r\nallow: GET\r\ncontent-length: 31\r\ncontent-type: application/json\r\n\r\n'  (153 bytes)

The content-length of 31 is the size of FastAPI’s body for this error, {"detail":"Method Not Allowed"}, and no body follows: uvicorn sends none in reply to HEAD, the rule you wrote by hand in Lesson 4.5. The third request asks for a book that does not exist:

client -> server  b'GET /books/99 HTTP/1.1\r\nHost: books.test\r\n\r\n'  (44 bytes)
server -> client  b'HTTP/1.1 404 Not Found\r\ndate: ...\r\nserver: uvicorn\r\ncontent-length: 25\r\ncontent-type: application/json\r\n\r\n{"detail":"no such book"}'  (157 bytes)

wire.fetch needed nothing new for any of this. It read each head, found content-length, and read exactly that many bytes, or none after HEAD: the framing rule from Chapter 9. No reply says connection: close, so under /1.1 uvicorn keeps each connection open for another request, and the length is the only way the client knows where a body ends; wire.fetch closes its side once it has read it. A client needs only bytes that follow HTTP, whichever program wrote them. The run ends:

--- uvicorn's log ---
(nothing)
--- result ---
uvicorn stopped on its own after 3 requests; the listening socket is closed
pl_http: every thread has stopped

If a function still raises NotImplementedError, its request gets HTTP/1.1 500 Internal Server Error, and uvicorn’s log names the , such as error: Exception in ASGI application: NotImplementedError: not written yet: Lesson 1.

Who wrote which line

Each line of the 404 came from one of three places:

LineWritten by
the status 404your function, through HTTPException
Not Found and the rest of the status lineuvicorn
date and server: uvicornuvicorn
content-type: application/json and content-length: 25FastAPI, which encoded the reply as and counted its bytes
the body {"detail":"no such book"}your function chose the reason; FastAPI wrote the JSON

In the 405, FastAPI chose the status and wrote allow: GET too; your function never ran. The header names are lowercase because the interface between the app and the server, ASGI, hands them over that way, so Starlette, the toolkit FastAPI is built on, lowercases content-type, content-length, and allow, and uvicorn writes its own date and server lines the same way. http.server wrote Server and Date. Names ignore case, so both are correct.

One more thing happens out of sight. FastAPI runs a plain def function such as get_book on a worker thread, so a slow function cannot hold up the server while it waits. A function written with async def would instead let one thread overlap many waits; this course keeps plain def, and Chapter 13 names async HTTP as a route to follow.

Which line did uvicorn add, not your function or FastAPI?

Your server sent 132 body bytes for the same two books; this run shows 120. Why?

Why does this run end on its own?

This run is for watching, and nothing in it is graded. Lesson 5 finishes the app with DELETE and the limit query. After that, Chapter 12 brings your own server back and joins it to a client.