// New returns an AddService backed by an HTTP server living at the remote // instance. We expect instance to come from a service discovery system, so // likely of the form "host:port". func New(instance string, tracer stdopentracing.Tracer, logger log.Logger) (addsvc.Service, error) { if !strings.HasPrefix(instance, "http") { instance = "http://" + instance } u, err := url.Parse(instance) if err != nil { return nil, err } // We construct a single ratelimiter middleware, to limit the total outgoing // QPS from this client to all methods on the remote instance. We also // construct per-endpoint circuitbreaker middlewares to demonstrate how // that's done, although they could easily be combined into a single breaker // for the entire remote instance, too. limiter := ratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(100, 100)) var sumEndpoint endpoint.Endpoint { sumEndpoint = httptransport.NewClient( "POST", copyURL(u, "/sum"), addsvc.EncodeHTTPGenericRequest, addsvc.DecodeHTTPSumResponse, httptransport.SetClientBefore(opentracing.FromHTTPRequest(tracer, "Sum", logger)), ).Endpoint() sumEndpoint = opentracing.TraceClient(tracer, "Sum")(sumEndpoint) sumEndpoint = limiter(sumEndpoint) sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: "Sum", Timeout: 30 * time.Second, }))(sumEndpoint) } var concatEndpoint endpoint.Endpoint { concatEndpoint = httptransport.NewClient( "POST", copyURL(u, "/concat"), addsvc.EncodeHTTPGenericRequest, addsvc.DecodeHTTPConcatResponse, httptransport.SetClientBefore(opentracing.FromHTTPRequest(tracer, "Concat", logger)), ).Endpoint() concatEndpoint = opentracing.TraceClient(tracer, "Concat")(concatEndpoint) concatEndpoint = limiter(concatEndpoint) sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: "Concat", Timeout: 30 * time.Second, }))(sumEndpoint) } return addsvc.Endpoints{ SumEndpoint: sumEndpoint, ConcatEndpoint: concatEndpoint, }, nil }
// MakeConcatEndpointFactory generates a Factory that transforms an http url // into an Endpoint. // // The path of the url is reset to /concat. func MakeConcatEndpointFactory(tracer opentracing.Tracer, tracingLogger log.Logger) loadbalancer.Factory { return func(instance string) (endpoint.Endpoint, io.Closer, error) { concatURL, err := url.Parse(instance) if err != nil { return nil, nil, err } concatURL.Path = "/concat" client := httptransport.NewClient( "GET", concatURL, server.EncodeConcatRequest, server.DecodeConcatResponse, httptransport.SetClient(nil), httptransport.SetClientBefore(kitot.ToHTTPRequest(tracer, tracingLogger)), ) return client.Endpoint(), nil, nil } }
func TestHTTPClient(t *testing.T) { var ( encode = func(*http.Request, interface{}) error { return nil } decode = func(*http.Response) (interface{}, error) { return struct{}{}, nil } headers = make(chan string, 1) headerKey = "X-Foo" headerVal = "abcde" ) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { headers <- r.Header.Get(headerKey) w.WriteHeader(http.StatusOK) })) client := httptransport.NewClient( "GET", mustParse(server.URL), encode, decode, httptransport.SetClientBefore(httptransport.SetRequestHeader(headerKey, headerVal)), ) _, err := client.Endpoint()(context.Background(), struct{}{}) if err != nil { t.Fatal(err) } var have string select { case have = <-headers: case <-time.After(time.Millisecond): t.Fatalf("timeout waiting for %s", headerKey) } if want := headerVal; want != have { t.Errorf("want %q, have %q", want, have) } }
func TestHTTPClient(t *testing.T) { var ( testbody = "testbody" encode = func(context.Context, *http.Request, interface{}) error { return nil } decode = func(_ context.Context, r *http.Response) (interface{}, error) { buffer := make([]byte, len(testbody)) r.Body.Read(buffer) return TestResponse{r.Body, string(buffer)}, nil } headers = make(chan string, 1) headerKey = "X-Foo" headerVal = "abcde" ) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { headers <- r.Header.Get(headerKey) w.WriteHeader(http.StatusOK) w.Write([]byte(testbody)) })) client := httptransport.NewClient( "GET", mustParse(server.URL), encode, decode, httptransport.SetClientBefore(httptransport.SetRequestHeader(headerKey, headerVal)), ) res, err := client.Endpoint()(context.Background(), struct{}{}) if err != nil { t.Fatal(err) } var have string select { case have = <-headers: case <-time.After(time.Millisecond): t.Fatalf("timeout waiting for %s", headerKey) } // Check that Request Header was successfully received if want := headerVal; want != have { t.Errorf("want %q, have %q", want, have) } // Check that the response was successfully decoded response, ok := res.(TestResponse) if !ok { t.Fatal("response should be TestResponse") } if want, have := testbody, response.String; want != have { t.Errorf("want %q, have %q", want, have) } // Check that response body was closed b := make([]byte, 1) _, err = response.Body.Read(b) if err == nil { t.Fatal("wanted error, got none") } if doNotWant, have := io.EOF, err; doNotWant == have { t.Errorf("do not want %q, have %q", doNotWant, have) } }