Browser nie daje JavaScriptowi pełnej kontroli nad HTTP
- data
- kategoria
- Frontend
- także w
- Application Security · Backend
- czytanie
- 1 min / 286 słów
fetch() wygląda jak generator requestu:
fetch(url, options);
Ale frontend nie dostaje raw HTTP socketu.
Dostaje kontrolowany interfejs browsera.
Browser dodaje własny stan
Kod:
fetch("/api/me");
może doprowadzić do requestu zawierającego między innymi cookies, negotiation headers i inne pola ustawione przez user agent.
JavaScript nie musi ich jawnie podawać.
Model jest więc:
application intent
|
v
Fetch API
|
v
browser policy + browser state
|
v
HTTP request
Nie każdy header możemy ustawić
Browser blokuje lub kontroluje część headers, ponieważ JavaScript działający na stronie nie może otrzymać pełnej możliwości podszywania się pod user agenta lub ingerowania w transport.
Dlatego request z browsera nie jest odpowiednikiem:
curl ...
curl jest HTTP clientem kontrolowanym przez użytkownika.
Kod strony działa wewnątrz security modelu browsera.
To fundamentalna różnica.
Cookies też nie są zwykłym headerem aplikacji
Frontend nie powinien budować:
headers: {
Cookie: "session=..."
}
Browser zarządza cookie store i decyduje, które cookies kwalifikują się do konkretnego requestu.
Fetch ma opcję:
fetch(url, {
credentials: "same-origin"
});
Domyślnie credentials są dołączane dla requestów same-origin.
Możemy również użyć:
credentials: "omit"
credentials: "include"
Ale nawet include nie omija reguł takich jak SameSite.
Cross-origin może dodać request, którego nie napisaliśmy
Kod:
fetch("https://api.example.net/users", {
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: "{}"
});
może spowodować wcześniejszy request:
OPTIONS /users
czyli CORS preflight.
Frontend napisał jeden fetch().
Network panel pokazuje dwa requesty.
To kolejny przykład:
JavaScript operation
!=
one network request
CORS omówimy osobno w serii security. Tutaj ważny jest model: browser może wprowadzić dodatkowy etap przed właściwym requestem.
Cache również siedzi pomiędzy kodem i siecią
Kod:
await fetch("/logo.svg");
nie gwarantuje, że dane polecą przez sieć.
Browser może mieć aktualną odpowiedź w cache.
Z perspektywy aplikacji nadal wykonaliśmy Fetch.
Z perspektywy origin servera request może w ogóle nie nadejść.
fetch()
|
v
browser cache?
├── hit -> Response
└── miss -> network
Cache zasługuje na osobną serię. Tutaj interesuje nas jedna rzecz: fetch() jest operacją platformy, a nie bezpośrednim wywołaniem sieci.
To samo dotyczy connections
Nawet kiedy request rzeczywiście trafia do sieci, browser nie musi tworzyć dla niego nowego połączenia.
Może wykorzystać już istniejące.
I właśnie connection reuse jest kluczowe do zrozumienia różnic między HTTP/1.1, HTTP/2 i HTTP/3.