From 946fa588f3e737c8676ed6e4e697752ce1f451ce Mon Sep 17 00:00:00 2001 From: Marcel Edmund Franke Date: Tue, 27 Feb 2018 19:04:33 +0100 Subject: [PATCH 1/2] Client & Server Generator: added support for protobuffer handling --- README.md | 1 - framework/transport/transport.go | 2 +- framework/xhttp/xhttp.go | 2 + generator/proto/go/api.go | 112 ++++++++++++----- .../api_hello_world/api_integration_test.go | 22 +++- .../api_hello_world/helloworld.proto.go | 116 +++++++++++++++++- internal/xgenerator/types/go.go | 1 + internal/xgenerator/types/goblock.go | 9 ++ 8 files changed, 227 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index a79aa90..f050272 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,6 @@ Please refer [**docs/DeveloperQuickStart.md**](https://github.com/donutloop/xser ## Roadmap -* Golang client & server: support for protobuffer content type * Multi language support ## Contribution diff --git a/framework/transport/transport.go b/framework/transport/transport.go index e7809d5..9aeeefc 100644 --- a/framework/transport/transport.go +++ b/framework/transport/transport.go @@ -167,7 +167,7 @@ func marshalErrorToJSON(terr errors.Error) []byte { } // doProtobufRequest is common code to make a request to the remote service. -func doProtobufRequest(ctx context.Context, client HTTPClient, url string, in, out proto.Message) (err error) { +func DoProtobufferRequest(ctx context.Context, client HTTPClient, url string, in, out proto.Message) (err error) { reqBodyBytes, err := proto.Marshal(in) if err != nil { return errors.ClientError("failed to marshal proto request", err) diff --git a/framework/xhttp/xhttp.go b/framework/xhttp/xhttp.go index 3026b7b..653cb01 100644 --- a/framework/xhttp/xhttp.go +++ b/framework/xhttp/xhttp.go @@ -3,3 +3,5 @@ package xhttp const ContentTypeHeader string = "Content-Type" const ApplicationJson = "application/json" + +const ApplicationProtobuf = "application/protobuf" diff --git a/generator/proto/go/api.go b/generator/proto/go/api.go index 8f616b8..df16c56 100644 --- a/generator/proto/go/api.go +++ b/generator/proto/go/api.go @@ -34,6 +34,11 @@ import ( "strings" ) +const ( + ServeJSON string = "JSON" + ServeProtobuffer string = "Protobuffer" +) + type API struct { filesHandled int currentPackage string // Go name of current package we're working on @@ -232,8 +237,14 @@ func (a *API) generateService(fileDescriptor *descriptor.FileDescriptorProto, se return nil, err } - // Server - goFile, err = a.generateClient("JSON", fileDescriptor, service, goFile) + // JSON Client + goFile, err = a.generateClient(ServeJSON, fileDescriptor, service, goFile) + if err != nil { + return nil, err + } + + // Protobuffer Client + goFile, err = a.generateClient(ServeProtobuffer, fileDescriptor, service, goFile) if err != nil { return nil, err } @@ -711,26 +722,35 @@ func (a *API) generateServerMethod(service *descriptor.ServiceDescriptorProto, m dispatcherMethod.DefIfBegin("modifiedHeader", token.EQL, `xhttp.ApplicationJson`) dispatcherMethod.Caller(types.NewUnsafeTypeReference(fmt.Sprintf("s.serve%sJSON", methName)), []string{"ctx", "resp", "req"}) + dispatcherMethod.Return(nil) + dispatcherMethod.DefElseIf("modifiedHeader", token.EQL, `xhttp.ApplicationProtobuf`) + dispatcherMethod.Caller(types.NewUnsafeTypeReference(fmt.Sprintf("s.serve%sProtobuffer", methName)), []string{"ctx", "resp", "req"}) + dispatcherMethod.Return(nil) dispatcherMethod.Else() dispatcherMethod.DefAssginCall([]string{"msg"}, types.NewUnsafeTypeReference("fmt.Sprintf"), []string{`"unexpected Content-Type: %q"`, "header"}) dispatcherMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.BadRouteError"), []string{"msg", "req.Method", "req.URL.Path"}) dispatcherMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"}) dispatcherMethod.CloseIf() - dispatcherMethod.Return(nil) structGenerator.AddMethod(dispatcherMethod) - structGenerator, err = a.generateServerJSONMethod(service, method, structGenerator) + structGenerator, err = a.generateServerServeMethod(ServeJSON, service, method, structGenerator) if err != nil { return nil, err } + + structGenerator, err = a.generateServerServeMethod(ServeProtobuffer, service, method, structGenerator) + if err != nil { + return nil, err + } + return structGenerator, nil } -func (a *API) generateServerJSONMethod(service *descriptor.ServiceDescriptorProto, method *descriptor.MethodDescriptorProto, structGenerator *types.StructGenerator) (*types.StructGenerator, error) { +func (a *API) generateServerServeMethod(contentType string, service *descriptor.ServiceDescriptorProto, method *descriptor.MethodDescriptorProto, structGenerator *types.StructGenerator) (*types.StructGenerator, error) { methName := types.CamelCase(method.GetName()) - serveMethod, err := types.NewGoMethod("s", fmt.Sprintf("*%s", structGenerator.StructMetaData.Name), fmt.Sprintf("serve%sJSON", methName), []*types.Parameter{ + serveMethod, err := types.NewGoMethod("s", fmt.Sprintf("*%s", structGenerator.StructMetaData.Name), fmt.Sprintf("serve%s%s", methName, contentType), []*types.Parameter{ { NameOfParameter: "ctx", Typ: types.NewUnsafeTypeReference("context.Context"), @@ -769,16 +789,40 @@ func (a *API) generateServerJSONMethod(service *descriptor.ServiceDescriptorProt serveMethod.Return() serveMethod.CloseIf() serveMethod.Defer(types.NewUnsafeTypeReference("transport.Closebody"), []string{"req.Body", "s.logErrorFunc"}) - serveMethod.DefNew("reqContent", types.NewUnsafeTypeReference(inputType)) - serveMethod.DefShortVar("unmarshaler", "jsonpb.Unmarshaler{AllowUnknownFields: true}") - serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("unmarshaler.Unmarshal"), []string{"req.Body", "reqContent"}) - serveMethod.DefIfBegin("err", token.NEQ, "nil") - serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to parse request json"`}) - serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"}) - serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"}) - serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"}) - serveMethod.Return() - serveMethod.CloseIf() + + if contentType == ServeJSON { + serveMethod.DefNew("reqContent", types.NewUnsafeTypeReference(inputType)) + serveMethod.DefShortVar("unmarshaler", "jsonpb.Unmarshaler{AllowUnknownFields: true}") + serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("unmarshaler.Unmarshal"), []string{"req.Body", "reqContent"}) + serveMethod.DefIfBegin("err", token.NEQ, "nil") + serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to parse request json"`}) + serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"}) + serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"}) + serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"}) + serveMethod.Return() + serveMethod.CloseIf() + } else if contentType == ServeProtobuffer { + serveMethod.DefAssginCall([]string{"buff", "err"}, types.NewUnsafeTypeReference("ioutil.ReadAll"), []string{"req.Body"}) + serveMethod.DefIfBegin("err", token.NEQ, "nil") + serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to read request proto"`}) + serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"}) + serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"}) + serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"}) + serveMethod.Return() + serveMethod.CloseIf() + serveMethod.DefNew("reqContent", types.NewUnsafeTypeReference(inputType)) + serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("proto.Unmarshal"), []string{"buff", "reqContent"}) + serveMethod.DefIfBegin("err", token.NEQ, "nil") + serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to parse request proto"`}) + serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"}) + serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"}) + serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"}) + serveMethod.Return() + serveMethod.CloseIf() + } else { + return nil, errors.New("content type isn't supported") + } + serveMethod.DefNew("respContent", types.NewUnsafeTypeReference(outputType)) responseCallWrapper, _ := types.NewAnonymousGoFunc("responseCallWrapper", nil, nil) responseDeferWrapper, _ := types.NewAnonymousGoFunc("responseDeferWrapper", nil, nil) @@ -808,22 +852,32 @@ func (a *API) generateServerJSONMethod(service *descriptor.ServiceDescriptorProt serveMethod.CloseIf() serveMethod.DefCall([]string{"ctx"}, types.NewUnsafeTypeReference("transport.CallResponsePrepared"), []string{"ctx", "s.hooks"}) - serveMethod.DefNew("buff", types.NewUnsafeTypeReference("bytes.Buffer")) - serveMethod.DefShortVar("marshaler", "&jsonpb.Marshaler{OrigName: true}") - serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("marshaler.Marshal"), []string{"buff", "respContent"}) - serveMethod.DefIfBegin("err", token.NEQ, "nil") - serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to marshal json response"`}) - serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"}) - serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"}) - serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"}) - serveMethod.Return() - serveMethod.CloseIf() + if contentType == ServeJSON { + serveMethod.DefNew("buff", types.NewUnsafeTypeReference("bytes.Buffer")) + serveMethod.DefShortVar("marshaler", "&jsonpb.Marshaler{OrigName: true}") + serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("marshaler.Marshal"), []string{"buff", "respContent"}) + serveMethod.DefIfBegin("err", token.NEQ, "nil") + serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to marshal json response"`}) + serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"}) + serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"}) + serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"}) + serveMethod.Return() + serveMethod.CloseIf() + serveMethod.DefAssginCall([]string{"respBytes"}, types.NewUnsafeTypeReference("buff.Bytes"), nil) + serveMethod.Caller(types.NewUnsafeTypeReference("req.Header.Set"), []string{"xhttp.ContentTypeHeader", "xhttp.ApplicationJson"}) + } else if contentType == ServeProtobuffer { + serveMethod.DefAssginCall([]string{"respBytes", "err"}, types.NewUnsafeTypeReference("proto.Marshal"), []string{"respContent"}) + serveMethod.DefIfBegin("err", token.NEQ, "nil") + serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to marshal json response"`}) + serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"}) + serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"}) + serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"}) + serveMethod.Return() + serveMethod.CloseIf() + } serveMethod.DefCall([]string{"ctx"}, types.NewUnsafeTypeReference("xcontext.WithStatusCode"), []string{"ctx", "http.StatusOK"}) - serveMethod.Caller(types.NewUnsafeTypeReference("req.Header.Set"), []string{"xhttp.ContentTypeHeader", "xhttp.ApplicationJson"}) serveMethod.Caller(types.NewUnsafeTypeReference("resp.WriteHeader"), []string{"http.StatusOK"}) - serveMethod.DefAssginCall([]string{"respBytes"}, types.NewUnsafeTypeReference("buff.Bytes"), nil) - serveMethod.DefCall([]string{"_", "err"}, types.NewUnsafeTypeReference("resp.Write"), []string{"respBytes"}) serveMethod.DefIfBegin("err", token.NEQ, "nil") diff --git a/integration_tests/api_hello_world/api_integration_test.go b/integration_tests/api_hello_world/api_integration_test.go index 0d09dc7..712ef73 100644 --- a/integration_tests/api_hello_world/api_integration_test.go +++ b/integration_tests/api_hello_world/api_integration_test.go @@ -28,7 +28,8 @@ func (s *HelloWorldServer) Hello(ctx context.Context, req *helloworld.HelloReq) return &helloworld.HelloResp{Text: "Hello " + req.Subject}, nil } -var client helloworld.HelloWorld +var JSONClient helloworld.HelloWorld +var ProtobufferClient helloworld.HelloWorld func TestMain(m *testing.M) { handler := helloworld.NewHelloWorldServer(&HelloWorldServer{}, nil) @@ -37,14 +38,27 @@ func TestMain(m *testing.M) { server := httptest.NewServer(mux) defer server.Close() - client = helloworld.NewHelloWorldJSONClient(server.URL, &http.Client{}) + JSONClient = helloworld.NewHelloWorldJSONClient(server.URL, &http.Client{}) + ProtobufferClient = helloworld.NewHelloWorldProtobufferClient(server.URL, &http.Client{}) // call flag.Parse() here if TestMain uses flags os.Exit(m.Run()) } -func TestHelloWorldCall(t *testing.T) { - resp, err := client.Hello(context.Background(), &helloworld.HelloReq{Subject: "World"}) +func TestHelloWorldJSONCall(t *testing.T) { + resp, err := JSONClient.Hello(context.Background(), &helloworld.HelloReq{Subject: "World"}) + if err != nil { + t.Fatal(err) + } + + expectedMessage := "Hello World" + if resp.Text != expectedMessage { + t.Fatalf(`unexpected text (actual: "%s", expected: "%s")`, resp.Text, expectedMessage) + } +} + +func TestHelloWorldProtobufferCall(t *testing.T) { + resp, err := ProtobufferClient.Hello(context.Background(), &helloworld.HelloReq{Subject: "World"}) if err != nil { t.Fatal(err) } diff --git a/integration_tests/api_hello_world/helloworld.proto.go b/integration_tests/api_hello_world/helloworld.proto.go index dfd050a..6f56e45 100644 --- a/integration_tests/api_hello_world/helloworld.proto.go +++ b/integration_tests/api_hello_world/helloworld.proto.go @@ -12,6 +12,7 @@ import ( "bytes" "context" "fmt" + "io/ioutil" "log" "net/http" "strings" @@ -23,6 +24,7 @@ import ( "github.com/donutloop/xservice/framework/xcontext" "github.com/donutloop/xservice/framework/xhttp" jsonpb "github.com/golang/protobuf/jsonpb" + proto "github.com/golang/protobuf/proto" ) // //[HelloWorldPathPrefix HelloWorld] is used for all URL paths on a %!s(MISSING) server. @@ -51,6 +53,20 @@ func (c *helloWorldJSONClient) Hello(ctx context.Context, in *HelloReq) (*HelloR return out, err } +type helloWorldProtobufferClient struct { + client transport.HTTPClient + urls [1]string +} + +func (c *helloWorldProtobufferClient) Hello(ctx context.Context, in *HelloReq) (*HelloResp, error) { + ctx = xcontext.WithPackageName(ctx, "example.helloworld") + ctx = xcontext.WithServiceName(ctx, "HelloWorld") + ctx = xcontext.WithMethodName(ctx, "Hello") + out := new(HelloResp) + err := transport.DoProtobufferRequest(ctx, c.client, c.urls[0], in, out) + return out, err +} + type helloWorldServer struct { HelloWorld hooks *hooks.ServerHooks @@ -103,12 +119,15 @@ func (s *helloWorldServer) serveHello(ctx context.Context, resp http.ResponseWri modifiedHeader = strings.TrimSpace(modifiedHeader) if modifiedHeader == xhttp.ApplicationJson { s.serveHelloJSON(ctx, resp, req) + return + } else if modifiedHeader == xhttp.ApplicationProtobuf { + s.serveHelloProtobuffer(ctx, resp, req) + return } else { msg := fmt.Sprintf("unexpected Content-Type: %q", header) terr := errors.BadRouteError(msg, req.Method, req.URL.Path) s.writeError(ctx, resp, terr) } - return } func (s *helloWorldServer) serveHelloJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { @@ -167,10 +186,82 @@ func (s *helloWorldServer) serveHelloJSON(ctx context.Context, resp http.Respons s.writeError(ctx, resp, terr) return } - ctx = xcontext.WithStatusCode(ctx, http.StatusOK) + respBytes := buff.Bytes() req.Header.Set(xhttp.ContentTypeHeader, xhttp.ApplicationJson) + ctx = xcontext.WithStatusCode(ctx, http.StatusOK) + resp.WriteHeader(http.StatusOK) + _, err = resp.Write(respBytes) + if err != nil { + s.logErrorFunc("error while writing response to client, but already sent response status code to 200: %s", err) + resp.WriteHeader(http.StatusInternalServerError) + return + } + transport.CallResponseSent(ctx, s.hooks) +} + +func (s *helloWorldServer) serveHelloProtobuffer(ctx context.Context, resp http.ResponseWriter, req *http.Request) { + var err error + ctx = xcontext.WithMethodName(ctx, "Hello") + ctx, err = transport.CallRequestRouted(ctx, s.hooks) + if err != nil { + s.writeError(ctx, resp, err) + return + } + defer transport.Closebody(req.Body, s.logErrorFunc) + + buff, err := ioutil.ReadAll(req.Body) + if err != nil { + err = errors.WrapErr(err, "failed to read request proto") + terr := errors.InternalErrorWith(err) + s.logErrorFunc("%v", err) + s.writeError(ctx, resp, terr) + return + } + reqContent := new(HelloReq) + err = proto.Unmarshal(buff, reqContent) + if err != nil { + err = errors.WrapErr(err, "failed to parse request proto") + terr := errors.InternalErrorWith(err) + s.logErrorFunc("%v", err) + s.writeError(ctx, resp, terr) + return + } + respContent := new(HelloResp) + responseCallWrapper := func() { + responseDeferWrapper := func() { + r := recover() + if r != nil { + terr := errors.InternalError("Internal service panic") + s.writeError(ctx, resp, terr) + panic(r) + } + } + defer responseDeferWrapper() + + respContent, err = s.Hello(ctx, reqContent) + } + responseCallWrapper() + if err != nil { + s.writeError(ctx, resp, err) + return + } + if respContent == nil { + terr := errors.InternalError("received a nil * HelloResp, and nil error while calling Hello. nil responses are not supported") + s.logErrorFunc("%v", err) + s.writeError(ctx, resp, terr) + return + } + ctx = transport.CallResponsePrepared(ctx, s.hooks) + respBytes, err := proto.Marshal(respContent) + if err != nil { + err = errors.WrapErr(err, "failed to marshal json response") + terr := errors.InternalErrorWith(err) + s.logErrorFunc("%v", err) + s.writeError(ctx, resp, terr) + return + } + ctx = xcontext.WithStatusCode(ctx, http.StatusOK) resp.WriteHeader(http.StatusOK) - respBytes := buff.Bytes() _, err = resp.Write(respBytes) if err != nil { s.logErrorFunc("error while writing response to client, but already sent response status code to 200: %s", err) @@ -207,6 +298,25 @@ func NewHelloWorldJSONClient(addr string, client transport.HTTPClient) HelloWorl urls: urls, } } +func NewHelloWorldProtobufferClient(addr string, client transport.HTTPClient) HelloWorld { + URLBase := transport.UrlBase(addr) + prefix := URLBase + HelloWorldPathPrefix + urls := [1]string{ + prefix + "Hello", + } + httpClient, ok := client.(*http.Client) + if ok == true { + httpClient = transport.WithoutRedirects(httpClient) + return &helloWorldProtobufferClient{ + client: httpClient, + urls: urls, + } + } + return &helloWorldProtobufferClient{ + client: client, + urls: urls, + } +} func NewHelloWorldServer(svc HelloWorld, hooks *hooks.ServerHooks, errorFunc ...transport.LogErrorFunc) server.Server { server := &helloWorldServer{ HelloWorld: svc, diff --git a/internal/xgenerator/types/go.go b/internal/xgenerator/types/go.go index 23fc4d8..5c6cfa5 100644 --- a/internal/xgenerator/types/go.go +++ b/internal/xgenerator/types/go.go @@ -43,6 +43,7 @@ const ( IfStatmentTpl string = "if %s %s %s {\n" IfStatmentWithOwnScopeTpl string = "if %s; %s %s %s {\n" ElseStatmentTpl string = "} else {\n" + ElseIfStatmentTpl string = "} else if %s %s %s {\n" IfEndTpl string = "}\n" RangeTpl string = "for %s,%s := range %s {\n" RangeEndTpl string = "}\n" diff --git a/internal/xgenerator/types/goblock.go b/internal/xgenerator/types/goblock.go index bc05667..1fe1e21 100644 --- a/internal/xgenerator/types/goblock.go +++ b/internal/xgenerator/types/goblock.go @@ -347,6 +347,15 @@ func (gen *GoBlockGenerator) DefIfBegin(rightSide string, operation token.Token, return nil } +func (gen *GoBlockGenerator) DefElseIf(rightSide string, operation token.Token, leftSide string) error { + if err := gen.validateIf(rightSide, operation, leftSide); err != nil { + return err + } + + gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(ElseIfStatmentTpl, rightSide, operation.String(), leftSide)) + return nil +} + func (gen *GoBlockGenerator) validateIf(rightSide string, operation token.Token, leftSide string) error { if rightSide == "" { return NewGeneratorErrorString(gen, "right side of if is missing") From 74bac9a54d8c207d04bcde2f2d412df1148ee360 Mon Sep 17 00:00:00 2001 From: Marcel Edmund Franke Date: Tue, 27 Feb 2018 19:17:00 +0100 Subject: [PATCH 2/2] Example: extended hello world code with creation and usage protobuffer client Signed-off-by: Marcel Edmund Franke --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f050272..1d7c854 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,9 @@ func main() { } ``` - Now you can just use the auto-generated Client to make remote calls to your new service: + Now you can just use the auto-generated JSON or Protobuffer Client to make remote calls to your new service: +##### JSON ```go package main @@ -100,6 +101,29 @@ func main() { } ``` +##### Protobuffer + +```go +package main + +import ( + "context" + "fmt" + "net/http" + + pb "github.com/donutloop/xservice-example/helloworld" +) + +func main() { + client := pb.NewHelloWorldProtobufferClient("http://localhost:8080", &http.Client{}) + + resp, err := client.Hello(context.Background(), &pb.HelloReq{Subject: "World"}) + if err == nil { + fmt.Println(resp.Text) // prints "Hello World" + } +} +``` + ## QuickStart for developers Please refer [**docs/DeveloperQuickStart.md**](https://github.com/donutloop/xservice/blob/master/docs/DeveloperQuickstartGuide.md)