Skip to content

Commit

Permalink
Merge pull request #16 from donutloop/api/refactoring
Browse files Browse the repository at this point in the history
Framework: moved encoding of response code section into transport pac…
  • Loading branch information
donutloop committed Mar 4, 2018
2 parents 0f3b849 + 97ba4ba commit d1005da
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 172 deletions.
65 changes: 65 additions & 0 deletions framework/transport/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/donutloop/xservice/framework/errors"
"github.com/donutloop/xservice/framework/hooks"
"github.com/donutloop/xservice/framework/xcontext"
"github.com/donutloop/xservice/framework/xhttp"
"github.com/gogo/protobuf/jsonpb"
"github.com/gogo/protobuf/proto"
"io"
Expand Down Expand Up @@ -403,3 +404,67 @@ func CallError(ctx context.Context, h *hooks.ServerHooks, err errors.Error) cont

// LogErrorFunc logs critical errors
type LogErrorFunc func(format string, args ...interface{})

func EncodeJSONResponse(ctx context.Context, resp http.ResponseWriter, content proto.Message) error {
buff := new(bytes.Buffer)
marshaler := &jsonpb.Marshaler{OrigName: true}
if err := marshaler.Marshal(buff, content); err != nil {
err = errors.WrapErr(err, "failed to marshal json response")
return errors.InternalErrorWith(err)
}
respBytes := buff.Bytes()
resp.Header().Set(xhttp.ContentTypeHeader, xhttp.ApplicationJson)
if _, err := resp.Write(respBytes); err != nil {
err = errors.WrapErr(err, "error while writing response to client, but already sent response status code to 200")
return errors.InternalErrorWith(err)
}
resp.WriteHeader(http.StatusOK)
ctx = xcontext.WithStatusCode(ctx, http.StatusOK)
return nil
}

// DecodeRequestFunc extracts a user-domain request object from an HTTP request object.
type DecodeRequestFunc func(ctx context.Context, resp *http.Request, content proto.Message) error

// EncodeResponseFunc encodes the passed response object to the proto encoder.
type EncodeResponseFunc func(ctx context.Context, resp http.ResponseWriter, content proto.Message) error

func DecodeJSONRequest(ctx context.Context, req *http.Request, message proto.Message) error {
unmarshaler := jsonpb.Unmarshaler{AllowUnknownFields: true}
if err := unmarshaler.Unmarshal(req.Body, message); err != nil {
err = errors.WrapErr(err, "failed to parse request json")
terr := errors.InternalErrorWith(err)
return terr
}
return nil
}

func EncodePROTOResponse(ctx context.Context, resp http.ResponseWriter, content proto.Message) error {
respBytes, err := proto.Marshal(content)
if err != nil {
err = errors.WrapErr(err, "failed to marshal proto response")
return errors.InternalErrorWith(err)
}
resp.Header().Set(xhttp.ContentTypeHeader, xhttp.ApplicationProtobuf)
if _, err := resp.Write(respBytes); err != nil {
err = errors.WrapErr(err, "error while writing response to client, but already sent response status code to 200")
return errors.InternalErrorWith(err)
}
resp.WriteHeader(http.StatusOK)
ctx = xcontext.WithStatusCode(ctx, http.StatusOK)
return nil
}

func DecodePROTORequest(ctx context.Context, req *http.Request, content proto.Message) error {
buff, err := ioutil.ReadAll(req.Body)
if err != nil {
err = errors.WrapErr(err, "failed to read request proto")
return errors.InternalErrorWith(err)
}
err = proto.Unmarshal(buff, content)
if err != nil {
err = errors.WrapErr(err, "failed to parse request proto")
return errors.InternalErrorWith(err)
}
return nil
}
96 changes: 23 additions & 73 deletions generator/proto/go/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ func (a *API) generateAdditionalImports(file *descriptor.FileDescriptorProto, go
return
}

goFile.Import("jsonpb", "github.com/golang/protobuf/jsonpb")
goFile.Import("", "github.com/donutloop/xservice/framework/transport")
goFile.Import("", "github.com/donutloop/xservice/framework/xcontext")
goFile.Import("", "github.com/donutloop/xservice/framework/errors")
Expand Down Expand Up @@ -721,10 +720,10 @@ func (a *API) generateServerMethod(service *descriptor.ServiceDescriptorProto, m
dispatcherMethod.DefCall([]string{"modifiedHeader"}, types.NewUnsafeTypeReference("strings.TrimSpace"), []string{"modifiedHeader"})

dispatcherMethod.DefIfBegin("modifiedHeader", token.EQL, `xhttp.ApplicationJson`)
dispatcherMethod.Caller(types.NewUnsafeTypeReference(fmt.Sprintf("s.serve%sJSON", methName)), []string{"ctx", "resp", "req"})
dispatcherMethod.Caller(types.NewUnsafeTypeReference(fmt.Sprintf("s.serve%sContent", methName)), []string{"ctx", "resp", "req", "transport.DecodeJSONRequest", "transport.EncodeJSONResponse"})
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.Caller(types.NewUnsafeTypeReference(fmt.Sprintf("s.serve%sContent", methName)), []string{"ctx", "resp", "req", "transport.DecodePROTORequest", "transport.EncodePROTOResponse"})
dispatcherMethod.Return(nil)
dispatcherMethod.Else()
dispatcherMethod.DefAssginCall([]string{"msg"}, types.NewUnsafeTypeReference("fmt.Sprintf"), []string{`"unexpected Content-Type: %q"`, "header"})
Expand All @@ -734,23 +733,18 @@ func (a *API) generateServerMethod(service *descriptor.ServiceDescriptorProto, m

structGenerator.AddMethod(dispatcherMethod)

structGenerator, err = a.generateServerServeMethod(ServeJSON, service, method, structGenerator)
if err != nil {
return nil, err
}

structGenerator, err = a.generateServerServeMethod(ServeProtobuffer, service, method, structGenerator)
structGenerator, err = a.generateServerServeMethod(service, method, structGenerator)
if err != nil {
return nil, err
}

return structGenerator, nil
}

func (a *API) generateServerServeMethod(contentType string, service *descriptor.ServiceDescriptorProto, method *descriptor.MethodDescriptorProto, structGenerator *types.StructGenerator) (*types.StructGenerator, error) {
func (a *API) generateServerServeMethod(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%s%s", methName, contentType), []*types.Parameter{
serveMethod, err := types.NewGoMethod("s", fmt.Sprintf("*%s", structGenerator.StructMetaData.Name), fmt.Sprintf("serve%sContent", methName), []*types.Parameter{
{
NameOfParameter: "ctx",
Typ: types.NewUnsafeTypeReference("context.Context"),
Expand All @@ -763,6 +757,14 @@ func (a *API) generateServerServeMethod(contentType string, service *descriptor.
NameOfParameter: "req",
Typ: types.NewUnsafeTypeReference("*http.Request"),
},
{
NameOfParameter: "decodeRequest",
Typ: types.NewUnsafeTypeReference("transport.DecodeRequestFunc"),
},
{
NameOfParameter: "encodeResponse",
Typ: types.NewUnsafeTypeReference("transport.EncodeResponseFunc"),
},
}, nil, "")

if err != nil {
Expand Down Expand Up @@ -790,38 +792,13 @@ func (a *API) generateServerServeMethod(contentType string, service *descriptor.
serveMethod.CloseIf()
serveMethod.Defer(types.NewUnsafeTypeReference("transport.Closebody"), []string{"req.Body", "s.logErrorFunc"})

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("reqContent", types.NewUnsafeTypeReference(inputType))
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("decodeRequest"), []string{"ctx", "req", "reqContent"})
serveMethod.DefIfBegin("err", token.NEQ, "nil")
serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "err"})
serveMethod.Return()
serveMethod.CloseIf()

serveMethod.DefNew("respContent", types.NewUnsafeTypeReference(outputType))
responseCallWrapper, _ := types.NewAnonymousGoFunc("responseCallWrapper", nil, nil)
Expand Down Expand Up @@ -852,37 +829,10 @@ func (a *API) generateServerServeMethod(contentType string, service *descriptor.
serveMethod.CloseIf()

serveMethod.DefCall([]string{"ctx"}, types.NewUnsafeTypeReference("transport.CallResponsePrepared"), []string{"ctx", "s.hooks"})
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("resp.WriteHeader"), []string{"http.StatusOK"})
serveMethod.DefCall([]string{"_", "err"}, types.NewUnsafeTypeReference("resp.Write"), []string{"respBytes"})

serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("encodeResponse"), []string{"ctx", "resp", "respContent"})
serveMethod.DefIfBegin("err", token.NEQ, "nil")
serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"error while writing response to client, but already sent response status code to 200: %s"`, "err"})
serveMethod.Caller(types.NewUnsafeTypeReference("resp.WriteHeader"), []string{"http.StatusInternalServerError"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "err"})
serveMethod.Return()
serveMethod.CloseIf()

Expand Down
105 changes: 6 additions & 99 deletions integration_tests/api_hello_world/helloworld.proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@
package helloworld

import (
"bytes"
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
Expand All @@ -23,8 +21,6 @@ import (
"github.com/donutloop/xservice/framework/transport"
"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.
Expand Down Expand Up @@ -118,10 +114,10 @@ func (s *helloWorldServer) serveHello(ctx context.Context, resp http.ResponseWri
modifiedHeader := strings.ToLower(header[:i])
modifiedHeader = strings.TrimSpace(modifiedHeader)
if modifiedHeader == xhttp.ApplicationJson {
s.serveHelloJSON(ctx, resp, req)
s.serveHelloContent(ctx, resp, req, transport.DecodeJSONRequest, transport.EncodeJSONResponse)
return
} else if modifiedHeader == xhttp.ApplicationProtobuf {
s.serveHelloProtobuffer(ctx, resp, req)
s.serveHelloContent(ctx, resp, req, transport.DecodePROTORequest, transport.EncodePROTOResponse)
return
} else {
msg := fmt.Sprintf("unexpected Content-Type: %q", header)
Expand All @@ -130,7 +126,7 @@ func (s *helloWorldServer) serveHello(ctx context.Context, resp http.ResponseWri
}
}

func (s *helloWorldServer) serveHelloJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) {
func (s *helloWorldServer) serveHelloContent(ctx context.Context, resp http.ResponseWriter, req *http.Request, decodeRequest transport.DecodeRequestFunc, encodeResponse transport.EncodeResponseFunc) {
var err error
ctx = xcontext.WithMethodName(ctx, "Hello")
ctx, err = transport.CallRequestRouted(ctx, s.hooks)
Expand All @@ -141,91 +137,12 @@ func (s *helloWorldServer) serveHelloJSON(ctx context.Context, resp http.Respons
defer transport.Closebody(req.Body, s.logErrorFunc)

reqContent := new(HelloReq)
unmarshaler := jsonpb.Unmarshaler{AllowUnknownFields: true}
err = unmarshaler.Unmarshal(req.Body, reqContent)
err = decodeRequest(ctx, req, reqContent)
if err != nil {
err = errors.WrapErr(err, "failed to parse request json")
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)
buff := new(bytes.Buffer)
marshaler := &jsonpb.Marshaler{OrigName: true}
err = marshaler.Marshal(buff, 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
}
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() {
Expand All @@ -252,20 +169,10 @@ func (s *helloWorldServer) serveHelloProtobuffer(ctx context.Context, resp http.
return
}
ctx = transport.CallResponsePrepared(ctx, s.hooks)
respBytes, err := proto.Marshal(respContent)
err = encodeResponse(ctx, resp, 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)
_, 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)
s.writeError(ctx, resp, err)
return
}
transport.CallResponseSent(ctx, s.hooks)
Expand Down

0 comments on commit d1005da

Please sign in to comment.