Server
The Post Office
We've built all the pieces:
- Requests -- reading the incoming letters
- Responses -- writing the reply letters
- Router -- the mailroom that sorts letters to handlers
- Templates -- form letters with blanks to fill in
Now we need the post office itself -- the building that sits on the street, waits for letters to arrive, sorts them, and sends replies.
That's the server.
How a Web Server Works
1. Open the door (bind to a port)
2. Wait for someone to walk in (accept a connection)
3. Read their letter (receive the HTTP request)
4. Sort it in the mailroom (dispatch through the router)
5. Hand them the reply (send the HTTP response)
6. Go back to step 2
In code:
from pyweb.router import Router
from pyweb.response import html_response
from pyweb.server import Server
router = Router()
@router.get("/")
def homepage(request):
return html_response("<h1>Welcome to my website!</h1>")
server = Server(router, host="127.0.0.1", port=8000)
server.serve_forever()
Visit http://127.0.0.1:8000 in your browser and you'll see your page!
What's a Port?
Think of the server's address like a big apartment building. The IP address (127.0.0.1) is the street address, and the port (8000) is the apartment number. Many servers can run on the same computer, each on a different port.
- Port 80 = the standard "front door" for websites
- Port 443 = the secure (HTTPS) door
- Port 8000 = a side door we use for development
What's a Socket?
Under the hood, the server uses a socket -- a connection point, like a mailbox slot. The server creates a socket, binds it to a port, and listens for incoming letters. When a letter arrives, it reads it, processes it, and drops the reply back into the slot.
Static Files
PyWeb can serve files directly from a folder -- HTML pages, CSS stylesheets, images, and JavaScript:
Now files in the ./public folder are accessible at /static/style.css,
/static/logo.png, etc.
Under the hood, router.static(...) registers a wildcard route
(GET /static/*) so that every sub-path -- not just /static exactly --
routes to the file server. If you prefer the explicit form:
from pyweb.static import serve_static
router.add_route("GET", "/static/*", serve_static("./public"))
Logging
Every request is logged so you can see what is happening:
Surviving Rude Visitors
Our post office serves one visitor at a time. That keeps the code simple, but it means one badly-behaved visitor could close the doors on everyone else. So the server protects itself:
- The visitor who hangs up halfway. If a connection breaks mid-request (a "reset"), we shrug, drop that one connection, and serve the next person.
- The visitor who says nothing. Every visitor gets a time limit
(
CLIENT_TIMEOUT, 5 seconds). If they stand silently at the counter, we politely show them out so the queue keeps moving. - The endless letter. Headers bigger than 16 KB get
431 Request Header Fields Too Large; a body bigger than 1 MB gets413 Payload Too Large-- answered before we try to read it all into memory.
Real servers like nginx do all of these too (and serve many visitors at once, which is a lesson for another day).
What We Test
- The server processes valid requests and returns correct responses.
- Malformed requests (the client's fault) return 400 Bad Request.
- Handlers that crash (our fault) return 500 Internal Server Error.
- Unknown paths return 404.
- The server can be started and stopped.
- A real server on a free port survives a client that hangs up halfway, a client that never speaks, oversized headers (431) and an oversized body (413) -- and keeps serving the next visitor.
What's Next?
You've built a complete web server from scratch! It can parse HTTP requests, route URLs, render templates, serve static files, and send responses. That's the same thing nginx, Apache, and Flask do -- just simpler and built for learning.