ЁЯУи рдзрдбрд╛ 11 тАФ Backends рд╢реА рдмреЛрд▓рдгреЗ: рдХрд╛рдЙрдВрдЯрд░рдЪреНрдпрд╛ рдЪрд┐рдареНрдареНрдпрд╛ рднрд░рдгреЗ
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 11 ┬╖ рдорд╛рдЧреАрд▓: lesson-10-testing ┬╖ рдкреБрдвреАрд▓: lesson-12-build-deploy-observe
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ10, рдЖрдгрд┐ рддреНрдпрд╛рд╕реЛрдмрдд рдлрд▓рдХ рдЖрдгрд┐ рдХрд╛рдЙрдВрдЯрд░ рдпрд╛рдВрдЪреНрдпрд╛рддрд▓реА рд╕реАрдорд╛: CORS, browser рдордзрд▓реА credentials, рдкреНрд░рддреНрдпреЗрдХ request рдЪреЗ рддреАрди рдЪреЗрд╣рд░реЗ, рдЖрдгрд┐ рджреЛрди рд╕реБрд░рдХреНрд╖рд╛ рдирд┐рдпрдо тАФ рдлрд▓рдХрд╛рд╡рд░ рдХрдзреАрдЪ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡реВ рдирдХрд╛, рдордЬрдХреВрд░ рдХрдзреАрдЪ HTML рдореНрд╣рдгреВрди рдШрд╛рд▓реВ рдирдХрд╛.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рдлрд▓рдХ рдХрд╛рдЙрдВрдЯрд░рд╡рд░ рдЪрд┐рдареНрдареНрдпрд╛ рднрд░рддреЛ (API рд╢рд╛рд│рд╛). рдлрд▓рдХ рдПрдХрд╛ рдЕрдиреЛрд│рдЦреА рдорд╛рдгрд╕рд╛рдЪреНрдпрд╛ browser рдордзреНрдпреЗ рдЪрд╛рд▓рддреЛ, рдореНрд╣рдгреВрди рдЪрд╛рд░ рдЧреЛрд╖реНрдЯреА рд╡реЗрдЧрд│реНрдпрд╛ рдЕрд╕рддрд╛рдд:
- CORS ЁЯЪз тАФ рдХрд╛рдЙрдВрдЯрд░рдиреЗ рдкрд░рд╡рд╛рдирдЧреА рджрд┐рд▓реНрдпрд╛рд╢рд┐рд╡рд╛рдп (
Access-Control-Allow-Origin) browser рдПрдХрд╛ origin рд╡рд░рдЪреНрдпрд╛ page рд▓рд╛ рджреБрд╕рд▒реНрдпрд╛ origin рдЪреЗ response рд╡рд╛рдЪреВ рджреЗрдд рдирд╛рд╣реА. рд╕рд╛рдзреНрдпрд╛GETрдкрд▓реАрдХрдбрдЪреНрдпрд╛ рдХреЛрдгрддреНрдпрд╛рд╣реА рдЧреЛрд╖реНрдЯреАрд╕рд╛рдареА browser рдЖрдзреА рдкрд░рд╡рд╛рдирдЧреА рдорд╛рдЧрддреЛ (рдПрдХOPTIONSpreflight). рдЖрдкрд▓рд╛ рдЫреЛрдЯрд╛ рдХрд╛рдЙрдВрдЯрд░ рджреЛрдиреНрд╣реАрдВрдирд╛ рдЙрддреНрддрд░ рджреЗрддреЛ. - Browser рдордзреНрдпреЗ рдХрд╛рд╣реАрдЪ рдЧреБрдкреНрдд рдирд╕рддреЗ ЁЯкк тАФ рддреБрдореНрд╣реА рдкрд╛рдард╡рд▓реЗрд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ byte
рд╡рд╛рдЪрддрд╛ рдпреЗрддреЛ. JavaScript рдордзреНрдпреЗ API keys рдирдХреЛрдд. Users рдирд╛ рдХрдореА рдХрд╛рд│ рдЯрд┐рдХрдгрд╛рд░реЗ
tokens рдорд┐рд│рддрд╛рдд (рдХрд┐рдВрд╡рд╛ JS рд╡рд╛рдЪреВ рд╢рдХрдд рдирд╛рд╣реА рдЕрд╢реА
httpOnlycookie); рджреАрд░реНрдШрдХрд╛рд│ рдЯрд┐рдХрдгрд╛рд▒реНрдпрд╛ machine keys servers рд╡рд░рдЪ рд░рд╛рд╣рддрд╛рдд. - рдиреЗрд╣рдореА рддреАрди рдЪреЗрд╣рд░реЗ тП│ тАФ loading, рдпрд╢, error. рдлрдХреНрдд рдпрд╢ рджрд╛рдЦрд╡рдгрд╛рд░рд╛ рдлрд▓рдХ, рдХрд╛рдЙрдВрдЯрд░ рдордВрдж рдХрд┐рдВрд╡рд╛ рдмрдВрдж рдЕрд╕рд▓рд╛ рдХреА рдХреЛрд░реЗ рдкрд╛рди рджрд╛рдЦрд╡рддреЛ.
- рдлрд▓рдХрд╛рд╡рд░ рдХрдзреАрдЪ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡реВ рдирдХрд╛ ЁЯЫбя╕П тАФ рддреБрдордЪреЗ form validation рд╣реЗ users
рд╕рд╛рдареА рдХреЗрд▓реЗрд▓реА рдореЗрд╣реЗрд░рдмрд╛рдиреА рдЖрд╣реЗ; рдХреЛрдгреАрд╣реА рдереЗрдЯ request рдкрд╛рдард╡реВ рд╢рдХрддреЛ. рдХрд╛рдЙрдВрдЯрд░ рдкреБрдиреНрд╣рд╛
validate рдХрд░рддреЛ рдЖрдгрд┐ рдЕрдбрдЪрдгреАрдВрд╕рд╣
400рдкрд░рдд рдХрд░рддреЛ (API рд╢рд╛рд│реЗрдЪрд╛ рдзрдбрд╛ 04). рдЖрдгрд┐ рдЙрд▓рдЯ рдмрд╛рдЬреВрдиреЗ: рдХрд╛рдЙрдВрдЯрд░рдЪрд╛ рдордЬрдХреВрд░ page рдордзреНрдпреЗinnerHTMLрдиреЗ рдХрдзреАрдЪ рдШрд╛рд▓реВ рдирдХрд╛ тАФ<img src=x onerror=alert(1)>рдирд╛рд╡рд╛рдЪрд╛ рд╡рд┐рджреНрдпрд╛рд░реНрдереА code рдЪрд╛рд▓рд╡реЗрд▓. рдиреЗрд╣рдореАtextContent.
ЁЯЧ║я╕П рдЖрдХреГрддреА
sequenceDiagram
participant B as ЁЯУМ board (browser)
participant C as ЁЯПв counter (API)
B->>C: 1 OPTIONS /api/students (preflight тАФ non-simple request)
C-->>B: 2 Access-Control-Allow-Origin/Methods/Headers
B->>C: 3 POST /api/students {name, class} тЖР board validated (kindness)
C-->>B: 4 400 {"error":{"code":"validation_failed"}} тЖР counter validated (truth)
Note over B: draw the error face ┬╖ textContent, never innerHTML
тЭУ рдХрд╛рдп
- Origin = scheme + host + port. рд╡реЗрдЧрд│рд╛ origin тЖТ CORS рд▓рд╛рдЧреВ рд╣реЛрддреЛ.
Simple requests (рд╕рд╛рдзреНрдпрд╛ content types рд╕рд╣ GET/POST) рдереЗрдЯ рдЬрд╛рддрд╛рдд; рдмрд╛рдХреА
рд╕рдЧрд│реНрдпрд╛рдВрдЪрд╛
OPTIONSрдиреЗ preflight рд╣реЛрддреЛ. - Credentials:
fetch(url, {credentials: 'include'})cross-origin cookies рдкрд╛рдард╡рддреЛ тАФ рдЖрдгрд┐ рдордЧ рдХрд╛рдЙрдВрдЯрд░рдиреЗ рдиреЗрдордХрд╛ origin рд╕рд╛рдВрдЧрд╛рд╡рд╛ рд▓рд╛рдЧрддреЛ (*рдирд╛рд╣реА) рдЖрдгрд┐Access-Control-Allow-Credentialsрд▓рд╛рд╡рд╛рд╡реЗ рд▓рд╛рдЧрддреЗ. - Token рдХреБрдареЗ рдареЗрд╡рд╛рдпрдЪрд╛: inject рдЭрд╛рд▓реЗрд▓реА рдХреЛрдгрддреАрд╣реА script
localStorageрд╡рд╛рдЪреВ рд╢рдХрддреЗ (XSS);httpOnlycookies JS рд╡рд╛рдЪреВ рд╢рдХрдд рдирд╛рд╣реА рдкрдг рддреНрдпрд╛рдВрдирд╛ CSRF рд╕рдВрд░рдХреНрд╖рдг рд▓рд╛рдЧрддреЗ (SameSite=Lax/Strict). рджреЛрдиреНрд╣реАрдХрдбреЗ рдореБрджрдд рдХрдореА рдареЗрд╡рд╛. - Code рдордзрд▓реЗ рддреАрди рдЪреЗрд╣рд░реЗ:
loadingрд▓рд╛рд╡рд╛ тЖТ render; fetch рд▓рд╛tryрдХрд░рд╛,r.okрддрдкрд╛рд╕рд╛, parse рдХрд░рд╛;catcherrorрд▓рд╛рд╡рддреЛ;finallyloadingрдХрд╛рдврддреЛ рдЖрдгрд┐ render рдХрд░рддреЛ. Error рдЪреЗрд╣рд▒реНрдпрд╛рд▓рд╛ retry рджреНрдпрд╛. - XSS рдкрд╛рд╕реВрди рдмрдЪрд╛рд╡:
textContent/createElement(рдХрд┐рдВрд╡рд╛ framework рдЪреЗ escaping), Content-Security-Policy header, рдЖрдгрд┐ data рд╕рд╣ рдХрдзреАрдЪinnerHTML,eval, рдХрд┐рдВрд╡рд╛new Functionрдирд╛рд╣реА. - Timeouts рдЖрдгрд┐ retries client рд╡рд░рд╣реА рд╣рд╡реЗрдд (API рд╢рд╛рд│реЗрдЪрд╛ рдзрдбрд╛ 10):
AbortSignal.timeout, рдЖрдгрд┐429/5xxрд╕рд╛рдареА backoff.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг front-end рдордзрд▓реНрдпрд╛ рд╕рдЧрд│реНрдпрд╛рдд рд╕рд╛рдорд╛рдиреНрдп рджреЛрди рд╕реБрд░рдХреНрд╖рд╛ рдШрдЯрдирд╛ рдпрд╛рдЪ рд╕реАрдореЗрд╡рд░ рдШрдбрддрд╛рдд (bundle рдордзреНрдпреЗ рдкрд╛рдард╡рд▓реЗрд▓реА рдЙрдШрдб рдЭрд╛рд▓реЗрд▓реА key; user рдЪреНрдпрд╛ рдордЬрдХреБрд░рд╛рддреВрди XSS), рдЖрдгрд┐ рд╕рдЧрд│реНрдпрд╛рдд рд╕рд╛рдорд╛рдиреНрдп рд╡рд┐рд╢реНрд╡рд╛рд╕рд╛рд░реНрд╣рддреЗрдЪреА рддрдХреНрд░рд╛рд░рд╣реА рдЗрдереЗрдЪ рдЕрд╕рддреЗ ("рддреЗ рдлрдХреНрдд рдлрд┐рд░рдд рд░рд╛рд╣рддреЗ" тАФ timeout рдирд╛рд╣реА, error рдЪреЗрд╣рд░рд╛ рдирд╛рд╣реА). рдЪрд╛рд░рдкреИрдХреА рддреАрди рдирд┐рдпрдорд╛рдВрдЪреА рдХрд┐рдВрдордд рдкреНрд░рддреНрдпреЗрдХреА рдПрдХ рдУрд│ рдЖрд╣реЗ.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
ui/app.js: load() рдЖрдгрд┐ enrol() рддреАрди рдЪреЗрд╣рд░реЗ рджрд╛рдЦрд╡рддрд╛рдд;
StudentCard user рдиреЗ рджрд┐рд▓реЗрд▓реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ value рд╕рд╛рдареА textContent рд╡рд╛рдкрд░рддреЛ.
ui/mock_api.py CORS headers рд▓рд╛рд╡рддреЛ, OPTIONS рд▓рд╛
рдЙрддреНрддрд░ рджреЗрддреЛ, form server-side рд╡рд░ рдкреБрдиреНрд╣рд╛ validate рдХрд░рддреЛ, рдЖрдгрд┐ ?slow=1 рд╡
?fail=1 рджреЗрддреЛ рдореНрд╣рдгрдЬреЗ рддреБрдореНрд╣реА рд╣рд╡рд╛ рддреЗрд╡реНрд╣рд╛ рдкреНрд░рддреНрдпреЗрдХ рдЪреЗрд╣рд░рд╛ рдкрд╛рд╣реВ рд╢рдХрддрд╛.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 ui/mock_api.py
# 1) the three faces: edit API in app.js to '/api/students?slow=1' (loading), then '?fail=1' (error), then back
# 2) never trust the board тАФ bypass the form entirely:
curl -s -X POST http://127.0.0.1:8000/api/students -H 'Content-Type: application/json' -d '{"name":"X","class":"9Z"}'
# тЖТ 400 validation_failed. The counter refused what the board never would have sent.
# 3) XSS, safely: enrol a student named <b>bold</b> тАФ the card shows the literal text (textContent).
# Then temporarily use innerHTML in StudentCard and enrol <img src=x onerror="alert('xss')"> тАФ see it fire. Undo it.
# 4) CORS: open the board from a different origin (python3 -m http.server 9001 in ui/) and watch the console тАФ
# then notice mock_api.py's Access-Control-Allow-Origin header is what makes the fetch work at all.
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
Loading рдЖрдгрд┐ error рдЪреЗрд╣рд░реЗ рд╣рд╡реЗ рддреЗрд╡реНрд╣рд╛ рджрд┐рд╕рддрд╛рдд рдЖрдгрд┐ рдХрдзреАрдЪ рдХреЛрд░реЗ рдкрд╛рди рд╕реЛрдбрдд рдирд╛рд╣реАрдд.
рдереЗрдЯ curl рд▓рд╛ 400 validation_failed рдорд┐рд│рддреЛ тАФ рдХрд╛рдЙрдВрдЯрд░ рдлрд▓рдХрд╛рд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рдд
рдирд╛рд╣реА рдпрд╛рдЪрд╛ рдкреБрд░рд╛рд╡рд╛. <b>bold</b> рд╡рд┐рджреНрдпрд╛рд░реНрдереА textContent рд╕рд╣ рдордЬрдХреВрд░ рдореНрд╣рдгреВрди
рджрд┐рд╕рддреЛ рдЖрдгрд┐ innerHTML рд╕рд╣ markup рдореНрд╣рдгреВрди (рдХрд┐рдВрд╡рд╛ рддреНрдпрд╛рд╣реВрди рд╡рд╛рдИрдЯ).
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
рдлрд▓рдХ request рдЪрд╛ рдкреНрд░рддреНрдпреЗрдХ рдирд┐рдХрд╛рд▓ рд╣рд╛рддрд╛рд│рддреЛ, рдЖрдгрд┐ рддреБрдореНрд╣реА рджреЛрдиреНрд╣реА рд╕реБрд░рдХреНрд╖рд╛ рдирд┐рдпрдо тАФ server-side validation рдЖрдгрд┐ escaping тАФ рджреЛрдиреНрд╣реА рдмрд╛рдЬреВрдВрдиреА рджрд╛рдЦрд╡рд▓реЗ.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- front-end code рдордзреНрдпреЗ рдХрд┐рдВрд╡рд╛ bundle рд╣реЛрдгрд╛рд▒реНрдпрд╛
.envfiles рдордзреНрдпреЗ API keys рдХрд┐рдВрд╡рд╛ secrets - user рдХрд┐рдВрд╡рд╛ API рдиреЗ рджрд┐рд▓реЗрд▓реНрдпрд╛ рдХреЛрдгрддреНрдпрд╛рд╣реА рдЧреЛрд╖реНрдЯреАрд╕рд╣
innerHTML - client-side validation рд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рдгреЗ (рдПрдХ
curlрдПрдХрд╛ рдУрд│реАрдд рддреНрдпрд╛рд▓рд╛ рд╡рд│рд╕рд╛ рдШрд╛рд▓рддреЛ) - credentials рд╕рд╣
Access-Control-Allow-Origin: *(browsers рддреЗ рдирд╛рдХрд╛рд░рддрд╛рдд, рдпреЛрдЧреНрдп рдХрд╛рд░рдгрд╛рд╕рд╛рдареА) - timeout рдирд╛рд╣реА, error рдЪреЗрд╣рд░рд╛ рдирд╛рд╣реА, retry рдирд╛рд╣реА
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдкреНрд░рддреНрдпреЗрдХ security checklist рд╡рд░рдЪреНрдпрд╛ рджреЛрди front-end рдЧреЛрд╖реНрдЯреА рдореНрд╣рдгрдЬреЗ XSS рдЖрдгрд┐ рдЙрдШрдб рдЭрд╛рд▓реЗрд▓реНрдпрд╛ keys, рдЖрдгрд┐ "page рдХрд╛рдпрдо рдлрд┐рд░рдд рд░рд╛рд╣рддреЗ" рд╣реЗ рд╕рдЧрд│реНрдпрд╛рдд рд╕рд╛рдорд╛рдиреНрдп support ticket рдЖрд╣реЗ. рдЪрд╛рд░ рдирд┐рдпрдо, рдЪрд╛рд░ рдУрд│реА, рдмрд╣реБрддреЗрдХ рдзреЛрдХрд╛.
тПня╕П рдкреБрдвреЗ
рд╢реЗрд╡рдЯрдЪрд╛ рдЯрдкреНрдкрд╛: build, deploy рдЖрдгрд┐ observe тАФ рд▓реЛрдХрд╛рдВрдирд╛ рджрд┐рд╕реЗрд▓ рдЕрд╢рд╛ рдард┐рдХрд╛рдгреА рдлрд▓рдХ рд▓рд╛рд╡рдгреЗ, рдЖрдгрд┐ рдХрд╛рдп рдШрдбрддреЗ рддреНрдпрд╛рд╡рд░ рд▓рдХреНрд╖ рдареЗрд╡рдгреЗ.
git checkout lesson-12-build-deploy-observe