Skip to content

Commit

Permalink
Merge pull request #29 from gengo/feature/format-error
Browse files Browse the repository at this point in the history
Format error message in JSON
  • Loading branch information
yugui committed Jul 8, 2015
2 parents c24e15e + 57f77b9 commit d430203
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 31 deletions.
40 changes: 20 additions & 20 deletions examples/a_bit_of_everything.pb.gw.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions examples/echo_service.pb.gw.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions protoc-gen-grpc-gateway/gengateway/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,13 @@ func Register{{$svc.GetName}}Handler(ctx context.Context, mux *runtime.ServeMux,
mux.Handle({{$b.HTTPMethod | printf "%q"}}, pattern_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
resp, err := request_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(runtime.AnnotateContext(ctx, req), client, req, pathParams)
if err != nil {
runtime.HTTPError(w, err)
runtime.HTTPError(ctx, w, err)
return
}
{{if $m.GetServerStreaming}}
runtime.ForwardResponseStream(w, func() (proto.Message, error) { return resp.Recv() })
{{else}}
runtime.ForwardResponseMessage(w, resp)
runtime.ForwardResponseMessage(ctx, w, resp)
{{end}}
})
{{end}}
Expand Down
39 changes: 36 additions & 3 deletions runtime/errors.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package runtime

import (
"encoding/json"
"io"
"net/http"

"github.com/golang/glog"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
Expand Down Expand Up @@ -51,10 +54,40 @@ func HTTPStatusFromCode(code codes.Code) int {
return http.StatusInternalServerError
}

// HTTPError replies to the request with the error.
var (
// HTTPError replies to the request with the error.
// You can set a custom function to this variable to customize error format.
HTTPError = DefaultHTTPError
)

type errorBody struct {
Error string `json:"error"`
}

// DefaultHTTPError is the default implementation of HTTPError.
// If "err" is an error from gRPC system, the function replies with the status code mapped by HTTPStatusFromCode.
// If otherwise, it replies with http.StatusInternalServerError.
func HTTPError(w http.ResponseWriter, err error) {
//
// The response body returned by this function is a JSON object,
// which contains a member whose key is "error" and whose value is err.Error().
func DefaultHTTPError(ctx context.Context, w http.ResponseWriter, err error) {
const fallback = `{"error": "failed to marshal error message"}`

w.Header().Set("Content-Type", "application/json")
body := errorBody{Error: err.Error()}
buf, merr := json.Marshal(body)
if merr != nil {
glog.Errorf("Failed to marshal error message %q: %v", body, merr)
w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, fallback); err != nil {
glog.Errorf("Failed to write response: %v", err)
}
return
}

st := HTTPStatusFromCode(grpc.Code(err))
http.Error(w, err.Error(), st)
w.WriteHeader(st)
if _, err := w.Write(buf); err != nil {
glog.Errorf("Failed to write response: %v", err)
}
}
55 changes: 55 additions & 0 deletions runtime/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package runtime_test

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/gengo/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)

func TestDefaultHTTPError(t *testing.T) {
ctx := context.Background()

for _, spec := range []struct {
err error
status int
msg string
}{
{
err: fmt.Errorf("example error"),
status: http.StatusInternalServerError,
msg: "example error",
},
{
err: grpc.Errorf(codes.NotFound, "no such resource"),
status: http.StatusNotFound,
msg: "no such resource",
},
} {
w := httptest.NewRecorder()
runtime.DefaultHTTPError(ctx, w, spec.err)

if got, want := w.Header().Get("Content-Type"), "application/json"; got != want {
t.Errorf(`w.Header().Get("Content-Type") = %q; want %q; on spec.err=%v`, got, want, spec.err)
}
if got, want := w.Code, spec.status; got != want {
t.Errorf("w.Code = %d; want %d", got, want)
}

body := make(map[string]string)
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Errorf("json.Unmarshal(%q, &body) failed with %v; want success", w.Body.Bytes(), err)
continue
}
if got, want := body["error"], spec.msg; !strings.Contains(got, want) {
t.Errorf(`body["error"] = %q; want %q; on spec.err=%v`, got, want, spec.err)
}
}
}
5 changes: 3 additions & 2 deletions runtime/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/golang/glog"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
)

type responseStreamChunk struct {
Expand Down Expand Up @@ -58,11 +59,11 @@ func ForwardResponseStream(w http.ResponseWriter, recv func() (proto.Message, er
}

// ForwardResponseMessage forwards the message from gRPC server to REST client.
func ForwardResponseMessage(w http.ResponseWriter, resp proto.Message) {
func ForwardResponseMessage(ctx context.Context, w http.ResponseWriter, resp proto.Message) {
buf, err := json.Marshal(resp)
if err != nil {
glog.Errorf("Marshal error: %v", err)
HTTPError(w, err)
HTTPError(ctx, w, err)
return
}

Expand Down

0 comments on commit d430203

Please sign in to comment.