diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..46bf039 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,16 @@ +changelog: + exclude: + labels: + - ignore-for-release + authors: + - dependabot + categories: + - title: Enhancements + labels: + - enhancement + - title: Bugfixes + labels: + - bug + - title: Other changes + labels: + - "*" diff --git a/README.md b/README.md index 819d162..57dc4c0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Go HTTP Client-Side Load Balancing +# httplb [![Build](https://github.com/bufbuild/httplb/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/bufbuild/httplb/actions/workflows/ci.yaml) [![Report Card](https://goreportcard.com/badge/github.com/bufbuild/httplb)](https://goreportcard.com/report/github.com/bufbuild/httplb) @@ -33,17 +33,13 @@ import ( "time" "github.com/bufbuild/httplb" - "github.com/bufbuild/httplb/resolver" + "github.com/bufbuild/httplb/picker" ) func main() { client := httplb.NewClient( - httplb.WithResolver( - // Use a resolver for IPv4. You can pass "ip" for dual-stack setups, - // or "ip6" for pure IPv6. This defaults to "ip" and assumes IPv6 - // is suitable for your deployment. - resolver.NewDNSResolver(net.DefaultResolver, "ip4", 5*time.Minute), - ), + // Switch from the default round-robin policy to power-of-two. + httplb.WithPicker(picker.NewPowerOfTwo), ) defer client.Close() resp, err := client.Get("https://example.com") @@ -55,7 +51,8 @@ func main() { } ``` -And here is how you can use `httplb` with `connect-go`: +If you're using [Connect](https://github.com/connectrpc/connect-go), you can +use `httplb` for your RPC clients: ```go func main() { @@ -99,6 +96,5 @@ This project is currently in **alpha**. The API should be considered unstable an ## Legal -Offered under the [Apache 2 license][badges_license]. +Offered under the [Apache 2 license](LICENSE). -[badges_license]: https://github.com/bufbuild/knit-go/blob/main/LICENSE diff --git a/balancer_test.go b/balancer_test.go index 3a2ee25..393eef1 100644 --- a/balancer_test.go +++ b/balancer_test.go @@ -206,7 +206,7 @@ func TestConnManager(t *testing.T) { func TestBalancer_BasicConnManagement(t *testing.T) { t.Parallel() pool := balancertesting.NewFakeConnPool() - balancer := newBalancer(context.Background(), balancertesting.NewFakePicker, health.NoOpChecker, pool) + balancer := newBalancer(context.Background(), balancertesting.NewFakePicker, health.NopChecker, pool) balancer.updateHook = balancertesting.DeterministicReconciler balancer.start() // Initial resolve diff --git a/client.go b/client.go index da4dc2f..0751f8b 100644 --- a/client.go +++ b/client.go @@ -40,17 +40,20 @@ var ( defaultResolver = resolver.NewDNSResolver(net.DefaultResolver, "ip", defaultNameTTL) ) -// Client is an HTTP client that includes client-side load balancing logic. It -// embeds a standard *[http.Client] and exposes some additional operations. +// Client is an HTTP client that supports configurable client-side load +// balancing, name resolution, and subsetting. It embeds the standard library's +// *[http.Client] and exposes some additional operations. // -// If working with a library that requires an *[http.Client], simply use the -// embedded Client field. If working with a library that requires an -// [http.RoundTripper], use the Transport field. +// If working with a library that requires an *[http.Client], use the embedded +// Client. If working with a library that requires an [http.RoundTripper], use +// the Transport field. type Client struct { *http.Client } -// NewClient returns a new HTTP client that uses the given options. +// NewClient constructs a new HTTP client optimized for server-to-server +// communication. By default, the client re-resolves addresses every 5 minutes +// and uses a round-robin load balancing policy. func NewClient(options ...ClientOption) *Client { var opts clientOptions for _, opt := range options { @@ -65,8 +68,8 @@ func NewClient(options ...ClientOption) *Client { } } -// Close closes the HTTP client, releasing any resources and stopping -// any associated background goroutines. +// Close the HTTP client, releasing any resources and stopping any associated +// background goroutines. func (c *Client) Close() error { transport, ok := c.Transport.(*mainTransport) if !ok { @@ -400,7 +403,7 @@ func (opts *clientOptions) applyDefaults() { opts.newPicker = picker.NewRoundRobin } if opts.healthChecker == nil { - opts.healthChecker = health.NoOpChecker + opts.healthChecker = health.NopChecker } if opts.dialFunc == nil { opts.dialFunc = defaultDialer.DialContext diff --git a/health/checker.go b/health/checker.go index 330e1d9..e6d2963 100644 --- a/health/checker.go +++ b/health/checker.go @@ -23,9 +23,9 @@ import ( //nolint:gochecknoglobals var ( - // NoOpChecker is a checker implementation that does nothing. It assumes + // NopChecker is a checker implementation that does nothing. It assumes // the state of all connections is healthy. - NoOpChecker Checker = noOpChecker{} + NopChecker Checker = nopChecker{} ) // Checker manages health checks. It creates new checking processes as new @@ -48,15 +48,15 @@ type Tracker interface { UpdateHealthState(conn.Conn, State) } -type noOpChecker struct{} +type nopChecker struct{} -func (n noOpChecker) New(_ context.Context, conn conn.Conn, tracker Tracker) io.Closer { +func (n nopChecker) New(_ context.Context, conn conn.Conn, tracker Tracker) io.Closer { go tracker.UpdateHealthState(conn, StateHealthy) - return noOpCloser{} + return nopCloser{} } -type noOpCloser struct{} +type nopCloser struct{} -func (n noOpCloser) Close() error { +func (n nopCloser) Close() error { return nil } diff --git a/overhead_test.go b/overhead_test.go index 09a885a..4df0d51 100644 --- a/overhead_test.go +++ b/overhead_test.go @@ -23,24 +23,24 @@ import ( "github.com/stretchr/testify/require" ) -type noopTransport struct{} +type nopTransport struct{} -func (n noopTransport) NewRoundTripper(string, string, TransportConfig) RoundTripperResult { +func (n nopTransport) NewRoundTripper(string, string, TransportConfig) RoundTripperResult { return RoundTripperResult{ RoundTripper: n, } } -func (noopTransport) RoundTrip(*http.Request) (*http.Response, error) { +func (nopTransport) RoundTrip(*http.Request) (*http.Response, error) { response := new(http.Response) response.StatusCode = 200 response.Body = http.NoBody return response, nil } -func BenchmarkNoOpTransportHTTPLB(b *testing.B) { +func BenchmarkNopTransportHTTPLB(b *testing.B) { client := NewClient( - WithTransport("http", noopTransport{}), + WithTransport("http", nopTransport{}), WithAllowBackendTarget("http", "localhost:0"), ) warmCtx, cancel := context.WithTimeout(context.Background(), time.Second) @@ -64,9 +64,9 @@ func BenchmarkNoOpTransportHTTPLB(b *testing.B) { }) } -func BenchmarkNoOpTransportNetHTTP(b *testing.B) { +func BenchmarkNopTransportNetHTTP(b *testing.B) { client := new(http.Client) - client.Transport = noopTransport{} + client.Transport = nopTransport{} b.SetParallelism(100) b.ResetTimer() b.RunParallel(func(p *testing.PB) {