Konrad Kowalski (rootsher)Principal Platform & Reliability Architect101111011100100101111001000101001101001010100001

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:

js
fetch(url, options);

Ale frontend nie dostaje raw HTTP socketu.

Dostaje kontrolowany interfejs browsera.

Browser dodaje własny stan

Kod:

js
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:

text
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:

bash
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ć:

js
headers: {
  Cookie: "session=..."
}

Browser zarządza cookie store i decyduje, które cookies kwalifikują się do konkretnego requestu.

Fetch ma opcję:

js
fetch(url, {
  credentials: "same-origin"
});

Domyślnie credentials są dołączane dla requestów same-origin.

Możemy również użyć:

js
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:

js
fetch("https://api.example.net/users", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json"
  },
  body: "{}"
});

może spowodować wcześniejszy request:

http
OPTIONS /users

czyli CORS preflight.

Frontend napisał jeden fetch().

Network panel pokazuje dwa requesty.

To kolejny przykład:

text
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:

js
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ść.

text
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.