ЁЯПл The SchoolтА║ЁЯЫОя╕П API GatewayтА║ЁЯЧ║я╕П рдзрдбрд╛ 03 тАФ Routes рдЖрдгрд┐ integrations: рдХреЛрдгрддрд╛ desk, рдХреЛрдгрддреЗ kitchen
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯЧ║я╕П рдзрдбрд╛ 03 тАФ Routes рдЖрдгрд┐ integrations: рдХреЛрдгрддрд╛ desk, рдХреЛрдгрддреЗ kitchen

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 03 ┬╖ рдорд╛рдЧреЗ: lesson-02-rest-http-websocket ┬╖ рдкреБрдвреЗ: lesson-04-mapping-validation


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ02, рдЖрдгрд┐ office desk рдХрд╕рд╛ рдирд┐рд╡рдбрддреЗ: path parameters ({id}), рд╕рдЧрд│реЗ рдкрдХрдбрдгрд╛рд░реЗ {proxy+}, ANY method, "рд╕рд░реНрд╡рд╛рдд рдиреЗрдордХрд╛ route рдЬрд┐рдВрдХрддреЛ" рд╣рд╛ рдирд┐рдпрдо, рдЖрдгрд┐ integration тАФ office visitor рд▓рд╛ рдХреБрдареЗ рдкреБрдвреЗ рдкрд╛рдард╡рддреЗ рддреЗ. apigw/demo.py рдордзрд▓реЗ routes() рдкреНрд░рддреНрдпреЗрдХ рдирд┐рдпрдо рджрд╛рдЦрд╡рддреЗ.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

Front office рдордзреНрдпреЗ desks рдЪрд╛ рдПрдХ рдлрд▓рдХ рдЖрд╣реЗ:

рдПрдХ visitor рдореНрд╣рдгрддреЛ "рд╡рд┐рджреНрдпрд╛рд░реНрдереА, рдореА". рджреЛрди desks рдмрд╕реВ рд╢рдХрддрд╛рдд: "рд╡рд┐рджреНрдпрд╛рд░реНрдереА, рдХреНрд░рдорд╛рдВрдХ ___" (рд░рд┐рдХрд╛рдореА рдЬрд╛рдЧрд╛ = "рдореА") рдЖрдгрд┐ "рд╡рд┐рджреНрдпрд╛рд░реНрдереА, рдореА". Clerk рдиреЗрд╣рдореА рддреНрдпрд╛рд▓рд╛ рдЬрд╛рд╕реНрдд рдиреЗрдордХреНрдпрд╛ desk рдХрдбреЗ рдкрд╛рдард╡рддреЛ. рдард░рд▓реЗрд▓рд╛ рд╢рдмреНрдж рд░рд┐рдХрд╛рдореНрдпрд╛ рдЬрд╛рдЧреЗрд╡рд░ рдЬрд┐рдВрдХрддреЛ, рдЖрдгрд┐ "рдХрд╛рд╣реАрд╣реА" desk рд╣реА рд╢реЗрд╡рдЯрдЪреА рдирд┐рд╡рдб рдЕрд╕рддреЗ.

рдЖрдгрд┐ рдЬрд░ visitor рдиреЗ рдлрдХреНрдд рдиреЛрдВрджреА рд╡рд╛рдЪрдгрд╛рд▒реНрдпрд╛ desk рд╡рд░ рд╡рд┐рджреНрдпрд╛рд░реНрдереНрдпрд╛рдЪреА рдиреЛрдВрдж рдлреЗрдХреВрди рджреНрдпрд╛рдпрд▓рд╛ рд╕рд╛рдВрдЧрд┐рддрд▓реЗ рддрд░? Desk рдЖрд╣реЗ, рдкрдг рддреНрдпрд╛ рдХрд╛рдорд╛рд╕рд╛рдареА рдирд╛рд╣реА: "рдкрд░рд╡рд╛рдирдЧреА рдирд╛рд╣реА".

рдкреНрд░рддреНрдпреЗрдХ desk рд▓рд╛ рдПрдХрд╛ kitchen рдХрдбреЗ рдЬрд╛рдгрд╛рд░реЗ рджрд╛рд░ рдЖрд╣реЗ тАФ рддреЗ рджрд╛рд░ рдореНрд╣рдгрдЬреЗ integration.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    r1["GET /students/me"] -->|"fixed word wins"| d1["ЁЯкС /students/me<br/>тЖТ me() ┬╖ JWT"]
    r2["GET /students/9"] -->|"id = 9"| d2["ЁЯкС /students/{id}<br/>тЖТ get_student()"]
    r3["GET /files/term1/maths/paper.pdf"] -->|"proxy = term1/maths/paper.pdf"| d3["ЁЯкС /files/{proxy+}<br/>тЖТ files()"]
    r4["DELETE /students/9"] -->|"path fits, method does not"| d4["ЁЯЪл 405 Method Not Allowed"]
    d2 --> k["ЁЯН│ integration<br/>Lambda ┬╖ HTTP ┬╖ AWS service ┬╖ mock ┬╖ VPC link"]

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/apigateway/lesson-diagrams.html#l03

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдЪреБрдХреАрдЪрд╛ route рд╣рд╛ рд╢рд╛рдВрдд bug рдЕрд╕рддреЛ: request рдЪреБрдХреАрдЪреНрдпрд╛ desk рд╡рд░ рдкреЛрд╣реЛрдЪрддреЗ рдЖрдгрд┐ рдХреЛрдгрд╛рд▓рд╛рд╣реА error рджрд┐рд╕рдд рдирд╛рд╣реА. {id} desk рдиреЗ рд╣рд╛рддрд╛рд│рд▓реЗрд▓рд╛ /students/me "me" рдирд╛рд╡рд╛рдЪрд╛ рд╡рд┐рджреНрдпрд╛рд░реНрдереА рд╢реЛрдзрддреЛ. рдЖрдгрд┐ ANY /{proxy+} рд╕реЛрдпреАрдЪреЗ рдЖрд╣реЗ, рдкрдг back end рдиреЗ рддрдкрд╛рд╕рд▓реЗ рдирд╛рд╣реА рддрд░ рддреЗ /admin/... рд╕реБрджреНрдзрд╛ back end рдХрдбреЗ рдкрд╛рдард╡рддреЗ. рдкреНрд░рддреНрдпреЗрдХ path рд▓рд╛ рдиреЗрдордХрд╛ рдХреЛрдгрддрд╛ desk рдЙрддреНрддрд░ рджреЗрддреЛ рддреЗ рдорд╛рд╣реАрдд рдареЗрд╡рд╛.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

apigw/gateway.py рдордзрд▓реЗ compile_path() рд╣реЗ {id} рд▓рд╛ "рдПрдХ segment" рдЖрдгрд┐ {proxy+} рд▓рд╛ "рдЙрд░рд▓реЗрд▓реЗ рд╕рдЧрд│реЗ" рдордзреНрдпреЗ рдмрджрд▓рддреЗ. Gateway.match() path рдЬреБрд│рдгрд╛рд░реЗ рдкреНрд░рддреНрдпреЗрдХ route рдЧреЛрд│рд╛ рдХрд░рддреЗ, рдпреЛрдЧреНрдп method (рдХрд┐рдВрд╡рд╛ ANY) рдЕрд╕рд▓реЗрд▓реЗ рдареЗрд╡рддреЗ, рдЖрдгрд┐ рд╕рд░реНрд╡рд╛рдд рдиреЗрдордХрд╛ рдирд┐рд╡рдбрддреЗ (Route.specificity()): рдХреЛрдгрддрд╛рд╣реА path рдЬреБрд│рд▓рд╛ рдирд╛рд╣реА тЖТ 404, path рдЬреБрд│рд▓рд╛ рдкрдг method рдирд╛рд╣реА тЖТ 405. Integration рдореНрд╣рдгрдЬреЗ рдПрдХ Python function, рдЬреНрдпрд╛рд▓рд╛ params, query, body, principal рдЖрдгрд┐ stageVariables рд╕рд╣ рдПрдХ event рдорд┐рд│рддреЛ тАФ Lambda proxy event рд╕рд╛рд░рдЦреАрдЪ рдЖрдХрд╛рд░рд╛рдЪреА рдХрд▓реНрдкрдирд╛.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 apigw/demo.py routes
python3 - <<'EOF'
import sys; sys.path.insert(0, "apigw"); from demo import office
from gateway import Route
gw, _ = office()
for m, p in [("GET", "/students/9"), ("GET", "/students/me"), ("GET", "/files/term1/maths/paper.pdf"), ("GET", "/files/"), ("DELETE", "/students/9"), ("GET", "/students/9/marks")]:
    r, params, code = gw.match(m, p)
    print(f"{m:<6} {p:<30} тЖТ {code} {r.path if r else None} {params}")
gw.add(Route("ANY", "/library/{proxy+}", lambda e: (200, {"method": e["method"], "rest": e["params"]["proxy"]})))
for m in ("GET", "DELETE"):
    print(m, gw.handle(m, "/library/books/42")[2])
EOF

рдЖрдгрд┐ рдЪрд╛рд▓реВ office рд╡рд░:

python3 apigw/demo.py serve &
sleep 1                                # give the office a second to open
curl localhost:8080/files/term1/maths/paper.pdf
curl -i -X DELETE localhost:8080/students/9
kill %1

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

routes рд╣реЗ GET /students/9 тЖТ 200 {'id': '9', 'name': 'Katrina', 'class': '9A'}, GET /students/me тЖТ 200 {'you': 'teacher-42'}, GET /files/term1/maths/paper.pdf тЖТ 200 {'file': 'term1/maths/paper.pdf'} рдЖрдгрд┐ DELETE /students/9 тЖТ 405 {'message': 'Method Not Allowed'} print рдХрд░рддреЗ.

рддреБрдордЪрд╛ snippet рд╣реЗ print рдХрд░рддреЛ:

GET    /students/9                    тЖТ 200 /students/{id} {'id': '9'}
GET    /students/me                   тЖТ 200 /students/me {}
GET    /files/term1/maths/paper.pdf   тЖТ 200 /files/{proxy+} {'proxy': 'term1/maths/paper.pdf'}
GET    /files/                        тЖТ 404 None None
DELETE /students/9                    тЖТ 405 None None
GET    /students/9/marks              тЖТ 404 None None
GET {'method': 'GET', 'rest': 'books/42'}
DELETE {'method': 'DELETE', 'rest': 'books/42'}

{proxy+} рд▓рд╛ рдХрд┐рдорд╛рди рдПрдХ character рд▓рд╛рдЧрддреЛ (/files/ тЖТ 404), {id} рдХрдзреАрдЪ / рдШреЗрдд рдирд╛рд╣реА (/students/9/marks тЖТ 404), рдЖрдгрд┐ ANY рдиреЗ GET рдЖрдгрд┐ DELETE рджреЛрдиреНрд╣реА рдШреЗрддрд▓реЗ. рдЪрд╛рд▓реВ office {"file": "term1/maths/paper.pdf"} рдЖрдгрд┐ HTTP/1.0 405 Method Not Allowed рдЕрд╕реЗ рдЙрддреНрддрд░ рджреЗрддреЗ.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рдХреЛрдгрддреНрдпрд╛рд╣реА path рд╕рд╛рдареА desk рдХреЛрдгрддрд╛ рддреЗ рддреБрдореНрд╣реА рдЖрдзреАрдЪ рд╕рд╛рдВрдЧреВ рд╢рдХрддрд╛: рдард░рд▓реЗрд▓рд╛ рд╢рдмреНрдж {id} рд╡рд░ рдЬрд┐рдВрдХрддреЛ, {proxy+} рдЙрд░рд▓реЗрд▓реЗ рдШреЗрддреЛ, рдЖрдгрд┐ рдЬреБрд│рдгрд╛рд░рд╛ path рдкрдг рдЪреБрдХреАрдЪреА method рдореНрд╣рдгрдЬреЗ 405, 404 рдирд╛рд╣реА.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

On a real account тАФ рддреЗрдЪ routes API Gateway extensions рд╕рд╣ OpenAPI рдордзреНрдпреЗ (aws apigateway import-rest-api --body fileb://school.yaml рдиреЗ import рдХрд░рд╛):

openapi: 3.0.1
info: { title: school-api, version: "1" }
paths:
  /students/{id}:
    get:
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      x-amazon-apigateway-integration:
        type: aws_proxy
        httpMethod: POST
        uri: arn:aws:apigateway:ap-south-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-south-1:111122223333:function:students/invocations
  /files/{proxy+}:
    x-amazon-apigateway-any-method:
      x-amazon-apigateway-integration:
        type: http_proxy
        httpMethod: ANY
        uri: https://files.internal.school.example/{proxy}
        requestParameters: { integration.request.path.proxy: method.request.path.proxy }

Client рд╢рд┐рд╡рд╛рдп рдПрдХ route test рдХрд░рд╛: aws apigateway test-invoke-method --rest-api-id abc123 --resource-id r1s2t3 --http-method GET --path-with-query-string /students/9.

ЁЯПн Production рдордзреНрдпреЗ рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: routes code рдордзреНрдпреЗ рдареЗрд╡рд╛ (OpenAPI, SAM, Terraform), code рд╕рд╛рд░рдЦреЗрдЪ рддреНрдпрд╛рдВрдЪреЗ review рдХрд░рд╛, рдЖрдгрд┐ back end рдХрдбреЗ publish рдХрд░реВ рдирдпреЗрдд рдЕрд╕реЗ paths рдЕрд╕рддреАрд▓ рддреЗрд╡реНрд╣рд╛ catch-all рдРрд╡рдЬреА рдиреЗрдордХреЗ routes рд╡рд╛рдкрд░рд╛.

тПня╕П рдкреБрдвреЗ

рдпреЛрдЧреНрдп desk рд╕рд╛рдкрдбрд▓рд╛. рдкрдг visitor рдЪрд╛ form рдЪреБрдХреАрдЪрд╛ рдЕрд╕реЗрд▓ рддрд░? Validation рдЖрдгрд┐ mapping тАФ kitchen рд▓рд╛ рджрд┐рд╕рдгреНрдпрд╛рдЖрдзреАрдЪ рдЪреБрдХреАрдЪрд╛ form рдирд╛рдХрд╛рд░рд╛.

git checkout lesson-04-mapping-validation

ЁЯЧ║я╕П Lesson 03 тАФ Routes & integrations: which desk, which kitchen

ЁЯУН You are here: Lesson 03 of 12 ┬╖ Previous: lesson-02-rest-http-websocket ┬╖ Next: lesson-04-mapping-validation


ЁЯУж What's in this branch

Lessons 01тАУ02, plus how the office picks a desk: path parameters ({id}), the greedy {proxy+}, the ANY method, the rule "the most specific route wins", and the integration тАФ where the office forwards the visitor. routes() in apigw/demo.py shows each rule.

ЁЯзТ Explain like I'm 5

The front office has a board of desks:

A visitor says "Students, me". Two desks could fit: "Students, number ___" (with the blank = "me") and "Students, ME". The clerk always sends them to the more exact desk. A fixed word beats a blank, and the "anything" desk is the last choice.

And if the visitor asks to throw away a student record at a desk that only reads records? The desk exists, but not for that job: "not allowed".

Each desk has a door to one kitchen тАФ that door is the integration.

ЁЯЧ║я╕П Diagram

flowchart LR
    r1["GET /students/me"] -->|"fixed word wins"| d1["ЁЯкС /students/me<br/>тЖТ me() ┬╖ JWT"]
    r2["GET /students/9"] -->|"id = 9"| d2["ЁЯкС /students/{id}<br/>тЖТ get_student()"]
    r3["GET /files/term1/maths/paper.pdf"] -->|"proxy = term1/maths/paper.pdf"| d3["ЁЯкС /files/{proxy+}<br/>тЖТ files()"]
    r4["DELETE /students/9"] -->|"path fits, method does not"| d4["ЁЯЪл 405 Method Not Allowed"]
    d2 --> k["ЁЯН│ integration<br/>Lambda ┬╖ HTTP ┬╖ AWS service ┬╖ mock ┬╖ VPC link"]

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/apigateway/lesson-diagrams.html#l03

тЭУ What

ЁЯдФ Why

Because a wrong route is a quiet bug: the request lands on the wrong desk and nobody sees an error. /students/me handled by the {id} desk looks up a student called "me". And ANY /{proxy+} is convenient, but it also sends /admin/... to the back end unless the back end checks it. Know exactly which desk answers each path.

ЁЯФз How (in this repo)

compile_path() in apigw/gateway.py turns {id} into "one segment" and {proxy+} into "the rest". Gateway.match() collects every route whose path fits, keeps those with the right method (or ANY), and picks the most specific (Route.specificity()): no fitting path тЖТ 404, a fitting path but no fitting method тЖТ 405. The integration is a Python function that receives an event with params, query, body, principal and stageVariables тАФ the same shape idea as a Lambda proxy event.

ЁЯзк Try it

python3 apigw/demo.py routes
python3 - <<'EOF'
import sys; sys.path.insert(0, "apigw"); from demo import office
from gateway import Route
gw, _ = office()
for m, p in [("GET", "/students/9"), ("GET", "/students/me"), ("GET", "/files/term1/maths/paper.pdf"), ("GET", "/files/"), ("DELETE", "/students/9"), ("GET", "/students/9/marks")]:
    r, params, code = gw.match(m, p)
    print(f"{m:<6} {p:<30} тЖТ {code} {r.path if r else None} {params}")
gw.add(Route("ANY", "/library/{proxy+}", lambda e: (200, {"method": e["method"], "rest": e["params"]["proxy"]})))
for m in ("GET", "DELETE"):
    print(m, gw.handle(m, "/library/books/42")[2])
EOF

And on the live office:

python3 apigw/demo.py serve &
sleep 1                                # give the office a second to open
curl localhost:8080/files/term1/maths/paper.pdf
curl -i -X DELETE localhost:8080/students/9
kill %1

тЬЕ Verify тАФ what you should see

routes prints GET /students/9 тЖТ 200 {'id': '9', 'name': 'Katrina', 'class': '9A'}, GET /students/me тЖТ 200 {'you': 'teacher-42'}, GET /files/term1/maths/paper.pdf тЖТ 200 {'file': 'term1/maths/paper.pdf'} and DELETE /students/9 тЖТ 405 {'message': 'Method Not Allowed'}.

Your snippet prints:

GET    /students/9                    тЖТ 200 /students/{id} {'id': '9'}
GET    /students/me                   тЖТ 200 /students/me {}
GET    /files/term1/maths/paper.pdf   тЖТ 200 /files/{proxy+} {'proxy': 'term1/maths/paper.pdf'}
GET    /files/                        тЖТ 404 None None
DELETE /students/9                    тЖТ 405 None None
GET    /students/9/marks              тЖТ 404 None None
GET {'method': 'GET', 'rest': 'books/42'}
DELETE {'method': 'DELETE', 'rest': 'books/42'}

{proxy+} needs at least one character (/files/ тЖТ 404), {id} never takes a / (/students/9/marks тЖТ 404), and ANY took both GET and DELETE. The live office answers {"file": "term1/maths/paper.pdf"} and HTTP/1.0 405 Method Not Allowed.

ЁЯПБ What you just proved

You can predict the desk for any path: a fixed word beats {id}, {proxy+} takes the rest, and a path that fits with the wrong method is a 405, not a 404.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ the same routes in OpenAPI with API Gateway extensions (import with aws apigateway import-rest-api --body fileb://school.yaml):

openapi: 3.0.1
info: { title: school-api, version: "1" }
paths:
  /students/{id}:
    get:
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      x-amazon-apigateway-integration:
        type: aws_proxy
        httpMethod: POST
        uri: arn:aws:apigateway:ap-south-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-south-1:111122223333:function:students/invocations
  /files/{proxy+}:
    x-amazon-apigateway-any-method:
      x-amazon-apigateway-integration:
        type: http_proxy
        httpMethod: ANY
        uri: https://files.internal.school.example/{proxy}
        requestParameters: { integration.request.path.proxy: method.request.path.proxy }

Test one route without a client: aws apigateway test-invoke-method --rest-api-id abc123 --resource-id r1s2t3 --http-method GET --path-with-query-string /students/9.

ЁЯПн Why this matters in production: keep routes in code (OpenAPI, SAM, Terraform), review them like code, and prefer exact routes over a catch-all when the back end has paths it should not publish.

тПня╕П Next

The right desk is found. But what if the visitor's form is wrong? Validation and mapping тАФ reject a bad form before the kitchen sees it.

git checkout lesson-04-mapping-validation
тЖР Previousrest http websocketNext тЖТmapping validation

This page is the lesson's README from the lesson-03-routes-integrations branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.