Art of Codeartofcode
LLeeaarrnniinngg
BBlloogg
TToooollss
GGaammeess AArreennaa
JJoobbss
LLiivvee CCoouurrsseess

Courses

  • System DesignHigh-level system design, one everyday scene at a time: 30 concepts and 15 interview rounds.712 lessons
All coursesTutorialsMy Learning

Categories

  • 01AI & Tools
  • 02Tutorials
  • 03Engineering
  • 04Reviews
  • 05Build Logs
  • 06Tales of Misfits
  • 07Opinion
  • 08Career
  • 09News

Read & write

All storiesEvery post, newest first.Write for usPublish your stories and earn from what you write.Writer leaderboardsWho is writing the most read posts.

Categories

  • PDF Tools25
  • Development09
  • Utilities10
  • Design04
  • Converters04
  • Generators02

Featured

JSON FormatterFormat, validate and minify JSON.Regex TesterTest patterns against live text.Merge PDFCombine PDFs in your browser.JWT DecoderRead a token’s header and claims.Password GeneratorStrong passwords, generated locally.Gradient GeneratorBuild CSS gradients visually.
All 53 tools

Play a round

DevdleOne developer word a day, six guesses.Python RushClassic snake, with coding icons to eat.Blink or BreakA quick test of your reflexes.
Games Arena

Learn with us

Live CoursesInstructor-led courses. Join a waitlist and be first in when one starts.LearningStructured programming courses with AI assistance and certificates.

System Design

3. Client and Server

Lesson 18 of 18

3. Client and Server

1
The picture to keep in your head
2
3.1 What exactly are a client and a server?
3
3.2 What's a request and a response?
4
3.3 What happens when you type a URL and press Enter?
5
3.4 What do GET, POST, PUT, PATCH and DELETE mean?
6
3.5 What do status codes tell you?
7
3.6 What goes in the headers and what goes in the body?
8
3.7 What's the difference between TCP and UDP?
9
3.8 How does HTTPS keep the conversation private?
10
3.9 Why do keep-alive, HTTP/2 and HTTP/3 make things faster?
11
3.10 What does "stateless" mean, and why do we want it?
12
3.11 How do cookies, sessions and tokens remember who you are?
13
Memory card and where the analogy breaks
14
Practice: five levels
15
Interview questions
16
Solutions
17
Sources
18
Code lab
3. Client and Server

Code lab

3 min read+10 XP

Code lab: Chapter 3

The full, tested code for this chapter. It uses real threads or a local web server, which Python in the browser can't run, so copy it and run python3 ch03_client_server.py on your machine. Every check should pass.

python

No tracks available

Lofi Player

artofcode

artofcode.in

The den of devs.

Stories, tools, games, and gear for developers.

Read the StoriesEExxpplloorree TToooollss
Art of Codeartofcode

Platform

  • BBlloogg
  • TToooollss
  • GGaammeess AArreennaa
  • CCoouurrsseess
  • LLeeaarrnniinngg
  • CCaammppuuss

Company

  • AAbboouutt UUss
  • JJoobbss && IInntteerrnnsshhiippss
  • CCoonnttaacctt

Legal

  • TTeerrmmss ooff SSeerrvviiccee
  • PPrriivvaaccyy PPoolliiccyy
  • RReeffuunndd PPoolliiccyy

Copyright © 2026 artofcode.in, All Rights Reserved.

IST·--:--

1"""Chapter 3 practice: a tiny HTTP server for a bookstore (Level 3 Build) and the
2'sessions stored on one server' bug behind a load balancer (Level 4 Debug).
3Standard library only. Run: python3 ch03_client_server.py"""
4import itertools
5import json
6import secrets
7import threading
8import urllib.error
9import urllib.request
10from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
11
12# ---- Level 3: a tiny bookstore API -------------------------------------------------
13
14BOOKS = {"42": {"id": "42", "title": "Designing Systems", "price": 499}}
15CART = []
16
17
18class BookstoreHandler(BaseHTTPRequestHandler):
19    def _send(self, status, body=None):
20        data = b"" if body is None else json.dumps(body).encode()
21        self.send_response(status)
22        self.send_header("Content-Type", "application/json")
23        self.send_header("Content-Length", str(len(data)))
24        self.end_headers()
25        self.wfile.write(data)
26
27    def do_GET(self):
28        if self.path.startswith("/books/"):
29            book = BOOKS.get(self.path.rsplit("/", 1)[1])
30            return self._send(200, book) if book else self._send(404, {"error": "no such book"})
31        self._send(404, {"error": "not found"})
32
33    def do_POST(self):
34        if self.path != "/cart":
35            return self._send(405, {"error": "method not allowed"})
36        length = int(self.headers.get("Content-Length", 0))
37        try:
38            item = json.loads(self.rfile.read(length) or b"{}")
39        except json.JSONDecodeError:
40            return self._send(400, {"error": "body is not valid JSON"})
41        if item.get("book_id") not in BOOKS:
42            return self._send(400, {"error": "unknown book_id"})
43        CART.append(item)
44        self._send(201, {"cart_size": len(CART)})
45
46    def do_DELETE(self):
47        self._send(405, {"error": "method not allowed"})
48
49    def log_message(self, *args):  # keep test output quiet
50        pass
51
52
53def request(method, url, body=None):
54    data = None if body is None else (body if isinstance(body, bytes) else json.dumps(body).encode())
55    req = urllib.request.Request(url, data=data, method=method,
56                                 headers={"Content-Type": "application/json"})
57    try:
58        with urllib.request.urlopen(req) as r:
59            return r.status, json.loads(r.read() or b"null")
60    except urllib.error.HTTPError as e:
61        return e.code, json.loads(e.read() or b"null")
62
63
64# ---- Level 4: sessions behind a load balancer ---------------------------------------
65
66class BuggyAppServer:
67    """Keeps logged-in sessions in its own memory."""
68
69    def __init__(self):
70        self.sessions = {}  # BUG: each server has its own, separate notebook
71
72    def login(self, user):
73        sid = secrets.token_hex(8)
74        self.sessions[sid] = user
75        return sid
76
77    def whoami(self, sid):
78        return self.sessions.get(sid)  # None means "please log in again"
79
80
81class SharedStore:
82    """Stands in for Redis: one notebook every server can read."""
83
84    def __init__(self):
85        self.data = {}
86
87
88class FixedAppServer:
89    def __init__(self, store):
90        self.store = store  # fix: every server uses the same shared store
91
92    def login(self, user):
93        sid = secrets.token_hex(8)
94        self.store.data[sid] = user
95        return sid
96
97    def whoami(self, sid):
98        return self.store.data.get(sid)
99
100
101def round_robin(servers):
102    return itertools.cycle(servers)
103
104
105def run_checks():
106    server = ThreadingHTTPServer(("127.0.0.1", 0), BookstoreHandler)
107    threading.Thread(target=server.serve_forever, daemon=True).start()
108    base = f"http://127.0.0.1:{server.server_address[1]}"
109    try:
110        assert request("GET", base + "/books/42") == (200, BOOKS["42"])
111        assert request("GET", base + "/books/7")[0] == 404
112        assert request("POST", base + "/cart", {"book_id": "42"}) == (201, {"cart_size": 1})
113        assert request("POST", base + "/cart", {"book_id": "nope"})[0] == 400
114        assert request("POST", base + "/cart", b"{not json")[0] == 400
115        assert request("DELETE", base + "/books/42")[0] == 405
116    finally:
117        server.shutdown()
118
119    # Level 4: two servers, round-robin load balancer.
120    lb = round_robin([BuggyAppServer(), BuggyAppServer()])
121    sid = next(lb).login("asha")          # login lands on server A
122    seen = [next(lb).whoami(sid) for _ in range(4)]   # B, A, B, A
123    assert seen == [None, "asha", None, "asha"], seen  # logged out on every other click
124
125    store = SharedStore()
126    lb = round_robin([FixedAppServer(store), FixedAppServer(store)])
127    sid = next(lb).login("asha")
128    assert [next(lb).whoami(sid) for _ in range(4)] == ["asha"] * 4
129
130    print("All checks pass. Buggy sessions:", seen)
131
132
133if __name__ == "__main__":
134    run_checks()