---
id: 6e0bce9e22a4bf7d9672c59e67bcf5ec
author: Lemon Mint
title: 'gosuda.org/chromenet: Stos sieciowy Chromium dla języka Go, zbudowany w sposób odtwarzalny z przypiętego wydania'
description: CGO_ENABLED=0 Go http.RoundTripper, który wysyła żądania przez własny stos //net Chromium (HTTP/1.1, HTTP/2, QUIC/HTTP/3, BoringSSL) w izolowanym workerze HQQ, z kodem źródłowym, toolchainami oraz sześcioma osadzonymi plikami binarnymi przypiętymi za pomocą SHA-256 i zbudowanymi w sandboxach gvx.
language: pl
date: 2026-11-09T00:00:00Z
path: /chromenet
go_package: gosuda.org/chromenet
go_repourl: https://github.com/gosuda/chromenet.git
category: network
---

`gosuda.org/chromenet` to `http.RoundTripper` w języku Go, który wysyła żądania przez własny stos sieciowy Chromium. Stosem jest `//net` z oficjalnego wydania Chromium: jego implementacje HTTP/1.1, HTTP/2 i QUIC/HTTP/3, BoringSSL, konfiguracja TLS Chrome oraz Chrome Root Store. Działa w osobnym procesie roboczym, `chrome_net_server`, a strona Go pozostaje czystym Go (`CGO_ENABLED=0`), komunikując się z procesem roboczym przez RPC [HQQ](https://gosuda.org/hqq) oparte na pamięci współdzielonej.

```bash
go get gosuda.org/chromenet@v0.1.10157081033800
```

## Dlaczego uruchamiać stos sieciowy Chromium

Program w Go, który komunikuje się z siecią za pomocą `net/http`, przedstawia się serwerowi jako klient Go. Jest to właściwe domyślne zachowanie w ruchu między usługami. Jest ono niewłaściwe wtedy, gdy zachowanie serwera zależy od tego, za jakiego klienta go uważa: punkty końcowe wymagające przeglądarki, oprogramowanie pośredniczące przeciwdziałające botom, sieci CDN serwujące różne wersje protokołów w zależności od odcisków TLS i HTTP/2 oraz testy, które muszą odtwarzać to, czego doświadcza prawdziwa przeglądarka.

Standardowa biblioteka Go nie może odtworzyć zachowania sieciowego przeglądarki. `crypto/tls` ma własną kolejność zestawów szyfrów i zbiór rozszerzeń, a `net/http` własne ustawienia HTTP/2. Biblioteki imitujące przeglądarkę przez kopiowanie bajtów ClientHello kopiują je dalej, wydanie po wydaniu. `chromenet` idzie inną drogą: nie imituje stosu sieciowego Chromium, lecz go uruchamia.

Pomiary na silniku `darwin/arm64` względem publicznego serwisu identyfikacji odcisków (`tls.peet.ws`) dla domyślnej konfiguracji wykazały:

| Właściwość                         | Zaobserwowana wartość                                                 |
| :--------------------------------- | :-------------------------------------------------------------------- |
| Wynegocjowany protokół             | `h2`                                                                  |
| JA4                                | `t13d1516h2_8daaf6152771_806a8c22fdea`                                |
| Odcisk HTTP/2 (format Akamai)      | `1:65536;2:0;4:6291456;6:262144\|15663105\|0\|m,a,s,p`                |
| User-Agent                         | pulpitowy Chrome w głównej wersji silnika, np. `Chrome/157.0.0.0`     |

Przy użyciu `ForceHTTP3` żądania do `cloudflare-quic.com` i `www.google.com` otrzymały odpowiedzi przez HTTP/3. Liczby pochodzą z jednej platformy z jednego dnia. Silnik jest stosem, więc zmieniają się one wraz ze zmianą przypiętego wydania Chromium.

## Użycie

`NewTransport` zwraca `http.RoundTripper`. Używaj go wszędzie tam, gdzie użyłbyś `http.Transport`:

```go
package main

import (
	"fmt"
	"io"
	"net/http"

	"gosuda.org/chromenet"
)

func main() {
	client := &http.Client{Transport: chromenet.NewTransport(chromenet.Options{})}

	resp, err := client.Get("https://example.com/")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.Proto, resp.StatusCode, len(body))
}
```

Przy pierwszym żądaniu moduł zapisuje osadzony plik binarny procesu roboczego dla Twojej platformy do katalogu cache użytkownika, weryfikuje jego skrót SHA-256 za pomocą `gosuda.org/hqq/secureexec`, uruchamia go z łączem nadrzędnym (`--parent-stdin`, dzięki czemu kończy działanie wraz z Twoim procesem) i otwiera pulę sesji. Nie trzeba niczego instalować.

Kilka zachowań jest zgodnych z `http.Transport`, dzięki czemu `http.Client` działa bez zmian:

- Transport nie podąża za przekierowaniami i nie przechowuje ciasteczek. Robi to `http.Client`, tak jak w przypadku `http.Transport`.
- Odpowiedzi skompresowane przez gzip, deflate, Brotli lub zstd docierają już zdekodowane, a `resp.Uncompressed` ma wartość true.
- Żądanie zajmuje jedną sesję silnika do momentu zamknięcia ciała odpowiedzi. Zamykaj ciała odpowiedzi.
- Anulowanie kontekstu żądania anuluje żądanie wewnątrz Chromium.
- Błędy sieciowe to wartości `*client.NetError` zawierające kod i nazwę błędu Chromium:

```go
var ne *client.NetError
if errors.As(err, &ne) {
	fmt.Println(ne.Code, ne.Name) // -105 ERR_NAME_NOT_RESOLVED
}
```

### Opcje

| Pole                          | Działanie                                                                                                                                  |
| :---------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
| `UserAgent`                   | Zastępuje domyślny User-Agent Chrome. Nagłówek żądania `User-Agent` nadal ma pierwszeństwo.                                                |
| `AcceptLanguage`              | Zastępuje `en-US,en;q=0.9`.                                                                                                                |
| `Proxy`                       | Reguły proxy Chromium, np. `http://127.0.0.1:8080` lub `socks5://127.0.0.1:1080`. Pusta wartość oznacza połączenie bezpośrednie.          |
| `DisableHTTP2`, `DisableQUIC` | Wyłączają te protokoły.                                                                                                                    |
| `ForceHTTP3`                  | Używa QUIC dla każdego originu, bez czekania na ogłoszenie `Alt-Svc`.                                                                      |
| `InsecureSkipVerify`          | Akceptuje dowolny certyfikat serwera.                                                                                                      |
| `Pool`                        | Sesje silnika do użycia. Wartość nil oznacza `client.DefaultPool()`, który pozwala na 64 równoległe żądania.                              |

Transporty z równymi opcjami współdzielą jeden `URLRequestContext` Chromium, więc pule połączeń, sesje HTTP/2 i sesje QUIC są ponownie używane między żądaniami. Ustawienie `HQQ_CHROME_NET_SERVER=/path/to/chrome_net_server` zastępuje osadzony proces roboczy, na przykład lokalną kompilacją.

## Jak to działa

| Warstwa             | Co tam działa                                                                                                                                                                                       |
| :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Twój program        | `http.Client` i `chromenet.Transport`, czyste Go.                                                                                                                                                   |
| Granica procesu     | RPC HQQ oparte na pamięci współdzielonej. Interfejs to `chrome_net.idl`, kompilowany przez `hqq-bindgen` do klienta Go i glue'u serwera w C.                                                       |
| `chrome_net_server` | Mała warstwa C++ nad Chromium: jeden wątek sieciowy, który jest właścicielem wszystkich obiektów `//net`, oraz wątki sesji blokujące się na żądaniu do czasu, aż wątek sieciowy odpowie.            |
| Chromium `//net`    | `URLRequestContext`, `URLRequest`, sesje HTTP/2 i QUIC, BoringSSL.                                                                                                                                  |

IDL zawiera sześć istotnych operacji: utworzenie silnika z opcji `key=value`, rozpoczęcie żądania z jego nagłówkiem (`METHOD URL` oraz wiersze nagłówków) i długością ciała, zapisanie fragmentów ciała żądania, oczekiwanie na nagłówek odpowiedzi, odczyt zdekodowanego ciała oraz zniszczenie uchwytów. Uchwyt żądania deklaruje `@cancel`, więc porzucone wywołanie przerywa żądanie wewnątrz Chromium, zamiast pozwalać mu działać do końca.

Izolacja stosu w procesie roboczym jest równie właściwością bezpieczeństwa, co właściwością pakowania. Kod sieciowy Chromium przetwarza wrogie dane z otwartego internetu. Jeśli ulegnie awarii, Twoja usługa w Go nie ulega razem z nim: wywołanie kończy się błędem, sesja jest odrzucana, a klient uruchamia nowy proces roboczy, gdy znów będzie go potrzebował.

## Budowa powtarzalna, przypinanie przez SHA-256

Plik binarny o rozmiarze 25 MB, zawierający Chromium, jest wiarygodny tylko wtedy, gdy można dokładnie określić, co weszło w jego skład. `chromenet` podąża za Chromium Stable tak, jak `gosuda.org/sqlite3` podąża za SQLite: kod źródłowy nigdy nie jest vendorowany, jest tylko przypięty.

| Przypięcie                                                                                                                              | Gdzie                          |
| :-------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------- |
| Wydanie Chromium i jego oficjalne archiwum źródłowe `-lite`                                                                             | `third_party/chromium/VERSION` |
| SHA-256 archiwum i każdego narzędzia przypiętego przez wydanie: clang, Rust, GN, ninja, runtime clang dla Windows, xwin                | `third_party/chromium/SHA256`  |
| Wersje MSVC CRT i Windows SDK pobierane przez xwin                                                                                      | `third_party/chromium/WINSDK`  |
| Wydanie runtime piaskownicy (`rungv`), digest obrazu gościa, data migawki Debiana                                                       | `scripts/engine/BUILDENV`      |
| Runtime C HQQ                                                                                                                           | `go.mod` i `go.sum`            |

Aktualne przypięcie to Chromium `157.0.8103.38`. Pobranie niezgodne z przypięciem zatrzymuje build. Wydanie stosuje dokładnie jedną łatę do Chromium: w systemie Linux `chrome_net_server` jest budowany bez NSS i weryfikuje certyfikaty serwerów wyłącznie przy użyciu Chrome Root Store, więc plik binarny nie wymaga niczego poza libc.

Silniki dla Linuksa i Windows są budowane wewnątrz piaskownic [gvx](https://gosuda.org/blog/posts/gvx-cross-platform-linux-containers-z48916d01): nieuprzywilejowanych procesów z przypiętym obrazem gościa i bez demona hosta. Silniki dla Windows są kompilowane krzyżowo na Linuksie przy użyciu clang-cl i lld-link z Chromium, przeciwko Windows SDK, które xwin pobiera od Microsoftu. Silniki dla macOS są budowane natywnie na runnerze macOS, ponieważ build Chromium dla macOS wymaga Xcode.

Ponowna budowa tego samego commita dała bajtowo identyczne pliki binarne w oddzielnych przebiegach CI. Pliki binarne wydania są bajtowo identyczne z tymi, które wcześniejszy przebieg CI zbudował dla commita wprowadzającego silnik do monorepo, na wszystkich sześciu platformach.

| Platforma       | Rozmiar pliku binarnego |
| :-------------- | ----------------------: |
| `linux/amd64`   |                 27.9 MB |
| `linux/arm64`   |                 27.4 MB |
| `darwin/amd64`  |                 26.2 MB |
| `darwin/arm64`  |                 23.9 MB |
| `windows/amd64` |                 27.3 MB |
| `windows/arm64` |                 25.8 MB |

Silniki dla Linuksa zależą wyłącznie od `libc`, `libm`, `libdl`, `libpthread` i `libgcc_s` i wymagają glibc 2.31 lub nowszej.

### Śledzenie Chromium i wydań

Zaplanowany workflow sprawdza Chromium Stable co sześć godzin. Dla nowego wydania przepisuje przypięcia, buduje i testuje wszystkie sześć silników na gałęzi `chromium/<version>`, poświadcza pliki binarne i commituje przypięcia razem z osadzonymi plikami binarnymi, ich skrótami i tekstami licencji do `main` w jednym commicie. W monorepo pliki `OWNERS` przyznają botowi workflow dokładnie te pliki, dzięki czemu aktualizacja trafia bez kroku ludzkiego, podczas gdy zmiany samego przepisu budowania (przypięcie Windows SDK i łata) nadal wymagają zatwierdzenia właściciela.

Każdy plik binarny wydania niesie poświadczenie pochodzenia (build provenance attestation) z workflow, który go zbudował:

```bash
gh attestation verify chrome_net_server_linux_amd64 --repo gosuda/chromenet
```

Teksty licencji tego, co zawiera każdy plik binarny (Chromium, BoringSSL, QUICHE i reszta grafu budowania), są generowane przez `tools/licenses/licenses.py` z samego Chromium i dołączane do wydania jako `NOTICE_<os>_<arch>.txt`.

## Ograniczenia

- `DialWebSocket` jest klientem RFC 6455 napisanym w czystym Go i nie korzysta ze stosu Chromium.
- `DialWebTransport` jest implementacją działającą w tym samym procesie. Nie łączy się z serwerem.
- Domyślne odciski JA4 i HTTP/2 są odciskami przypiętego wydania Chromium. Chromium zmienia je z wydania na wydanie, a `chromenet` za nimi podąża.
- Pliki binarne macOS są podpisane ad-hoc i nie są notaryzowane.
- Proces roboczy kosztuje pamięć. Każdy plik binarny silnika zajmuje od 24 do 28 MB na dysku, a runtime Chromium uruchamia pulę wątków oraz wątek sieciowy.
- Sesja silnika jest zajęta do momentu zamknięcia ciała odpowiedzi. Program, który nie zamyka ciał odpowiedzi, wyczerpie pulę.

## Źródło

Repozytorium to [github.com/gosuda/chromenet](https://github.com/gosuda/chromenet), lustrzane odbicie `//net/chromenet` z monorepo gosuda. Przepis budowania znajduje się w `scripts/engine`, glue silnika w `engine/`, a interfejs w `chrome_net.idl`.
