diff --git a/Makefile b/Makefile index 6038ff0..2609401 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,13 @@ - -.PHONY: demo -demo: +.PHONY: generate +generate: rm -vf demo/output/* rm -vf demo/output-commonjs/* go run cmd/gotsrpc/gotsrpc.go demo/config.yml go run cmd/gotsrpc/gotsrpc.go -skipgotsrpc demo/config-commonjs.yml - tsc --outFile cmd/demo/demo.js demo/demo.ts + tsc --outFile cmd/demo/demo.js demo/demo.ts + +.PHONY: demo +demo: generate cd cmd/demo && go run demo.go .PHONY: install diff --git a/README.md b/README.md index 483ca32..a88ccd7 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ gotsrpc gotsrpc.yml Will generate client and server side go and TypeScript code. Have fun! -## config expamples +## config examples ### commonjs diff --git a/client.go b/client.go index b06dced..582ce27 100644 --- a/client.go +++ b/client.go @@ -1,50 +1,93 @@ package gotsrpc import ( - "encoding/json" + "bytes" "fmt" + "github.com/pkg/errors" + "github.com/ugorji/go/codec" + "io" "io/ioutil" "net/http" - "strings" ) // ClientTransport to use for calls var ClientTransport = &http.Transport{} -// CallClient calls a method on the remove service -func CallClient(url string, endpoint string, method string, args []interface{}, reply []interface{}) error { - // Marshall args - jsonArgs := []string{} - for _, value := range args { - jsonArg, err := json.Marshal(value) - if err != nil { - return err - } - jsonArgs = append(jsonArgs, string(jsonArg)) +var _ Client = &bufferedClient{} + +type Client interface { + Call(url string, endpoint string, method string, args []interface{}, reply []interface{}) (err error) +} + +func NewClient(httpClient *http.Client) Client { + if httpClient == nil { + httpClient = http.DefaultClient } + return &bufferedClient{client: httpClient} +} + +func newRequest(url string, contentType string, reader io.Reader) (r *http.Request, err error) { + request, errRequest := http.NewRequest("POST", url, reader) + if errRequest != nil { + return nil, errors.Wrap(errRequest, "could not create a request") + } + request.Header.Set("Content-Type", contentType) + request.Header.Set("Accept", contentType) + return request, nil +} + +type bufferedClient struct { + client *http.Client +} + +// CallClient calls a method on the remove service +func (c *bufferedClient) Call(url string, endpoint string, method string, args []interface{}, reply []interface{}) (err error) { + // Marshall args + + b := new(bytes.Buffer) + errEncode := codec.NewEncoder(b, msgpackHandle).Encode(args) + if errEncode != nil { + return errors.Wrap(errEncode, "could not encode argument") + } + // Create request - request := "[" + strings.Join(jsonArgs, ",") + "]" // Create post url postURL := fmt.Sprintf("%s%s/%s", url, endpoint, method) // Post - client := &http.Client{Transport: ClientTransport} - resp, err := client.Post(postURL, "application/json", strings.NewReader(request)) - if err != nil { - return err + + request, errRequest := newRequest(postURL, msgpackContentType, b) + if errRequest != nil { + return errRequest } - defer resp.Body.Close() - // Read in body - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err + + resp, errDo := c.client.Do(request) + if errDo != nil { + return errors.Wrap(err, "could not execute request") } + // Check status if resp.StatusCode != http.StatusOK { + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } return fmt.Errorf("%s: %s", resp.Status, string(body)) } - // Unmarshal reply - if err := json.Unmarshal(body, &reply); err != nil { - return err + + var errDecode error + switch resp.Header.Get("Content-Type") { + case msgpackContentType: + errDecode = codec.NewDecoder(resp.Body, msgpackHandle).Decode(reply) + case jsonContentType: + errDecode = codec.NewDecoder(resp.Body, jsonHandle).Decode(reply) + default: + errDecode = codec.NewDecoder(resp.Body, jsonHandle).Decode(reply) } - return nil + + // Unmarshal reply + if errDecode != nil { + return errors.Wrap(errDecode, "could not decode response from client") + } + return err } diff --git a/demo/demo_complex.go b/demo/demo_complex.go index d1f7751..b038a3a 100644 --- a/demo/demo_complex.go +++ b/demo/demo_complex.go @@ -1,6 +1,7 @@ package demo import nstd "github.com/foomo/gotsrpc/demo/nested" +//go:generate codecgen -o values.generated.go demo_complex.go type Address struct { City string `json:"city,omitempty"` @@ -31,7 +32,11 @@ type Person struct { DNA []byte } + func (d *Demo) ExtractAddress(person *Person) (addr *Address, e *Err) { + if person == nil { + return nil, nil + } if person.AddressPtr != nil { return person.AddressPtr, nil } @@ -46,7 +51,7 @@ func (d *Demo) MapCrap() (crap map[string][]int) { return map[string][]int{} } -func (d *Demo) Nest() *nstd.Nested { +func (d *Demo) Nest() []*nstd.Nested { return nil } diff --git a/demo/gorpc.go b/demo/gorpc.go index 11e0d00..9ef207f 100644 --- a/demo/gorpc.go +++ b/demo/gorpc.go @@ -1,4 +1,5 @@ -// Code generated by gotsrpc https://github.com/foomo/gotsrpc DO NOT EDIT. +// Code generated by gotsrpc https://github.com/foomo/gotsrpc - DO NOT EDIT. + package demo import ( @@ -146,7 +147,7 @@ type ( DemoNestRequest struct { } DemoNestResponse struct { - RetNest_0 *nested.Nested + RetNest_0 []*nested.Nested } DemoTestScalarInPlaceRequest struct { diff --git a/demo/gorpcclient.go b/demo/gorpcclient.go index af08a63..843c418 100644 --- a/demo/gorpcclient.go +++ b/demo/gorpcclient.go @@ -1,4 +1,5 @@ -// Code generated by gotsrpc https://github.com/foomo/gotsrpc DO NOT EDIT. +// Code generated by gotsrpc https://github.com/foomo/gotsrpc - DO NOT EDIT. + package demo import ( @@ -22,17 +23,17 @@ func NewFooGoRPCClient(addr string, tlsConfig *tls.Config) *FooGoRPCClient { return client } -func (goTSRPCClientInstance *FooGoRPCClient) Start() { - goTSRPCClientInstance.Client.Start() +func (tsc *FooGoRPCClient) Start() { + tsc.Client.Start() } -func (goTSRPCClientInstance *FooGoRPCClient) Stop() { - goTSRPCClientInstance.Client.Stop() +func (tsc *FooGoRPCClient) Stop() { + tsc.Client.Stop() } -func (goTSRPCClientInstance *FooGoRPCClient) Hello(number int64) (retHello_0 int, clientErr error) { +func (tsc *FooGoRPCClient) Hello(number int64) (retHello_0 int, clientErr error) { req := FooHelloRequest{Number: number} - rpcCallRes, rpcCallErr := goTSRPCClientInstance.Client.Call(req) + rpcCallRes, rpcCallErr := tsc.Client.Call(req) if rpcCallErr != nil { clientErr = rpcCallErr return @@ -55,17 +56,17 @@ func NewDemoGoRPCClient(addr string, tlsConfig *tls.Config) *DemoGoRPCClient { return client } -func (goTSRPCClientInstance *DemoGoRPCClient) Start() { - goTSRPCClientInstance.Client.Start() +func (tsc *DemoGoRPCClient) Start() { + tsc.Client.Start() } -func (goTSRPCClientInstance *DemoGoRPCClient) Stop() { - goTSRPCClientInstance.Client.Stop() +func (tsc *DemoGoRPCClient) Stop() { + tsc.Client.Stop() } -func (goTSRPCClientInstance *DemoGoRPCClient) ExtractAddress(person *Person) (addr *Address, e *Err, clientErr error) { +func (tsc *DemoGoRPCClient) ExtractAddress(person *Person) (addr *Address, e *Err, clientErr error) { req := DemoExtractAddressRequest{Person: person} - rpcCallRes, rpcCallErr := goTSRPCClientInstance.Client.Call(req) + rpcCallRes, rpcCallErr := tsc.Client.Call(req) if rpcCallErr != nil { clientErr = rpcCallErr return @@ -74,9 +75,9 @@ func (goTSRPCClientInstance *DemoGoRPCClient) ExtractAddress(person *Person) (ad return response.Addr, response.E, nil } -func (goTSRPCClientInstance *DemoGoRPCClient) GiveMeAScalar() (amount nested.Amount, wahr nested.True, hier ScalarInPlace, clientErr error) { +func (tsc *DemoGoRPCClient) GiveMeAScalar() (amount nested.Amount, wahr nested.True, hier ScalarInPlace, clientErr error) { req := DemoGiveMeAScalarRequest{} - rpcCallRes, rpcCallErr := goTSRPCClientInstance.Client.Call(req) + rpcCallRes, rpcCallErr := tsc.Client.Call(req) if rpcCallErr != nil { clientErr = rpcCallErr return @@ -85,9 +86,9 @@ func (goTSRPCClientInstance *DemoGoRPCClient) GiveMeAScalar() (amount nested.Amo return response.Amount, response.Wahr, response.Hier, nil } -func (goTSRPCClientInstance *DemoGoRPCClient) Hello(name string) (retHello_0 string, retHello_1 *Err, clientErr error) { +func (tsc *DemoGoRPCClient) Hello(name string) (retHello_0 string, retHello_1 *Err, clientErr error) { req := DemoHelloRequest{Name: name} - rpcCallRes, rpcCallErr := goTSRPCClientInstance.Client.Call(req) + rpcCallRes, rpcCallErr := tsc.Client.Call(req) if rpcCallErr != nil { clientErr = rpcCallErr return @@ -96,9 +97,9 @@ func (goTSRPCClientInstance *DemoGoRPCClient) Hello(name string) (retHello_0 str return response.RetHello_0, response.RetHello_1, nil } -func (goTSRPCClientInstance *DemoGoRPCClient) HelloInterface(anything interface{}, anythingMap map[string]interface{}, anythingSlice []interface{}) (clientErr error) { +func (tsc *DemoGoRPCClient) HelloInterface(anything interface{}, anythingMap map[string]interface{}, anythingSlice []interface{}) (clientErr error) { req := DemoHelloInterfaceRequest{Anything: anything, AnythingMap: anythingMap, AnythingSlice: anythingSlice} - _, rpcCallErr := goTSRPCClientInstance.Client.Call(req) + _, rpcCallErr := tsc.Client.Call(req) if rpcCallErr != nil { clientErr = rpcCallErr return @@ -106,9 +107,9 @@ func (goTSRPCClientInstance *DemoGoRPCClient) HelloInterface(anything interface{ return nil } -func (goTSRPCClientInstance *DemoGoRPCClient) HelloScalarError() (err *ScalarError, clientErr error) { +func (tsc *DemoGoRPCClient) HelloScalarError() (err *ScalarError, clientErr error) { req := DemoHelloScalarErrorRequest{} - rpcCallRes, rpcCallErr := goTSRPCClientInstance.Client.Call(req) + rpcCallRes, rpcCallErr := tsc.Client.Call(req) if rpcCallErr != nil { clientErr = rpcCallErr return @@ -117,9 +118,9 @@ func (goTSRPCClientInstance *DemoGoRPCClient) HelloScalarError() (err *ScalarErr return response.Err, nil } -func (goTSRPCClientInstance *DemoGoRPCClient) MapCrap() (crap map[string][]int, clientErr error) { +func (tsc *DemoGoRPCClient) MapCrap() (crap map[string][]int, clientErr error) { req := DemoMapCrapRequest{} - rpcCallRes, rpcCallErr := goTSRPCClientInstance.Client.Call(req) + rpcCallRes, rpcCallErr := tsc.Client.Call(req) if rpcCallErr != nil { clientErr = rpcCallErr return @@ -128,9 +129,9 @@ func (goTSRPCClientInstance *DemoGoRPCClient) MapCrap() (crap map[string][]int, return response.Crap, nil } -func (goTSRPCClientInstance *DemoGoRPCClient) Nest() (retNest_0 *nested.Nested, clientErr error) { +func (tsc *DemoGoRPCClient) Nest() (retNest_0 []*nested.Nested, clientErr error) { req := DemoNestRequest{} - rpcCallRes, rpcCallErr := goTSRPCClientInstance.Client.Call(req) + rpcCallRes, rpcCallErr := tsc.Client.Call(req) if rpcCallErr != nil { clientErr = rpcCallErr return @@ -139,9 +140,9 @@ func (goTSRPCClientInstance *DemoGoRPCClient) Nest() (retNest_0 *nested.Nested, return response.RetNest_0, nil } -func (goTSRPCClientInstance *DemoGoRPCClient) TestScalarInPlace() (retTestScalarInPlace_0 ScalarInPlace, clientErr error) { +func (tsc *DemoGoRPCClient) TestScalarInPlace() (retTestScalarInPlace_0 ScalarInPlace, clientErr error) { req := DemoTestScalarInPlaceRequest{} - rpcCallRes, rpcCallErr := goTSRPCClientInstance.Client.Call(req) + rpcCallRes, rpcCallErr := tsc.Client.Call(req) if rpcCallErr != nil { clientErr = rpcCallErr return diff --git a/demo/gotsrpc.go b/demo/gotsrpc.go index c4fda78..8614bc9 100644 --- a/demo/gotsrpc.go +++ b/demo/gotsrpc.go @@ -1,4 +1,5 @@ -// Code generated by gotsrpc https://github.com/foomo/gotsrpc DO NOT EDIT. +// Code generated by gotsrpc https://github.com/foomo/gotsrpc - DO NOT EDIT. + package demo import ( diff --git a/demo/gotsrpcclient.go b/demo/gotsrpcclient.go index 19077df..d1d6313 100644 --- a/demo/gotsrpcclient.go +++ b/demo/gotsrpcclient.go @@ -1,4 +1,5 @@ -// Code generated by gotsrpc https://github.com/foomo/gotsrpc DO NOT EDIT. +// Code generated by gotsrpc https://github.com/foomo/gotsrpc - DO NOT EDIT. + package demo import ( @@ -6,97 +7,124 @@ import ( nested "github.com/foomo/gotsrpc/demo/nested" ) -type FooGoTSRPCClient struct { - URL string - EndPoint string +type FooGoTSRPCClient interface { + Hello(number int64) (retHello_0 int, clientErr error) } -func NewDefaultFooGoTSRPCClient(url string) *FooGoTSRPCClient { +type tsrpcFooGoTSRPCClient struct { + URL string + EndPoint string + Client gotsrpc.Client +} + +func NewDefaultFooGoTSRPCClient(url string) FooGoTSRPCClient { return NewFooGoTSRPCClient(url, "/service/foo") } -func NewFooGoTSRPCClient(url string, endpoint string) *FooGoTSRPCClient { - return &FooGoTSRPCClient{ +func NewFooGoTSRPCClient(url string, endpoint string) FooGoTSRPCClient { + return &tsrpcFooGoTSRPCClient{ URL: url, EndPoint: endpoint, + Client: gotsrpc.NewClient(nil), } } -func (goTSRPCClientInstance *FooGoTSRPCClient) Hello(number int64) (retHello_0 int, clientErr error) { +func (tsc *tsrpcFooGoTSRPCClient) SetClient(client gotsrpc.Client) { + tsc.Client = client +} +func (tsc *tsrpcFooGoTSRPCClient) Hello(number int64) (retHello_0 int, clientErr error) { args := []interface{}{number} reply := []interface{}{&retHello_0} - clientErr = gotsrpc.CallClient(goTSRPCClientInstance.URL, goTSRPCClientInstance.EndPoint, "Hello", args, reply) + clientErr = tsc.Client.Call(tsc.URL, tsc.EndPoint, "Hello", args, reply) return } -type DemoGoTSRPCClient struct { - URL string - EndPoint string +type DemoGoTSRPCClient interface { + ExtractAddress(person *Person) (addr *Address, e *Err, clientErr error) + GiveMeAScalar() (amount nested.Amount, wahr nested.True, hier ScalarInPlace, clientErr error) + Hello(name string) (retHello_0 string, retHello_1 *Err, clientErr error) + HelloInterface(anything interface{}, anythingMap map[string]interface{}, anythingSlice []interface{}) (clientErr error) + HelloScalarError() (err *ScalarError, clientErr error) + MapCrap() (crap map[string][]int, clientErr error) + Nest() (retNest_0 []*nested.Nested, clientErr error) + TestScalarInPlace() (retTestScalarInPlace_0 ScalarInPlace, clientErr error) } -func NewDefaultDemoGoTSRPCClient(url string) *DemoGoTSRPCClient { +var _ DemoGoTSRPCClient = &tsrpcDemoGoTSRPCClient{} + +type tsrpcDemoGoTSRPCClient struct { + URL string + EndPoint string + Client gotsrpc.Client +} + +func NewDefaultDemoGoTSRPCClient(url string) DemoGoTSRPCClient { return NewDemoGoTSRPCClient(url, "/service/demo") } -func NewDemoGoTSRPCClient(url string, endpoint string) *DemoGoTSRPCClient { - return &DemoGoTSRPCClient{ +func NewDemoGoTSRPCClient(url string, endpoint string) DemoGoTSRPCClient { + return &tsrpcDemoGoTSRPCClient{ URL: url, EndPoint: endpoint, + Client: gotsrpc.NewClient(nil), } } -func (goTSRPCClientInstance *DemoGoTSRPCClient) ExtractAddress(person *Person) (addr *Address, e *Err, clientErr error) { +func (tsc *tsrpcDemoGoTSRPCClient) SetClient(client gotsrpc.Client) { + tsc.Client = client +} +func (tsc *tsrpcDemoGoTSRPCClient) ExtractAddress(person *Person) (addr *Address, e *Err, clientErr error) { args := []interface{}{person} reply := []interface{}{&addr, &e} - clientErr = gotsrpc.CallClient(goTSRPCClientInstance.URL, goTSRPCClientInstance.EndPoint, "ExtractAddress", args, reply) + clientErr = tsc.Client.Call(tsc.URL, tsc.EndPoint, "ExtractAddress", args, reply) return } -func (goTSRPCClientInstance *DemoGoTSRPCClient) GiveMeAScalar() (amount nested.Amount, wahr nested.True, hier ScalarInPlace, clientErr error) { +func (tsc *tsrpcDemoGoTSRPCClient) GiveMeAScalar() (amount nested.Amount, wahr nested.True, hier ScalarInPlace, clientErr error) { args := []interface{}{} reply := []interface{}{&amount, &wahr, &hier} - clientErr = gotsrpc.CallClient(goTSRPCClientInstance.URL, goTSRPCClientInstance.EndPoint, "GiveMeAScalar", args, reply) + clientErr = tsc.Client.Call(tsc.URL, tsc.EndPoint, "GiveMeAScalar", args, reply) return } -func (goTSRPCClientInstance *DemoGoTSRPCClient) Hello(name string) (retHello_0 string, retHello_1 *Err, clientErr error) { +func (tsc *tsrpcDemoGoTSRPCClient) Hello(name string) (retHello_0 string, retHello_1 *Err, clientErr error) { args := []interface{}{name} reply := []interface{}{&retHello_0, &retHello_1} - clientErr = gotsrpc.CallClient(goTSRPCClientInstance.URL, goTSRPCClientInstance.EndPoint, "Hello", args, reply) + clientErr = tsc.Client.Call(tsc.URL, tsc.EndPoint, "Hello", args, reply) return } -func (goTSRPCClientInstance *DemoGoTSRPCClient) HelloInterface(anything interface{}, anythingMap map[string]interface{}, anythingSlice []interface{}) (clientErr error) { +func (tsc *tsrpcDemoGoTSRPCClient) HelloInterface(anything interface{}, anythingMap map[string]interface{}, anythingSlice []interface{}) (clientErr error) { args := []interface{}{anything, anythingMap, anythingSlice} reply := []interface{}{} - clientErr = gotsrpc.CallClient(goTSRPCClientInstance.URL, goTSRPCClientInstance.EndPoint, "HelloInterface", args, reply) + clientErr = tsc.Client.Call(tsc.URL, tsc.EndPoint, "HelloInterface", args, reply) return } -func (goTSRPCClientInstance *DemoGoTSRPCClient) HelloScalarError() (err *ScalarError, clientErr error) { +func (tsc *tsrpcDemoGoTSRPCClient) HelloScalarError() (err *ScalarError, clientErr error) { args := []interface{}{} reply := []interface{}{&err} - clientErr = gotsrpc.CallClient(goTSRPCClientInstance.URL, goTSRPCClientInstance.EndPoint, "HelloScalarError", args, reply) + clientErr = tsc.Client.Call(tsc.URL, tsc.EndPoint, "HelloScalarError", args, reply) return } -func (goTSRPCClientInstance *DemoGoTSRPCClient) MapCrap() (crap map[string][]int, clientErr error) { +func (tsc *tsrpcDemoGoTSRPCClient) MapCrap() (crap map[string][]int, clientErr error) { args := []interface{}{} reply := []interface{}{&crap} - clientErr = gotsrpc.CallClient(goTSRPCClientInstance.URL, goTSRPCClientInstance.EndPoint, "MapCrap", args, reply) + clientErr = tsc.Client.Call(tsc.URL, tsc.EndPoint, "MapCrap", args, reply) return } -func (goTSRPCClientInstance *DemoGoTSRPCClient) Nest() (retNest_0 *nested.Nested, clientErr error) { +func (tsc *tsrpcDemoGoTSRPCClient) Nest() (retNest_0 []*nested.Nested, clientErr error) { args := []interface{}{} reply := []interface{}{&retNest_0} - clientErr = gotsrpc.CallClient(goTSRPCClientInstance.URL, goTSRPCClientInstance.EndPoint, "Nest", args, reply) + clientErr = tsc.Client.Call(tsc.URL, tsc.EndPoint, "Nest", args, reply) return } -func (goTSRPCClientInstance *DemoGoTSRPCClient) TestScalarInPlace() (retTestScalarInPlace_0 ScalarInPlace, clientErr error) { +func (tsc *tsrpcDemoGoTSRPCClient) TestScalarInPlace() (retTestScalarInPlace_0 ScalarInPlace, clientErr error) { args := []interface{}{} reply := []interface{}{&retTestScalarInPlace_0} - clientErr = gotsrpc.CallClient(goTSRPCClientInstance.URL, goTSRPCClientInstance.EndPoint, "TestScalarInPlace", args, reply) + clientErr = tsc.Client.Call(tsc.URL, tsc.EndPoint, "TestScalarInPlace", args, reply) return } diff --git a/demo/gotsrpcclient_test.go b/demo/gotsrpcclient_test.go new file mode 100644 index 0000000..b4dfd4f --- /dev/null +++ b/demo/gotsrpcclient_test.go @@ -0,0 +1,130 @@ +package demo + +import ( + "fmt" + "github.com/foomo/gotsrpc/demo/nested" + "github.com/stretchr/testify/assert" + "math/rand" + "net/http/httptest" + "net/url" + "strconv" + "testing" + "time" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + +func RandStringRunes(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} + + +var ( + client DemoGoTSRPCClient + server *httptest.Server +) + +func setup() { + server = httptest.NewServer(NewDefaultDemoGoTSRPCProxy(&Demo{}, []string{})) + serverUrl, _ := url.Parse(server.URL) + client = NewDefaultDemoGoTSRPCClient(serverUrl.String()) +} + +func teardown() { + server.Close() +} + +func TestDefault(t *testing.T) { + setup() + defer teardown() + + resp, errServer, errClient := client.Hello("stefan") + assert.NoError(t, errClient) + assert.Nil(t, errServer) + fmt.Println(resp) +} + +func benchmarkRequests(b *testing.B, count int) { + setup() + defer teardown() + + person := GeneratePerson(count) + b.ResetTimer() + for n := 0; n < b.N; n++ { + client.ExtractAddress(person) + } +} + +func BenchmarkRequest1(b *testing.B) { benchmarkRequests(b, 1) } +func BenchmarkRequest10(b *testing.B) { benchmarkRequests(b, 10) } +func BenchmarkRequest100(b *testing.B) { benchmarkRequests(b, 100) } +func BenchmarkRequest1000(b *testing.B) { benchmarkRequests(b, 1000) } +func BenchmarkRequest10000(b *testing.B) { benchmarkRequests(b, 10000) } +func BenchmarkRequest100000(b *testing.B) { benchmarkRequests(b, 100000) } + +func GeneratePerson(count int) *Person { + person := &Person{} + person.AddressPtr = GenerateAddress() + person.Addresses = map[string]*Address{} + for i := 0; i < count; i++ { + person.Addresses[strconv.Itoa(i)] = GenerateAddress() + } + return person +} + +func GenerateAddress() *Address { + gen := func() string{ + return RandStringRunes(32) + } + genarr := func(count int) (ret []string) { + ret = make([]string, count) + for i := 0; i < count; i++ { + ret[i] = gen() + } + return + } + return &Address{ + City: gen(), + Signs: genarr(100), + SecretServerCrap: false, + PeoplePtr: nil, + ArrayOfMaps: nil, + ArrayArrayAddress: nil, + People: nil, + MapCrap: nil, + NestedPtr: &nested.Nested{ + Name: gen(), + SuperNestedString: struct { + Ha int64 + }{ + Ha: 011, + }, + SuperNestedPtr: &struct { + Bla string + }{ + Bla: gen(), + }, + }, + NestedStruct: nested.Nested{ + Name: gen(), + SuperNestedString: struct { + Ha int64 + }{ + Ha: 0, + }, + SuperNestedPtr: &struct { + Bla string + }{ + Bla: gen(), + }, + }, + } +} diff --git a/demo/output-commonjs/client.ts b/demo/output-commonjs/client.ts index c51d408..b30adcf 100644 --- a/demo/output-commonjs/client.ts +++ b/demo/output-commonjs/client.ts @@ -29,7 +29,7 @@ export class DemoClient { mapCrap(success:(crap:{[index:string]:number[]}) => void, err:(request:XMLHttpRequest, e?:Error) => void) { this.transport(this.endPoint, "MapCrap", [], success, err); } - nest(success:(ret:github_com_foomo_gotsrpc_demo_nested.Nested) => void, err:(request:XMLHttpRequest, e?:Error) => void) { + nest(success:(ret:github_com_foomo_gotsrpc_demo_nested.Nested[]) => void, err:(request:XMLHttpRequest, e?:Error) => void) { this.transport(this.endPoint, "Nest", [], success, err); } testScalarInPlace(success:(ret:string) => void, err:(request:XMLHttpRequest, e?:Error) => void) { diff --git a/demo/output/client.ts b/demo/output/client.ts index 83c0d12..79bc933 100644 --- a/demo/output/client.ts +++ b/demo/output/client.ts @@ -54,7 +54,7 @@ module GoTSRPC.Demo { mapCrap(success:(crap:{[index:string]:number[]}) => void, err:(request:XMLHttpRequest, e?:Error) => void) { this.transport(this.endPoint, "MapCrap", [], success, err); } - nest(success:(ret:GoTSRPC.Demo.Nested.Nested) => void, err:(request:XMLHttpRequest, e?:Error) => void) { + nest(success:(ret:GoTSRPC.Demo.Nested.Nested[]) => void, err:(request:XMLHttpRequest, e?:Error) => void) { this.transport(this.endPoint, "Nest", [], success, err); } testScalarInPlace(success:(ret:string) => void, err:(request:XMLHttpRequest, e?:Error) => void) { diff --git a/glide.lock b/glide.lock index f58b0bc..da5851a 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 249ea428695a3cfe9f583980181fb650d69d107954538d0684342754f61c0c68 -updated: 2017-10-12T16:13:47.627906627+02:00 +hash: 629adb2b190665615fa6558666181d5fb97f23adecccfe1675ff0dec498207ec +updated: 2018-11-22T15:17:52.360766+01:00 imports: - name: github.com/beorn7/perks version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 @@ -30,14 +30,19 @@ imports: - name: github.com/prometheus/procfs version: abf152e5f3e97f2fafac028d2cc06c1feb87ffa5 - name: github.com/stretchr/testify - version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 + version: f35b8ab0b5a2cef36673838d662e249dd9c94686 subpackages: - assert +- name: github.com/ugorji/go + version: b4c50a2b199d93b13dc15e78929cfb23bfdf21ab + subpackages: + - codec - name: github.com/valyala/gorpc version: 908281bef77441f2d0ec1792189b4032a1dace0c - name: golang.org/x/tools version: e4b401d06e5ee9f990011dc4f24cd24162131565 subpackages: + - go/ast/astutil - imports - name: gopkg.in/yaml.v2 version: a5b47d31c556af34a302ce5d659e6fea44d90de0 diff --git a/glide.yaml b/glide.yaml index 0621ccf..337abf4 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,5 +1,6 @@ package: github.com/foomo/gotsrpc import: +- package: github.com/pkg/errors - package: github.com/prometheus/client_golang version: ~0.8.0 subpackages: @@ -11,3 +12,7 @@ import: - package: golang.org/x/tools subpackages: - imports +- package: github.com/ugorji/go + version: ^1.1.1 + subpackages: + - codec diff --git a/go.go b/go.go index 7c9e5d7..aa20902 100644 --- a/go.go +++ b/go.go @@ -340,6 +340,46 @@ func renderTSRPCServiceProxies(services ServiceList, fullPackageName string, pac return nil } +type goMethod struct { + name string + params []string + args []string + rets []string + returns []string +} + +func newMethodSignature(method *Method, aliases map[string]string, fullPackageName string) goMethod { + var args []string + var params []string + for _, a := range goMethodArgsWithoutHTTPContextRelatedArgs(method) { + args = append(args, a.Name) + params = append(params, a.Name+" "+a.Value.goType(aliases, fullPackageName)) + } + var rets []string + var returns []string + for i, r := range method.Return { + name := r.Name + if len(name) == 0 { + name = fmt.Sprintf("ret%s_%d", method.Name, i) + } + rets = append(rets, "&"+name) + returns = append(returns, name+" "+r.Value.goType(aliases, fullPackageName)) + } + returns = append(returns, "clientErr error") + + return goMethod{ + name: method.Name, + params: params, + args: args, + rets: rets, + returns: returns, + } +} + +func (ms *goMethod) renderSignature() string { + return ms.name + `(` + strings.Join(ms.params, ", ") + `) (` + strings.Join(ms.returns, ", ") + `)` +} + func renderTSRPCServiceClients(services ServiceList, fullPackageName string, packageName string, config *config.Target, g *code) error { aliases := map[string]string{ "github.com/foomo/gotsrpc": "gotsrpc", @@ -366,46 +406,49 @@ func renderTSRPCServiceClients(services ServiceList, fullPackageName string, pac continue } - clientName := service.Name + "GoTSRPCClient" + interfaceName := service.Name + "GoTSRPCClient" + clientName := "tsrpc" + interfaceName + + //Render Interface + g.l(`type ` + interfaceName + ` interface { `) + for _, method := range service.Methods { + ms := newMethodSignature(method, aliases, fullPackageName) + g.l(ms.renderSignature()) + } + g.l(`} `) + + //Render Constructors g.l(` type ` + clientName + ` struct { URL string EndPoint string + Client gotsrpc.Client } - func NewDefault` + clientName + `(url string) *` + clientName + ` { - return New` + clientName + `(url, "` + service.Endpoint + `") + func NewDefault` + interfaceName + `(url string) ` + interfaceName + ` { + return New` + interfaceName + `(url, "` + service.Endpoint + `") } - func New` + clientName + `(url string, endpoint string) *` + clientName + ` { + func New` + interfaceName + `(url string, endpoint string) ` + interfaceName + ` { return &` + clientName + `{ URL: url, EndPoint: endpoint, + Client: gotsrpc.NewClient(nil), } - } - `) + }`) + + //Render Methods + g.l(` + func (tsc *` + clientName + `) SetClient(client gotsrpc.Client) { + tsc.Client = client + }`) + for _, method := range service.Methods { - args := []string{} - params := []string{} - for _, a := range goMethodArgsWithoutHTTPContextRelatedArgs(method) { - args = append(args, a.Name) - params = append(params, a.Name+" "+a.Value.goType(aliases, fullPackageName)) - } - rets := []string{} - returns := []string{} - for i, r := range method.Return { - name := r.Name - if len(name) == 0 { - name = fmt.Sprintf("ret%s_%d", method.Name, i) - } - rets = append(rets, "&"+name) - returns = append(returns, name+" "+r.Value.goType(aliases, fullPackageName)) - } - returns = append(returns, "clientErr error") - g.l(`func (goTSRPCClientInstance *` + clientName + `) ` + method.Name + `(` + strings.Join(params, ", ") + `) (` + strings.Join(returns, ", ") + `) {`) - g.l(`args := []interface{}{` + strings.Join(args, ", ") + `}`) - g.l(`reply := []interface{}{` + strings.Join(rets, ", ") + `}`) - g.l(`clientErr = gotsrpc.CallClient(goTSRPCClientInstance.URL, goTSRPCClientInstance.EndPoint, "` + method.Name + `", args, reply)`) + ms := newMethodSignature(method, aliases, fullPackageName) + g.l(`func (tsc *` + clientName + `) ` + ms.renderSignature() + ` {`) + g.l(`args := []interface{}{` + strings.Join(ms.args, ", ") + `}`) + g.l(`reply := []interface{}{` + strings.Join(ms.rets, ", ") + `}`) + g.l(`clientErr = tsc.Client.Call(tsc.URL, tsc.EndPoint, "` + method.Name + `", args, reply)`) g.l(`return`) g.l(`}`) g.nl() @@ -602,6 +645,7 @@ func renderGoRPCServiceClients(services ServiceList, fullPackageName string, pac if !config.IsGoRPC(service.Name) { continue } + clientName := service.Name + "GoRPCClient" // Client type g.l(` @@ -621,12 +665,12 @@ func renderGoRPCServiceClients(services ServiceList, fullPackageName string, pac return client } - func (goTSRPCClientInstance *` + clientName + `) Start() { - goTSRPCClientInstance.Client.Start() + func (tsc *` + clientName + `) Start() { + tsc.Client.Start() } - func (goTSRPCClientInstance *` + clientName + `) Stop() { - goTSRPCClientInstance.Client.Stop() + func (tsc *` + clientName + `) Stop() { + tsc.Client.Stop() } `) g.nl() @@ -649,12 +693,12 @@ func renderGoRPCServiceClients(services ServiceList, fullPackageName string, pac returns = append(returns, name+" "+r.Value.goType(aliases, fullPackageName)) } returns = append(returns, "clientErr error") - g.l(`func (goTSRPCClientInstance *` + clientName + `) ` + method.Name + `(` + strings.Join(params, ", ") + `) (` + strings.Join(returns, ", ") + `) {`) + g.l(`func (tsc *` + clientName + `) ` + method.Name + `(` + strings.Join(params, ", ") + `) (` + strings.Join(returns, ", ") + `) {`) g.l(`req := ` + service.Name + method.Name + `Request{` + strings.Join(args, ", ") + `}`) if len(rets) > 0 { - g.l(`rpcCallRes, rpcCallErr := goTSRPCClientInstance.Client.Call(req)`) + g.l(`rpcCallRes, rpcCallErr := tsc.Client.Call(req)`) } else { - g.l(`_, rpcCallErr := goTSRPCClientInstance.Client.Call(req)`) + g.l(`_, rpcCallErr := tsc.Client.Call(req)`) } g.l(`if rpcCallErr != nil {`) g.l(`clientErr = rpcCallErr`) @@ -733,8 +777,10 @@ func renderImports(aliases map[string]string, packageName string) string { imports += alias + " \"" + importPath + "\"\n" } return ` - // Code generated by gotsrpc https://github.com/foomo/gotsrpc DO NOT EDIT. + // Code generated by gotsrpc https://github.com/foomo/gotsrpc - DO NOT EDIT. + package ` + packageName + ` + import ( ` + imports + ` ) diff --git a/gotsrpc.go b/gotsrpc.go index 3237e31..30b3714 100644 --- a/gotsrpc.go +++ b/gotsrpc.go @@ -3,12 +3,12 @@ package gotsrpc import ( "context" "encoding/json" - "errors" "fmt" + "github.com/pkg/errors" + "github.com/ugorji/go/codec" "go/ast" "go/parser" "go/token" - "io/ioutil" "net/http" "path" "sort" @@ -39,18 +39,20 @@ func ErrorMethodNotAllowed(w http.ResponseWriter) { func LoadArgs(args interface{}, callStats *CallStats, r *http.Request) error { start := time.Now() - - body, err := ioutil.ReadAll(r.Body) - if err != nil { - return err + var errDecode error + switch r.Header.Get("Content-Type") { + case msgpackContentType: + errDecode = codec.NewDecoder(r.Body, msgpackHandle).Decode(args) + default: + errDecode = codec.NewDecoder(r.Body, jsonHandle).Decode(args) } - errLoad := loadArgs(&args, body) - if errLoad != nil { - return errLoad + + if errDecode != nil { + return errors.Wrap(errDecode, "could not decode arguments") } if callStats != nil { callStats.Unmarshalling = time.Now().Sub(start) - callStats.RequestSize = len(body) + callStats.RequestSize = int(r.ContentLength) } return nil } @@ -81,30 +83,33 @@ func ClearStats(r *http.Request) { // Reply despite the fact, that this is a public method - do not call it, it will be called by generated code func Reply(response []interface{}, stats *CallStats, r *http.Request, w http.ResponseWriter) { + writer := newResponseWriterWithLength(w) serializationStart := time.Now() - jsonBytes, err := json.Marshal(response) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("could not serialize response")) + var errEncode error + + switch r.Header.Get("Accept") { + case msgpackContentType: + writer.Header().Set("Content-Type", msgpackContentType) + errEncode = codec.NewEncoder(writer, msgpackHandle).Encode(response) + case jsonContentType: + writer.Header().Set("Content-Type", jsonContentType) + errEncode = codec.NewEncoder(writer, jsonHandle).Encode(response) + default: + writer.Header().Set("Content-Type", jsonContentType) + errEncode = codec.NewEncoder(writer, jsonHandle).Encode(response) + } + + if errEncode != nil { + fmt.Println(errEncode) + http.Error(w, "could not encode data to accepted format", http.StatusInternalServerError) return } + if stats != nil { - stats.ResponseSize = len(jsonBytes) + stats.ResponseSize = writer.length stats.Marshalling = time.Now().Sub(serializationStart) } - //r = r.WithContext(ctx) - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.Write(jsonBytes) - //fmt.Println("replied with stats", stats, "on", ctx) -} - -func jsonDump(v interface{}) { - jsonBytes, err := json.MarshalIndent(v, "", " ") - if err != nil { - fmt.Println("an error occured", err) - } - fmt.Println(string(jsonBytes)) + //writer.WriteHeader(http.StatusOK) } func parseDir(goPaths []string, packageName string) (map[string]*ast.Package, error) { diff --git a/transport.go b/transport.go new file mode 100644 index 0000000..1522ea2 --- /dev/null +++ b/transport.go @@ -0,0 +1,36 @@ +package gotsrpc + +import ( + "github.com/ugorji/go/codec" + "net/http" +) + +var ( + msgpackHandle = &codec.MsgpackHandle{} + msgpackContentType = "application/msgpack; charset=utf-8" +) + +var ( + jsonHandle = &codec.JsonHandle{} + jsonContentType = "application/json; charset=utf-8" +) + + +type responseWriterWithLength struct { + http.ResponseWriter + length int +} + +func newResponseWriterWithLength(w http.ResponseWriter) *responseWriterWithLength { + return &responseWriterWithLength{w, 0} +} + +func (w *responseWriterWithLength) Write(b []byte) (n int, err error) { + n, err = w.ResponseWriter.Write(b) + w.length += n + return +} + +func (w *responseWriterWithLength) Length() int { + return w.length +}