learning-website-django1-2 Exercise 2: Try It With Real Requests =========================================================================== Unit tests use Django's test client. Also try the real development server with real HTTP requests, sending the Host header the way a proxy would, and decide how you will give the sites their names on your own machine. Save as try_server.py in the project folder. It starts the server twice (one process serving every site, and one pinned with LW_SITE=languages), requests both with curl, and stops them: """try_server.py: start the development server and request it with curl, the way a proxy would. Run from the project folder with the project's Python: python try_server.py""" import os, socket, subprocess, sys, time PORT, PINNED_PORT = 8765, 8766 def start(port, extra_env=None): env = dict(os.environ, **(extra_env or {})) proc = subprocess.Popen([sys.executable, "manage.py", "runserver", f"127.0.0.1:{port}", "--noreload"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=env) for _ in range(60): with socket.socket() as s: if s.connect_ex(("127.0.0.1", port)) == 0: return proc time.sleep(0.25) proc.kill() raise SystemExit("server did not start") def curl(label, port, url, *args): out = subprocess.run(["curl.exe", "-s", "-o", "-", "-w", " [%{http_code}]", *args, url], capture_output=True, text=True).stdout body, _, code = out.rpartition(" ") first = body.strip().splitlines()[0][:60] if body.strip() else "" print(f" {label:<46} {code:<7} {first}") servers = [start(PORT), start(PINNED_PORT, {"LW_SITE": "languages"})] try: print("one process serving all sites (port %d):" % PORT) r = lambda host: ("--resolve", f"{host}:{PORT}:127.0.0.1") # name -> 127.0.0.1, like a hosts file curl("languages.localhost /", PORT, f"http://languages.localhost:{PORT}/", *r("languages.localhost")) curl("languages.localhost /hungary/x/", PORT, f"http://languages.localhost:{PORT}/hungary/x/", *r("languages.localhost")) curl("systems.localhost /hungary/x/ (wrong site)", PORT, f"http://systems.localhost:{PORT}/hungary/x/", *r("systems.localhost")) curl("Host: evil.example.org", PORT, f"http://127.0.0.1:{PORT}/", "-H", "Host: evil.example.org") curl("Host: 127.0.0.1:%d (no proxy host)" % PORT, PORT, f"http://127.0.0.1:{PORT}/") print("process pinned with LW_SITE=languages (port %d):" % PINNED_PORT) curl("Host: languages.localhost", PINNED_PORT, f"http://127.0.0.1:{PINNED_PORT}/", "-H", "Host: languages.localhost") curl("Host: systems.localhost", PINNED_PORT, f"http://127.0.0.1:{PINNED_PORT}/", "-H", "Host: systems.localhost") finally: for p in servers: p.terminate() p.wait(timeout=10) print("servers stopped") Run it with the project's Python: python try_server.py Output (checked by running it): one process serving all sites (port 8765): languages.localhost / [200] Languages (site: languages) languages.localhost /hungary/x/ [200] site=languages path=hungary/x/ systems.localhost /hungary/x/ (wrong site) [404] Host: evil.example.org [400] Host: 127.0.0.1:8765 (no proxy host) [400] process pinned with LW_SITE=languages (port 8766): Host: languages.localhost [200] Languages (site: languages) Host: systems.localhost [400] servers stopped Reading the results ------------------- - The first request uses curl's --resolve option to make languages.localhost point at 127.0.0.1 without touching any system file. That is the quickest way to test a named site from the command line. - systems.localhost asking for /hungary/x/ gets a 404, because the systems site's URL configuration has no hungary route. - An unknown host name gets 400, and so does a request addressed to 127.0.0.1 directly. That second case is what a proxy that does NOT pass the original host name would cause (Exercise 3). - The pinned process answers 200 for languages.localhost and 400 for systems.localhost: ALLOWED_HOSTS was narrowed to the one site. Giving sites names on your own machine -------------------------------------- Do not rely on *.localhost resolving. On the Windows machine used to write this course, Python could not resolve languages.localhost, and on Ubuntu 24.04 under WSL (no systemd) `getent hosts languages.localhost` returned nothing, although plain localhost worked. Some browsers send *.localhost to your own machine themselves, but curl, scripts and other tools use the system resolver. Debian machines differ: whether it works depends on the name-service modules installed, so check it with `getent hosts languages.localhost` before relying on it. Reliable options, best first: 1. Add the names to the hosts file. This script prints the line (run it from the project folder, and add the result to /etc/hosts on Linux, or C:\Windows\System32\drivers\etc\hosts on Windows, as administrator): """hosts_entries.py: print the lines to add to the hosts file for local development. Linux and macOS: /etc/hosts Windows: C:\\Windows\\System32\\drivers\\etc\\hosts""" from config.sites_config import site_hosts print("127.0.0.1 " + " ".join(site_hosts("dev"))) Output: 127.0.0.1 languages.localhost webdevelopment.localhost programming.localhost systems.localhost ai.localhost humanities.localhost lifeskills.localhost creative.localhost 2. curl --resolve languages.localhost:8000:127.0.0.1 http://languages.localhost:8000/ 3. curl -H "Host: languages.localhost" http://127.0.0.1:8000/ You will not use this machine for development, so check which of these works on the machine you do use. WHY THIS WORKS AS AN ANSWER --------------------------- Seeing real responses (including the failures) checks the whole chain, not just Django's own pieces. The notes on name resolution come from running the check on the machines available, and say what could not be checked.