How the Web Works
Expert · 18 min read · ▶ live playground · ✦ checkpoint
Every web page, mobile app, and Python service that talks over the internet is built on the same small conversation: ask for something, get an answer back. Once you can read that conversation, APIs become clear too: an API is a program boundary that lets software ask other software for work or data.
This page does not contact the internet. Your browser Python runtime is offline, so you will inspect canned web messages and JSON, a text data format for structured values, in the same shape real web traffic uses.
The web conversation
A client is a program that sends a message asking for something, such as a browser, mobile app, or Python script. A server is a program that waits for those messages, does work, and sends answers back.
A resource is a named piece of data or behavior, like one book record, a reading-log list, or a checkout action. A request is the message a client sends to ask for a resource or action. A response is the message the server sends back.
A protocol is an agreed set of message rules, and HTTP is the protocol most web clients and servers use for requests and responses. Think of HTTP like an order slip at a cafe: the customer writes the action, the counter, a few notes, and sometimes the main content; the cafe sends back a tray with a status label and the result.
A URL is the address of a web resource, and the path is the part after the server name that names the resource on that server. A query string is the optional ?name=value part after the path that carries filters or search values. A method is the HTTP action word, such as GET for reading data. A header is a named extra fact about the message, like "I accept JSON." A body is the optional main content carried inside a request or response.
raw_request = """GET /api/books?author=Mina HTTP/1.1
Host: api.bookclub.test
Accept: application/json
User-Agent: Python learner
"""
request_line, *header_lines = raw_request.strip().splitlines()
method, target, protocol = request_line.split()
path, query_string = target.split("?", 1)
headers = {}
for line in header_lines:
name, value = line.split(": ", 1)
headers[name.lower()] = value
print("Method:", method)
print("Path:", path)
print("Query:", query_string)
print("Protocol:", protocol)
print("Accepts:", headers["accept"])Method: GETPath: /api/booksQuery: author=MinaProtocol: HTTP/1.1Accepts: application/json
Those pieces are boring in the best way. A server does not guess your intent from vibes. It reads the method, path, headers, and body, then decides what response to send.
Status codes
A status code is a three-digit number in an HTTP response that summarizes what happened. It is the stamp on the cafe tray: before you inspect the food, you know whether the order worked, needs correction, or hit a kitchen problem.
Status codes come in families:
1xxmeans informational/provisional, a temporary "keep going" note before the final response.2xxmeans success, such as200 OKfor a normal response or201 Createdafter creating something.3xxmeans redirection, where the client may need a different URL.4xxmeans the client sent a request the server cannot or will not satisfy, such as400 Bad Request,401 Unauthorized,403 Forbidden, or404 Not Found.5xxmeans the server failed while trying to handle a valid-looking request.
def explain_status(code):
if 100 <= code < 200:
return "informational/provisional"
if 200 <= code < 300:
return "success"
if 300 <= code < 400:
return "redirect"
if 400 <= code < 500:
return "client-side problem"
if 500 <= code < 600:
return "server-side problem"
return "unusual status"
responses = [
(100, "Continue"),
(200, "OK"),
(201, "Created"),
(404, "Not Found"),
(500, "Internal Server Error"),
]
for code, phrase in responses:
print(f"{code} {phrase}: {explain_status(code)}")100 Continue: informational/provisional200 OK: success201 Created: success404 Not Found: client-side problem500 Internal Server Error: server-side problem
The status code does not replace the response body. It gives the first, quick reading. The body can carry page text, JSON data, or an error explanation.
raw_response = """HTTP/1.1 404 Not Found
Content-Type: application/json
{"error": "book not found", "requested_id": 42}
"""
header_text, body_text = raw_response.split("\n\n", 1)
status_line, content_type_line = header_text.splitlines()
protocol, status_text, reason = status_line.split(" ", 2)
status_code = int(status_text)
print("Protocol:", protocol)
print("Status:", status_code)
print("Reason:", reason)
print("Body:", body_text.strip())Protocol: HTTP/1.1Status: 404Reason: Not FoundBody: {"error": "book not found", "requested_id": 42}
REST as an API shape
An API is a documented program boundary. REST is an API design style that treats application data as named resources and uses HTTP methods to work with those resources.
The concrete analogy: REST is like a library desk with labeled shelves and action stamps. /books/42 names the shelf card; GET, POST, PATCH, and DELETE tell the clerk what kind of action you want.
An endpoint is a specific method plus URL path that an API agrees to handle. REST APIs usually make endpoint names nouns, not verbs, because the method already carries the action.
routes = [
("GET", "/books", "list books"),
("GET", "/books/42", "read one book"),
("POST", "/books", "create a book"),
("PATCH", "/books/42", "update part of a book"),
("DELETE", "/books/42", "remove a book"),
]
for method, path, meaning in routes:
print(f"{method:<6} {path:<12} -> {meaning}")GET /books -> list booksGET /books/42 -> read one bookPOST /books -> create a bookPATCH /books/42 -> update part of a bookDELETE /books/42 -> remove a book
REST is a style, not a Python package. In lesson 23.2, you will be the client consuming APIs. In lesson 23.3, you will see how Python can be the server that defines endpoints.
JSON, the API data format
JSON is a text data format for sending structured values between programs. It is the packing slip both sides can read: strings, numbers, booleans, empty values, lists, and key-value records written in one predictable text shape.
A JSON object is a key-value collection that maps naturally to a Python dictionary. A JSON array is an ordered collection that maps naturally to a Python list. JSON is not Python, though: JSON uses double-quoted strings, true, false, and null; Python turns those into True, False, and None when you parse it.
import json
response_body = """
{
"book": {
"id": 42,
"title": "Clean APIs",
"available": true,
"tags": ["web", "python"]
},
"borrower": null
}
"""
payload = json.loads(response_body)
book = payload["book"]
print(f"{book['title']} is tagged: {', '.join(book['tags'])}")
print("Available?", book["available"])
print("Borrower:", payload["borrower"])
print("Python type:", type(payload).__name__)Clean APIs is tagged: web, pythonAvailable? TrueBorrower: NonePython type: dict
That json.loads() call is the handoff point. Before it runs, you have text. After it runs, you have normal Python objects: dictionaries, lists, strings, numbers, booleans, and None.
Try it - read a whole API response
This playground parses one complete canned response: first line, headers, and JSON body. Change the status code to 500, remove one item, or edit the next_page value and run it again.
When a real Python client calls an API, the networking library handles the actual internet exchange. Your job is still this: check the status, read the headers that matter, parse the JSON body, and then work with ordinary Python data.
Checkpoint
Answer all three to mark this lesson complete
You now have the web conversation map: HTTP request in, HTTP response out, REST for naming actions, and JSON for carrying structured data. Next, you will use that map as a Python client: consuming APIs, sending headers, handling pages of results, and scraping responsibly when an API is not available.