{ "cells": [ { "cell_type": "raw", "metadata": {}, "source": [ "---\n", "title: \"Notebook 10: OS, Networks, Cloud & the Web — The Case of the Vanishing Website\"\n", "subtitle: \"COMP 1150 — Computer Science Concepts\"\n", "author: \"Brendan Shea, PhD\"\n", "date: last-modified\n", "---" ] }, { "cell_type": "markdown", "metadata": { "colab_header": true }, "source": [ "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/brendanpshea/computing_concepts_python/blob/main/v2/notebooks/COMP1150_NB10_OSNetworksWeb.ipynb) \n", "[Download .ipynb](https://raw.githubusercontent.com/brendanpshea/computing_concepts_python/main/v2/notebooks/COMP1150_NB10_OSNetworksWeb.ipynb) · [View on GitHub](https://github.com/brendanpshea/computing_concepts_python/blob/main/v2/notebooks/COMP1150_NB10_OSNetworksWeb.ipynb)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Learning Outcomes\n", "\n", "By the end of this notebook, you will be able to:\n", "\n", "- Explain what an operating system does: managing processes, files, and memory\n", "- Describe how a request crosses the internet: IP addresses, DNS, packets, and TCP\n", "- Read an HTTP conversation: requests, responses, and status codes\n", "- Call a real web API with Python's `requests` library and parse the JSON it returns\n", "- Distinguish the cloud service models — IaaS, PaaS, and SaaS — and give an example of each\n", "- Build and run a tiny web server with Flask, and explain the request/response cycle\n", "\n", "*Maps to course LOs: 3*\n", "\n", "## The Case of the Vanishing Website\n", "\n", "**Irene Adler** runs Adler Streaming, a video platform with two million subscribers. This morning, it vanished.\n", "\n", "Not for everyone. That's the maddening part. Support tickets say the site is down in Edinburgh but fine in Cardiff. Down on phones, fine on laptops. One customer watched half a film, refreshed, and got nothing.\n", "\n", "Irene doesn't panic; she hires. Specifically, she hires **Baker Street Diagnostics**, a two-person consultancy run by **Sherlock Holmes** — a man who believes every technical failure is a crime scene — and **Dr. John Watson**, who translates Sherlock into English.\n", "\n", "Sherlock's opening statement, delivered while Irene is still explaining:\n", "\n", "> \"Your website is not a place, Ms. Adler. It is a *conversation* — a request that leaves a customer's device, crosses machines you've never met, and returns with an answer. Somewhere along that road, the conversation is dying. We will follow the request. Every hop is a suspect. We eliminate them one at a time.\"\n", "\n", "That sentence is this notebook's whole plan.\n", "\n", "So far in this course, your programs have lived alone: one file, one machine, no outside world. But almost nothing you actually *use* works that way. Every app on your phone is a conversation between your device and distant computers — crossing an operating system, a network, and somebody's rented cloud.\n", "\n", "To find Irene's fault, we have to understand every suspect on the road:\n", "\n", "1. **The machine itself** — what an operating system actually does.\n", "2. **The address lookup** — how `adlerstreaming.com` becomes a number a network can use.\n", "3. **The journey** — packets, and the internet's delivery service.\n", "4. **The conversation** — HTTP, the strict little language of the web.\n", "5. **The server's home** — the cloud, and whose computer it really is.\n", "\n", "And then the finale: the only way to truly understand a web server is to *become one*. At the end, you'll build and run your own.\n", "\n", "## 1. The Machine Underneath: What an Operating System Does\n", "\n", "Sherlock starts every case the same way: at the scene, with the body. Here, the \"body\" is a computer — and the first thing to understand about any computer is that your programs never actually touch it.\n", "\n", "Between every program and the physical hardware sits the **operating system** (OS) — Windows, macOS, Linux, Android, iOS. It has three jobs that matter to us:\n", "\n", "- **Processes.** A **process** is one running program. The OS starts them, stops them, and slices CPU time among them so that dozens can *appear* to run at once.\n", "- **Files.** The OS owns the disk and organizes it into the **file system** — folders and files with names, so no program has to know where the physical bits live.\n", "- **Memory.** The OS hands each process its own protected slice of memory, so a buggy game can't scribble over your open documents.\n", "\n", "A useful mental picture: the OS is a building manager. Programs are tenants. Tenants never wire their own electricity — they ask the manager, and the manager deals with the building.\n", "\n", "### The Crime Scene Is a Real Computer\n", "\n", "Here's a fact that surprises most people: when you use Google Colab, you're not using \"a website\" — you've been lent a **real Linux computer** in a Google data center. And Linux will happily let us interrogate it.\n", "\n", "In a code cell, a line starting with `!` is not Python — it's a command sent straight to the operating system. The next cell asks the OS to list its running **processes**. Run it in Colab and examine the scene." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "vEvwSbi95BRi", "outputId": "95f30f00-7cce-44e1-d8e5-0e40ea594c96" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND\n", "root 1 0.0 0.0 984 560 ? Ss 18:12 0:00 /sbin/docker-init -- /datalab/run.sh\n", "root 7 0.1 0.4 1096532 63052 ? Sl 18:12 0:01 /tools/node/bin/node /datalab/web/app.js\n", "root 20 0.0 0.0 7372 3484 ? S 18:12 0:00 /bin/bash -e /usr/local/colab/bin/oom_monitor.sh\n", "root 22 0.0 0.0 7372 1832 ? S 18:12 0:00 /bin/bash -e /datalab/run.sh\n", "root 23 0.0 0.2 1286600 33756 ? Sl 18:12 0:00 /usr/colab/bin/kernel_manager_proxy --listen_port=6000 --target_port=9000 --logtostderr --listen_host=172.28.0.12 --target_host=172.28.0.12 --tunnel_background_save_url=https://colab.research.google.com/tun/m/cc48301118ce562b961b3c22d803539adc1e0c19/m-s-kkb-use1c2-31zqywppzm7l5 --tunnel_background_save_delay=10s --tunnel_periodic_background_save_frequency=30m0s --enable_output_coalescing=true --output_coalescing_required=true --use_oneplatform_for_bg_save=true\n", "root 39 0.0 0.0 5804 2140 ? Ss 18:12 0:00 tail -n +0 -F /root/.config/Google/DriveFS/Logs/drive_fs.txt\n", "root 45 0.0 0.0 5804 2184 ? Ss 18:12 0:00 tail -n +0 -F /root/.config/Google/DriveFS/Logs/dpb.txt\n", "root 65 1.9 0.0 0 0 ? Z 18:13 0:18 [python3] \n", "root 66 0.3 0.5 99252 76096 ? S 18:13 0:03 python3 /usr/local/bin/colab-fileshim.py\n", "root 87 0.8 1.2 637984 159508 ? Sl 18:13 0:08 /usr/bin/python3 /usr/local/bin/jupyter-server --debug --transport=\"ipc\" --ip=172.28.0.12 --ServerApp.token= --port=9000 --FileContentsManager.root_dir=/ --FileContentsManager.allow_hidden=True --ServerApp.log_format=\"|%(levelname)s|%(message)s\" --ServerApp.iopub_data_rate_limit=1e10 --MappingKernelManager.root_dir=/content\n", "root 994 0.2 0.8 668808 107708 ? Ssl 18:16 0:01 /usr/bin/python3 -m colab_kernel_launcher -f /root/.local/share/jupyter/runtime/kernel-180040b3-3639-4fe0-af43-7fa3e852da0a.json\n", "root 1015 0.0 0.1 1275492 19012 ? Sl 18:16 0:00 /usr/colab/bin/language_service -- node /datalab/web/pyright/pyright-langserver.js --stdio --cancellationReceive=file:cc248ef7ddcccccae8f1f92d48f8ca0827c030636e\n", "root 1022 1.1 1.4 1706480 197776 ? Sl 18:16 0:08 node /datalab/web/pyright/pyright-langserver.js --stdio --cancellationReceive=file:cc248ef7ddcccccae8f1f92d48f8ca0827c030636e\n", "root 3879 0.0 0.0 5772 1952 ? S 18:28 0:00 sleep 1\n" ] } ], "source": [ "#| eval: false\n", "# Run this in Colab: every row is one process (one running program).\n", "!ps aux | head -15" ] }, { "cell_type": "markdown", "metadata": { "id": "nq1BZgq45BRk" }, "source": [ "You'll see a table: each row is one process, with its ID number (`PID`), how much CPU and memory it's using, and the command that started it. Somewhere in that list you'll find `python3` — *that row is this notebook*. Your code is just one tenant among many, and the OS is managing all of them at once.\n", "\n", "Now the other two jobs — files and memory. The next cell peeks at the top of the file system, then asks the OS how much memory it's handed out." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "kCuFZ0un5BRm", "outputId": "7282dda7-02c3-4006-955a-d03e09d58ce6" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "bin\t datalab home\t lib32 media proc\t\t root srv tools\n", "boot\t dev\t kaggle lib64 mnt\t python-apt\t run sys usr\n", "content etc\t lib\t libx32 opt\t python-apt.tar.xz sbin tmp var\n", " total used free shared buff/cache available\n", "Mem: 12Gi 836Mi 8.7Gi 2.0Mi 3.1Gi 11Gi\n", "Swap: 0B 0B 0B\n" ] } ], "source": [ "#| eval: false\n", "# Run this in Colab: the top of the file system, then the memory situation.\n", "!ls /\n", "!free -h" ] }, { "cell_type": "markdown", "metadata": { "id": "bneZh7QA5BRn" }, "source": [ "The `ls /` listing shows the standard rooms of a Linux building — `home` (your files), `usr` (installed programs), `tmp` (scratch space). And `free -h` shows the memory ledger: total, used, available. Every byte your Python variables occupy was granted by the OS — and will be reclaimed by it when your process ends.\n", "\n", "One picture ties the three jobs together. The diagram below shows where the OS sits." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "cellView": "form", "colab": { "base_uri": "https://localhost:8080/", "height": 320 }, "execution": { "iopub.execute_input": "2026-07-20T21:32:32.865246Z", "iopub.status.busy": "2026-07-20T21:32:32.864893Z", "iopub.status.idle": "2026-07-20T21:32:33.724915Z", "shell.execute_reply": "2026-07-20T21:32:33.722735Z" }, "jupyter": { "source_hidden": true }, "id": "xmSpS1Vs5BRo", "outputId": "7def33fa-1ddf-4e90-ce72-a3e24b28f1e9" }, "outputs": [ { "output_type": "execute_result", "data": { "image/svg+xml": "\n\n\n\n\n\n%3\n\n\napps\n\nPrograms (processes)\nthis notebook · a browser · Flask\n\n\n\nos\n\nOperating System\nschedules processes · owns files · grants memory\n\n\n\napps->os\n\n\n  requests (open a file, more memory...)\n\n\n\nhw\n\nHardware\nCPU · RAM · disk · network card\n\n\n\nos->hw\n\n\n  actual control\n\n\n\n", "text/plain": [ "" ] }, "metadata": {}, "execution_count": 4 } ], "source": [ "#| echo: false\n", "#| fig-alt: \"Layer diagram: applications send requests such as opening a file or asking for memory down to the operating system, which holds actual control of the hardware.\"\n", "#@title 📊 Diagram: where the operating system sits (click to show code)\n", "import graphviz\n", "\n", "g = graphviz.Digraph()\n", "g.attr(rankdir=\"TB\", bgcolor=\"transparent\")\n", "g.attr(\"node\", shape=\"box\", style=\"rounded,filled\", fontname=\"Helvetica\")\n", "\n", "g.node(\"apps\", \"Programs (processes)\\nthis notebook · a browser · Flask\", fillcolor=\"#dde8f0\")\n", "g.node(\"os\", \"Operating System\\nschedules processes · owns files · grants memory\", fillcolor=\"#f3e9d8\")\n", "g.node(\"hw\", \"Hardware\\nCPU · RAM · disk · network card\", fillcolor=\"#e3efe1\")\n", "\n", "g.edge(\"apps\", \"os\", label=\" requests (open a file, more memory...)\")\n", "g.edge(\"os\", \"hw\", label=\" actual control\")\n", "g" ] }, { "cell_type": "markdown", "metadata": { "id": "dJuE9BI05BRs" }, "source": [ "**Reading it:** every arrow passes through the middle box. Programs at the top never command the hardware directly — they *ask* the OS, and the OS decides. That protection is why one crashing app doesn't take down your whole machine (and why, when a whole *server* goes silent, the OS's process table is the first place a detective looks — remember that for later).\n", "\n", "### ✏️ Your Turn — The Scene of the Machine\n", "\n", "Sherlock leaves you at the scene with three questions. Answer each by running a command in Colab (use the two cells above as models):\n", "\n", "1. How many processes mention `python` in their command? (Try `!ps aux | grep python`.)\n", "2. What's inside the `/tmp` directory right now?\n", "3. Roughly how much memory is *available* on your Colab machine?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "-ELQQFcI5BRu" }, "outputs": [], "source": [ "#| eval: false\n", "# TODO: your three investigation commands, one per line." ] }, { "cell_type": "markdown", "metadata": { "id": "geQosrB65BRw" }, "source": [ "### 💭 Think About It — Three Hundred Tenants\n", "\n", "Your phone is running a few hundred processes *right now* — and you've never had to know that until this sentence.\n", "\n", "That ignorance is a designed gift: the OS hides the machine so completely that most people never meet it. What's one other system in your life (not a computer) that works hard so you can ignore it? What happens when *that* one breaks?\n", "\n", "## 2. Finding the Address: DNS and IP\n", "\n", "The Colab machine checks out — suspect one eliminated. Sherlock moves to the road itself, and brings in a specialist: **Mary Morstan**, whose company, Morstan Logistics, moves fifty thousand parcels a day.\n", "\n", "Mary's rule: *\"You can't deliver to a name. You deliver to an address.\"*\n", "\n", "The internet agrees. Every machine on a network has an **IP address** — a number like `142.250.187.206`. Numbers, not names, are what the network routes. But nobody types numbers. You type `adlerstreaming.com`, and something must translate.\n", "\n", "That something is **DNS** — the Domain Name System: the internet's phone book, translating names into IP addresses millions of times per second, invisibly, before any real conversation begins.\n", "\n", "Python can ask DNS directly. The general pattern:\n", "\n", "```\n", "socket.gethostbyname(\"website name\")\n", "```\n", "\n", "The next cell looks up three real names." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "execution": { "iopub.execute_input": "2026-07-20T21:32:33.792258Z", "iopub.status.busy": "2026-07-20T21:32:33.791682Z", "iopub.status.idle": "2026-07-20T21:32:33.898095Z", "shell.execute_reply": "2026-07-20T21:32:33.896698Z" }, "id": "6DMOu0AY5BRw", "outputId": "5b45d63d-f704-4c69-f01f-15bd91a81c6b" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "google.com -> 142.251.219.142\n", "wikipedia.org -> 208.80.154.224\n", "python.org -> 151.101.128.223\n" ] } ], "source": [ "import socket\n", "\n", "for site in [\"google.com\", \"wikipedia.org\", \"python.org\"]:\n", " print(f\"{site:16} -> {socket.gethostbyname(site)}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "UGa8GvqY5BRx" }, "source": [ "Three names, three numeric addresses — you just used the same lookup your browser performs before loading any page. (Run it twice on different days and `google.com` may answer with *different* numbers. Big sites have many machines behind one name; DNS hands out addresses like a receptionist directing a queue.)\n", "\n", "And here is **suspect two** in Irene's case: if a region's DNS servers hold a stale or wrong address, the site \"vanishes\" — for those users only. Everyone else, using healthy phone books, sees nothing wrong. It fits the Edinburgh tickets... but Sherlock isn't done eliminating.\n", "\n", "### The Journey: Packets and TCP\n", "\n", "Once your device has the address, Mary's world takes over completely — because data travels the way parcels do.\n", "\n", "A message isn't sent whole. It's chopped into **packets** — small chunks, each stamped with the destination address and a sequence number — and each packet finds its *own* way across the network, hopping from router to router. They can arrive out of order. Some don't arrive at all.\n", "\n", "The internet's fix is a protocol called **TCP**, and it works exactly like Mary's tracking system:\n", "\n", "1. Number every parcel in the shipment.\n", "2. The receiver confirms each one that arrives.\n", "3. Anything unconfirmed gets re-sent.\n", "4. Reassemble in numbered order — the message arrives complete, or the sender knows it didn't.\n", "\n", "The diagram below is Sherlock's suspect map: the full road a request travels. Keep it in mind for the rest of the case." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "cellView": "form", "colab": { "base_uri": "https://localhost:8080/", "height": 205 }, "execution": { "iopub.execute_input": "2026-07-20T21:32:33.902273Z", "iopub.status.busy": "2026-07-20T21:32:33.901776Z", "iopub.status.idle": "2026-07-20T21:32:34.925801Z", "shell.execute_reply": "2026-07-20T21:32:34.923076Z" }, "jupyter": { "source_hidden": true }, "id": "iWg1MFHE5BRy", "outputId": "d4bf2911-50f8-403d-e0bb-9dce2ef766f9" }, "outputs": [ { "output_type": "execute_result", "data": { "image/svg+xml": "\n\n\n\n\n\n%3\n\n\nbrowser\n\nYour device\n(browser or app)\n\n\n\ndns\n\nDNS\nname -> IP address\n\n\n\nbrowser->dns\n\n\n1. where is\nadlerstreaming.com?\n\n\n\nnet\n\nThe network\npackets hop router to router\n\n\n\nbrowser->net\n\n\n3. request, in packets\n\n\n\ndns->browser\n\n\n2. 142.250.x.x\n\n\n\nnet->browser\n\n\n6. page arrives\n\n\n\nserver\n\nThe server\na process on someone's machine\n\n\n\nnet->server\n\n\n4. reassembled (TCP)\n\n\n\nserver->net\n\n\n5. response, in packets\n\n\n\n", "text/plain": [ "" ] }, "metadata": {}, "execution_count": 5 } ], "source": [ "#| echo: false\n", "#| fig-alt: \"Diagram of loading a web page: the browser asks DNS to turn a name into an IP address, then sends its request across the network to the server and receives the response back along the same path.\"\n", "#@title 📊 Diagram: the road a request travels — the suspect map (click to show code)\n", "import graphviz\n", "\n", "g = graphviz.Digraph()\n", "g.attr(rankdir=\"LR\", bgcolor=\"transparent\")\n", "g.attr(\"node\", shape=\"box\", style=\"rounded,filled\", fontname=\"Helvetica\", fillcolor=\"#dde8f0\")\n", "\n", "g.node(\"browser\", \"Your device\\n(browser or app)\")\n", "g.node(\"dns\", \"DNS\\nname -> IP address\", fillcolor=\"#f3e9d8\")\n", "g.node(\"net\", \"The network\\npackets hop router to router\")\n", "g.node(\"server\", \"The server\\na process on someone's machine\", fillcolor=\"#e3efe1\")\n", "\n", "g.edge(\"browser\", \"dns\", label=\"1. where is\\nadlerstreaming.com?\")\n", "g.edge(\"dns\", \"browser\", label=\"2. 142.250.x.x\")\n", "g.edge(\"browser\", \"net\", label=\"3. request, in packets\")\n", "g.edge(\"net\", \"server\", label=\"4. reassembled (TCP)\")\n", "g.edge(\"server\", \"net\", label=\"5. response, in packets\")\n", "g.edge(\"net\", \"browser\", label=\"6. page arrives\")\n", "g" ] }, { "cell_type": "markdown", "metadata": { "id": "BhCGg2wZ5BRz" }, "source": [ "**Reading it:** follow the numbers. The name lookup (1–2) happens *before* the real request; the request and response (3–6) each cross the network as packets that TCP reassembles. Every box is one of Sherlock's suspects: the device, the phone book, the road, the server.\n", "\n", "### The Official Map: The TCP/IP Model\n", "\n", "Sherlock's suspect map is homemade. But detectives the world over need to compare notes, so the networking world long ago agreed on one *shared* map: the **TCP/IP model**. It describes every network conversation as four stacked **layers**, each doing one job and relying on the layer below it.\n", "\n", "You've already met almost every layer in this notebook — you just didn't know their formal names yet.\n", "\n", "| Layer | Its one job | You've met it as... |\n", "|---|---|---|\n", "| **Application** | what the messages *mean* | HTTP, DNS — the conversations (next section) |\n", "| **Transport** | complete, in-order delivery | TCP — Mary's numbered, confirmed parcels |\n", "| **Internet** | addressing and routing | IP addresses; packets hopping router to router |\n", "| **Link** | the physical hop | Wi-Fi, ethernet cable, fiber — the actual wire |\n", "\n", ": The four layers of the TCP/IP model and the one job of each.\n", "\n", "**Reading it:** each layer only talks to the layers directly above and below it. HTTP never worries about lost packets — that's Transport's job. TCP never worries about which cable to use — that's Link's job. This division of labor is *why the internet works*: engineers can replace an entire layer (copper wire → fiber → 5G) without anyone above it noticing. It's the same trick an operating system plays — hide the machinery, expose a promise — stacked four layers high.\n", "\n", "### ✏️ Your Turn — Mary's Address Book\n", "\n", "Mary wants a mini address book. Look up the IP addresses of **three websites you actually use** (pick your own). Then run your lookups a second time.\n", "\n", "Did any address change between runs — or differ from a classmate's result for the same site? Write one sentence proposing *why* a single name might answer with different numbers." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "z7a0B4UO5BRz" }, "outputs": [], "source": [ "#| eval: false\n", "import socket\n", "\n", "# TODO: look up three sites you use. Run it twice. Compare.\n", "my_sites = [\"\", \"\", \"\"]" ] }, { "cell_type": "markdown", "metadata": { "id": "lYW9RvAN5BR0" }, "source": [ "## 3. The Conversation: HTTP\n", "\n", "The road is clear — Mary's packets are flowing. Sherlock turns to what's *said* on that road. Time to question Irene's site directly, in its own language.\n", "\n", "The web's language is **HTTP** (HyperText Transfer Protocol), and it is a rigidly polite conversation with exactly two turns:\n", "\n", "- **The request** — your device says: a *verb* (usually `GET`, meaning \"please send me...\") plus an address.\n", "- **The response** — the server answers with two things: the content, and a three-digit **status code** saying how it went.\n", "\n", "Sherlock reads status codes the way he reads witnesses:\n", "\n", "| Code | Meaning | Sherlock's translation |\n", "|---|---|---|\n", "| **200** | OK | \"The witness is cooperating.\" |\n", "| **301** | Moved permanently | \"He's given a forwarding address.\" |\n", "| **404** | Not found | \"There is no such person at this address.\" |\n", "| **500** | Server error | \"The witness has fainted mid-sentence.\" |\n", "\n", ": Common HTTP status codes and what each one means.\n", "\n", "### Making Requests in Python\n", "\n", "Python speaks HTTP through the `requests` library. The pattern couldn't be shorter:\n", "\n", "```\n", "response = requests.get(\"the URL\")\n", "response.status_code # the three-digit verdict\n", "response.json() # the content, if it's JSON\n", "```\n", "\n", "Let's question a real server. Open-Meteo is a free weather service with an API — and since the next section of this case takes us to London, the next cell asks for the current weather near Baker Street." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "execution": { "iopub.execute_input": "2026-07-20T21:32:34.930410Z", "iopub.status.busy": "2026-07-20T21:32:34.929854Z", "iopub.status.idle": "2026-07-20T21:32:36.405345Z", "shell.execute_reply": "2026-07-20T21:32:36.403215Z" }, "id": "iYaR5nE65BR0", "outputId": "d1220ed7-7b93-45ad-d1c3-2cb7fc88a563" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Status code: 200\n", "{'time': '2026-07-20T21:30', 'interval': 900, 'temperature': 19.8, 'windspeed': 13.3, 'winddirection': 124, 'is_day': 0, 'weathercode': 1}\n" ] } ], "source": [ "import requests\n", "\n", "url = \"https://api.open-meteo.com/v1/forecast\"\n", "response = requests.get(url, params={\n", " \"latitude\": 51.52, \"longitude\": -0.16, # Baker Street, London\n", " \"current_weather\": True,\n", "})\n", "\n", "print(\"Status code:\", response.status_code)\n", "print(response.json()[\"current_weather\"])" ] }, { "cell_type": "markdown", "metadata": { "id": "vVWFuq1l5BR1" }, "source": [ "### Understanding the Code\n", "\n", "- `requests.get(url, params=...)` sent a real HTTP request across the real internet — DNS lookup, packets, TCP, everything from the suspect map, in one line.\n", "- `response.status_code` came back `200`: the witness cooperated.\n", "- `response.json()` converted the reply into Python data. The server answered in **JSON** — labeled, nested data that arrives as dictionaries and lists you already know how to handle.\n", "\n", "A server whose job is answering programs (rather than showing pages to humans) is called an **API** — an *application programming interface*. This is **Dr. Watson's** world: his telehealth app never scrapes weather off a webpage; it asks an API and gets clean JSON. Most modern software is exactly this — programs having HTTP conversations with other programs.\n", "\n", "### Reading the Envelope: Headers\n", "\n", "There's more to an HTTP message than its content and its status code. Every request and every response also carries **headers** — labeled notes on the outside of the message, like the writing on an envelope: who sent it, what's inside, how to handle it.\n", "\n", "Sherlock, naturally, reads envelopes before letters. The next cell makes a request and inspects three headers from the response." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "execution": { "iopub.execute_input": "2026-07-20T21:32:36.412632Z", "iopub.status.busy": "2026-07-20T21:32:36.411242Z", "iopub.status.idle": "2026-07-20T21:32:37.348796Z", "shell.execute_reply": "2026-07-20T21:32:37.346986Z" }, "id": "sAmmjrG05BR2", "outputId": "b5540378-3236-47e0-88d8-de2a3329b639" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Content-Type : application/json; charset=utf-8\n", "Server : github.com\n", "Date : Mon, 20 Jul 2026 21:32:19 GMT\n" ] } ], "source": [ "import requests\n", "\n", "response = requests.get(\"https://api.github.com\")\n", "\n", "for header in [\"Content-Type\", \"Server\", \"Date\"]:\n", " print(f\"{header:14}: {response.headers[header]}\")" ] }, { "cell_type": "markdown", "metadata": { "id": "V7GFrVbC5BR3" }, "source": [ "Three notes on the envelope:\n", "\n", "- **Content-Type** tells the receiver how to *read* the body — `application/json` means \"parse me as JSON\"; a web page would say `text/html`. Your browser obeys this header on every page you visit.\n", "- **Server** is the software that answered — servers introduce themselves.\n", "- **Date** is when the response was written, by the server's own clock.\n", "\n", "Headers are where much of the web's hidden machinery lives: logins, cookies, caching rules, security policies. We'll meet the security ones properly in the next notebook — for now, know that every message you send carries an envelope, and the envelope talks.\n", "\n", "### 🔮 Predict Before You Run\n", "\n", "The next cell deliberately asks GitHub's API for a page that does not exist.\n", "\n", "Look at Sherlock's status-code table above. **Before running it**, write down the code you expect." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "execution": { "iopub.execute_input": "2026-07-20T21:32:37.353934Z", "iopub.status.busy": "2026-07-20T21:32:37.353387Z", "iopub.status.idle": "2026-07-20T21:32:38.238391Z", "shell.execute_reply": "2026-07-20T21:32:38.236529Z" }, "id": "EKKhC6Q15BR3", "outputId": "4fe1c44a-5799-4ce4-9e55-96b3ab0faa9e" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Status code: 404\n" ] } ], "source": [ "import requests\n", "\n", "response = requests.get(\"https://api.github.com/this-page-does-not-exist\")\n", "print(\"Status code:\", response.status_code)" ] }, { "cell_type": "markdown", "metadata": { "id": "OxTYsVDm5BR4" }, "source": [ "**404 — no such person at this address.** Note what *didn't* happen: no crash, no silence. The server answered, calmly, with a code meaning \"that's not a thing.\" Good HTTP conversations fail *informatively* — and a diagnostician like Sherlock learns as much from a 404 as from a 200.\n", "\n", "(As for Irene's site? Her servers are returning 200s... when the request reaches them at all. Suspects three and four — the road and the conversation — are eliminated. The circle tightens.)\n", "\n", "### ✏️ Your Turn — Irene's Weather Wall\n", "\n", "Irene wants a weather widget for her office wall: the current weather for a city of *your* choice.\n", "\n", "Find your city's rough latitude and longitude (search engines answer this instantly), call the Open-Meteo API like the example above, and print **just two things**: the temperature and the wind speed, pulled out of the JSON by key." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "BD7uXEEw5BR5" }, "outputs": [], "source": [ "#| eval: false\n", "import requests\n", "\n", "# TODO: your city's coordinates, one API call, print temperature and windspeed only.\n", "my_latitude =\n", "my_longitude =" ] }, { "cell_type": "markdown", "metadata": { "id": "Az_BeBtd5BR5" }, "source": [ "## 4. Whose Computer Is It Anyway? The Cloud\n", "\n", "One suspect left: the server itself. But before we knock on its door, Sherlock asks his brother a deceptively simple question: *where does a website live?*\n", "\n", "**Mycroft Holmes** runs the Whitehall Cloud Office — he decides where the government's systems live — and his answer is the least mystical definition you'll ever hear:\n", "\n", "> \"The **cloud** is someone else's computer. Rented by the slice, billed by the hour, reachable over the network. The only interesting question is *how big a slice* you rent.\"\n", "\n", "That question — how much of the machine is your problem versus the landlord's — has exactly three standard answers, and a housing analogy makes them stick:\n", "\n", "- **IaaS** (*Infrastructure as a Service*) — **rent bare land.** You get raw virtual machines; you install and manage everything above the hardware. Total control, total responsibility. (Amazon EC2.)\n", "- **PaaS** (*Platform as a Service*) — **rent an apartment.** You bring only your code; the landlord runs the machines, the OS, the plumbing. (Heroku, Google App Engine.)\n", "- **SaaS** (*Software as a Service*) — **book a hotel room.** You just *use* finished software through a browser; nothing underneath is your problem. (Gmail, Google Docs.)\n", "\n", "One more row for the table: **you've been a cloud customer this whole course.** Colab is finished software you use through a browser (SaaS) — that hands you a managed machine to run code on (which is what a PaaS does). The Linux box you interrogated in section 1? A slice of someone else's computer, exactly as Mycroft says.\n", "\n", "| Model | Housing version | You manage | Example |\n", "|---|---|---|---|\n", "| IaaS | bare land | OS, software, code — everything | Amazon EC2 |\n", "| PaaS | apartment | just your code and data | Heroku, App Engine |\n", "| SaaS | hotel room | nothing — you're a user | Gmail, Colab |\n", "\n", ": Cloud service models compared by what you manage, with an example of each.\n", "\n", "And Irene? Adler Streaming rents from a PaaS. Which means the last suspect — her server — is a *process running on a machine she has never seen.* Hold that thought.\n", "\n", "### 💭 Think About It — Mycroft's Dilemma\n", "\n", "Mycroft must decide where the national health records system lives: on servers the government owns and staffs, or rented from a giant cloud provider that runs infrastructure better than any government ever has.\n", "\n", "Give one strong reason for each choice. Then the harder question: who should hold the keys to data like that — and is \"whoever runs servers best\" the right answer?\n", "\n", "## 5. Becoming the Server\n", "\n", "Four suspects eliminated: the client machine, DNS, the network, the HTTP conversation. What remains, however improbable, must be the truth — the fault is *at the server*.\n", "\n", "But what actually **is** a server? Strip away the mystique and it's almost disappointing:\n", "\n", "> A **server** is a program that waits. It listens for HTTP requests, and for each one, it runs some code and returns a response.\n", "\n", "That's it. It's a process — a tenant of some operating system, like every program in section 1. And the fastest way to understand one is to *be* one. Python's most popular tool for this is **Flask**, and its whole shape fits in a skeleton:\n", "\n", "```\n", "app = Flask(__name__)\n", "\n", "@app.route(\"/some/path\")\n", "def handler():\n", " return \"the response\"\n", "```\n", "\n", "Each `@app.route(...)` line declares: *when a request arrives for this path, run this function and send back whatever it returns.* A path-plus-function pair is called a **route**.\n", "\n", "### First, a Helper to Run Your Servers\n", "\n", "A live web server has one awkward habit: it **runs forever**, waiting for requests. If you start it the naive way, the cell freezes on a spinner and nothing else can happen — and re-running the cell tries to grab a port that's already taken and crashes.\n", "\n", "So we'll run every server in this notebook through one small helper, `run_web_app`, defined in the next cell. Run it **once** now; you won't need to read it (it's plumbing), but here's what it does for you:\n", "\n", "- **Runs the server in the background**, so the cell returns immediately instead of freezing.\n", "- **Is safe to re-run** — it shuts down any previous server on the same port before starting, so you'll never see \"port already in use.\"\n", "- **Shows your site two ways**: an **inline preview** right in the notebook (works for everyone, no login), *and* a pop-out link to a full browser tab." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2026-07-20T21:32:38.243786Z", "iopub.status.busy": "2026-07-20T21:32:38.242964Z", "iopub.status.idle": "2026-07-20T21:32:38.347517Z", "shell.execute_reply": "2026-07-20T21:32:38.345352Z" }, "id": "YUuHuzqE5BR6" }, "outputs": [], "source": [ "# ── Run this once. It's plumbing — you don't need to read it. ──\n", "import socket, threading, time\n", "from werkzeug.serving import make_server\n", "from IPython.display import display, HTML\n", "\n", "_SERVERS = {} # remembers running servers so re-running a cell won't crash\n", "\n", "def run_web_app(app, port=5000, height=450):\n", " \"\"\"Run a Flask app in the background; show it inline + as a pop-out link.\n", " Safe to re-run: shuts down any previous server on this port first.\"\"\"\n", " # 1. Shut down a server already on this port (e.g. from a previous run).\n", " if port in _SERVERS:\n", " _SERVERS[port].shutdown()\n", " del _SERVERS[port]\n", "\n", " # 2. Start Flask in a BACKGROUND thread so this cell doesn't freeze.\n", " server = make_server(\"0.0.0.0\", port, app)\n", " _SERVERS[port] = server\n", " threading.Thread(target=server.serve_forever, daemon=True).start()\n", "\n", " # 3. Wait until the server is really accepting connections — no guessing.\n", " for _ in range(50):\n", " with socket.socket() as probe:\n", " if probe.connect_ex((\"127.0.0.1\", port)) == 0:\n", " break\n", " time.sleep(0.1)\n", "\n", " # 4. Show it. Inline preview first (works for everyone); pop-out link second.\n", " try:\n", " from google.colab.output import serve_kernel_port_as_iframe, serve_kernel_port_as_window\n", " display(HTML(f\"✅ Your site is live on port {port}. \"\n", " f\"Preview is below; the pop-out tab needs a Google login.\"))\n", " serve_kernel_port_as_iframe(port, height=str(height))\n", " serve_kernel_port_as_window(port)\n", " except ImportError: # not in Colab (e.g. local Jupyter)\n", " display(HTML(f'Live on port {port}: '\n", " f'open your site »'))" ] }, { "cell_type": "markdown", "metadata": { "id": "idEUMs-H5BR6" }, "source": [ "> **Why two views?** The pop-out tab uses a Colab feature tied to *your* Google login, so it only opens for people signed into a Google account in Chrome. The **inline preview** has no such requirement — it renders your site right inside the notebook, for everyone. We lead with the inline one.\n", "\n", "### A Little HTML: Tags That Wrap Text\n", "\n", "Our routes are about to return **HTML** — the language every web page is written in. You don't need to master it; you need a handful of **tags**. A tag wraps a piece of text to say what it *is*, and almost always comes in an open/close pair:\n", "\n", "```\n", "the content goes here\n", "```\n", "\n", "The closing tag is the same word with a slash. These six are all we'll use, and they read almost like English:\n", "\n", "| Tag | Wraps text as | Example |\n", "|-----|---------------|---------|\n", "| `

…

` | a big heading | `

Welcome

` |\n", "| `

…

` | a paragraph | `

Hello there.

` |\n", "| `…` | **bold** | `open` |\n", "| `
    …
` | a bulleted list (the container) | `
    …
` |\n", "| `
  • …
  • ` | one item inside a list | `
  • First tip
  • ` |\n", "| `…` | a clickable link to `/path` | `Status` |\n", "\n", ": Basic HTML tags, what each one wraps, and an example of each.\n", "\n", "That last one — the **link** — is how a visitor travels between your routes, which is exactly what the `NAV` bar below is built from. A page, then, is just a string with a few tags in it. You already know how to build strings.\n", "\n", "### Mrs. Hudson Opens the Doors\n", "\n", "Naturally, our server will be hosted by a landlady. **Mrs. Hudson** — of Hudson Hosting — presents the next cell: a complete, working web server in about a dozen lines.\n", "\n", "Notice two things in it. First, every page returns the same `NAV` bar of links — so once your site is live, you can **click between routes** and watch the request/response cycle happen. Second, the last line hands the app to `run_web_app` instead of blocking forever: no spinner, no stop button, and you can re-run it as often as you like." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 541 }, "execution": { "iopub.execute_input": "2026-07-20T21:32:38.353456Z", "iopub.status.busy": "2026-07-20T21:32:38.352526Z", "iopub.status.idle": "2026-07-20T21:32:38.580257Z", "shell.execute_reply": "2026-07-20T21:32:38.578047Z" }, "id": "P_JMqJkv5BR7", "outputId": "12a35802-4aa0-4bc2-be6a-a3ad4bca5d47" }, "outputs": [ { "output_type": "display_data", "data": { "text/plain": [ "" ], "text/html": [ "✅ Your site is live on port 5000. Preview is below; the pop-out tab needs a Google login." ] }, "metadata": {} }, { "output_type": "display_data", "data": { "text/plain": [ "" ], "application/javascript": [ "(async (port, path, width, height, cache, element) => {\n", " if (!google.colab.kernel.accessAllowed && !cache) {\n", " return;\n", " }\n", " element.appendChild(document.createTextNode(''));\n", " const url = await google.colab.kernel.proxyPort(port, {cache});\n", " const iframe = document.createElement('iframe');\n", " iframe.src = new URL(path, url).toString();\n", " iframe.height = height;\n", " iframe.width = width;\n", " iframe.style.border = 0;\n", " iframe.allow = [\n", " 'accelerometer',\n", " 'autoplay',\n", " 'camera',\n", " 'clipboard-read',\n", " 'clipboard-write',\n", " 'gyroscope',\n", " 'magnetometer',\n", " 'microphone',\n", " 'serial',\n", " 'usb',\n", " 'xr-spatial-tracking',\n", " ].join('; ');\n", " element.appendChild(iframe);\n", " })(5000, \"/\", \"100%\", \"450\", false, window.element)" ] }, "metadata": {} }, { "output_type": "stream", "name": "stdout", "text": [ "\u001b[31mWarning: This function may stop working due to changes in browser security.\n", "Try `serve_kernel_port_as_iframe` instead. \u001b[0m\n" ] }, { "output_type": "display_data", "data": { "text/plain": [ "" ], "application/javascript": [ "(async (port, path, text, element) => {\n", " if (!google.colab.kernel.accessAllowed) {\n", " return;\n", " }\n", " element.appendChild(document.createTextNode(''));\n", " const url = await google.colab.kernel.proxyPort(port);\n", " const anchor = document.createElement('a');\n", " anchor.href = new URL(path, url).toString();\n", " anchor.target = '_blank';\n", " anchor.setAttribute('data-href', url + path);\n", " anchor.textContent = text;\n", " element.appendChild(anchor);\n", " })(5000, \"/\", \"https://localhost:5000/\", window.element)" ] }, "metadata": {} } ], "source": [ "from flask import Flask\n", "\n", "app = Flask(__name__)\n", "\n", "# A shared navigation bar, so every page links to the others.\n", "NAV = ('
    ')\n", "\n", "@app.route(\"/\")\n", "def home():\n", " return NAV + \"

    221B Web Services

    Mrs. Hudson welcomes you.

    \"\n", "\n", "@app.route(\"/status\")\n", "def status():\n", " return NAV + \"

    Status

    We are open. Cases solved: 47.

    \"\n", "\n", "run_web_app(app, port=5000) # non-blocking, re-runnable, shows inline" ] }, { "cell_type": "markdown", "metadata": { "id": "aHZRyUBr5BR8" }, "source": [ "Use the preview above — or the pop-out tab — and you are *visiting your own website*, served by a process you started on a rented Linux machine in a data center. Click **Status** in the nav bar (or add `/status` to the address) and Flask runs the `status()` function and sends back the page it builds. Look closely at each route: it just returns a **string with a few HTML tags in it** — the heading, paragraph, and bold text you met a moment ago.\n", "\n", "Trace one full visit against the suspect map: browser → (address lookup) → packets → **your process** → its return value → packets → rendered page. The **request/response cycle**, end to end — and this time you own the last box.\n", "\n", "*(Want a link that works on your phone, off Colab? A tool called `pyngrok` can tunnel your server to a public URL — it needs a free ngrok account and token, so we leave it as an optional side quest.)*\n", "\n", "### Routes That Listen: URL Parameters\n", "\n", "Mrs. Hudson's two routes are fixed addresses — `/` and `/status` always answer the same way. But think back to section 3: the weather API answered differently depending on the coordinates *you* sent. Real servers listen.\n", "\n", "Flask does this by putting a placeholder in the route's path:\n", "\n", "```\n", "@app.route(\"/tip/\")\n", "def one_tip(tip_id):\n", " ...\n", "```\n", "\n", "The angle brackets declare a **URL parameter**: whatever number appears in that spot of the address becomes the argument `tip_id`, handed straight to your function. Visit `/tip/2`, and `tip_id` is `2`.\n", "\n", "The next cell upgrades the server: a list of case tips, and a route that serves any *one* of them by number — politely refusing numbers that don't exist." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 541 }, "execution": { "iopub.execute_input": "2026-07-20T21:32:38.585280Z", "iopub.status.busy": "2026-07-20T21:32:38.584526Z", "iopub.status.idle": "2026-07-20T21:32:39.091021Z", "shell.execute_reply": "2026-07-20T21:32:39.088980Z" }, "id": "iYf53eAa5BR8", "outputId": "76b46acb-f259-4693-e140-95fc38a33b32" }, "outputs": [ { "output_type": "display_data", "data": { "text/plain": [ "" ], "text/html": [ "✅ Your site is live on port 5000. Preview is below; the pop-out tab needs a Google login." ] }, "metadata": {} }, { "output_type": "display_data", "data": { "text/plain": [ "" ], "application/javascript": [ "(async (port, path, width, height, cache, element) => {\n", " if (!google.colab.kernel.accessAllowed && !cache) {\n", " return;\n", " }\n", " element.appendChild(document.createTextNode(''));\n", " const url = await google.colab.kernel.proxyPort(port, {cache});\n", " const iframe = document.createElement('iframe');\n", " iframe.src = new URL(path, url).toString();\n", " iframe.height = height;\n", " iframe.width = width;\n", " iframe.style.border = 0;\n", " iframe.allow = [\n", " 'accelerometer',\n", " 'autoplay',\n", " 'camera',\n", " 'clipboard-read',\n", " 'clipboard-write',\n", " 'gyroscope',\n", " 'magnetometer',\n", " 'microphone',\n", " 'serial',\n", " 'usb',\n", " 'xr-spatial-tracking',\n", " ].join('; ');\n", " element.appendChild(iframe);\n", " })(5000, \"/\", \"100%\", \"450\", false, window.element)" ] }, "metadata": {} }, { "output_type": "stream", "name": "stdout", "text": [ "\u001b[31mWarning: This function may stop working due to changes in browser security.\n", "Try `serve_kernel_port_as_iframe` instead. \u001b[0m\n" ] }, { "output_type": "display_data", "data": { "text/plain": [ "" ], "application/javascript": [ "(async (port, path, text, element) => {\n", " if (!google.colab.kernel.accessAllowed) {\n", " return;\n", " }\n", " element.appendChild(document.createTextNode(''));\n", " const url = await google.colab.kernel.proxyPort(port);\n", " const anchor = document.createElement('a');\n", " anchor.href = new URL(path, url).toString();\n", " anchor.target = '_blank';\n", " anchor.setAttribute('data-href', url + path);\n", " anchor.textContent = text;\n", " element.appendChild(anchor);\n", " })(5000, \"/\", \"https://localhost:5000/\", window.element)" ] }, "metadata": {} } ], "source": [ "from flask import Flask\n", "\n", "app = Flask(__name__)\n", "\n", "tips = [\n", " {\"id\": 1, \"from\": \"anonymous\", \"tip\": \"The landlady saw everything.\"},\n", " {\"id\": 2, \"from\": \"Wiggins\", \"tip\": \"Check the server logs from Tuesday.\"},\n", " {\"id\": 3, \"from\": \"a concerned neighbor\", \"tip\": \"The process died at 3 AM.\"},\n", "]\n", "\n", "# Nav bar with a link to each tip, so you can click through them.\n", "links = \" | \".join(f'Tip #{t[\"id\"]}' for t in tips)\n", "NAV = f\"
    \"\n", "\n", "@app.route(\"/\")\n", "def home():\n", " return NAV + \"

    The Tip Line

    Pick a tip above.

    \"\n", "\n", "@app.route(\"/tip/\")\n", "def one_tip(tip_id):\n", " for tip in tips:\n", " if tip[\"id\"] == tip_id:\n", " return NAV + f\"

    Tip #{tip['id']}

    From {tip['from']}:

    {tip['tip']}

    \"\n", " return NAV + f\"

    No such tip

    There is no tip #{tip_id} on file.

    \", 404\n", "\n", "@app.route(\"/tips\")\n", "def tips_page():\n", " rows = \"\"\n", " for tip in tips:\n", " rows += f\"
  • Tip #{tip['id']} from {tip['from']}: {tip['tip']}
  • \"\n", " return NAV + f\"

    The Tip Ledger

      {rows}
    \"\n", "\n", "run_web_app(app, port=5000) # reuses port 5000 — the helper frees it first" ] }, { "cell_type": "markdown", "metadata": { "id": "PSXKdroW5BR9" }, "source": [ "### Understanding the Code\n", "\n", "- `tips` is a list of dictionaries — the same shape as the JSON you pulled from APIs in section 3. Data in a program and data on a page are the same facts, dressed differently.\n", "- `` captures the number from the address and passes it to the function as a real Python `int`.\n", "- The loop searches for a matching tip, then drops its fields into an f-string of HTML tags — a page **built on the spot** from data.\n", "- The last line is the polite refusal: `return page, 404` sets the **status code** yourself. Visit `/tip/99` and you get a clean \"no such tip\" page *and* a 404 — \"no such person at this address,\" said properly. Your server now *fails informatively*, exactly like the well-behaved APIs you questioned earlier.\n", "\n", "### Pages That Are Computed, Not Stored\n", "\n", "One more secret of the web, hiding in plain sight: most web pages don't exist until you ask for them.\n", "\n", "There is no file anywhere containing your social-media feed or your bank statement page. When your request arrives, a function *builds* that HTML on the spot — from data — and returns it. A page is just a string a server computed. You already know how to compute strings.\n", "\n", "The next cell adds one more route — a `/tips` page that lists *all* the tips at once, by looping over the data and gluing together one `
  • ` list item per tip." ] }, { "cell_type": "markdown", "source": [ "```python\n", "# You need to copy this to the previous code cell\n", "# Also, add it to the navbar\n", "\n", "@app.route(\"/tips\")\n", "def tips_page():\n", " rows = \"\"\n", " for tip in tips:\n", " rows += f\"
  • Tip #{tip['id']} from {tip['from']}: {tip['tip']}
  • \"\n", " return NAV + f\"

    The Tip Ledger

      {rows}
    \"\n", "```" ], "metadata": { "id": "1Y4NCIYoEF2u" } }, { "cell_type": "markdown", "metadata": { "id": "bYMP1reG5BR-" }, "source": [ "Add this route to the server cell above, re-run, and visit `/tips`: `/tip/2` shows *one* tip, `/tips` shows *all* of them — and neither page exists until you ask. Both are computed from the same list the moment the request arrives.\n", "\n", "Real Flask apps keep the HTML in separate files called **templates**, with slots where the data drops in — same idea, tidier housekeeping. You'll see templates again; the concept is the part that matters: *on the modern web, pages are the output of functions.*\n", "\n", "One suspect remains in Irene's case. Time to close it.\n", "\n", "### Case Closed\n", "\n", "Armed with all five layers, Sherlock reads Adler Streaming's server logs — and the culprit is almost insultingly mundane.\n", "\n", "One of Irene's three server processes **crashed two days ago**. Nothing restarted it. The two survivors handled the load most of the time — but whenever traffic spiked, requests routed to the dead tenant's address simply... vanished. Down in Edinburgh, fine in Cardiff; down at 8 PM, fine at 3 AM.\n", "\n", "One `ps` on the right machine — section 1, the very first tool in this notebook — would have shown a process table with a corpse in it. Sherlock's verdict: *\"Your website did not vanish, Ms. Adler. One-third of it stopped existing, and nobody was watching the tenants.\"*\n", "\n", "The fix is a monitor that restarts dead processes. The lesson is the notebook: **the web is layers, and every layer is just something you now understand.**\n", "\n", "### ✏️ Your Turn — Your Own Route\n", "\n", "Reopen Mrs. Hudson's server cell and add a **third route** at the path `/about` that returns a short HTML paragraph about *you* (or your favorite detective).\n", "\n", "Re-run the cell, visit `/about`, and confirm your page is live. Then, for one bonus mark of Sherlock's respect: visit a path you *didn't* define and note what Flask returns." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "y7JQZl9l5BR_" }, "outputs": [], "source": [ "#| eval: false\n", "# TODO: copy the server from above, add an /about route, re-run, visit it." ] }, { "cell_type": "markdown", "metadata": { "id": "jMWgQ1685BR_" }, "source": [ "## ✏️ Capstone — The Baker Street Tip Line\n", "\n", "Time to run your own corner of the web. You'll design a small web service and have an AI assistant (Gemini, Claude, or ChatGPT) write the Flask code — while you make the design decisions and verify every route.\n", "\n", "**The default theme:** an anonymous tip line for Baker Street Diagnostics — a home page, a page that lists all the tips, and a route that looks up one tip by number. **Or reskin it**: a fan site, a team status board, a restaurant menu. Any theme with a page + data works.\n", "\n", "### Step 0 — Design First (before touching the AI)\n", "\n", "In the cell below, write your design as plain text:\n", "\n", "- Your **three routes**: their paths, and what HTML page each returns\n", "- The data behind them (a list of dictionaries is plenty — no database needed)\n", "- One route must take a **URL parameter** — like `/tip/2` returning tip #2\n", "\n", "*(Double-click and write your design here.)*\n", "\n", "**Route 1:** ...\n", "\n", "**Route 2:** ...\n", "\n", "**Route 3:** ...\n", "\n", "**The data:** ...\n", "\n", "### Step 1 — Build It *(prompt #1)*\n", "\n", "Turn your design into a prompt. A skeleton to fill in:\n", "\n", "> I'm learning Flask in Google Colab. Write a complete Flask app with these three routes: **[paste your design]**.\n", "> Store the data as a Python list of dictionaries defined at the top.\n", "> Give every HTML page a shared `NAV` bar linking to the other routes.\n", "> Do **not** call `app.run(...)`. A helper named `run_web_app(app, port=5000)` is already defined — end the code with that line instead.\n", "> One code block, no explanations.\n", "\n", "Paste the result below, run it, and **visit every route** in the opened tab. Don't move on until all three answer." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "RZRAt6qP5BSA" }, "outputs": [], "source": [ "#| eval: false\n", "# ✏️ Paste your AI-built Flask app here, run it, and visit every route." ] }, { "cell_type": "markdown", "metadata": { "id": "Lt8bHXRP5BSA" }, "source": [ "### Step 2 — Question Your Own Witness *(prompt #2)*\n", "\n", "In section 3 you interrogated other people's servers with `requests`. Now interrogate **your own** — the two halves of this notebook shaking hands.\n", "\n", "While your server cell is running, test like a detective — click and visit, since you can't easily `requests.get` your own Colab tab from outside:\n", "\n", "1. Visit each page — does the HTML render? Every field you designed showing up?\n", "2. Visit your parameter route with a number that **exists** — right tip?\n", "3. Visit it with a number that **doesn't exist** — what happens? (If it crashes with an ugly error page: that's a finding. Ask the AI to make it return a polite \"not found\" page and a `404` status instead.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "RlZVoiq45BSB" }, "outputs": [], "source": [ "#| eval: false\n", "# ✏️ Paste your improved app here (with the polite 404 handling), re-run, re-test." ] }, { "cell_type": "markdown", "metadata": { "id": "XePaHG5T5BSB" }, "source": [ "### Step 3 — One Extension, Then Reflect\n", "\n", "Add **one** bell or whistle — your choice: a route that returns *how many* tips there are; a search route like `/tips/urgent`; or a home page that lists links to every other route. Re-test everything after (a change that breaks an old route is the oldest bug in the book).\n", "\n", "Then reflect below, 2–3 sentences: What did the AI get wrong or almost-wrong? What did you only catch by *visiting the routes yourself*?\n", "\n", "*AI is a fast first draft. You verify.*" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "szEFPvXJ5BSB" }, "outputs": [], "source": [ "#| eval: false\n", "# ✏️ Paste your final, extended app here." ] }, { "cell_type": "markdown", "metadata": { "id": "8oWFnoi95BSB" }, "source": [ "*(Your 2–3 sentence reflection — double-click to edit.)*\n", "\n", "## Key Terms\n", "\n", "- **API (application programming interface)** — A server whose job is answering programs rather than people, usually in JSON.\n", "- **Cloud** — Computers owned by someone else, rented over the network by the slice.\n", "- **DNS (Domain Name System)** — The internet's phone book: translates names like `python.org` into IP addresses.\n", "- **File system** — The OS's organization of storage into named files and folders.\n", "- **Flask** — A small Python library for building web servers.\n", "- **Header** — A labeled note on the outside of an HTTP request or response, like *Content-Type*; the envelope around the message.\n", "- **HTML** — The language web pages are written in: text wrapped in tags (`

    `, `

    `, ``…) that say what each piece *is*.\n", "- **HTTP** — The web's request/response language, spoken between clients and servers.\n", "- **IaaS / PaaS / SaaS** — The three cloud rental sizes: raw machines / a platform for your code / finished software.\n", "- **IP address** — The numeric address that identifies a machine on a network.\n", "- **Operating system (OS)** — The software layer that manages processes, files, and memory between programs and hardware.\n", "- **Packet** — A small, addressed chunk of data; messages cross the internet as many packets.\n", "- **Process** — One running program, managed (started, scheduled, stopped) by the OS.\n", "- **Request/response cycle** — The full round trip: client asks, server's code runs, answer returns.\n", "- **Route** — In a web server, one path (like `/status`) paired with the function that answers it.\n", "- **Server** — A program that waits for requests and returns responses.\n", "- **Status code** — The three-digit verdict in every HTTP response: 200 OK, 404 not found, 500 server error.\n", "- **TCP** — The protocol that numbers, confirms, re-sends, and reassembles packets so messages arrive complete.\n", "- **TCP/IP model** — The standard four-layer map of network communication: application, transport, internet, link.\n", "- **Template** — An HTML file with slots where a server drops data before sending the page; on the modern web, pages are computed, not stored.\n", "- **URL parameter** — A placeholder in a route (like `/tip/`) that captures part of the address and passes it to the function.\n", "\n", "## Summary\n", "\n", "Every app is a conversation across layers, and you now know each one. The operating system runs processes, owns files, and grants memory — programs never touch hardware directly. DNS turns names into IP addresses; TCP moves data as numbered, confirmed packets. On top of that road, HTTP holds a strict two-turn conversation of requests and responses, with status codes as verdicts — and an API is just a server that answers programs in JSON. The cloud is someone else's computer rented in three sizes (IaaS, PaaS, SaaS). And a web server, demystified, is a program that waits: with Flask, a dozen lines make you a citizen of the web rather than a visitor. When something \"vanishes,\" it's one of these layers — and now you know how to interrogate every suspect.\n", "\n", "## What's Next\n", "\n", "You just built a server and opened its door to the network. Here's the thought that should keep you up tonight: *anyone* can knock. The next notebook is about exactly that — security. Who else is sending requests to your server, what are they after, and how do systems defend the data behind the door?\n", "\n", "*COMP 1150 — Computer Science Concepts · Brendan Shea, PhD*\n", "*Content licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).*\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3", "path": "C:\\Users\\brend\\AppData\\Local\\Programs\\Python\\Python313\\share\\jupyter\\kernels\\python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.9" }, "colab": { "provenance": [] } }, "nbformat": 4, "nbformat_minor": 0 }