CMPUT-404 · Lesson 12 of 12 · 36 min

Capstone: a full-stack course-booking application

This capstone brings the course together by designing, building, testing and planning the deployment of Seatwise, an invented course-section booking application for a university like the ones described in this catalogue. Students browse sections, see how many seats are left and register; staff create sections and export class lists. The goal is not a production system but a complete, coherent design in which every earlier lesson has a visible place: HTTP and the browser (lessons 1 to 4), front-end architecture and performance (5 and 6), server-side code, data and APIs (7 to 9), security (10) and operations (11). The code runs as shown, including its tests.

Requirements

Good projects start by writing down what the system must do and how well. Functional requirements describe behaviour: list sections with seats remaining; let a signed-in student register for a section that has space; prevent duplicate registrations and overbooking; send a confirmation email; let staff manage sections. Non-functional requirements describe qualities: meet WCAG 2.2 AA; achieve good Core Web Vitals on a mid-range phone; survive registration day, when thousands of students arrive within minutes; protect personal information under applicable privacy law; and deploy without downtime. Writing measurable targets turns these into testable commitments.

Architecture

Seatwise uses server-rendered HTML for its pages, so the section list appears quickly and works without JavaScript, with a small JavaScript module that enhances the register buttons into in-place updates through a JSON API. This islands-style approach (lesson 5) fits an application that is mostly lists and forms, ships little JavaScript and keeps accessibility simple. The same API can later serve a mobile app. Behind an nginx reverse proxy, three stateless application containers share a PostgreSQL database, a Redis instance for rate limiting and the job queue, and a worker process that sends confirmation emails.

Seatwise architecture. Browser connects over HTTPS to nginx, which serves static assets and forwards to three web containers. Web containers read and write PostgreSQL, use Redis for rate limits and to enqueue email jobs, and a worker container takes jobs from Redis and sends email. Labels show server-rendered HTML pages and a JSON API under /api.
Seatwise architecture: progressive enhancement over server-rendered pages, stateless web containers, database, cache and worker

Data model and API

Three tables capture the domain: users, sections with a capacity, and registrations linking them, with a UNIQUE constraint on section and user so a student cannot register twice. Migrations (lesson 8) create them. The API is small and resource-oriented (lesson 9): GET /api/sections lists sections with seats left, and POST /api/sections/{id}/registrations registers the signed-in user, returning 201 with the new count, 409 when the section is full or the user is already registered, 401 without a session, 403 without a valid CSRF token, and 405 for other methods. Errors use application/problem+json.

Implementation

The core application is a single WSGI class using only the standard library, so every mechanism is visible. It signs its session cookie with HMAC and uses the __Host- prefix; it requires a CSRF token in a custom header for the JSON API, which also triggers a CORS preflight for any cross-origin caller; it escapes all HTML output; and it registers forside a transaction that first takes SQLite's write lock with BEGIN IMMEDIATE, so the capacity check and the insert cannot interleave with another request's. A PostgreSQL version would lock the section row with SELECT ... FOR UPDATE instead. The application and its tests appear together in the next section.

Testing

Tests are part of the deliverable. Because a WSGI application is just a callable, the tests call it directly with hand-built requests, which makes them fast and independent of the network. They cover the happy path and each failure: escaping, missing login, missing or wrong CSRF token, tampered cookies, duplicate registration, capacity, unknown sections, wrong methods, and, most importantly, concurrency. The concurrent test starts 20 threads, each with its own database connection like separate servers, competing for 5 seats in a file-backed database, and asserts that exactly 5 succeed. CMPUT-301 covers testing practice more broadly, including test doubles and continuous integration.

seatwise.py: the application (signed sessions, CSRF, escaping, problem details, transactional registration) and nine tests, including 20 concurrent registrations competing for 5 seats
# seatwise.py: the application and its tests in one file, run in-process (no network needed).
import hashlib, hmac, html, io, json, secrets, sqlite3, base64, re
from urllib.parse import parse_qs

SCHEMA = """
CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT UNIQUE NOT NULL, role TEXT NOT NULL DEFAULT 'student');
CREATE TABLE IF NOT EXISTS sections (id TEXT PRIMARY KEY, course TEXT NOT NULL, title TEXT NOT NULL,
                                     capacity INTEGER NOT NULL CHECK (capacity > 0));
CREATE TABLE IF NOT EXISTS registrations (id INTEGER PRIMARY KEY, section_id TEXT NOT NULL REFERENCES sections(id),
                                       user_id INTEGER NOT NULL REFERENCES users(id), UNIQUE (section_id, user_id));
"""

class CourseBook:
    """Seatwise: a small course-section booking service (WSGI)."""
    def __init__(self, db_path=":memory:", secret=None):
        self.db = sqlite3.connect(db_path, check_same_thread=False)
        self.db.execute("PRAGMA foreign_keys = ON"); self.db.executescript(SCHEMA)
        self.secret = secret or secrets.token_bytes(32)

    # --- sessions ---------------------------------------------------------
    def sign(self, data):
        raw = base64.urlsafe_b64encode(json.dumps(data).encode()).decode()
        return raw + "." + hmac.new(self.secret, raw.encode(), hashlib.sha256).hexdigest()
    def session(self, environ):
        m = re.search(r"(?:^|;\s*)__Host-sid=([^;]+)", environ.get("HTTP_COOKIE", ""))
        if not m: return {}
        raw, _, mac = m.group(1).rpartition(".")
        good = hmac.new(self.secret, raw.encode(), hashlib.sha256).hexdigest()
        return json.loads(base64.urlsafe_b64decode(raw)) if hmac.compare_digest(mac, good) else {}

    # --- domain -----------------------------------------------------------
    def seats_left(self, sid):
        cap, = self.db.execute("SELECT capacity FROM sections WHERE id = ?", (sid,)).fetchone()
        n, = self.db.execute("SELECT count(*) FROM registrations WHERE section_id = ?", (sid,)).fetchone()
        return cap - n
    def register(self, sid, uid):
        with self.db:                                         # BEGIN IMMEDIATE serializes writers in SQLite
            self.db.execute("BEGIN IMMEDIATE") if not self.db.in_transaction else None
            if self.db.execute("SELECT 1 FROM sections WHERE id = ?", (sid,)).fetchone() is None:
                return 404, {"title": "Not Found", "detail": "no such section"}
            if self.seats_left(sid) <= 0:
                return 409, {"title": "Conflict", "detail": "section is full"}
            try:
                self.db.execute("INSERT INTO registrations (section_id, user_id) VALUES (?, ?)", (sid, uid))
            except sqlite3.IntegrityError:
                return 409, {"title": "Conflict", "detail": "already registered"}
        return 201, {"section": sid, "seats_left": self.seats_left(sid)}

    # --- HTTP -------------------------------------------------------------
    def __call__(self, environ, start_response):
        method, path = environ["REQUEST_METHOD"], environ["PATH_INFO"]
        sess = self.session(environ)
        status, body, ctype, extra = 200, None, "application/json", []
        if method == "GET" and path == "/api/sections":
            rows = self.db.execute("SELECT id, course, title FROM sections ORDER BY id").fetchall()
            body = {"items": [{"id": i, "course": c, "title": t, "seats_left": self.seats_left(i)} for i, c, t in rows]}
        elif method == "GET" and path == "/sections":
            rows = self.db.execute("SELECT id, course, title FROM sections ORDER BY id").fetchall()
            items = "".join(f"<li>{html.escape(c)} {html.escape(t)}: {self.seats_left(i)} seats left</li>" for i, c, t in rows)
            body, ctype = f"<main id=\"main\"><h1>Sections</h1><ul>{items}</ul></main>", "text/html; charset=utf-8"
        elif m := re.fullmatch(r"/api/sections/([\w-]+)/registrations", path):
            if method != "POST":
                status, body, extra = 405, {"title": "Method Not Allowed"}, [("Allow", "POST")]
            elif "uid" not in sess:
                status, body = 401, {"title": "Unauthorized", "detail": "sign in first"}
            elif environ.get("HTTP_X_CSRF_TOKEN") != sess.get("csrf"):
                status, body = 403, {"title": "Forbidden", "detail": "bad CSRF token"}
            else:
                status, body = self.register(m.group(1), sess["uid"])
        else:
            status, body = 404, {"title": "Not Found"}
        if status >= 400: ctype = "application/problem+json"; body = {"status": status, **body}
        data = (body if isinstance(body, str) else json.dumps(body)).encode()
        reason = {200: "OK", 201: "Created", 401: "Unauthorized", 403: "Forbidden", 404: "Not Found",
                  405: "Method Not Allowed", 409: "Conflict"}[status]
        start_response(f"{status} {reason}", [("Content-Type", ctype), ("Content-Length", str(len(data))),
                       ("X-Content-Type-Options", "nosniff"), *extra])
        return [data]


# --- tests ------------------------------------------------------------------
import unittest, threading

def call(app, method, path, cookie="", csrf=None, body=b""):
    env = {"REQUEST_METHOD": method, "PATH_INFO": path, "HTTP_COOKIE": cookie,
           "wsgi.input": io.BytesIO(body), "CONTENT_LENGTH": str(len(body))}
    if csrf: env["HTTP_X_CSRF_TOKEN"] = csrf
    out = {}
    data = b"".join(app(env, lambda s, h: out.update(status=int(s.split()[0]), headers=dict(h))))
    return out["status"], out["headers"], data.decode()

class SeatwiseTest(unittest.TestCase):
    def setUp(self):
        self.app = CourseBook()
        self.app.db.executescript("""
            INSERT INTO sections VALUES ('CMPUT404-A1', 'CMPUT 404', 'Web Applications', 2),
                                        ('CMPUT404-B1', 'CMPUT 404', 'Web <Apps> & More', 30);
            INSERT INTO users (email) VALUES ('a@example.ca'), ('b@example.ca'), ('c@example.ca');""")
    def login(self, uid):
        s = {"uid": uid, "csrf": f"tok{uid}"}
        return f"__Host-sid={self.app.sign(s)}", s["csrf"]

    def test_list_sections_json(self):
        status, h, body = call(self.app, "GET", "/api/sections")
        self.assertEqual(status, 200)
        self.assertEqual([s["seats_left"] for s in json.loads(body)["items"]], [2, 30])
    def test_html_is_escaped(self):
        _, h, body = call(self.app, "GET", "/sections")
        self.assertIn("Web &lt;Apps&gt; &amp; More", body)
        self.assertNotIn("<Apps>", body)
    def test_register_requires_login(self):
        self.assertEqual(call(self.app, "POST", "/api/sections/CMPUT404-A1/registrations")[0], 401)
    def test_register_requires_csrf(self):
        cookie, _ = self.login(1)
        self.assertEqual(call(self.app, "POST", "/api/sections/CMPUT404-A1/registrations", cookie, "wrong")[0], 403)
    def test_tampered_cookie_is_anonymous(self):
        cookie, tok = self.login(1)
        bad = cookie[:-1] + ("0" if cookie[-1] != "0" else "1")
        self.assertEqual(call(self.app, "POST", "/api/sections/CMPUT404-A1/registrations", bad, tok)[0], 401)
    def test_register_then_duplicate(self):
        cookie, tok = self.login(1)
        s, h, b = call(self.app, "POST", "/api/sections/CMPUT404-A1/registrations", cookie, tok)
        self.assertEqual((s, json.loads(b)["seats_left"]), (201, 1))
        s, h, b = call(self.app, "POST", "/api/sections/CMPUT404-A1/registrations", cookie, tok)
        self.assertEqual((s, h["Content-Type"]), (409, "application/problem+json"))
    def test_capacity_is_enforced(self):
        codes = [call(self.app, "POST", "/api/sections/CMPUT404-A1/registrations", *self.login(u))[0] for u in (1, 2, 3)]
        self.assertEqual(codes, [201, 201, 409])
    def test_unknown_section_and_method(self):
        cookie, tok = self.login(1)
        self.assertEqual(call(self.app, "POST", "/api/sections/NOPE/registrations", cookie, tok)[0], 404)
        s, h, _ = call(self.app, "GET", "/api/sections/CMPUT404-A1/registrations")
        self.assertEqual((s, h.get("Allow")), (405, "POST"))
    def test_concurrent_registrations_never_overbook(self):
        import os, tempfile
        path = os.path.join(tempfile.mkdtemp(), "seatwise.db")
        app = CourseBook(path, self.app.secret)
        app.db.executescript("INSERT INTO sections VALUES ('LAB-1', 'CMPUT 404', 'Lab', 5);" +
                             "".join(f"INSERT INTO users (email) VALUES ('u{i}@example.ca');" for i in range(20)))
        results, lock = [], threading.Lock()
        def worker(uid):
            mine = CourseBook(path, app.secret)            # one connection per thread, like separate servers
            mine.db.execute("PRAGMA busy_timeout = 5000")
            s = {"uid": uid, "csrf": "t"}
            code = call(mine, "POST", "/api/sections/LAB-1/registrations", f"__Host-sid={mine.sign(s)}", "t")[0]
            with lock: results.append(code)
        threads = [threading.Thread(target=worker, args=(u,)) for u in range(1, 21)]
        for t in threads: t.start()
        for t in threads: t.join()
        self.assertEqual((results.count(201), results.count(409)), (5, 15))
        self.assertEqual(app.seats_left("LAB-1"), 0)

if __name__ == "__main__":
    buf = io.StringIO()
    result = unittest.main(argv=["seatwise"], exit=False,
                           testRunner=unittest.TextTestRunner(stream=buf, verbosity=2)).result
    for line in buf.getvalue().splitlines():
        if " ... " in line: print(line.split(" (")[0], "...", line.rsplit(" ", 1)[1])
    print(f"{result.testsRun} tests, {len(result.failures)} failures, {len(result.errors)} errors")
Output
test_capacity_is_enforced ... ok
test_concurrent_registrations_never_overbook ... ok
test_html_is_escaped ... ok
test_list_sections_json ... ok
test_register_requires_csrf ... ok
test_register_requires_login ... ok
test_register_then_duplicate ... ok
test_tampered_cookie_is_anonymous ... ok
test_unknown_section_and_method ... ok
9 tests, 0 failures, 0 errors

Front end, accessibility and performance

The page is plain server-rendered HTML that works with JavaScript disabled: each section has a real form that posts and redirects. A small module, loaded with a CSP nonce, intercepts the submit, calls the JSON API with the CSRF token and updates the seat count in place. The count sits in an aria-live region, so screen-reader users hear the change, and button labels name the section, so a list of buttons is not just repeated Register labels. Only a few kilobytes of JavaScript are shipped, so INP stays low even on modest phones. Usability testing with students, including disabled students, would confirm that the flow works in practice; CMPUT-421 covers user research and evaluation methods.

A progressively enhanced register button with a live region and CSRF header (listing)
<!-- The registration widget: progressive enhancement over a server-rendered list (listing). -->
<li>
  CMPUT 404 Web Applications: <span id="left-A1" aria-live="polite">2 seats left</span>
  <form method="post" action="/sections/CMPUT404-A1/register" data-section="CMPUT404-A1">
    <input type="hidden" name="csrf" value="(token from session)">
    <button type="submit">Register for section A1</button>
  </form>
</li>
<script type="module" nonce="(per-response nonce)">
  for (const form of document.querySelectorAll('form[data-section]')) {
    form.addEventListener('submit', async e => {
      e.preventDefault();                                  // without JavaScript, the form still posts
      const id = form.dataset.section, button = form.querySelector('button');
      button.disabled = true;
      const r = await fetch(`/api/sections/${id}/registrations`, {
        method: 'POST', headers: { 'X-CSRF-Token': form.csrf.value } });
      const body = await r.json();
      document.getElementById(`left-${id.split('-')[1]}`).textContent =
        r.ok ? `${body.seats_left} seats left. You are registered.` : body.detail;
      button.disabled = r.ok;
    });
  }
</script>

The performance plan follows lesson 6: section lists are server-rendered and compressed; static assets are fingerprinted and cached for a year; the API response for the section list is cached for a few seconds in Redis because thousands of students request it at once, while registration always goes to the database; and a Lighthouse CI budget runs on every pull request.

Security and privacy review

A short threat model asks what could go wrong for each part. A student might try to register another student or exceed capacity: object-level checks use only the session's user, and the transaction enforces capacity. A malicious site might trigger registrations: SameSite cookies and the CSRF header stop it. Scripts might hammer the register endpoint on registration day: a token bucket per user and per IP, enforced in Redis and nginx, limits them. Section titles entered by staff might contain markup: escaping and CSP neutralize it. Dependencies might become vulnerable: lock files and automated scanning catch it. Logs might leak personal data: they record user identifiers, not names or emails.

Because Seatwise processes students' personal information, the design follows privacy law. A Canadian university's public-sector activities fall under provincial law, such as Alberta's Freedom of Information and Protection of Privacy Act or Ontario's FIPPA, while a private provider of the same service would be under PIPEDA, or under Quebec's Law 25 for Quebec students, which requires a privacy impact assessment before deploying such a system and before storing data outside Quebec. In all cases the design collects only what registration needs, states the purpose at collection, keeps class lists only as long as academic records policy requires, restricts staff access by role, logs access to personal data, and has a breach response plan. Accessibility obligations under provincial law, such as AODA in Ontario, and the Accessible Canada Act for federally regulated organizations, are met by targeting WCAG 2.2 AA.

Deployment plan

The pilot deployment uses containers on a small cluster of virtual machines, described by a Compose file; a larger rollout could move the same images to Kubernetes or a managed container platform without code changes, because the application follows the twelve-factor rules. Releases are built by the CI pipeline after tests pass, tagged with a version, and rolled out one container at a time behind health checks, with database migrations following expand-and-contract so that old and new containers can run together. Backups of the database run nightly with point-in-time recovery, and restores are tested each term.

A Compose description of the pilot deployment, with secrets supplied by the host (listing)
# compose.yaml: the deployment for a pilot term (listing). Secrets come from the host, not the file.
services:
  web:
    image: registry.example.ca/seatwise:1.4.2
    environment:
      DATABASE_URL: postgres://seatwise@db:5432/seatwise
      SESSION_SECRET_FILE: /run/secrets/session_secret
    secrets: [session_secret]
    deploy: { replicas: 3 }
    healthcheck: { test: ["CMD", "python", "-m", "seatwise.healthcheck"], interval: 10s }
  worker:
    image: registry.example.ca/seatwise:1.4.2
    command: ["python", "-m", "seatwise.worker"]          # sends confirmation emails from the queue
  proxy:
    image: nginx:1.27
    ports: ["443:443"]
    volumes: ["./nginx.conf:/etc/nginx/nginx.conf:ro"]
  db:
    image: postgres:17
    volumes: ["pgdata:/var/lib/postgresql/data"]
  redis:
    image: redis:7
secrets:
  session_secret: { file: /etc/seatwise/session_secret }
volumes:
  pgdata: {}
A registration-day capacity estimate using Little's law
# Will it survive registration day? A capacity estimate from expected traffic, using Little's law.
students, window_min = 9000, 15                 # most students try to register for the first 15 minutes
requests_per_student = 12                       # page loads, API calls, retries
peak_factor = 3                                 # the first minute is busier than average
rate = students * requests_per_student / (window_min * 60) * peak_factor
service_s = 0.060                               # mean time per request in a worker
workers_needed = rate * service_s / 0.7         # Little's law, at 70% target utilization
print(f"peak rate      {rate:6.0f} requests/s")
print(f"busy workers   {rate * service_s:6.1f} on average")
print(f"provision      {workers_needed:6.1f} -> {int(-(-workers_needed // 8))} servers of 8 workers, +1 spare")
db_writes = students * 2 / (window_min * 60) * peak_factor
print(f"database writes {db_writes:5.0f}/s at peak (registrations + email jobs): fine for one primary")
Output
peak rate         360 requests/s
busy workers     21.6 on average
provision        30.9 -> 4 servers of 8 workers, +1 spare
database writes    60/s at peak (registrations + email jobs): fine for one primary

The estimate suggests a modest fleet, but it rests on assumptions that a load test must check: traffic shape, retry storms when pages are slow, and the cost of the busiest queries. A rehearsal a week before registration, replaying the expected traffic against a staging copy, is the real test. Monitoring tracks the RED metrics for each endpoint, the queue backlog, database connections and the SLO, with an on-call engineer for registration day and a rollback plan ready.

Worked examples

Worked problem

Tracing an registration end to end

Follow one click on Register for section A1 from the browser to the database and back.

Show solution
  1. The module prevents the default submit and sends POST /api/sections/CMPUT404-A1/registrations with the cookie and X-CSRF-Token header over HTTPS (lessons 1, 4).
  2. nginx terminates TLS, applies the rate limit and forwards to one of three web containers (lesson 11).
  3. The application verifies the cookie signature, checks the CSRF token and runs the registration transaction (lessons 7, 8, 10).
  4. It returns 201 with seats_left as JSON and queues a confirmation email job (lessons 9, 11).
  5. The module updates the live region, which a screen reader announces (lessons 2, 5).

Answer: Every layer of the course participates in a single click.

Worked problem

Interpreting a failing test

After a refactor, test_concurrent_registrations_never_overbook fails with 6 successes and 14 conflicts. What probably happened?

Show solution
  1. Six successes for five seats means two requests both saw one seat left and both inserted.
  2. The capacity check and insert are no longer in the same serialized transaction; perhaps BEGIN IMMEDIATE was removed or the check moved outside the transaction.
  3. Restore the locking transaction (or SELECT ... FOR UPDATE in PostgreSQL), or enforce capacity with a database constraint or trigger.

Answer: A race between check and insert; restore the serialized transaction.

Worked problem

Deciding what to cache

On registration day the section list is requested 1500 times per second and changes with every registration. Can it be cached?

Show solution
  1. Seat counts change constantly, but showing a count a few seconds old is acceptable because registration itself checks capacity.
  2. Cache the list for 2 seconds in Redis: at most one database query per 2 seconds instead of 3000.
  3. Never cache the registration response or per-user data in shared caches.
  4. Show the authoritative count returned by the registration response after each click.

Answer: Yes, briefly: correctness is protected by the transaction, not by the list.

Common mistakes

  • MistakeBuild the full SPA first and add accessibility later.

    InsteadStart from accessible server-rendered HTML and enhance it; retrofitting accessibility is costly.

  • MistakeTest only the happy path.

    InsteadTest every failure status and concurrency; the overbooking test catches the most serious bug.

  • MistakeCheck capacity in JavaScript before registering.

    InsteadClient checks are for convenience; the server transaction is the authority.

  • MistakeSize servers from average traffic.

    InsteadRegistration day is a peak many times the average; plan from the peak with headroom.

  • MistakeDeploy migrations and code together in one step.

    InsteadUse expand-and-contract so old and new versions work during rolling deployments.

Summary and key terms

  • Write functional and measurable non-functional requirements first.
  • Server-rendered pages enhanced by a small module suit list-and-form applications.
  • A small resource-oriented API with problem details serves the page and future clients.
  • Transactions and constraints prevent overbooking; tests prove it under concurrency.
  • Security combines signed cookies, CSRF tokens, escaping, CSP, rate limits and object checks.
  • Privacy and accessibility law shape data collection, retention and interface design.
  • Twelve-factor containers, rolling deployments, capacity estimates and monitoring complete the plan.
Functional requirement
A statement of what the system must do.
Non-functional requirement
A measurable quality such as speed, accessibility or availability.
Progressive enhancement
Building a working baseline in HTML and adding JavaScript improvements on top.
Threat model
A structured list of what could go wrong and how the design prevents it.
BEGIN IMMEDIATE
A SQLite statement that takes the write lock at the start of a transaction.
Load test
Generating realistic traffic against a system to verify its capacity.
Health check
An endpoint or command that reports whether an instance can serve requests.
Rollback plan
Prepared steps for returning to the previous release if a deployment fails.
Privacy impact assessment
An evaluation of privacy risks before deploying a system that handles personal information.
Live region
An element whose changes are announced by screen readers.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Give one functional and one non-functional requirement for Seatwise.

    Show answer

    Functional: register a student in a section with space. Non-functional: p75 LCP at most 2.5 s.

  2. Why does Seatwise use server-rendered pages with a small module instead of an SPA?

    Show answer

    It is mostly lists and forms; server rendering is fast, accessible and works without JavaScript.

  3. Which status codes can the registration endpoint return, and when?

    Show answer

    201 success, 401 not signed in, 403 bad CSRF token, 404 unknown section, 405 wrong method, 409 full or already registered.

  4. What does BEGIN IMMEDIATE achieve in the register method?

    Show answer

    It takes the write lock first, so the capacity check and insert are not interleaved with another writer.

  5. In the test run, how many of the 20 concurrent registrations succeeded?

    Show answer

    Exactly 5, matching the section capacity.

  6. Why is the CSRF token sent in a custom header for the API?

    Show answer

    Other sites cannot read it, and a custom header also forces a CORS preflight for cross-origin callers.

  7. Why is the seat count in an aria-live region?

    Show answer

    So screen readers announce the updated count after registering.

  8. According to the capacity estimate, roughly what is the peak request rate?

    Show answer

    About 360 requests per second.

  9. Why can the section list be cached briefly but not the registration response?

    Show answer

    Slightly stale counts are harmless because registration checks capacity; registration responses are per-user and must be fresh.

  10. Name two things monitored on registration day.

    Show answer

    Request rate, errors and latency per endpoint; queue backlog; database connections; SLO burn.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz