7 Responsive, efficient, and testable
This chapter pivots from language mechanics to practical, real‑world Go, showing how to build programs that are responsive, efficient, and easy to test by leaning on the standard library. It frames cancellation as a first‑class design concern with context.Context, emphasizes streaming I/O for low memory usage, and illustrates how interface composition clarifies intent. The narrative evolves a concurrent “HIT” client from a simulated pipeline into a production‑style HTTP tool, then hardens it through optimization and testing techniques that are idiomatic in Go.
First, the chapter fixes goroutine leaks by threading Context through a concurrent pipeline: the producer and subsequent stages listen on ctx.Done() and shut down cleanly, and SendN derives and cancels its own Context to avoid dangling workers when consumers stop early. It improves UX by catching Ctrl+C with signal.NotifyContext so the program can cancel work yet still print a summary. On the HTTP path, it replaces simulation with real requests via http.Client and processes responses efficiently by streaming Response.Body. Instead of buffering everything (io.ReadAll), it uses io.Copy to io.Discard to count bytes with constant memory, and clarifies why Response.Body is an io.ReadCloser through interface embedding and composition.
Next, it boosts throughput by tuning http.Client’s Transport: reusing one Transport, matching MaxIdleConnsPerHost to concurrency, disabling redirects for cleaner measurements, and setting per‑request timeouts to avoid pipeline stalls—yielding dramatic performance gains. For testability, it exploits the Client.Transport RoundTripper interface: a function type adapter fakes round trips for fast, hermetic unit tests, and httptest.Server enables end‑to‑end checks over real TCP/HTTP. Together, these patterns demonstrate how Go’s standard interfaces (Context, Reader/Writer/Closer, RoundTripper, Handler) encourage decoupled designs that cancel promptly, stream efficiently, and are straightforward to verify.
SendN runs a concurrent pipeline that simulates sending requests using Send. It returns a Results iterator that pushes each Result from the pipeline to consumers. Summarize consumes the iterator and produces a Summary.
Closing the producer's output channel after it detects that the Context was canceled causes the remaining stages to detect the channel closure and stop.
Cancellation signals propagate through the hierarchy. Canceling the parent cancels itself and its children. The cross-out indicates the cancellation.
Sending a request to a server using an HTTP client and receiving a response. Once we receive a response, we can read its body as a byte stream.
Reading and storing everything from the io.Reader in memory.
Reading from the io.Reader by reusing a 32 KB buffer in memory. Copy reads a 32 KB data block into its buffer and writes before reading the next one.
The HIT client goroutines send concurrent HTTP requests to the HTTP server via the http.Client, with connections in the http.Transporter's pool.
Summary
- context.Context is an interface with many implementations. It's the idiomatic way to propagate cancellation signals across package APIs and goroutines. To respond to cancellation signals from a Context, a consumer must actively listen to them.
- Context is immutable, but we can derive a new one from an existing one.
- Context is hierarchic. Canceling the parent also cancels its children.
- context.Background is a non-cancelable root Context.
- context.WithTimeout returns a Context that cancels after a timeout.
- Context.Done returns a channel that becomes ready when the Context is canceled.
- Context.Err returns the cancellation reason; otherwise, it returns nil.
- Always cancel a Context to free up resources after finishing using it.
- Context communication is one-way: only from parents to children.
- Context is conventionally passed as the first parameter to a function.
- Avoid storing a Context in a struct type.
- Request.Clone makes a shallow copy of the Request with a Context.
- Channels need both sender and receiver to be ready to exchange messages.
- signal.NotifyContext returns a Context that cancels after receiving the specified signal from the operating system. The returned function turns off signal trapping.
- The context package helps us build reliable, efficient, and responsive programs.
- http is a package primarily containing Client and Server types.
- Sending a request and returning a response is called an HTTP round-trip.
- http.Client implements an HTTP client.
- http.DefaultClient is a preconfigured default HTTP client for sending requests.
- http.Request represents an HTTP request to send to an HTTP server.
- http.Response represents a response from an HTTP request.
- http.Transport connects to HTTP servers to send HTTP requests and and return HTTP responses. It maintains a TCP connection pool to HTTP servers.
- http.NewRequest returns a new *http.Request.
- http.NewRequestWithContext returns a new *http.Request with a Context.
- http.Client.Do sends an *http.Request and returns an *http.Response.
- Response.StatusCode holds the HTTP status code of an HTTP response.
- Response.Body is an io.ReadCloser that allows us to read HTTP response bodies.
- io.ReadCloser is an interface that embeds io.Reader and io.Writer.
- Embedding an interface promotes its methods to the embedder. Conventionally, the new interface that embeds other interfaces is named with a "-er" suffix only once.
- Compose larger interfaces by embedding smaller ones to clarify intent.
- io.Reader defines the protocol of reading from a data stream in byte chunks.
- io.Reader.Read reads bytes into a []byte and returns the count and an error.
- io.Closer defines the protocol of closing a resource.
- io.Closer.Close is a method that returns an error.
- Closing the Response.Body allows the Client to reuse connections.
- Composition and efficient stream processing are two key themes in Go.
- The io package provides functionality for working with I/O.
- io.ReadAll entirely reads an io.Reader into memory as a []byte.
- io.Copy reads from a Reader and writes to a Writer in 32 KB byte chunks.
- io.Discard is an io.Writer that throws away data without writing it.
- http.Client.Do may return a nil Response.Body if an error occurs.
- Always check Close() errors on any stream that flushes or syncs.
- http.Client.Transport uses an http.RoundTripper to send requests.
- http.RoundTripper is an interface with a RoundTrip method.
- Interface fields in structs may help to improve reusability.
- Using the same http.Transport allows reusing pooled connections.
- http.DefaultTransport is the http package's default RoundTripper.
- http.DefaultClient uses http.DefaultTransport as its RoundTripper.
- http.Client.CheckRedirect determines the HTTP redirection policy.
- http.Client.Timeout is the timeout for each round-trip.
- http.Client.MaxIdleConnsPerHost is the maximum idle connection per host.
- http.Client.Transport allows passing it a RoundTripper to change its behavior. Passing a fake RoundTripper allows unit testing without running an HTTP server.
- We can satisfy interfaces using any concrete type, including function types.
- httptest.Server runs an http.Server tuned for testing purposes.
- http.Server serves incoming HTTP requests with an http.Handler.
- http.Handler is an interface with a Handle method for serving HTTP requests.
- http.HandlerFunc converts a function with the right signature to a Handler.
- httptest.Server.Close closes the http.Server.
- httptest.Server.URL returns the http.Server's URL.
- sync/atomic provides concurrency-safe counters and types.
- atomic.Int64 is a concurrency-safe 64-bit atomic integer counter.
Go by Example ebook for free