├── testproto ├── testproto.go ├── test.proto └── test.pb.go ├── go.mod ├── LICENSE ├── README.md ├── conn_helpers_test.go ├── encode.go ├── conn_test.go ├── go.sum └── conn.go /testproto/testproto.go: -------------------------------------------------------------------------------- 1 | package testproto 2 | 3 | //go:generate sh -c "protoc ./*.proto --go_out=plugins=grpc:./" 4 | -------------------------------------------------------------------------------- /testproto/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package testproto; 4 | 5 | service TestService { 6 | rpc Stream(stream Bytes) returns (stream Bytes); 7 | } 8 | 9 | message Bytes { 10 | bytes data = 1; 11 | } 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mitchellh/go-grpc-net-conn 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/golang/protobuf v1.3.3 7 | github.com/stretchr/testify v1.5.1 8 | golang.org/x/net v0.0.0-20190311183353-d8887717615a 9 | google.golang.org/grpc v1.28.1 10 | ) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mitchell Hashimoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-grpc-net-conn [![Godoc](https://godoc.org/github.com/mitchellh/go-grpc-net-conn?status.svg)](https://godoc.org/github.com/mitchellh/go-grpc-net-conn) 2 | 3 | go-grpc-net-conn is a Go library that creates a `net.Conn` implementation 4 | on top of gRPC streams. If the stream is bidirectional (both the request and 5 | response of an RPC is a stream) then the `net.Conn` is a 6 | [full-duplex connection](https://en.wikipedia.org/wiki/Duplex_(telecommunications)#Full_duplex). 7 | 8 | ## Installation 9 | 10 | Standard `go get`: 11 | 12 | ``` 13 | $ go get github.com/mitchellh/go-grpc-net-conn 14 | ``` 15 | 16 | ## Usage & Example 17 | 18 | For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/go-grpc-net-conn). 19 | 20 | A brief example is shown below. Note that the only minor complexity is 21 | populating the required fields for the `Conn` structure. This package needs 22 | to know how to encode and decode the byte slices onto your expected protobuf 23 | message types. 24 | 25 | Imagine a protobuf service that looks like the following: 26 | 27 | ```proto 28 | syntax = "proto3"; 29 | 30 | package example; 31 | 32 | service ExampleService { 33 | rpc Stream(stream Bytes) returns (stream Bytes); 34 | } 35 | 36 | message Bytes { 37 | bytes data = 1; 38 | } 39 | ``` 40 | 41 | You can use this in the following way: 42 | 43 | ```go 44 | // Call our streaming endpoint 45 | resp, err := client.Stream(context.Background()) 46 | 47 | // We need to create a callback so the conn knows how to decode/encode 48 | // arbitrary byte slices for our proto type. 49 | fieldFunc := func(msg proto.Message) *[]byte { 50 | return &msg.(*example.Bytes).Data 51 | } 52 | 53 | // Wrap our conn around the response. 54 | conn := &grpc_net_conn.Conn{ 55 | Stream: resp, 56 | Request: &example.Bytes{}, 57 | Response: &example.Bytes{}, 58 | Encode: SimpleEncoder(fieldFunc), 59 | Decode: SimpleDecoder(fieldFunc), 60 | } 61 | 62 | // conn implements net.Conn so use it as you would! 63 | ``` 64 | -------------------------------------------------------------------------------- /conn_helpers_test.go: -------------------------------------------------------------------------------- 1 | package grpc_net_conn 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "testing" 7 | 8 | "github.com/golang/protobuf/proto" 9 | "github.com/stretchr/testify/require" 10 | "google.golang.org/grpc" 11 | 12 | "github.com/mitchellh/go-grpc-net-conn/testproto" 13 | ) 14 | 15 | func testStreamConn( 16 | stream grpc.Stream, 17 | ) *Conn { 18 | dataFieldFunc := func(msg proto.Message) *[]byte { 19 | return &msg.(*testproto.Bytes).Data 20 | } 21 | 22 | return &Conn{ 23 | Stream: stream, 24 | Request: &testproto.Bytes{}, 25 | Response: &testproto.Bytes{}, 26 | Encode: SimpleEncoder(dataFieldFunc), 27 | Decode: SimpleDecoder(dataFieldFunc), 28 | } 29 | } 30 | 31 | // testStreamClient returns a fully connected stream client. 32 | func testStreamClient( 33 | t *testing.T, 34 | impl testproto.TestServiceServer, 35 | ) testproto.TestService_StreamClient { 36 | // Get our gRPC client/server 37 | conn, server := testGRPCConn(t, func(s *grpc.Server) { 38 | testproto.RegisterTestServiceServer(s, impl) 39 | }) 40 | t.Cleanup(func() { server.Stop() }) 41 | t.Cleanup(func() { conn.Close() }) 42 | 43 | // Connect for streaming 44 | resp, err := testproto.NewTestServiceClient(conn).Stream( 45 | context.Background()) 46 | require.NoError(t, err) 47 | 48 | // Return our client 49 | return resp 50 | } 51 | 52 | // testGRPCConn returns a gRPC client conn and grpc server that are connected 53 | // together and configured. The register function is used to register services 54 | // prior to the Serve call. This is used to test gRPC connections. 55 | func testGRPCConn(t *testing.T, register func(*grpc.Server)) (*grpc.ClientConn, *grpc.Server) { 56 | t.Helper() 57 | 58 | // Create a listener 59 | l, err := net.Listen("tcp", "127.0.0.1:0") 60 | if err != nil { 61 | t.Fatalf("err: %s", err) 62 | } 63 | 64 | server := grpc.NewServer() 65 | register(server) 66 | go server.Serve(l) 67 | 68 | // Connect to the server 69 | conn, err := grpc.Dial( 70 | l.Addr().String(), 71 | grpc.WithBlock(), 72 | grpc.WithInsecure()) 73 | if err != nil { 74 | t.Fatalf("err: %s", err) 75 | } 76 | 77 | // Connection successful, close the listener 78 | l.Close() 79 | 80 | return conn, server 81 | } 82 | -------------------------------------------------------------------------------- /encode.go: -------------------------------------------------------------------------------- 1 | package grpc_net_conn 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | ) 6 | 7 | // Encoder encodes a byte slice to write into the destination proto.Message. 8 | // You do not need to copy the slice; you may use it directly. 9 | // 10 | // You do not have to encode the full byte slice in one packet. You can 11 | // choose to chunk your packets by returning 0 < n < len(p) and the 12 | // Conn will repeatedly send subsequent messages by slicing into the 13 | // byte slice. 14 | type Encoder func(proto.Message, []byte) (int, error) 15 | 16 | // Decode is given a Response value and expects you to decode the 17 | // response value into the byte slice given. You MUST decode up to 18 | // len(p) if available. 19 | // 20 | // This should return the data slice directly from m. The length of this 21 | // is used to determine if there is more data and the offset for the next 22 | // read. 23 | type Decoder func(m proto.Message, offset int, p []byte) ([]byte, error) 24 | 25 | // SimpleEncoder is the easiest way to generate an Encoder for a proto.Message. 26 | // You just give it a callback that gets the pointer to the byte slice field 27 | // and a valid encoder will be generated. 28 | // 29 | // Example: given a structure that has a field "Data []byte", you could: 30 | // 31 | // SimpleEncoder(func(msg proto.Message) *[]byte { 32 | // return &msg.(*MyStruct).Data 33 | // }) 34 | // 35 | func SimpleEncoder(f func(proto.Message) *[]byte) Encoder { 36 | return func(msg proto.Message, p []byte) (int, error) { 37 | bytePtr := f(msg) 38 | *bytePtr = p 39 | return len(p), nil 40 | } 41 | } 42 | 43 | // SimpleDecoder is the easiest way to generate a Decoder for a proto.Message. 44 | // Provide a callback that gets the pointer to the byte slice field and a 45 | // valid decoder will be generated. 46 | func SimpleDecoder(f func(proto.Message) *[]byte) Decoder { 47 | return func(msg proto.Message, offset int, p []byte) ([]byte, error) { 48 | bytePtr := f(msg) 49 | copy(p, (*bytePtr)[offset:]) 50 | return *bytePtr, nil 51 | } 52 | } 53 | 54 | // ChunkedEncoder ensures that data to encode is chunked at the proper size. 55 | func ChunkedEncoder(enc Encoder, size int) Encoder { 56 | return func(msg proto.Message, p []byte) (int, error) { 57 | if len(p) > size { 58 | p = p[:size] 59 | } 60 | 61 | return enc(msg, p) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /conn_test.go: -------------------------------------------------------------------------------- 1 | package grpc_net_conn 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/mitchellh/go-grpc-net-conn/testproto" 9 | ) 10 | 11 | func TestConn(t *testing.T) { 12 | impl := &testServer{ 13 | Send: [][]byte{ 14 | []byte("hello"), 15 | []byte("bye"), 16 | }, 17 | } 18 | 19 | t.Run("full data", func(t *testing.T) { 20 | require := require.New(t) 21 | 22 | conn := testStreamConn(testStreamClient(t, impl)) 23 | data := make([]byte, 1024) 24 | n, err := conn.Read(data) 25 | require.NoError(err) 26 | require.Equal(len("hello"), n) 27 | require.Equal("hello", string(data[:n])) 28 | }) 29 | 30 | t.Run("partial read", func(t *testing.T) { 31 | require := require.New(t) 32 | 33 | conn := testStreamConn(testStreamClient(t, impl)) 34 | data := make([]byte, 3) 35 | 36 | // Read first time partial 37 | n, err := conn.Read(data) 38 | require.NoError(err) 39 | require.Equal(3, n) 40 | require.Equal("hel", string(data[:n])) 41 | 42 | // Read again full result 43 | n, err = conn.Read(data) 44 | require.NoError(err) 45 | require.Equal(2, n) 46 | require.Equal("lo", string(data[:n])) 47 | 48 | // Read again next message 49 | n, err = conn.Read(data) 50 | require.NoError(err) 51 | require.Equal(3, n) 52 | require.Equal("bye", string(data[:n])) 53 | }) 54 | } 55 | 56 | func TestConn_chunkedWrites(t *testing.T) { 57 | impl := &testServer{ 58 | Chunk: 3, 59 | Send: [][]byte{ 60 | []byte("hello"), 61 | []byte("bye"), 62 | }, 63 | } 64 | 65 | require := require.New(t) 66 | 67 | conn := testStreamConn(testStreamClient(t, impl)) 68 | data := make([]byte, 1024) 69 | 70 | // We expect two chunks 71 | n, err := conn.Read(data) 72 | require.NoError(err) 73 | require.Equal(3, n) 74 | require.Equal("hel", string(data[:n])) 75 | 76 | n, err = conn.Read(data) 77 | require.NoError(err) 78 | require.Equal(2, n) 79 | require.Equal("lo", string(data[:n])) 80 | 81 | n, err = conn.Read(data) 82 | require.NoError(err) 83 | require.Equal(3, n) 84 | require.Equal("bye", string(data[:n])) 85 | } 86 | 87 | type testServer struct { 88 | Send [][]byte 89 | Chunk int 90 | } 91 | 92 | func (s *testServer) Stream(stream testproto.TestService_StreamServer) error { 93 | // Get our conn 94 | conn := testStreamConn(stream) 95 | if s.Chunk > 0 { 96 | conn.Encode = ChunkedEncoder(conn.Encode, s.Chunk) 97 | } 98 | 99 | for _, data := range s.Send { 100 | if _, err := conn.Write(data); err != nil { 101 | return err 102 | } 103 | } 104 | 105 | <-stream.Context().Done() 106 | return nil 107 | } 108 | 109 | var _ testproto.TestServiceServer = (*testServer)(nil) 110 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 6 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 9 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 10 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 11 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 12 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 13 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 14 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 15 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 16 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 17 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 21 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 22 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 23 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 24 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 25 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 26 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 27 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 28 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 29 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 30 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 31 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 32 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 33 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 34 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 35 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 36 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 37 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 38 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 39 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 40 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 41 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 42 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 43 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 44 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 45 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 46 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 47 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 48 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 49 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 50 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 51 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 52 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 53 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 54 | google.golang.org/grpc v1.28.1 h1:C1QC6KzgSiLyBabDi87BbjaGreoRgGUF5nOyvfrAZ1k= 55 | google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 56 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 57 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 58 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 59 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 60 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 61 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package grpc_net_conn 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "time" 7 | 8 | "github.com/golang/protobuf/proto" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | // Conn implements net.Conn across a gRPC stream. You must populate many 13 | // of the exported structs on this field so please read the documentation. 14 | // 15 | // There are a number of limitations to this implementation, typically due 16 | // limitations of visibility given by the gRPC stream. Methods such as 17 | // LocalAddr, RemoteAddr, deadlines, etc. do not work. 18 | // 19 | // As documented on net.Conn, it is safe for concurrent read/write. 20 | type Conn struct { 21 | // Stream is the stream to wrap into a Conn. This can be either a client 22 | // or server stream and we will perform correctly. 23 | Stream grpc.Stream 24 | 25 | // Request is the type to use for sending request data to the streaming 26 | // endpoint. This must be a non-nil allocated value and must NOT point to 27 | // the same value as Response since they may be used concurrently. 28 | // 29 | // The Reset method is never called on Request so you may set some 30 | // fields on the request type and they will be sent for every request 31 | // unless the Encode field changes it. 32 | Request proto.Message 33 | 34 | // Response is the type to use for reading response data. This must be 35 | // a non-nil allocated value and must NOT point to the same value as Request 36 | // since they may be used concurrently. 37 | // 38 | // The Reset method will be called on Response during Reads so data you 39 | // set initially will be lost. 40 | Response proto.Message 41 | 42 | // ResponseLock, if non-nil, will be locked while calling SendMsg 43 | // on the Stream. This can be used to prevent concurrent access to 44 | // SendMsg which is unsafe. 45 | ResponseLock *sync.Mutex 46 | 47 | // Encode encodes messages into the Request. See Encoder for more information. 48 | Encode Encoder 49 | 50 | // Decode decodes messages from the Response into a byte slice. See 51 | // Decoder for more information. 52 | Decode Decoder 53 | 54 | // readOffset tracks where we've read up to if we're reading a result 55 | // that didn't fully fit into the target slice. See Read. 56 | readOffset int 57 | 58 | // locks to ensure that only one reader/writer are operating at a time. 59 | // Go documents the `net.Conn` interface as being safe for simultaneous 60 | // readers/writers so we need to implement locking. 61 | readLock, writeLock sync.Mutex 62 | } 63 | 64 | // Read implements io.Reader. 65 | func (c *Conn) Read(p []byte) (int, error) { 66 | c.readLock.Lock() 67 | defer c.readLock.Unlock() 68 | 69 | // Attempt to read a value only if we're not still decoding a 70 | // partial read value from the last result. 71 | if c.readOffset == 0 { 72 | if err := c.Stream.RecvMsg(c.Response); err != nil { 73 | return 0, err 74 | } 75 | } 76 | 77 | // Decode into our slice 78 | data, err := c.Decode(c.Response, c.readOffset, p) 79 | 80 | // If we have an error or we've decoded the full amount then we're done. 81 | // The error case is obvious. The case where we've read the full amount 82 | // is also a terminating condition and err == nil (we know this since its 83 | // checking that case) so we return the full amount and no error. 84 | if err != nil || len(data) <= (len(p)+c.readOffset) { 85 | // Reset the read offset since we're done. 86 | n := len(data) - c.readOffset 87 | c.readOffset = 0 88 | 89 | // Reset our response value for the next read and so that we 90 | // don't potentially store a large response structure in memory. 91 | c.Response.Reset() 92 | 93 | return n, err 94 | } 95 | 96 | // We didn't read the full amount so we need to store this for future reads 97 | c.readOffset += len(p) 98 | 99 | return len(p), nil 100 | } 101 | 102 | // Write implements io.Writer. 103 | func (c *Conn) Write(p []byte) (int, error) { 104 | c.writeLock.Lock() 105 | defer c.writeLock.Unlock() 106 | 107 | total := len(p) 108 | for { 109 | // Encode our data into the request. Any error means we abort. 110 | n, err := c.Encode(c.Request, p) 111 | if err != nil { 112 | return 0, err 113 | } 114 | 115 | // We lock for SendMsg if we have a lock set. 116 | if c.ResponseLock != nil { 117 | c.ResponseLock.Lock() 118 | } 119 | 120 | // Send our message. Any error we also just abort out. 121 | err = c.Stream.SendMsg(c.Request) 122 | if c.ResponseLock != nil { 123 | c.ResponseLock.Unlock() 124 | } 125 | if err != nil { 126 | return 0, err 127 | } 128 | 129 | // If we sent the full amount of data, we're done. We respond with 130 | // "total" in case we sent across multiple frames. 131 | if n == len(p) { 132 | return total, nil 133 | } 134 | 135 | // We sent partial data so we continue writing the remainder 136 | p = p[n:] 137 | } 138 | } 139 | 140 | // Close will close the client if this is a client. If this is a server 141 | // stream this does nothing since gRPC expects you to close the stream by 142 | // returning from the RPC call. 143 | // 144 | // This calls CloseSend underneath for clients, so read the documentation 145 | // for that to understand the semantics of this call. 146 | func (c *Conn) Close() error { 147 | if cs, ok := c.Stream.(grpc.ClientStream); ok { 148 | // We have to acquire the write lock since the gRPC docs state: 149 | // "It is also not safe to call CloseSend concurrently with SendMsg." 150 | c.writeLock.Lock() 151 | defer c.writeLock.Unlock() 152 | return cs.CloseSend() 153 | } 154 | 155 | return nil 156 | } 157 | 158 | // LocalAddr returns nil. 159 | func (c *Conn) LocalAddr() net.Addr { return nil } 160 | 161 | // RemoteAddr returns nil. 162 | func (c *Conn) RemoteAddr() net.Addr { return nil } 163 | 164 | // SetDeadline is non-functional due to limitations on how gRPC works. 165 | // You can mimic deadlines often using call options. 166 | func (c *Conn) SetDeadline(time.Time) error { return nil } 167 | 168 | // SetReadDeadline is non-functional, see SetDeadline. 169 | func (c *Conn) SetReadDeadline(time.Time) error { return nil } 170 | 171 | // SetWriteDeadline is non-functional, see SetDeadline. 172 | func (c *Conn) SetWriteDeadline(time.Time) error { return nil } 173 | 174 | var _ net.Conn = (*Conn)(nil) 175 | -------------------------------------------------------------------------------- /testproto/test.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: test.proto 3 | 4 | package testproto 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | 10 | import ( 11 | context "golang.org/x/net/context" 12 | grpc "google.golang.org/grpc" 13 | ) 14 | 15 | // Reference imports to suppress errors if they are not otherwise used. 16 | var _ = proto.Marshal 17 | var _ = fmt.Errorf 18 | var _ = math.Inf 19 | 20 | // This is a compile-time assertion to ensure that this generated file 21 | // is compatible with the proto package it is being compiled against. 22 | // A compilation error at this line likely means your copy of the 23 | // proto package needs to be updated. 24 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 25 | 26 | type Bytes struct { 27 | Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` 28 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 29 | XXX_unrecognized []byte `json:"-"` 30 | XXX_sizecache int32 `json:"-"` 31 | } 32 | 33 | func (m *Bytes) Reset() { *m = Bytes{} } 34 | func (m *Bytes) String() string { return proto.CompactTextString(m) } 35 | func (*Bytes) ProtoMessage() {} 36 | func (*Bytes) Descriptor() ([]byte, []int) { 37 | return fileDescriptor_test_b835bf38d58226ca, []int{0} 38 | } 39 | func (m *Bytes) XXX_Unmarshal(b []byte) error { 40 | return xxx_messageInfo_Bytes.Unmarshal(m, b) 41 | } 42 | func (m *Bytes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 43 | return xxx_messageInfo_Bytes.Marshal(b, m, deterministic) 44 | } 45 | func (dst *Bytes) XXX_Merge(src proto.Message) { 46 | xxx_messageInfo_Bytes.Merge(dst, src) 47 | } 48 | func (m *Bytes) XXX_Size() int { 49 | return xxx_messageInfo_Bytes.Size(m) 50 | } 51 | func (m *Bytes) XXX_DiscardUnknown() { 52 | xxx_messageInfo_Bytes.DiscardUnknown(m) 53 | } 54 | 55 | var xxx_messageInfo_Bytes proto.InternalMessageInfo 56 | 57 | func (m *Bytes) GetData() []byte { 58 | if m != nil { 59 | return m.Data 60 | } 61 | return nil 62 | } 63 | 64 | func init() { 65 | proto.RegisterType((*Bytes)(nil), "testproto.Bytes") 66 | } 67 | 68 | // Reference imports to suppress errors if they are not otherwise used. 69 | var _ context.Context 70 | var _ grpc.ClientConn 71 | 72 | // This is a compile-time assertion to ensure that this generated file 73 | // is compatible with the grpc package it is being compiled against. 74 | const _ = grpc.SupportPackageIsVersion4 75 | 76 | // TestServiceClient is the client API for TestService service. 77 | // 78 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 79 | type TestServiceClient interface { 80 | Stream(ctx context.Context, opts ...grpc.CallOption) (TestService_StreamClient, error) 81 | } 82 | 83 | type testServiceClient struct { 84 | cc *grpc.ClientConn 85 | } 86 | 87 | func NewTestServiceClient(cc *grpc.ClientConn) TestServiceClient { 88 | return &testServiceClient{cc} 89 | } 90 | 91 | func (c *testServiceClient) Stream(ctx context.Context, opts ...grpc.CallOption) (TestService_StreamClient, error) { 92 | stream, err := c.cc.NewStream(ctx, &_TestService_serviceDesc.Streams[0], "/testproto.TestService/Stream", opts...) 93 | if err != nil { 94 | return nil, err 95 | } 96 | x := &testServiceStreamClient{stream} 97 | return x, nil 98 | } 99 | 100 | type TestService_StreamClient interface { 101 | Send(*Bytes) error 102 | Recv() (*Bytes, error) 103 | grpc.ClientStream 104 | } 105 | 106 | type testServiceStreamClient struct { 107 | grpc.ClientStream 108 | } 109 | 110 | func (x *testServiceStreamClient) Send(m *Bytes) error { 111 | return x.ClientStream.SendMsg(m) 112 | } 113 | 114 | func (x *testServiceStreamClient) Recv() (*Bytes, error) { 115 | m := new(Bytes) 116 | if err := x.ClientStream.RecvMsg(m); err != nil { 117 | return nil, err 118 | } 119 | return m, nil 120 | } 121 | 122 | // TestServiceServer is the server API for TestService service. 123 | type TestServiceServer interface { 124 | Stream(TestService_StreamServer) error 125 | } 126 | 127 | func RegisterTestServiceServer(s *grpc.Server, srv TestServiceServer) { 128 | s.RegisterService(&_TestService_serviceDesc, srv) 129 | } 130 | 131 | func _TestService_Stream_Handler(srv interface{}, stream grpc.ServerStream) error { 132 | return srv.(TestServiceServer).Stream(&testServiceStreamServer{stream}) 133 | } 134 | 135 | type TestService_StreamServer interface { 136 | Send(*Bytes) error 137 | Recv() (*Bytes, error) 138 | grpc.ServerStream 139 | } 140 | 141 | type testServiceStreamServer struct { 142 | grpc.ServerStream 143 | } 144 | 145 | func (x *testServiceStreamServer) Send(m *Bytes) error { 146 | return x.ServerStream.SendMsg(m) 147 | } 148 | 149 | func (x *testServiceStreamServer) Recv() (*Bytes, error) { 150 | m := new(Bytes) 151 | if err := x.ServerStream.RecvMsg(m); err != nil { 152 | return nil, err 153 | } 154 | return m, nil 155 | } 156 | 157 | var _TestService_serviceDesc = grpc.ServiceDesc{ 158 | ServiceName: "testproto.TestService", 159 | HandlerType: (*TestServiceServer)(nil), 160 | Methods: []grpc.MethodDesc{}, 161 | Streams: []grpc.StreamDesc{ 162 | { 163 | StreamName: "Stream", 164 | Handler: _TestService_Stream_Handler, 165 | ServerStreams: true, 166 | ClientStreams: true, 167 | }, 168 | }, 169 | Metadata: "test.proto", 170 | } 171 | 172 | func init() { proto.RegisterFile("test.proto", fileDescriptor_test_b835bf38d58226ca) } 173 | 174 | var fileDescriptor_test_b835bf38d58226ca = []byte{ 175 | // 112 bytes of a gzipped FileDescriptorProto 176 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e, 177 | 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x04, 0xb1, 0xc1, 0x4c, 0x25, 0x69, 0x2e, 0x56, 178 | 0xa7, 0xca, 0x92, 0xd4, 0x62, 0x21, 0x21, 0x2e, 0x96, 0x94, 0xc4, 0x92, 0x44, 0x09, 0x46, 0x05, 179 | 0x46, 0x0d, 0x9e, 0x20, 0x30, 0xdb, 0xc8, 0x9e, 0x8b, 0x3b, 0x24, 0xb5, 0xb8, 0x24, 0x38, 0xb5, 180 | 0xa8, 0x2c, 0x33, 0x39, 0x55, 0xc8, 0x80, 0x8b, 0x2d, 0xb8, 0xa4, 0x28, 0x35, 0x31, 0x57, 0x48, 181 | 0x40, 0x0f, 0x6e, 0x82, 0x1e, 0x58, 0xbb, 0x14, 0x86, 0x88, 0x06, 0xa3, 0x01, 0x63, 0x12, 0x1b, 182 | 0x58, 0xc0, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xcd, 0xb4, 0x86, 0x5e, 0x7d, 0x00, 0x00, 0x00, 183 | } 184 | --------------------------------------------------------------------------------