Codebase list golang-github-go-kit-kit / d8776e0
[transport/http] adding request and response encoders for Protobuf (#809) * [transport/http] adding request and response encoders for Protobuf * returning proto error, allowing for double status write * creating proto supackage * new files JP Robinson authored 4 years ago Peter Bourgon committed 4 years ago
5 changed file(s) with 280 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 package proto
1
2 import (
3 "bytes"
4 "context"
5 "errors"
6 "io/ioutil"
7 "net/http"
8
9 httptransport "github.com/go-kit/kit/transport/http"
10 "github.com/golang/protobuf/proto"
11 )
12
13 // EncodeProtoRequest is an EncodeRequestFunc that serializes the request as Protobuf.
14 // If the request implements Headerer, the provided headers will be applied
15 // to the request. If the given request does not implement proto.Message, an error will
16 // be returned.
17 func EncodeProtoRequest(_ context.Context, r *http.Request, preq interface{}) error {
18 r.Header.Set("Content-Type", "application/x-protobuf")
19 if headerer, ok := preq.(httptransport.Headerer); ok {
20 for k := range headerer.Headers() {
21 r.Header.Set(k, headerer.Headers().Get(k))
22 }
23 }
24 req, ok := preq.(proto.Message)
25 if !ok {
26 return errors.New("response does not implement proto.Message")
27 }
28
29 b, err := proto.Marshal(req)
30 if err != nil {
31 return err
32 }
33 r.ContentLength = int64(len(b))
34 r.Body = ioutil.NopCloser(bytes.NewReader(b))
35 return nil
36 }
0 // Code generated by protoc-gen-go. DO NOT EDIT.
1 // source: proto_test.proto
2
3 package proto
4
5 import (
6 fmt "fmt"
7 math "math"
8
9 proto "github.com/golang/protobuf/proto"
10 )
11
12 // Reference imports to suppress errors if they are not otherwise used.
13 var _ = proto.Marshal
14 var _ = fmt.Errorf
15 var _ = math.Inf
16
17 // This is a compile-time assertion to ensure that this generated file
18 // is compatible with the proto package it is being compiled against.
19 // A compilation error at this line likely means your copy of the
20 // proto package needs to be updated.
21 const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
22
23 type Cat struct {
24 Age int32 `protobuf:"varint,1,opt,name=Age,proto3" json:"Age,omitempty"`
25 Breed string `protobuf:"bytes,2,opt,name=Breed,proto3" json:"Breed,omitempty"`
26 Name string `protobuf:"bytes,3,opt,name=Name,proto3" json:"Name,omitempty"`
27 XXX_NoUnkeyedLiteral struct{} `json:"-"`
28 XXX_unrecognized []byte `json:"-"`
29 XXX_sizecache int32 `json:"-"`
30 }
31
32 func (m *Cat) Reset() { *m = Cat{} }
33 func (m *Cat) String() string { return proto.CompactTextString(m) }
34 func (*Cat) ProtoMessage() {}
35 func (*Cat) Descriptor() ([]byte, []int) {
36 return fileDescriptor_a794ba8d0e5440a3, []int{0}
37 }
38
39 func (m *Cat) XXX_Unmarshal(b []byte) error {
40 return xxx_messageInfo_Cat.Unmarshal(m, b)
41 }
42 func (m *Cat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
43 return xxx_messageInfo_Cat.Marshal(b, m, deterministic)
44 }
45 func (m *Cat) XXX_Merge(src proto.Message) {
46 xxx_messageInfo_Cat.Merge(m, src)
47 }
48 func (m *Cat) XXX_Size() int {
49 return xxx_messageInfo_Cat.Size(m)
50 }
51 func (m *Cat) XXX_DiscardUnknown() {
52 xxx_messageInfo_Cat.DiscardUnknown(m)
53 }
54
55 var xxx_messageInfo_Cat proto.InternalMessageInfo
56
57 func (m *Cat) GetAge() int32 {
58 if m != nil {
59 return m.Age
60 }
61 return 0
62 }
63
64 func (m *Cat) GetBreed() string {
65 if m != nil {
66 return m.Breed
67 }
68 return ""
69 }
70
71 func (m *Cat) GetName() string {
72 if m != nil {
73 return m.Name
74 }
75 return ""
76 }
77
78 func init() {
79 proto.RegisterType((*Cat)(nil), "Cat")
80 }
81
82 func init() { proto.RegisterFile("proto_test.proto", fileDescriptor_a794ba8d0e5440a3) }
83
84 var fileDescriptor_a794ba8d0e5440a3 = []byte{
85 // 98 bytes of a gzipped FileDescriptorProto
86 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x28, 0x28, 0xca, 0x2f,
87 0xc9, 0x8f, 0x2f, 0x49, 0x2d, 0x2e, 0xd1, 0x03, 0x33, 0x95, 0x1c, 0xb9, 0x98, 0x9d, 0x13, 0x4b,
88 0x84, 0x04, 0xb8, 0x98, 0x1d, 0xd3, 0x53, 0x25, 0x18, 0x15, 0x18, 0x35, 0x58, 0x83, 0x40, 0x4c,
89 0x21, 0x11, 0x2e, 0x56, 0xa7, 0xa2, 0xd4, 0xd4, 0x14, 0x09, 0x26, 0x05, 0x46, 0x0d, 0xce, 0x20,
90 0x08, 0x47, 0x48, 0x88, 0x8b, 0xc5, 0x2f, 0x31, 0x37, 0x55, 0x82, 0x19, 0x2c, 0x08, 0x66, 0x27,
91 0xb1, 0x81, 0x4d, 0x32, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x5f, 0x95, 0x83, 0x0a, 0x5d, 0x00,
92 0x00, 0x00,
93 }
0 package proto
1
2 import (
3 "io/ioutil"
4 "net/http"
5 "net/http/httptest"
6 "testing"
7
8 "github.com/golang/protobuf/proto"
9 )
10
11 func TestEncodeProtoRequest(t *testing.T) {
12 cat := &Cat{Name: "Ziggy", Age: 13, Breed: "Lumpy"}
13
14 r := httptest.NewRequest(http.MethodGet, "/cat", nil)
15
16 err := EncodeProtoRequest(nil, r, cat)
17 if err != nil {
18 t.Errorf("expected no encoding errors but got: %s", err)
19 return
20 }
21
22 const xproto = "application/x-protobuf"
23 if typ := r.Header.Get("Content-Type"); typ != xproto {
24 t.Errorf("expected content type of %q, got %q", xproto, typ)
25 return
26 }
27
28 bod, err := ioutil.ReadAll(r.Body)
29 if err != nil {
30 t.Errorf("expected no read errors but got: %s", err)
31 return
32 }
33 defer r.Body.Close()
34
35 var got Cat
36 err = proto.Unmarshal(bod, &got)
37 if err != nil {
38 t.Errorf("expected no proto errors but got: %s", err)
39 return
40 }
41
42 if !proto.Equal(&got, cat) {
43 t.Errorf("expected cats to be equal but got:\n\n%#v\n\nwant:\n\n%#v", got, cat)
44 return
45 }
46 }
47
48 func TestEncodeProtoResponse(t *testing.T) {
49 cat := &Cat{Name: "Ziggy", Age: 13, Breed: "Lumpy"}
50
51 wr := httptest.NewRecorder()
52
53 err := EncodeProtoResponse(nil, wr, cat)
54 if err != nil {
55 t.Errorf("expected no encoding errors but got: %s", err)
56 return
57 }
58
59 w := wr.Result()
60
61 const xproto = "application/x-protobuf"
62 if typ := w.Header.Get("Content-Type"); typ != xproto {
63 t.Errorf("expected content type of %q, got %q", xproto, typ)
64 return
65 }
66
67 if w.StatusCode != http.StatusTeapot {
68 t.Errorf("expected status code of %d, got %d", http.StatusTeapot, w.StatusCode)
69 return
70 }
71
72 bod, err := ioutil.ReadAll(w.Body)
73 if err != nil {
74 t.Errorf("expected no read errors but got: %s", err)
75 return
76 }
77 defer w.Body.Close()
78
79 var got Cat
80 err = proto.Unmarshal(bod, &got)
81 if err != nil {
82 t.Errorf("expected no proto errors but got: %s", err)
83 return
84 }
85
86 if !proto.Equal(&got, cat) {
87 t.Errorf("expected cats to be equal but got:\n\n%#v\n\nwant:\n\n%#v", got, cat)
88 return
89 }
90 }
91
92 func (c *Cat) StatusCode() int {
93 return http.StatusTeapot
94 }
0 syntax = "proto3";
1
2 message Cat {
3 int32 Age = 1;
4 string Breed = 2;
5 string Name = 3;
6 }
0 package proto
1
2 import (
3 "context"
4 "errors"
5 "net/http"
6
7 httptransport "github.com/go-kit/kit/transport/http"
8 "github.com/golang/protobuf/proto"
9 )
10
11 // EncodeProtoResponse is an EncodeResponseFunc that serializes the response as Protobuf.
12 // Many Proto-over-HTTP services can use it as a sensible default. If the response
13 // implements Headerer, the provided headers will be applied to the response. If the
14 // response implements StatusCoder, the provided StatusCode will be used instead of 200.
15 func EncodeProtoResponse(ctx context.Context, w http.ResponseWriter, pres interface{}) error {
16 res, ok := pres.(proto.Message)
17 if !ok {
18 return errors.New("response does not implement proto.Message")
19 }
20 w.Header().Set("Content-Type", "application/x-protobuf")
21 if headerer, ok := w.(httptransport.Headerer); ok {
22 for k := range headerer.Headers() {
23 w.Header().Set(k, headerer.Headers().Get(k))
24 }
25 }
26 code := http.StatusOK
27 if sc, ok := pres.(httptransport.StatusCoder); ok {
28 code = sc.StatusCode()
29 }
30 w.WriteHeader(code)
31 if code == http.StatusNoContent {
32 return nil
33 }
34 if res == nil {
35 return nil
36 }
37 b, err := proto.Marshal(res)
38 if err != nil {
39 return err
40 }
41 _, err = w.Write(b)
42 if err != nil {
43 return err
44 }
45 return nil
46 }