Artifact 5f92f072d514bd85643a53d79bde6423428cf92a30bfebd7072cfac05b1a2f7d:
- File
internal/sync/transport_http.go
— part of check-in
[9594cf3bae]
at
2026-10-29 03:06:23
on branch main
— perf(wasm): add a libfossil_nohttp build tag that leaves out net/http
Under TinyGo, importing net/http at all links its TLS and X.509 code (crypto/x509, encoding/asn1, math/big's integers, AES-GCM, SHA-3, netip) whether or not a request is ever made: a five-line program that calls only http.StatusText grows by about 430 KB raw. The library imports it for its HTTP client transport and its HTTP server, which a program that serves sync through HandleSyncWithOpts and supplies its own transport never calls.
Built with -tags libfossil_nohttp, the library leaves out NewHTTPTransport, WithHTTPClient, Repo.Pull, Repo.XferHandler and Repo.ServeHTTP, and the internal/sync client, server, redirect handling and peer-address trace they use. HandleSync, HandleSyncWithOpts, Sync over a host Transport and everything else are unchanged. Without the tag nothing changes. The retry classifier keeps the two statuses it read from net/http as constants, and DNS fault classification moves to its own file so it stays in the tagged build.
For the html-artifact-publisher engine (TinyGo 0.43.0, Go 1.26.0) the tag takes the gzipped binary from 1,100,251 to 1,003,279 bytes. A test builds the library, db and simio with the tag for the host and js/wasm and checks none links net/http, net/http/httptrace or crypto/tls, and that the untagged library still does. (user: [email protected], size: 2921) [annotate] [blame] [check-ins using] [more...]
//go:build !libfossil_nohttp package sync // This file holds the net/http sync client. A build with the libfossil_nohttp tag // leaves it out, and with it net/http, for a host that supplies its own // transport (docs: Install, TinyGo). import ( "context" "errors" "fmt" "net/http" "github.com/danmestas/go-libfossil/internal/xfer" ) // HTTPTransport speaks Fossil's HTTP /xfer protocol. // Fossil routes to /xfer based on Content-Type: application/x-fossil, // NOT the URL path. URL should be the repo root (e.g. "http://localhost:8080"). type HTTPTransport struct { URL string // repo root, e.g. "http://localhost:8080" peer PeerAddr } // PeerAddr returns the IP address of the server the last exchange reached. func (t *HTTPTransport) PeerAddr() string { return t.peer.Get() } func (t *HTTPTransport) Exchange(ctx context.Context, req *xfer.Message) (*xfer.Message, error) { if req == nil { panic("sync.HTTPTransport.Exchange: req must not be nil") } sent, cookie := req.SplitLoginCookie() body, err := sent.Encode() if err != nil { return nil, fmt.Errorf("sync.HTTPTransport encode: %w", err) } resp, err := PostXfer(t.peer.Trace(ctx), http.DefaultClient, t.URL, xfer.ContentTypeCompressed, cookie, body) if err != nil { return nil, fmt.Errorf("sync.HTTPTransport do: %w", err) } defer resp.Body.Close() // Only 200 is a sync reply (§4-7). Any other status, a 2xx included, is not // the protocol answering: report it rather than hand its body to the // decoder, which would read the peer's outage as a framing defect. Whether // the status is worth another attempt is StatusError's to say. if resp.StatusCode != http.StatusOK { return nil, &StatusError{Code: resp.StatusCode, Status: resp.Status} } respBody, err := xfer.ReadBody(resp.Body) if errors.Is(err, xfer.ErrBodyTooLarge) { // The reply is the peer's, and resending gets the same one. return nil, err } if err != nil { // The reply was cut short on the wire. Nothing has been interpreted // yet, so the same request may safely be sent again. return nil, &transportFault{ err: fmt.Errorf("sync.HTTPTransport read: %w", err), retryable: true, } } // §4: the reply's framing is given by its Content-Type, not guessed. A // clone-v3 reply arrives uncompressed; a pull reply arrives as the // compressed container. // // A failure from here on is a protocol fault: the reply arrived whole and // did not parse. It is left untagged so the retry classifier fails it // fast — resending would only make a malformed reply intermittent. // §4-8: a reply with no Content-Type is the compressed container, an // x-fossil-debug or x-fossil-uncompressed type is plain cards, x-fossil is // the compressed container, and any foreign type (an HTML error page from a // proxy) is not a sync reply and is refused here. return xfer.DecodeReply(respBody, resp.Header.Get("Content-Type")) }