ЁЯЧ║я╕П рдзрдбрд╛ 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 рдЪрд╛ рдПрдХ рдлрд▓рдХ рдЖрд╣реЗ:
- "рд╡рд┐рджреНрдпрд╛рд░реНрдереА, рдХреНрд░рдорд╛рдВрдХ ___" тАФ рд░рд┐рдХрд╛рдореНрдпрд╛ рдЬрд╛рдЧреЗрдд рдХреЛрдгрддрд╛рд╣реА рдХреНрд░рдорд╛рдВрдХ рдмрд╕рддреЛ (
/students/{id}). - "рд╡рд┐рджреНрдпрд╛рд░реНрдереА, рдореА" тАФ рдлрдХреНрдд "рдорд▓рд╛ рдорд╛рдЭреНрдпрд╛рдмрджреНрджрд▓ рд╕рд╛рдВрдЧрд╛" рдпрд╛рд╕рд╛рдареАрдЪрд╛ desk (
/students/me). - "Files, рдпрд╛рдЪреНрдпрд╛рдирдВрддрд░ рдХрд╛рд╣реАрд╣реА" тАФ рдкреНрд░рддреНрдпреЗрдХ folder рдордзрд▓реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ file рд╕рд╛рдареА рдПрдХрдЪ desk
(
/files/{proxy+}).
рдПрдХ 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 тАФ method рдЕрдзрд┐рдХ path, рдЙрджрд╛.
GET /students/{id}. REST API рдордзреНрдпреЗ path рдЪреЗ рднрд╛рдЧ рдореНрд╣рдгрдЬреЗ resources рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХрд╛рд▓рд╛ methods рдЕрд╕рддрд╛рдд; HTTP API рдордзреНрдпреЗ route рдПрдХрд╛рдЪ string рдореНрд╣рдгреВрди рд▓рд┐рд╣рд┐рд▓рд╛ рдЬрд╛рддреЛ,GET /students/{id}. - Path parameter тАФ
{id}рд╣реЗ path рдЪрд╛ рдПрдХ segment рдЬреБрд│рд╡рддреЗ (/рдирд╛рд╣реА). рддреА value back end рдкрд░реНрдпрдВрдд рдкреЛрд╣реЛрдЪрддреЗ, рдЙрджрд╛.id = 9. - Greedy path variable тАФ
{proxy+}рддреНрдпрд╛рдЪреНрдпрд╛рдирдВрддрд░рдЪреЗ рд╕рдЧрд│реЗ рдЬреБрд│рд╡рддреЗ, slashes рд╕рд╣. рддреЛ path рдЪрд╛ рд╢реЗрд╡рдЯрдЪрд╛ рднрд╛рдЧ рдЕрд╕рд╛рдпрд▓рд╛рдЪ рд╣рд╡рд╛. - ANY тАФ GET, POST, PUT, DELETE рдЖрдгрд┐ рдмрд╛рдХреА рд╕рдЧрд│реЗ рдкрдХрдбрдгрд╛рд░реА рдПрдХрдЪ method.
ANY /{proxy+}рдкреНрд░рддреНрдпреЗрдХ request рдПрдХрд╛рдЪ back end рдХрдбреЗ рдкрд╛рдард╡рддреЗ (web frameworks рд╕рд╣ рд╣реЗ рдиреЗрд╣рдореАрдЪреЗ рдЖрд╣реЗ). HTTP APIs рдордзреНрдпреЗ, рдЗрддрд░ рдХреЛрдгрддрд╛рд╣реА route рдЬреБрд│рдд рдирд╛рд╣реА рддреНрдпрд╛ рд╕рдЧрд│реНрдпрд╛рд╕рд╛рдареА$defaultroute рд╕реБрджреНрдзрд╛ рдЕрд╕рддреЛ. - рд╕рд░реНрд╡рд╛рдд рдиреЗрдордХрд╛ рдЬрд┐рдВрдХрддреЛ тАФ рдард░рд▓реЗрд▓рд╛ segment
{param}рд╡рд░ рдЬрд┐рдВрдХрддреЛ, рдЖрдгрд┐ greedy{proxy+}рд╣рд╛ рд╢реЗрд╡рдЯрдЪрд╛ рдкрд░реНрдпрд╛рдп рдЕрд╕рддреЛ. - Integration тАФ office рдХреБрдареЗ рдкреБрдвреЗ рдкрд╛рдард╡рддреЗ:
- Lambda тАФ proxy (рдкреВрд░реНрдг request event рдореНрд╣рдгреВрди рдЖрдд рдЬрд╛рддреЗ; function рдкреВрд░реНрдг response рдмрдирд╡рддреЗ) рдХрд┐рдВрд╡рд╛ custom (office input рдЖрдгрд┐ output рдирд╡реНрдпрд╛рдиреЗ рдорд╛рдВрдбрддреЗ тАФ рдзрдбрд╛ 04);
- HTTP тАФ proxy рдХрд┐рдВрд╡рд╛ custom, рдХреЛрдгрддреНрдпрд╛рд╣реА public HTTP back end рдХрдбреЗ;
- AWS service тАФ рдЙрджрд╛. DynamoDB, SQS рдХрд┐рдВрд╡рд╛ Step Functions рд▓рд╛ рдереЗрдЯ call рдХрд░рд╛;
- mock тАФ office рд╕реНрд╡рддрдГрдЪ рдЙрддреНрддрд░ рджреЗрддреЗ (tests рдЖрдгрд┐ CORS preflights рд╕рд╛рдареА рд╕реЛрдпреАрдЪреЗ);
- VPC link тАФ рддреБрдордЪреНрдпрд╛ VPC рдордзрд▓реНрдпрд╛ load balancer рдХрдбреЗ private integration.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг рдЪреБрдХреАрдЪрд╛ 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 рдирд╛рд╣реА.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
/students/meрд╢рд┐рд╡рд╛рдп/students/{id}тАФ "me" рд╣рд╛ student id рдмрдирддреЛ- рдХреЛрдгрд╛рд▓рд╛рд╣реА publish рдХрд░рд╛рдпрдЪреЗ рдирд╡реНрд╣рддреЗ рдЕрд╕реЗ admin paths рдЕрд╕рд▓реЗрд▓реНрдпрд╛ app рд╕рдореЛрд░
ANY /{proxy+} - Lambda proxy integration рдиреЗ
{"statusCode": 200, "headers": {...}, "body": "..."}рдРрд╡рдЬреА рд╕рд╛рдзреА dict рдкрд░рдд рдХрд░рдгреЗ тАФ caller рд▓рд╛502 Internal server errorрдорд┐рд│рддреЛ - рдЦрд▒реНрдпрд╛ REST API рдЪрд╛
403 Missing Authentication Tokenрд╣рд╛ auth рдЪрд╛ рдкреНрд░рд╢реНрди рд╕рдордЬрдгреЗ тАФ рддреНрдпрд╛рдЪрд╛ рдЕрд░реНрде рд╕рд╣рд╕рд╛ "рдЕрд╕рд╛ route рдХрд┐рдВрд╡рд╛ method рдирд╛рд╣реА" рдЕрд╕рд╛ рдЕрд╕рддреЛ
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
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