Moved to google.golang.org/genproto/googleapis/api/annotations

Fixes #52
This commit is contained in:
Valerio Gheri
2017-03-31 18:01:58 +02:00
parent 024c5a4e4e
commit c40779224f
2037 changed files with 831329 additions and 1854 deletions

2
vendor/github.com/go-kit/kit/transport/doc.go generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// Package transport contains bindings to concrete transports.
package transport

37
vendor/github.com/go-kit/kit/transport/grpc/README.md generated vendored Normal file
View File

@@ -0,0 +1,37 @@
# grpc
[gRPC](http://www.grpc.io/) is an excellent, modern IDL and transport for microservices.
If you're starting a greenfield project, Go kit strongly recommends gRPC as your default transport.
And using gRPC and Go kit together is very simple.
First, define your service using protobuf3.
This is explained [in gRPC documentation](http://www.grpc.io/docs/#defining-a-service).
See [add.proto](https://github.com/go-kit/kit/blob/ec8b02591ee873433565a1ae9d317353412d1d27/examples/addsvc/pb/add.proto) for an example.
Make sure the proto definition matches your service's Go kit (interface) definition.
Next, get the protoc compiler.
Unfortunately, this needs to be done from source.
Fortunately, it's pretty straightforward.
```
brew install autoconf automake libtool
git clone https://github.com/google/protobuf
cd protobuf
./autogen.sh ; ./configure ; make ; make install
```
Then, compile your service definition, from .proto to .go.
```
protoc add.proto --go_out=plugins=grpc:.
```
Finally, write a tiny binding from your service definition to the gRPC definition.
It's a simple conversion from one domain to another.
See [grpc_binding.go](https://github.com/go-kit/kit/blob/ec8b02591ee873433565a1ae9d317353412d1d27/examples/addsvc/grpc_binding.go) for an example.
That's it!
The gRPC binding can be bound to a listener and serve normal gRPC requests.
And within your service, you can use standard Go kit components and idioms.
See [addsvc](https://github.com/go-kit/kit/tree/master/examples/addsvc) for a complete working example with gRPC support.
And remember: Go kit services can support multiple transports simultaneously.

View File

@@ -0,0 +1,50 @@
package test
import (
"context"
"google.golang.org/grpc"
"github.com/go-kit/kit/endpoint"
grpctransport "github.com/go-kit/kit/transport/grpc"
"github.com/go-kit/kit/transport/grpc/_grpc_test/pb"
)
type clientBinding struct {
test endpoint.Endpoint
}
func (c *clientBinding) Test(ctx context.Context, a string, b int64) (context.Context, string, error) {
response, err := c.test(ctx, TestRequest{A: a, B: b})
if err != nil {
return nil, "", err
}
r := response.(*TestResponse)
return r.Ctx, r.V, nil
}
func NewClient(cc *grpc.ClientConn) Service {
return &clientBinding{
test: grpctransport.NewClient(
cc,
"pb.Test",
"Test",
encodeRequest,
decodeResponse,
&pb.TestResponse{},
grpctransport.ClientBefore(
injectCorrelationID,
),
grpctransport.ClientBefore(
displayClientRequestHeaders,
),
grpctransport.ClientAfter(
displayClientResponseHeaders,
displayClientResponseTrailers,
),
grpctransport.ClientAfter(
extractConsumedCorrelationID,
),
).Endpoint(),
}
}

View File

@@ -0,0 +1,141 @@
package test
import (
"context"
"fmt"
"google.golang.org/grpc/metadata"
)
type metaContext string
const (
correlationID metaContext = "correlation-id"
responseHDR metaContext = "my-response-header"
responseTRLR metaContext = "my-response-trailer"
correlationIDTRLR metaContext = "correlation-id-consumed"
)
/* client before functions */
func injectCorrelationID(ctx context.Context, md *metadata.MD) context.Context {
if hdr, ok := ctx.Value(correlationID).(string); ok {
fmt.Printf("\tClient found correlationID %q in context, set metadata header\n", hdr)
(*md)[string(correlationID)] = append((*md)[string(correlationID)], hdr)
}
return ctx
}
func displayClientRequestHeaders(ctx context.Context, md *metadata.MD) context.Context {
if len(*md) > 0 {
fmt.Println("\tClient >> Request Headers:")
for key, val := range *md {
fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1])
}
}
return ctx
}
/* server before functions */
func extractCorrelationID(ctx context.Context, md metadata.MD) context.Context {
if hdr, ok := md[string(correlationID)]; ok {
cID := hdr[len(hdr)-1]
ctx = context.WithValue(ctx, correlationID, cID)
fmt.Printf("\tServer received correlationID %q in metadata header, set context\n", cID)
}
return ctx
}
func displayServerRequestHeaders(ctx context.Context, md metadata.MD) context.Context {
if len(md) > 0 {
fmt.Println("\tServer << Request Headers:")
for key, val := range md {
fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1])
}
}
return ctx
}
/* server after functions */
func injectResponseHeader(ctx context.Context, md *metadata.MD, _ *metadata.MD) context.Context {
*md = metadata.Join(*md, metadata.Pairs(string(responseHDR), "has-a-value"))
return ctx
}
func displayServerResponseHeaders(ctx context.Context, md *metadata.MD, _ *metadata.MD) context.Context {
if len(*md) > 0 {
fmt.Println("\tServer >> Response Headers:")
for key, val := range *md {
fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1])
}
}
return ctx
}
func injectResponseTrailer(ctx context.Context, _ *metadata.MD, md *metadata.MD) context.Context {
*md = metadata.Join(*md, metadata.Pairs(string(responseTRLR), "has-a-value-too"))
return ctx
}
func injectConsumedCorrelationID(ctx context.Context, _ *metadata.MD, md *metadata.MD) context.Context {
if hdr, ok := ctx.Value(correlationID).(string); ok {
fmt.Printf("\tServer found correlationID %q in context, set consumed trailer\n", hdr)
*md = metadata.Join(*md, metadata.Pairs(string(correlationIDTRLR), hdr))
}
return ctx
}
func displayServerResponseTrailers(ctx context.Context, _ *metadata.MD, md *metadata.MD) context.Context {
if len(*md) > 0 {
fmt.Println("\tServer >> Response Trailers:")
for key, val := range *md {
fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1])
}
}
return ctx
}
/* client after functions */
func displayClientResponseHeaders(ctx context.Context, md metadata.MD, _ metadata.MD) context.Context {
if len(md) > 0 {
fmt.Println("\tClient << Response Headers:")
for key, val := range md {
fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1])
}
}
return ctx
}
func displayClientResponseTrailers(ctx context.Context, _ metadata.MD, md metadata.MD) context.Context {
if len(md) > 0 {
fmt.Println("\tClient << Response Trailers:")
for key, val := range md {
fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1])
}
}
return ctx
}
func extractConsumedCorrelationID(ctx context.Context, _ metadata.MD, md metadata.MD) context.Context {
if hdr, ok := md[string(correlationIDTRLR)]; ok {
fmt.Printf("\tClient received consumed correlationID %q in metadata trailer, set context\n", hdr[len(hdr)-1])
ctx = context.WithValue(ctx, correlationIDTRLR, hdr[len(hdr)-1])
}
return ctx
}
/* CorrelationID context handlers */
func SetCorrelationID(ctx context.Context, v string) context.Context {
return context.WithValue(ctx, correlationID, v)
}
func GetConsumedCorrelationID(ctx context.Context) string {
if trlr, ok := ctx.Value(correlationIDTRLR).(string); ok {
return trlr
}
return ""
}

View File

@@ -0,0 +1,3 @@
package pb
//go:generate protoc test.proto --go_out=plugins=grpc:.

View File

@@ -0,0 +1,167 @@
// Code generated by protoc-gen-go.
// source: test.proto
// DO NOT EDIT!
/*
Package pb is a generated protocol buffer package.
It is generated from these files:
test.proto
It has these top-level messages:
TestRequest
TestResponse
*/
package pb
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type TestRequest struct {
A string `protobuf:"bytes,1,opt,name=a" json:"a,omitempty"`
B int64 `protobuf:"varint,2,opt,name=b" json:"b,omitempty"`
}
func (m *TestRequest) Reset() { *m = TestRequest{} }
func (m *TestRequest) String() string { return proto.CompactTextString(m) }
func (*TestRequest) ProtoMessage() {}
func (*TestRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *TestRequest) GetA() string {
if m != nil {
return m.A
}
return ""
}
func (m *TestRequest) GetB() int64 {
if m != nil {
return m.B
}
return 0
}
type TestResponse struct {
V string `protobuf:"bytes,1,opt,name=v" json:"v,omitempty"`
}
func (m *TestResponse) Reset() { *m = TestResponse{} }
func (m *TestResponse) String() string { return proto.CompactTextString(m) }
func (*TestResponse) ProtoMessage() {}
func (*TestResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *TestResponse) GetV() string {
if m != nil {
return m.V
}
return ""
}
func init() {
proto.RegisterType((*TestRequest)(nil), "pb.TestRequest")
proto.RegisterType((*TestResponse)(nil), "pb.TestResponse")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for Test service
type TestClient interface {
Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error)
}
type testClient struct {
cc *grpc.ClientConn
}
func NewTestClient(cc *grpc.ClientConn) TestClient {
return &testClient{cc}
}
func (c *testClient) Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) {
out := new(TestResponse)
err := grpc.Invoke(ctx, "/pb.Test/Test", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Test service
type TestServer interface {
Test(context.Context, *TestRequest) (*TestResponse, error)
}
func RegisterTestServer(s *grpc.Server, srv TestServer) {
s.RegisterService(&_Test_serviceDesc, srv)
}
func _Test_Test_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TestRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TestServer).Test(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/pb.Test/Test",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TestServer).Test(ctx, req.(*TestRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Test_serviceDesc = grpc.ServiceDesc{
ServiceName: "pb.Test",
HandlerType: (*TestServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Test",
Handler: _Test_Test_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "test.proto",
}
func init() { proto.RegisterFile("test.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 129 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e,
0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2a, 0x48, 0x52, 0xd2, 0xe4, 0xe2, 0x0e, 0x49,
0x2d, 0x2e, 0x09, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0xe2, 0xe1, 0x62, 0x4c, 0x94, 0x60,
0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x62, 0x4c, 0x04, 0xf1, 0x92, 0x24, 0x98, 0x14, 0x18, 0x35, 0x98,
0x83, 0x18, 0x93, 0x94, 0x64, 0xb8, 0x78, 0x20, 0x4a, 0x8b, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x41,
0xb2, 0x65, 0x30, 0xb5, 0x65, 0x46, 0xc6, 0x5c, 0x2c, 0x20, 0x59, 0x21, 0x6d, 0x28, 0xcd, 0xaf,
0x57, 0x90, 0xa4, 0x87, 0x64, 0xb4, 0x94, 0x00, 0x42, 0x00, 0x62, 0x80, 0x12, 0x43, 0x12, 0x1b,
0xd8, 0x21, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x49, 0xfc, 0xd8, 0xf1, 0x96, 0x00, 0x00,
0x00,
}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
package pb;
service Test {
rpc Test (TestRequest) returns (TestResponse) {}
}
message TestRequest {
string a = 1;
int64 b = 2;
}
message TestResponse {
string v = 1;
}

View File

@@ -0,0 +1,27 @@
package test
import (
"context"
"github.com/go-kit/kit/transport/grpc/_grpc_test/pb"
)
func encodeRequest(ctx context.Context, req interface{}) (interface{}, error) {
r := req.(TestRequest)
return &pb.TestRequest{A: r.A, B: r.B}, nil
}
func decodeRequest(ctx context.Context, req interface{}) (interface{}, error) {
r := req.(*pb.TestRequest)
return TestRequest{A: r.A, B: r.B}, nil
}
func encodeResponse(ctx context.Context, resp interface{}) (interface{}, error) {
r := resp.(*TestResponse)
return &pb.TestResponse{V: r.V}, nil
}
func decodeResponse(ctx context.Context, resp interface{}) (interface{}, error) {
r := resp.(*pb.TestResponse)
return &TestResponse{V: r.V, Ctx: ctx}, nil
}

View File

@@ -0,0 +1,70 @@
package test
import (
"context"
"fmt"
oldcontext "golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
grpctransport "github.com/go-kit/kit/transport/grpc"
"github.com/go-kit/kit/transport/grpc/_grpc_test/pb"
)
type service struct{}
func (service) Test(ctx context.Context, a string, b int64) (context.Context, string, error) {
return nil, fmt.Sprintf("%s = %d", a, b), nil
}
func NewService() Service {
return service{}
}
func makeTestEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(TestRequest)
newCtx, v, err := svc.Test(ctx, req.A, req.B)
return &TestResponse{
V: v,
Ctx: newCtx,
}, err
}
}
type serverBinding struct {
test grpctransport.Handler
}
func (b *serverBinding) Test(ctx oldcontext.Context, req *pb.TestRequest) (*pb.TestResponse, error) {
_, response, err := b.test.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return response.(*pb.TestResponse), nil
}
func NewBinding(svc Service) *serverBinding {
return &serverBinding{
test: grpctransport.NewServer(
makeTestEndpoint(svc),
decodeRequest,
encodeResponse,
grpctransport.ServerBefore(
extractCorrelationID,
),
grpctransport.ServerBefore(
displayServerRequestHeaders,
),
grpctransport.ServerAfter(
injectResponseHeader,
injectResponseTrailer,
injectConsumedCorrelationID,
),
grpctransport.ServerAfter(
displayServerResponseHeaders,
displayServerResponseTrailers,
),
),
}
}

View File

@@ -0,0 +1,17 @@
package test
import "context"
type Service interface {
Test(ctx context.Context, a string, b int64) (context.Context, string, error)
}
type TestRequest struct {
A string
B int64
}
type TestResponse struct {
Ctx context.Context
V string
}

119
vendor/github.com/go-kit/kit/transport/grpc/client.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
package grpc
import (
"context"
"fmt"
"reflect"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/go-kit/kit/endpoint"
)
// Client wraps a gRPC connection and provides a method that implements
// endpoint.Endpoint.
type Client struct {
client *grpc.ClientConn
serviceName string
method string
enc EncodeRequestFunc
dec DecodeResponseFunc
grpcReply reflect.Type
before []ClientRequestFunc
after []ClientResponseFunc
}
// NewClient constructs a usable Client for a single remote endpoint.
// Pass an zero-value protobuf message of the RPC response type as
// the grpcReply argument.
func NewClient(
cc *grpc.ClientConn,
serviceName string,
method string,
enc EncodeRequestFunc,
dec DecodeResponseFunc,
grpcReply interface{},
options ...ClientOption,
) *Client {
if strings.IndexByte(serviceName, '.') == -1 {
serviceName = "pb." + serviceName
}
c := &Client{
client: cc,
method: fmt.Sprintf("/%s/%s", serviceName, method),
enc: enc,
dec: dec,
// We are using reflect.Indirect here to allow both reply structs and
// pointers to these reply structs. New consumers of the client should
// use structs directly, while existing consumers will not break if they
// remain to use pointers to structs.
grpcReply: reflect.TypeOf(
reflect.Indirect(
reflect.ValueOf(grpcReply),
).Interface(),
),
before: []ClientRequestFunc{},
after: []ClientResponseFunc{},
}
for _, option := range options {
option(c)
}
return c
}
// ClientOption sets an optional parameter for clients.
type ClientOption func(*Client)
// ClientBefore sets the RequestFuncs that are applied to the outgoing gRPC
// request before it's invoked.
func ClientBefore(before ...ClientRequestFunc) ClientOption {
return func(c *Client) { c.before = append(c.before, before...) }
}
// ClientAfter sets the ClientResponseFuncs that are applied to the incoming
// gRPC response prior to it being decoded. This is useful for obtaining
// response metadata and adding onto the context prior to decoding.
func ClientAfter(after ...ClientResponseFunc) ClientOption {
return func(c *Client) { c.after = append(c.after, after...) }
}
// Endpoint returns a usable endpoint that will invoke the gRPC specified by the
// client.
func (c Client) Endpoint() endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
req, err := c.enc(ctx, request)
if err != nil {
return nil, err
}
md := &metadata.MD{}
for _, f := range c.before {
ctx = f(ctx, md)
}
ctx = metadata.NewContext(ctx, *md)
var header, trailer metadata.MD
grpcReply := reflect.New(c.grpcReply).Interface()
if err = grpc.Invoke(
ctx, c.method, req, grpcReply, c.client,
grpc.Header(&header), grpc.Trailer(&trailer),
); err != nil {
return nil, err
}
for _, f := range c.after {
ctx = f(ctx, header, trailer)
}
response, err := c.dec(ctx, grpcReply)
if err != nil {
return nil, err
}
return response, nil
}
}

View File

@@ -0,0 +1,59 @@
package grpc_test
import (
"context"
"fmt"
"net"
"testing"
"google.golang.org/grpc"
test "github.com/go-kit/kit/transport/grpc/_grpc_test"
"github.com/go-kit/kit/transport/grpc/_grpc_test/pb"
)
const (
hostPort string = "localhost:8002"
)
func TestGRPCClient(t *testing.T) {
var (
server = grpc.NewServer()
service = test.NewService()
)
sc, err := net.Listen("tcp", hostPort)
if err != nil {
t.Fatalf("unable to listen: %+v", err)
}
defer server.GracefulStop()
go func() {
pb.RegisterTestServer(server, test.NewBinding(service))
_ = server.Serve(sc)
}()
cc, err := grpc.Dial(hostPort, grpc.WithInsecure())
if err != nil {
t.Fatalf("unable to Dial: %+v", err)
}
client := test.NewClient(cc)
var (
a = "the answer to life the universe and everything"
b = int64(42)
cID = "request-1"
ctx = test.SetCorrelationID(context.Background(), cID)
)
responseCTX, v, err := client.Test(ctx, a, b)
if want, have := fmt.Sprintf("%s = %d", a, b), v; want != have {
t.Fatalf("want %q, have %q", want, have)
}
if want, have := cID, test.GetConsumedCorrelationID(responseCTX); want != have {
t.Fatalf("want %q, have %q", want, have)
}
}

2
vendor/github.com/go-kit/kit/transport/grpc/doc.go generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// Package grpc provides a gRPC binding for endpoints.
package grpc

View File

@@ -0,0 +1,29 @@
package grpc
import (
"context"
)
// DecodeRequestFunc extracts a user-domain request object from a gRPC request.
// It's designed to be used in gRPC servers, for server-side endpoints. One
// straightforward DecodeRequestFunc could be something that decodes from the
// gRPC request message to the concrete request type.
type DecodeRequestFunc func(context.Context, interface{}) (request interface{}, err error)
// EncodeRequestFunc encodes the passed request object into the gRPC request
// object. It's designed to be used in gRPC clients, for client-side endpoints.
// One straightforward EncodeRequestFunc could something that encodes the object
// directly to the gRPC request message.
type EncodeRequestFunc func(context.Context, interface{}) (request interface{}, err error)
// EncodeResponseFunc encodes the passed response object to the gRPC response
// message. It's designed to be used in gRPC servers, for server-side endpoints.
// One straightforward EncodeResponseFunc could be something that encodes the
// object directly to the gRPC response message.
type EncodeResponseFunc func(context.Context, interface{}) (response interface{}, err error)
// DecodeResponseFunc extracts a user-domain response object from a gRPC
// response object. It's designed to be used in gRPC clients, for client-side
// endpoints. One straightforward DecodeResponseFunc could be something that
// decodes from the gRPC response message to the concrete response type.
type DecodeResponseFunc func(context.Context, interface{}) (response interface{}, err error)

View File

@@ -0,0 +1,76 @@
package grpc
import (
"context"
"encoding/base64"
"strings"
"google.golang.org/grpc/metadata"
)
const (
binHdrSuffix = "-bin"
)
// ClientRequestFunc may take information from context and use it to construct
// metadata headers to be transported to the server. ClientRequestFuncs are
// executed after creating the request but prior to sending the gRPC request to
// the server.
type ClientRequestFunc func(context.Context, *metadata.MD) context.Context
// ServerRequestFunc may take information from the received metadata header and
// use it to place items in the request scoped context. ServerRequestFuncs are
// executed prior to invoking the endpoint.
type ServerRequestFunc func(context.Context, metadata.MD) context.Context
// ServerResponseFunc may take information from a request context and use it to
// manipulate the gRPC response metadata headers and trailers. ResponseFuncs are
// only executed in servers, after invoking the endpoint but prior to writing a
// response.
type ServerResponseFunc func(ctx context.Context, header *metadata.MD, trailer *metadata.MD) context.Context
// ClientResponseFunc may take information from a gRPC metadata header and/or
// trailer and make the responses available for consumption. ClientResponseFuncs
// are only executed in clients, after a request has been made, but prior to it
// being decoded.
type ClientResponseFunc func(ctx context.Context, header metadata.MD, trailer metadata.MD) context.Context
// SetRequestHeader returns a ClientRequestFunc that sets the specified metadata
// key-value pair.
func SetRequestHeader(key, val string) ClientRequestFunc {
return func(ctx context.Context, md *metadata.MD) context.Context {
key, val := EncodeKeyValue(key, val)
(*md)[key] = append((*md)[key], val)
return ctx
}
}
// SetResponseHeader returns a ResponseFunc that sets the specified metadata
// key-value pair.
func SetResponseHeader(key, val string) ServerResponseFunc {
return func(ctx context.Context, md *metadata.MD, _ *metadata.MD) context.Context {
key, val := EncodeKeyValue(key, val)
(*md)[key] = append((*md)[key], val)
return ctx
}
}
// SetResponseTrailer returns a ResponseFunc that sets the specified metadata
// key-value pair.
func SetResponseTrailer(key, val string) ServerResponseFunc {
return func(ctx context.Context, _ *metadata.MD, md *metadata.MD) context.Context {
key, val := EncodeKeyValue(key, val)
(*md)[key] = append((*md)[key], val)
return ctx
}
}
// EncodeKeyValue sanitizes a key-value pair for use in gRPC metadata headers.
func EncodeKeyValue(key, val string) (string, string) {
key = strings.ToLower(key)
if strings.HasSuffix(key, binHdrSuffix) {
v := base64.StdEncoding.EncodeToString([]byte(val))
val = string(v)
}
return key, val
}

123
vendor/github.com/go-kit/kit/transport/grpc/server.go generated vendored Normal file
View File

@@ -0,0 +1,123 @@
package grpc
import (
oldcontext "golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
)
// Handler which should be called from the gRPC binding of the service
// implementation. The incoming request parameter, and returned response
// parameter, are both gRPC types, not user-domain.
type Handler interface {
ServeGRPC(ctx oldcontext.Context, request interface{}) (oldcontext.Context, interface{}, error)
}
// Server wraps an endpoint and implements grpc.Handler.
type Server struct {
e endpoint.Endpoint
dec DecodeRequestFunc
enc EncodeResponseFunc
before []ServerRequestFunc
after []ServerResponseFunc
logger log.Logger
}
// NewServer constructs a new server, which implements wraps the provided
// endpoint and implements the Handler interface. Consumers should write
// bindings that adapt the concrete gRPC methods from their compiled protobuf
// definitions to individual handlers. Request and response objects are from the
// caller business domain, not gRPC request and reply types.
func NewServer(
e endpoint.Endpoint,
dec DecodeRequestFunc,
enc EncodeResponseFunc,
options ...ServerOption,
) *Server {
s := &Server{
e: e,
dec: dec,
enc: enc,
logger: log.NewNopLogger(),
}
for _, option := range options {
option(s)
}
return s
}
// ServerOption sets an optional parameter for servers.
type ServerOption func(*Server)
// ServerBefore functions are executed on the HTTP request object before the
// request is decoded.
func ServerBefore(before ...ServerRequestFunc) ServerOption {
return func(s *Server) { s.before = append(s.before, before...) }
}
// ServerAfter functions are executed on the HTTP response writer after the
// endpoint is invoked, but before anything is written to the client.
func ServerAfter(after ...ServerResponseFunc) ServerOption {
return func(s *Server) { s.after = append(s.after, after...) }
}
// ServerErrorLogger is used to log non-terminal errors. By default, no errors
// are logged.
func ServerErrorLogger(logger log.Logger) ServerOption {
return func(s *Server) { s.logger = logger }
}
// ServeGRPC implements the Handler interface.
func (s Server) ServeGRPC(ctx oldcontext.Context, req interface{}) (oldcontext.Context, interface{}, error) {
// Retrieve gRPC metadata.
md, ok := metadata.FromContext(ctx)
if !ok {
md = metadata.MD{}
}
for _, f := range s.before {
ctx = f(ctx, md)
}
request, err := s.dec(ctx, req)
if err != nil {
s.logger.Log("err", err)
return ctx, nil, err
}
response, err := s.e(ctx, request)
if err != nil {
s.logger.Log("err", err)
return ctx, nil, err
}
var mdHeader, mdTrailer metadata.MD
for _, f := range s.after {
ctx = f(ctx, &mdHeader, &mdTrailer)
}
grpcResp, err := s.enc(ctx, response)
if err != nil {
s.logger.Log("err", err)
return ctx, nil, err
}
if len(mdHeader) > 0 {
if err = grpc.SendHeader(ctx, mdHeader); err != nil {
s.logger.Log("err", err)
return ctx, nil, err
}
}
if len(mdTrailer) > 0 {
if err = grpc.SetTrailer(ctx, mdTrailer); err != nil {
s.logger.Log("err", err)
return ctx, nil, err
}
}
return ctx, grpcResp, nil
}

150
vendor/github.com/go-kit/kit/transport/http/client.go generated vendored Normal file
View File

@@ -0,0 +1,150 @@
package http
import (
"bytes"
"context"
"encoding/json"
"encoding/xml"
"io/ioutil"
"net/http"
"net/url"
"golang.org/x/net/context/ctxhttp"
"github.com/go-kit/kit/endpoint"
)
// Client wraps a URL and provides a method that implements endpoint.Endpoint.
type Client struct {
client *http.Client
method string
tgt *url.URL
enc EncodeRequestFunc
dec DecodeResponseFunc
before []RequestFunc
after []ClientResponseFunc
bufferedStream bool
}
// NewClient constructs a usable Client for a single remote method.
func NewClient(
method string,
tgt *url.URL,
enc EncodeRequestFunc,
dec DecodeResponseFunc,
options ...ClientOption,
) *Client {
c := &Client{
client: http.DefaultClient,
method: method,
tgt: tgt,
enc: enc,
dec: dec,
before: []RequestFunc{},
after: []ClientResponseFunc{},
bufferedStream: false,
}
for _, option := range options {
option(c)
}
return c
}
// ClientOption sets an optional parameter for clients.
type ClientOption func(*Client)
// SetClient sets the underlying HTTP client used for requests.
// By default, http.DefaultClient is used.
func SetClient(client *http.Client) ClientOption {
return func(c *Client) { c.client = client }
}
// ClientBefore sets the RequestFuncs that are applied to the outgoing HTTP
// request before it's invoked.
func ClientBefore(before ...RequestFunc) ClientOption {
return func(c *Client) { c.before = append(c.before, before...) }
}
// ClientAfter sets the ClientResponseFuncs applied to the incoming HTTP
// request prior to it being decoded. This is useful for obtaining anything off
// of the response and adding onto the context prior to decoding.
func ClientAfter(after ...ClientResponseFunc) ClientOption {
return func(c *Client) { c.after = append(c.after, after...) }
}
// BufferedStream sets whether the Response.Body is left open, allowing it
// to be read from later. Useful for transporting a file as a buffered stream.
func BufferedStream(buffered bool) ClientOption {
return func(c *Client) { c.bufferedStream = buffered }
}
// Endpoint returns a usable endpoint that invokes the remote endpoint.
func (c Client) Endpoint() endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
req, err := http.NewRequest(c.method, c.tgt.String(), nil)
if err != nil {
return nil, err
}
if err = c.enc(ctx, req, request); err != nil {
return nil, err
}
for _, f := range c.before {
ctx = f(ctx, req)
}
resp, err := ctxhttp.Do(ctx, c.client, req)
if err != nil {
return nil, err
}
if !c.bufferedStream {
defer resp.Body.Close()
}
for _, f := range c.after {
ctx = f(ctx, resp)
}
response, err := c.dec(ctx, resp)
if err != nil {
return nil, err
}
return response, nil
}
}
// EncodeJSONRequest is an EncodeRequestFunc that serializes the request as a
// JSON object to the Request body. Many JSON-over-HTTP services can use it as
// a sensible default. If the request implements Headerer, the provided headers
// will be applied to the request.
func EncodeJSONRequest(c context.Context, r *http.Request, request interface{}) error {
r.Header.Set("Content-Type", "application/json; charset=utf-8")
if headerer, ok := request.(Headerer); ok {
for k := range headerer.Headers() {
r.Header.Set(k, headerer.Headers().Get(k))
}
}
var b bytes.Buffer
r.Body = ioutil.NopCloser(&b)
return json.NewEncoder(&b).Encode(request)
}
// EncodeXMLRequest is an EncodeRequestFunc that serializes the request as a
// XML object to the Request body. If the request implements Headerer,
// the provided headers will be applied to the request.
func EncodeXMLRequest(c context.Context, r *http.Request, request interface{}) error {
r.Header.Set("Content-Type", "text/xml; charset=utf-8")
if headerer, ok := request.(Headerer); ok {
for k := range headerer.Headers() {
r.Header.Set(k, headerer.Headers().Get(k))
}
}
var b bytes.Buffer
r.Body = ioutil.NopCloser(&b)
return xml.NewEncoder(&b).Encode(request)
}

View File

@@ -0,0 +1,217 @@
package http_test
import (
"context"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
httptransport "github.com/go-kit/kit/transport/http"
)
type TestResponse struct {
Body io.ReadCloser
String string
}
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"
afterHeaderKey = "X-The-Dude"
afterHeaderVal = "Abides"
afterVal = ""
afterFunc = func(ctx context.Context, r *http.Response) context.Context {
afterVal = r.Header.Get(afterHeaderKey)
return ctx
}
)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
headers <- r.Header.Get(headerKey)
w.Header().Set(afterHeaderKey, afterHeaderVal)
w.WriteHeader(http.StatusOK)
w.Write([]byte(testbody))
}))
client := httptransport.NewClient(
"GET",
mustParse(server.URL),
encode,
decode,
httptransport.ClientBefore(httptransport.SetRequestHeader(headerKey, headerVal)),
httptransport.ClientAfter(afterFunc),
)
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 Response header set from server was received in SetClientAfter
if want, have := afterVal, afterHeaderVal; 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)
}
}
func TestHTTPClientBufferedStream(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) {
return TestResponse{r.Body, ""}, nil
}
)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(testbody))
}))
client := httptransport.NewClient(
"GET",
mustParse(server.URL),
encode,
decode,
httptransport.BufferedStream(true),
)
res, err := client.Endpoint()(context.Background(), struct{}{})
if err != nil {
t.Fatal(err)
}
// Check that the response was successfully decoded
response, ok := res.(TestResponse)
if !ok {
t.Fatal("response should be TestResponse")
}
// Check that response body was NOT closed
b := make([]byte, len(testbody))
_, err = response.Body.Read(b)
if want, have := io.EOF, err; have != want {
t.Fatalf("want %q, have %q", want, have)
}
if want, have := testbody, string(b); want != have {
t.Errorf("want %q, have %q", want, have)
}
}
func TestEncodeJSONRequest(t *testing.T) {
var header http.Header
var body string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil && err != io.EOF {
t.Fatal(err)
}
header = r.Header
body = string(b)
}))
defer server.Close()
serverURL, err := url.Parse(server.URL)
if err != nil {
t.Fatal(err)
}
client := httptransport.NewClient(
"POST",
serverURL,
httptransport.EncodeJSONRequest,
func(context.Context, *http.Response) (interface{}, error) { return nil, nil },
).Endpoint()
for _, test := range []struct {
value interface{}
body string
}{
{nil, "null\n"},
{12, "12\n"},
{1.2, "1.2\n"},
{true, "true\n"},
{"test", "\"test\"\n"},
{enhancedRequest{Foo: "foo"}, "{\"foo\":\"foo\"}\n"},
} {
if _, err := client(context.Background(), test.value); err != nil {
t.Error(err)
continue
}
if body != test.body {
t.Errorf("%v: actual %#v, expected %#v", test.value, body, test.body)
}
}
if _, err := client(context.Background(), enhancedRequest{Foo: "foo"}); err != nil {
t.Fatal(err)
}
if _, ok := header["X-Edward"]; !ok {
t.Fatalf("X-Edward value: actual %v, expected %v", nil, []string{"Snowden"})
}
if v := header.Get("X-Edward"); v != "Snowden" {
t.Errorf("X-Edward string: actual %v, expected %v", v, "Snowden")
}
}
func mustParse(s string) *url.URL {
u, err := url.Parse(s)
if err != nil {
panic(err)
}
return u
}
type enhancedRequest struct {
Foo string `json:"foo"`
}
func (e enhancedRequest) Headers() http.Header { return http.Header{"X-Edward": []string{"Snowden"}} }

2
vendor/github.com/go-kit/kit/transport/http/doc.go generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// Package http provides a general purpose HTTP binding for endpoints.
package http

View File

@@ -0,0 +1,30 @@
package http
import (
"context"
"net/http"
)
// DecodeRequestFunc extracts a user-domain request object from an HTTP
// request object. It's designed to be used in HTTP servers, for server-side
// endpoints. One straightforward DecodeRequestFunc could be something that
// JSON decodes from the request body to the concrete response type.
type DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error)
// EncodeRequestFunc encodes the passed request object into the HTTP request
// object. It's designed to be used in HTTP clients, for client-side
// endpoints. One straightforward EncodeRequestFunc could something that JSON
// encodes the object directly to the request body.
type EncodeRequestFunc func(context.Context, *http.Request, interface{}) error
// EncodeResponseFunc encodes the passed response object to the HTTP response
// writer. It's designed to be used in HTTP servers, for server-side
// endpoints. One straightforward EncodeResponseFunc could be something that
// JSON encodes the object directly to the response body.
type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error
// DecodeResponseFunc extracts a user-domain response object from an HTTP
// response object. It's designed to be used in HTTP clients, for client-side
// endpoints. One straightforward DecodeResponseFunc could be something that
// JSON decodes from the response body to the concrete response type.
type DecodeResponseFunc func(context.Context, *http.Response) (response interface{}, err error)

View File

@@ -0,0 +1,36 @@
package http
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
)
func ExamplePopulateRequestContext() {
handler := NewServer(
func(ctx context.Context, request interface{}) (response interface{}, err error) {
fmt.Println("Method", ctx.Value(ContextKeyRequestMethod).(string))
fmt.Println("RequestPath", ctx.Value(ContextKeyRequestPath).(string))
fmt.Println("RequestURI", ctx.Value(ContextKeyRequestURI).(string))
fmt.Println("X-Request-ID", ctx.Value(ContextKeyRequestXRequestID).(string))
return struct{}{}, nil
},
func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil },
func(context.Context, http.ResponseWriter, interface{}) error { return nil },
ServerBefore(PopulateRequestContext),
)
server := httptest.NewServer(handler)
defer server.Close()
req, _ := http.NewRequest("PATCH", fmt.Sprintf("%s/search?q=sympatico", server.URL), nil)
req.Header.Set("X-Request-Id", "a1b2c3d4e5")
http.DefaultClient.Do(req)
// Output:
// Method PATCH
// RequestPath /search
// RequestURI /search?q=sympatico
// X-Request-ID a1b2c3d4e5
}

View File

@@ -0,0 +1,128 @@
package http
import (
"context"
"net/http"
)
// RequestFunc may take information from an HTTP request and put it into a
// request context. In Servers, RequestFuncs are executed prior to invoking the
// endpoint. In Clients, RequestFuncs are executed after creating the request
// but prior to invoking the HTTP client.
type RequestFunc func(context.Context, *http.Request) context.Context
// ServerResponseFunc may take information from a request context and use it to
// manipulate a ResponseWriter. ServerResponseFuncs are only executed in
// servers, after invoking the endpoint but prior to writing a response.
type ServerResponseFunc func(context.Context, http.ResponseWriter) context.Context
// ClientResponseFunc may take information from an HTTP request and make the
// response available for consumption. ClientResponseFuncs are only executed in
// clients, after a request has been made, but prior to it being decoded.
type ClientResponseFunc func(context.Context, *http.Response) context.Context
// SetContentType returns a ServerResponseFunc that sets the Content-Type header
// to the provided value.
func SetContentType(contentType string) ServerResponseFunc {
return SetResponseHeader("Content-Type", contentType)
}
// SetResponseHeader returns a ServerResponseFunc that sets the given header.
func SetResponseHeader(key, val string) ServerResponseFunc {
return func(ctx context.Context, w http.ResponseWriter) context.Context {
w.Header().Set(key, val)
return ctx
}
}
// SetRequestHeader returns a RequestFunc that sets the given header.
func SetRequestHeader(key, val string) RequestFunc {
return func(ctx context.Context, r *http.Request) context.Context {
r.Header.Set(key, val)
return ctx
}
}
// PopulateRequestContext is a RequestFunc that populates several values into
// the context from the HTTP request. Those values may be extracted using the
// corresponding ContextKey type in this package.
func PopulateRequestContext(ctx context.Context, r *http.Request) context.Context {
for k, v := range map[contextKey]string{
ContextKeyRequestMethod: r.Method,
ContextKeyRequestURI: r.RequestURI,
ContextKeyRequestPath: r.URL.Path,
ContextKeyRequestProto: r.Proto,
ContextKeyRequestHost: r.Host,
ContextKeyRequestRemoteAddr: r.RemoteAddr,
ContextKeyRequestXForwardedFor: r.Header.Get("X-Forwarded-For"),
ContextKeyRequestXForwardedProto: r.Header.Get("X-Forwarded-Proto"),
ContextKeyRequestAuthorization: r.Header.Get("Authorization"),
ContextKeyRequestReferer: r.Header.Get("Referer"),
ContextKeyRequestUserAgent: r.Header.Get("User-Agent"),
ContextKeyRequestXRequestID: r.Header.Get("X-Request-Id"),
} {
ctx = context.WithValue(ctx, k, v)
}
return ctx
}
type contextKey int
const (
// ContextKeyRequestMethod is populated in the context by
// PopulateRequestContext. Its value is r.Method.
ContextKeyRequestMethod contextKey = iota
// ContextKeyRequestURI is populated in the context by
// PopulateRequestContext. Its value is r.RequestURI.
ContextKeyRequestURI
// ContextKeyRequestPath is populated in the context by
// PopulateRequestContext. Its value is r.URL.Path.
ContextKeyRequestPath
// ContextKeyRequestProto is populated in the context by
// PopulateRequestContext. Its value is r.Proto.
ContextKeyRequestProto
// ContextKeyRequestHost is populated in the context by
// PopulateRequestContext. Its value is r.Host.
ContextKeyRequestHost
// ContextKeyRequestRemoteAddr is populated in the context by
// PopulateRequestContext. Its value is r.RemoteAddr.
ContextKeyRequestRemoteAddr
// ContextKeyRequestXForwardedFor is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("X-Forwarded-For").
ContextKeyRequestXForwardedFor
// ContextKeyRequestXForwardedProto is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("X-Forwarded-Proto").
ContextKeyRequestXForwardedProto
// ContextKeyRequestAuthorization is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("Authorization").
ContextKeyRequestAuthorization
// ContextKeyRequestReferer is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("Referer").
ContextKeyRequestReferer
// ContextKeyRequestUserAgent is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("User-Agent").
ContextKeyRequestUserAgent
// ContextKeyRequestXRequestID is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("X-Request-Id").
ContextKeyRequestXRequestID
// ContextKeyResponseHeaders is populated in the context whenever a
// ServerFinalizerFunc is specified. Its value is of type http.Header, and
// is captured only once the entire response has been written.
ContextKeyResponseHeaders
// ContextKeyResponseSize is populated in the context whenever a
// ServerFinalizerFunc is specified. Its value is of type int64.
ContextKeyResponseSize
)

View File

@@ -0,0 +1,30 @@
package http_test
import (
"context"
"net/http/httptest"
"testing"
httptransport "github.com/go-kit/kit/transport/http"
)
func TestSetHeader(t *testing.T) {
const (
key = "X-Foo"
val = "12345"
)
r := httptest.NewRecorder()
httptransport.SetResponseHeader(key, val)(context.Background(), r)
if want, have := val, r.Header().Get(key); want != have {
t.Errorf("want %q, have %q", want, have)
}
}
func TestSetContentType(t *testing.T) {
const contentType = "application/json"
r := httptest.NewRecorder()
httptransport.SetContentType(contentType)(context.Background(), r)
if want, have := contentType, r.Header().Get("Content-Type"); want != have {
t.Errorf("want %q, have %q", want, have)
}
}

221
vendor/github.com/go-kit/kit/transport/http/server.go generated vendored Normal file
View File

@@ -0,0 +1,221 @@
package http
import (
"context"
"encoding/json"
"net/http"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
)
// Server wraps an endpoint and implements http.Handler.
type Server struct {
e endpoint.Endpoint
dec DecodeRequestFunc
enc EncodeResponseFunc
before []RequestFunc
after []ServerResponseFunc
errorEncoder ErrorEncoder
finalizer ServerFinalizerFunc
logger log.Logger
}
// NewServer constructs a new server, which implements http.Server and wraps
// the provided endpoint.
func NewServer(
e endpoint.Endpoint,
dec DecodeRequestFunc,
enc EncodeResponseFunc,
options ...ServerOption,
) *Server {
s := &Server{
e: e,
dec: dec,
enc: enc,
errorEncoder: DefaultErrorEncoder,
logger: log.NewNopLogger(),
}
for _, option := range options {
option(s)
}
return s
}
// ServerOption sets an optional parameter for servers.
type ServerOption func(*Server)
// ServerBefore functions are executed on the HTTP request object before the
// request is decoded.
func ServerBefore(before ...RequestFunc) ServerOption {
return func(s *Server) { s.before = append(s.before, before...) }
}
// ServerAfter functions are executed on the HTTP response writer after the
// endpoint is invoked, but before anything is written to the client.
func ServerAfter(after ...ServerResponseFunc) ServerOption {
return func(s *Server) { s.after = append(s.after, after...) }
}
// ServerErrorEncoder is used to encode errors to the http.ResponseWriter
// whenever they're encountered in the processing of a request. Clients can
// use this to provide custom error formatting and response codes. By default,
// errors will be written with the DefaultErrorEncoder.
func ServerErrorEncoder(ee ErrorEncoder) ServerOption {
return func(s *Server) { s.errorEncoder = ee }
}
// ServerErrorLogger is used to log non-terminal errors. By default, no errors
// are logged. This is intended as a diagnostic measure. Finer-grained control
// of error handling, including logging in more detail, should be performed in a
// custom ServerErrorEncoder or ServerFinalizer, both of which have access to
// the context.
func ServerErrorLogger(logger log.Logger) ServerOption {
return func(s *Server) { s.logger = logger }
}
// ServerFinalizer is executed at the end of every HTTP request.
// By default, no finalizer is registered.
func ServerFinalizer(f ServerFinalizerFunc) ServerOption {
return func(s *Server) { s.finalizer = f }
}
// ServeHTTP implements http.Handler.
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if s.finalizer != nil {
iw := &interceptingWriter{w, http.StatusOK, 0}
defer func() {
ctx = context.WithValue(ctx, ContextKeyResponseHeaders, iw.Header())
ctx = context.WithValue(ctx, ContextKeyResponseSize, iw.written)
s.finalizer(ctx, iw.code, r)
}()
w = iw
}
for _, f := range s.before {
ctx = f(ctx, r)
}
request, err := s.dec(ctx, r)
if err != nil {
s.logger.Log("err", err)
s.errorEncoder(ctx, err, w)
return
}
response, err := s.e(ctx, request)
if err != nil {
s.logger.Log("err", err)
s.errorEncoder(ctx, err, w)
return
}
for _, f := range s.after {
ctx = f(ctx, w)
}
if err := s.enc(ctx, w, response); err != nil {
s.logger.Log("err", err)
s.errorEncoder(ctx, err, w)
return
}
}
// ErrorEncoder is responsible for encoding an error to the ResponseWriter.
// Users are encouraged to use custom ErrorEncoders to encode HTTP errors to
// their clients, and will likely want to pass and check for their own error
// types. See the example shipping/handling service.
type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter)
// ServerFinalizerFunc can be used to perform work at the end of an HTTP
// request, after the response has been written to the client. The principal
// intended use is for request logging. In addition to the response code
// provided in the function signature, additional response parameters are
// provided in the context under keys with the ContextKeyResponse prefix.
type ServerFinalizerFunc func(ctx context.Context, code int, r *http.Request)
// EncodeJSONResponse is a EncodeResponseFunc that serializes the response as a
// JSON object to the ResponseWriter. Many JSON-over-HTTP services can use it as
// a sensible default. If the response implements Headerer, the provided headers
// will be applied to the response. If the response implements StatusCoder, the
// provided StatusCode will be used instead of 200.
func EncodeJSONResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if headerer, ok := response.(Headerer); ok {
for k := range headerer.Headers() {
w.Header().Set(k, headerer.Headers().Get(k))
}
}
code := http.StatusOK
if sc, ok := response.(StatusCoder); ok {
code = sc.StatusCode()
}
w.WriteHeader(code)
if code == http.StatusNoContent {
return nil
}
return json.NewEncoder(w).Encode(response)
}
// DefaultErrorEncoder writes the error to the ResponseWriter, by default a
// content type of text/plain, a body of the plain text of the error, and a
// status code of 500. If the error implements Headerer, the provided headers
// will be applied to the response. If the error implements json.Marshaler, and
// the marshaling succeeds, a content type of application/json and the JSON
// encoded form of the error will be used. If the error implements StatusCoder,
// the provided StatusCode will be used instead of 500.
func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) {
contentType, body := "text/plain; charset=utf-8", []byte(err.Error())
if marshaler, ok := err.(json.Marshaler); ok {
if jsonBody, marshalErr := marshaler.MarshalJSON(); marshalErr == nil {
contentType, body = "application/json; charset=utf-8", jsonBody
}
}
w.Header().Set("Content-Type", contentType)
if headerer, ok := err.(Headerer); ok {
for k := range headerer.Headers() {
w.Header().Set(k, headerer.Headers().Get(k))
}
}
code := http.StatusInternalServerError
if sc, ok := err.(StatusCoder); ok {
code = sc.StatusCode()
}
w.WriteHeader(code)
w.Write(body)
}
// StatusCoder is checked by DefaultErrorEncoder. If an error value implements
// StatusCoder, the StatusCode will be used when encoding the error. By default,
// StatusInternalServerError (500) is used.
type StatusCoder interface {
StatusCode() int
}
// Headerer is checked by DefaultErrorEncoder. If an error value implements
// Headerer, the provided headers will be applied to the response writer, after
// the Content-Type is set.
type Headerer interface {
Headers() http.Header
}
type interceptingWriter struct {
http.ResponseWriter
code int
written int64
}
// WriteHeader may not be explicitly called, so care must be taken to
// initialize w.code to its default value of http.StatusOK.
func (w *interceptingWriter) WriteHeader(code int) {
w.code = code
w.ResponseWriter.WriteHeader(code)
}
func (w *interceptingWriter) Write(p []byte) (int, error) {
n, err := w.ResponseWriter.Write(p)
w.written += int64(n)
return n, err
}

View File

@@ -0,0 +1,349 @@
package http_test
import (
"context"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
)
func TestServerBadDecode(t *testing.T) {
handler := httptransport.NewServer(
func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil },
func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, errors.New("dang") },
func(context.Context, http.ResponseWriter, interface{}) error { return nil },
)
server := httptest.NewServer(handler)
defer server.Close()
resp, _ := http.Get(server.URL)
if want, have := http.StatusInternalServerError, resp.StatusCode; want != have {
t.Errorf("want %d, have %d", want, have)
}
}
func TestServerBadEndpoint(t *testing.T) {
handler := httptransport.NewServer(
func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errors.New("dang") },
func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil },
func(context.Context, http.ResponseWriter, interface{}) error { return nil },
)
server := httptest.NewServer(handler)
defer server.Close()
resp, _ := http.Get(server.URL)
if want, have := http.StatusInternalServerError, resp.StatusCode; want != have {
t.Errorf("want %d, have %d", want, have)
}
}
func TestServerBadEncode(t *testing.T) {
handler := httptransport.NewServer(
func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil },
func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil },
func(context.Context, http.ResponseWriter, interface{}) error { return errors.New("dang") },
)
server := httptest.NewServer(handler)
defer server.Close()
resp, _ := http.Get(server.URL)
if want, have := http.StatusInternalServerError, resp.StatusCode; want != have {
t.Errorf("want %d, have %d", want, have)
}
}
func TestServerErrorEncoder(t *testing.T) {
errTeapot := errors.New("teapot")
code := func(err error) int {
if err == errTeapot {
return http.StatusTeapot
}
return http.StatusInternalServerError
}
handler := httptransport.NewServer(
func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errTeapot },
func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil },
func(context.Context, http.ResponseWriter, interface{}) error { return nil },
httptransport.ServerErrorEncoder(func(_ context.Context, err error, w http.ResponseWriter) { w.WriteHeader(code(err)) }),
)
server := httptest.NewServer(handler)
defer server.Close()
resp, _ := http.Get(server.URL)
if want, have := http.StatusTeapot, resp.StatusCode; want != have {
t.Errorf("want %d, have %d", want, have)
}
}
func TestServerHappyPath(t *testing.T) {
step, response := testServer(t)
step()
resp := <-response
defer resp.Body.Close()
buf, _ := ioutil.ReadAll(resp.Body)
if want, have := http.StatusOK, resp.StatusCode; want != have {
t.Errorf("want %d, have %d (%s)", want, have, buf)
}
}
func TestMultipleServerBefore(t *testing.T) {
var (
headerKey = "X-Henlo-Lizer"
headerVal = "Helllo you stinky lizard"
statusCode = http.StatusTeapot
responseBody = "go eat a fly ugly\n"
done = make(chan struct{})
)
handler := httptransport.NewServer(
endpoint.Nop,
func(context.Context, *http.Request) (interface{}, error) {
return struct{}{}, nil
},
func(_ context.Context, w http.ResponseWriter, _ interface{}) error {
w.Header().Set(headerKey, headerVal)
w.WriteHeader(statusCode)
w.Write([]byte(responseBody))
return nil
},
httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context {
ctx = context.WithValue(ctx, "one", 1)
return ctx
}),
httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context {
if _, ok := ctx.Value("one").(int); !ok {
t.Error("Value was not set properly when multiple ServerBefores are used")
}
close(done)
return ctx
}),
)
server := httptest.NewServer(handler)
defer server.Close()
go http.Get(server.URL)
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("timeout waiting for finalizer")
}
}
func TestMultipleServerAfter(t *testing.T) {
var (
headerKey = "X-Henlo-Lizer"
headerVal = "Helllo you stinky lizard"
statusCode = http.StatusTeapot
responseBody = "go eat a fly ugly\n"
done = make(chan struct{})
)
handler := httptransport.NewServer(
endpoint.Nop,
func(context.Context, *http.Request) (interface{}, error) {
return struct{}{}, nil
},
func(_ context.Context, w http.ResponseWriter, _ interface{}) error {
w.Header().Set(headerKey, headerVal)
w.WriteHeader(statusCode)
w.Write([]byte(responseBody))
return nil
},
httptransport.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context {
ctx = context.WithValue(ctx, "one", 1)
return ctx
}),
httptransport.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context {
if _, ok := ctx.Value("one").(int); !ok {
t.Error("Value was not set properly when multiple ServerAfters are used")
}
close(done)
return ctx
}),
)
server := httptest.NewServer(handler)
defer server.Close()
go http.Get(server.URL)
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("timeout waiting for finalizer")
}
}
func TestServerFinalizer(t *testing.T) {
var (
headerKey = "X-Henlo-Lizer"
headerVal = "Helllo you stinky lizard"
statusCode = http.StatusTeapot
responseBody = "go eat a fly ugly\n"
done = make(chan struct{})
)
handler := httptransport.NewServer(
endpoint.Nop,
func(context.Context, *http.Request) (interface{}, error) {
return struct{}{}, nil
},
func(_ context.Context, w http.ResponseWriter, _ interface{}) error {
w.Header().Set(headerKey, headerVal)
w.WriteHeader(statusCode)
w.Write([]byte(responseBody))
return nil
},
httptransport.ServerFinalizer(func(ctx context.Context, code int, _ *http.Request) {
if want, have := statusCode, code; want != have {
t.Errorf("StatusCode: want %d, have %d", want, have)
}
responseHeader := ctx.Value(httptransport.ContextKeyResponseHeaders).(http.Header)
if want, have := headerVal, responseHeader.Get(headerKey); want != have {
t.Errorf("%s: want %q, have %q", headerKey, want, have)
}
responseSize := ctx.Value(httptransport.ContextKeyResponseSize).(int64)
if want, have := int64(len(responseBody)), responseSize; want != have {
t.Errorf("response size: want %d, have %d", want, have)
}
close(done)
}),
)
server := httptest.NewServer(handler)
defer server.Close()
go http.Get(server.URL)
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("timeout waiting for finalizer")
}
}
type enhancedResponse struct {
Foo string `json:"foo"`
}
func (e enhancedResponse) StatusCode() int { return http.StatusPaymentRequired }
func (e enhancedResponse) Headers() http.Header { return http.Header{"X-Edward": []string{"Snowden"}} }
func TestEncodeJSONResponse(t *testing.T) {
handler := httptransport.NewServer(
func(context.Context, interface{}) (interface{}, error) { return enhancedResponse{Foo: "bar"}, nil },
func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil },
httptransport.EncodeJSONResponse,
)
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL)
if err != nil {
t.Fatal(err)
}
if want, have := http.StatusPaymentRequired, resp.StatusCode; want != have {
t.Errorf("StatusCode: want %d, have %d", want, have)
}
if want, have := "Snowden", resp.Header.Get("X-Edward"); want != have {
t.Errorf("X-Edward: want %q, have %q", want, have)
}
buf, _ := ioutil.ReadAll(resp.Body)
if want, have := `{"foo":"bar"}`, strings.TrimSpace(string(buf)); want != have {
t.Errorf("Body: want %s, have %s", want, have)
}
}
type noContentResponse struct{}
func (e noContentResponse) StatusCode() int { return http.StatusNoContent }
func TestEncodeNoContent(t *testing.T) {
handler := httptransport.NewServer(
func(context.Context, interface{}) (interface{}, error) { return noContentResponse{}, nil },
func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil },
httptransport.EncodeJSONResponse,
)
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL)
if err != nil {
t.Fatal(err)
}
if want, have := http.StatusNoContent, resp.StatusCode; want != have {
t.Errorf("StatusCode: want %d, have %d", want, have)
}
buf, _ := ioutil.ReadAll(resp.Body)
if want, have := 0, len(buf); want != have {
t.Errorf("Body: want no content, have %d bytes", have)
}
}
type enhancedError struct{}
func (e enhancedError) Error() string { return "enhanced error" }
func (e enhancedError) StatusCode() int { return http.StatusTeapot }
func (e enhancedError) MarshalJSON() ([]byte, error) { return []byte(`{"err":"enhanced"}`), nil }
func (e enhancedError) Headers() http.Header { return http.Header{"X-Enhanced": []string{"1"}} }
func TestEnhancedError(t *testing.T) {
handler := httptransport.NewServer(
func(context.Context, interface{}) (interface{}, error) { return nil, enhancedError{} },
func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil },
func(_ context.Context, w http.ResponseWriter, _ interface{}) error { return nil },
)
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if want, have := http.StatusTeapot, resp.StatusCode; want != have {
t.Errorf("StatusCode: want %d, have %d", want, have)
}
if want, have := "1", resp.Header.Get("X-Enhanced"); want != have {
t.Errorf("X-Enhanced: want %q, have %q", want, have)
}
buf, _ := ioutil.ReadAll(resp.Body)
if want, have := `{"err":"enhanced"}`, strings.TrimSpace(string(buf)); want != have {
t.Errorf("Body: want %s, have %s", want, have)
}
}
func testServer(t *testing.T) (step func(), resp <-chan *http.Response) {
var (
stepch = make(chan bool)
endpoint = func(context.Context, interface{}) (interface{}, error) { <-stepch; return struct{}{}, nil }
response = make(chan *http.Response)
handler = httptransport.NewServer(
endpoint,
func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil },
func(context.Context, http.ResponseWriter, interface{}) error { return nil },
httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { return ctx }),
httptransport.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context { return ctx }),
)
)
go func() {
server := httptest.NewServer(handler)
defer server.Close()
resp, err := http.Get(server.URL)
if err != nil {
t.Error(err)
return
}
response <- resp
}()
return func() { stepch <- true }, response
}

4
vendor/github.com/go-kit/kit/transport/httprp/doc.go generated vendored Normal file
View File

@@ -0,0 +1,4 @@
// Package httprp provides an HTTP reverse-proxy transport. HTTP handlers that
// need to proxy requests to another HTTP service can do so with this package by
// specifying the URL to forward the request to.
package httprp

View File

@@ -0,0 +1,57 @@
package httprp
import (
"context"
"net/http"
"net/http/httputil"
"net/url"
)
// RequestFunc may take information from an HTTP request and put it into a
// request context. BeforeFuncs are executed prior to invoking the
// endpoint.
type RequestFunc func(context.Context, *http.Request) context.Context
// Server is a proxying request handler.
type Server struct {
proxy http.Handler
before []RequestFunc
errorEncoder func(w http.ResponseWriter, err error)
}
// NewServer constructs a new server that implements http.Server and will proxy
// requests to the given base URL using its scheme, host, and base path.
// If the target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
func NewServer(
baseURL *url.URL,
options ...ServerOption,
) *Server {
s := &Server{
proxy: httputil.NewSingleHostReverseProxy(baseURL),
}
for _, option := range options {
option(s)
}
return s
}
// ServerOption sets an optional parameter for servers.
type ServerOption func(*Server)
// ServerBefore functions are executed on the HTTP request object before the
// request is decoded.
func ServerBefore(before ...RequestFunc) ServerOption {
return func(s *Server) { s.before = append(s.before, before...) }
}
// ServeHTTP implements http.Handler.
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
for _, f := range s.before {
ctx = f(ctx, r)
}
s.proxy.ServeHTTP(w, r)
}

View File

@@ -0,0 +1,158 @@
package httprp_test
import (
"context"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"testing"
httptransport "github.com/go-kit/kit/transport/httprp"
)
func TestServerHappyPathSingleServer(t *testing.T) {
originServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("hey"))
}))
defer originServer.Close()
originURL, _ := url.Parse(originServer.URL)
handler := httptransport.NewServer(
originURL,
)
proxyServer := httptest.NewServer(handler)
defer proxyServer.Close()
resp, _ := http.Get(proxyServer.URL)
if want, have := http.StatusOK, resp.StatusCode; want != have {
t.Errorf("want %d, have %d", want, have)
}
responseBody, _ := ioutil.ReadAll(resp.Body)
if want, have := "hey", string(responseBody); want != have {
t.Errorf("want %q, have %q", want, have)
}
}
func TestServerHappyPathSingleServerWithServerOptions(t *testing.T) {
const (
headerKey = "X-TEST-HEADER"
headerVal = "go-kit-proxy"
)
originServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if want, have := headerVal, r.Header.Get(headerKey); want != have {
t.Errorf("want %q, have %q", want, have)
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("hey"))
}))
defer originServer.Close()
originURL, _ := url.Parse(originServer.URL)
handler := httptransport.NewServer(
originURL,
httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context {
r.Header.Add(headerKey, headerVal)
return ctx
}),
)
proxyServer := httptest.NewServer(handler)
defer proxyServer.Close()
resp, _ := http.Get(proxyServer.URL)
if want, have := http.StatusOK, resp.StatusCode; want != have {
t.Errorf("want %d, have %d", want, have)
}
responseBody, _ := ioutil.ReadAll(resp.Body)
if want, have := "hey", string(responseBody); want != have {
t.Errorf("want %q, have %q", want, have)
}
}
func TestServerOriginServerNotFoundResponse(t *testing.T) {
originServer := httptest.NewServer(http.NotFoundHandler())
defer originServer.Close()
originURL, _ := url.Parse(originServer.URL)
handler := httptransport.NewServer(
originURL,
)
proxyServer := httptest.NewServer(handler)
defer proxyServer.Close()
resp, _ := http.Get(proxyServer.URL)
if want, have := http.StatusNotFound, resp.StatusCode; want != have {
t.Errorf("want %d, have %d", want, have)
}
}
func TestServerOriginServerUnreachable(t *testing.T) {
// create a server, then promptly shut it down
originServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
originURL, _ := url.Parse(originServer.URL)
originServer.Close()
handler := httptransport.NewServer(
originURL,
)
proxyServer := httptest.NewServer(handler)
defer proxyServer.Close()
resp, _ := http.Get(proxyServer.URL)
switch resp.StatusCode {
case http.StatusBadGateway: // go1.7 and beyond
break
case http.StatusInternalServerError: // to go1.7
break
default:
t.Errorf("want %d or %d, have %d", http.StatusBadGateway, http.StatusInternalServerError, resp.StatusCode)
}
}
func TestMultipleServerBefore(t *testing.T) {
const (
headerKey = "X-TEST-HEADER"
headerVal = "go-kit-proxy"
)
originServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if want, have := headerVal, r.Header.Get(headerKey); want != have {
t.Errorf("want %q, have %q", want, have)
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("hey"))
}))
defer originServer.Close()
originURL, _ := url.Parse(originServer.URL)
handler := httptransport.NewServer(
originURL,
httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context {
r.Header.Add(headerKey, headerVal)
return ctx
}),
httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context {
return ctx
}),
)
proxyServer := httptest.NewServer(handler)
defer proxyServer.Close()
resp, _ := http.Get(proxyServer.URL)
if want, have := http.StatusOK, resp.StatusCode; want != have {
t.Errorf("want %d, have %d", want, have)
}
responseBody, _ := ioutil.ReadAll(resp.Body)
if want, have := "hey", string(responseBody); want != have {
t.Errorf("want %q, have %q", want, have)
}
}

View File

@@ -0,0 +1,14 @@
# net/rpc
[net/rpc](https://golang.org/pkg/net/rpc) is an RPC transport that's part of the Go standard library.
It's a simple and fast transport that's appropriate when all of your services are written in Go.
Using net/rpc with Go kit is very simple.
Just write a simple binding from your service definition to the net/rpc definition.
See [netrpc_binding.go](https://github.com/go-kit/kit/blob/ec8b02591ee873433565a1ae9d317353412d1d27/examples/addsvc/netrpc_binding.go) for an example.
That's it!
The net/rpc binding can be registered to a name, and bound to an HTTP handler, the same as any other net/rpc endpoint.
And within your service, you can use standard Go kit components and idioms.
See [addsvc](https://github.com/go-kit/kit/tree/master/examples/addsvc) for a complete working example with net/rpc support.
And remember: Go kit services can support multiple transports simultaneously.

View File

@@ -0,0 +1,32 @@
# Thrift
[Thrift](https://thrift.apache.org/) is a large IDL and transport package from Apache, popularized by Facebook.
Thrift is well-supported in Go kit, for organizations that already have significant Thrift investment.
And using Thrift with Go kit is very simple.
First, define your service in the Thrift IDL.
The [Thrift IDL documentation](https://thrift.apache.org/docs/idl) provides more details.
See [add.thrift](https://github.com/go-kit/kit/blob/ec8b02591ee873433565a1ae9d317353412d1d27/examples/addsvc/_thrift/add.thrift) for an example.
Make sure the Thrift definition matches your service's Go kit (interface) definition.
Next, [download Thrift](https://thrift.apache.org/download) and [install the compiler](https://thrift.apache.org/docs/install/).
On a Mac, you may be able to `brew install thrift`.
Then, compile your service definition, from .thrift to .go.
You'll probably want to specify the package_prefix option to the --gen go flag.
See [THRIFT-3021](https://issues.apache.org/jira/browse/THRIFT-3021) for more details.
```
thrift -r --gen go:package_prefix=github.com/my-org/my-repo/thrift/gen-go/ add.thrift
```
Finally, write a tiny binding from your service definition to the Thrift definition.
It's a straightforward conversion from one domain to the other.
See [thrift_binding.go](https://github.com/go-kit/kit/blob/ec8b02591ee873433565a1ae9d317353412d1d27/examples/addsvc/thrift_binding.go) for an example.
That's it!
The Thrift binding can be bound to a listener and serve normal Thrift requests.
And within your service, you can use standard Go kit components and idioms.
Unfortunately, setting up a Thrift listener is rather laborious and nonidiomatic in Go.
Fortunately, [addsvc](https://github.com/go-kit/kit/tree/master/examples/addsvc) is a complete working example with Thrift support.
And remember: Go kit services can support multiple transports simultaneously.