HTTP client
Breeze's outbound HTTP client, built on gnet โ the same event-loop engine the server runs on.
c := client.New()
defer c.Close()
resp, err := c.Get("http://auth-service/verify")
if err != nil {
return err
}
if resp.OK() {
fmt.Println(resp.String())
} With headers โ what Fleet tracing uses to inject trace context:
req := client.NewRequest("POST", url, body).
SetHeader("Content-Type", "application/json").
SetHeader("Traceparent", tc.String())
resp, err := c.Do(req) Why gnet, not net/http
The server side of Breeze is built on gnet. Sharing the engine for outbound
calls means both directions use one non-blocking I/O model and one
connection-pooling strategy, rather than an event-loop server sitting beside net/http's goroutine-per-connection machinery for every outgoing call. The
cost: gnet is a raw TCP byte-stream engine with no notion of HTTP, so
request serialisation and response parsing are implemented in this package
rather than inherited from the standard library โ which is where the
limitations below come from.
Limitations โ read these first
This client exists for service-to-service JSON traffic. For anything else, net/http is the right answer with no penalty.
| Limitation | Detail |
|---|---|
| HTTP/1.1 only | no HTTP/2 โ far past diminishing returns for JSON between services |
TLS via crypto/tls | the handshake is done by tls.Dial, then the connection is handed to gnet with Enroll |
| No chunked requests | chunked responses are decoded; requests always carry Content-Length |
| No redirect following | a 3xx is returned as-is |
| Bodies fully buffered | wrong tool for SSE or large downloads |
| No pipelining | one in-flight request per connection |
Config
c := client.New(client.Config{
Timeout: 10 * time.Second,
MaxIdleConnsPerHost: 128,
}) | Field | Default | Meaning |
|---|---|---|
Timeout | 30s | bounds the whole call: connect + write + read |
MaxIdleConnsPerHost | 64 | idle-connection budget per upstream host |
DialTimeout | 5s | bounds establishing the connection |
MaxResponseBytes | 32 MiB | caps the response body |
UserAgent | "breeze-client/1" | sent unless the request sets its own |
TLSConfig | nil | replaces the default tls.Config |
MaxIdleConnsPerHost defaults to 64 rather than net/http's 2 โ 2 is
sized for a browser calling many hosts once, 64 for a service calling the
same few upstreams on every request. Timeout covers the whole call
rather than each phase, since a per-phase timeout lets a slow upstream spend
the budget three times over.
Requests and responses
req := client.NewRequest("POST", "https://api.example.com/users", body)
req.SetHeader("Content-Type", "application/json") // replaces
req.AddHeader("X-Tag", "b") // appends
val, ok := req.GetHeader("Content-Type")
hdr := req.Header() // the http.Header
req = req.WithContext(ctx) // cancellation SetHeader, AddHeader and WithContext return the request, so they
chain.
type Response struct {
Status int
Header http.Header
Body []byte
}
resp.OK() // 2xx โ nil-safe
resp.String() // body as a string โ nil-safe Both methods are nil-safe, so resp.OK() on an error path is false rather than a panic โ the natural shape (check the error, then check the
status) reads cleanly when the second check cannot itself fail.
Convenience methods
c.Get(url)
c.Post(url, contentType, body)
c.PostJSON(url, body) // Content-Type: application/json
c.Do(req) // the general form Sentinel errors
| Error | When |
|---|---|
ErrNilRequest | Do(nil) |
ErrNoURL | the request's URL is empty or whitespace |
ErrResponseTooLarge | the body exceeded MaxResponseBytes |
Lifecycle
c := client.New()
defer c.Close() The gnet engine starts lazily, on the first request, not by New โ a Client constructed and never used costs a struct, which is what makes it
reasonable for a library to hold one as a field. Close shuts the engine
down and releases pooled connections. A Client is safe for concurrent use;
that is the point of the per-host pools. c.Config() returns the effective
configuration with defaults applied.
Who uses it
- Fleet tracing โ span export to the aggregator, with
Traceparentinjected viaSetHeader. See Fleet Tracing. - MCP live tools โ reading a running service's dashboard API.
Both are the shape this client is for: JSON, service-to-service, small bodies, the same few upstreams on every call.