diff --git a/README.md b/README.md index 9112eb0eeca..b303415a1ee 100644 --- a/README.md +++ b/README.md @@ -206,7 +206,7 @@ To use the same port for custom HTTP handlers (e.g. serving `swagger.json`), gRP * Method parameters in query string * Enum fields in path parameter (including repeated enum fields). * Mapping streaming APIs to newline-delimited JSON streams -* Mapping HTTP headers with `Grpc-Metadata-` prefix to gRPC metadata +* Mapping HTTP headers with `Grpc-Metadata-` prefix to gRPC metadata (prefixed with `grpcgateway-`) * Optionally emitting API definition for [Swagger](http://swagger.io). * Setting [gRPC timeouts](http://www.grpc.io/docs/guides/wire.html) through inbound HTTP `Grpc-Timeout` header. @@ -229,7 +229,8 @@ But patch is welcome. * HTTP request source IP is added as `X-Forwarded-For` gRPC request header * HTTP request host is added as `X-Forwarded-Host` gRPC request header * HTTP `Authorization` header is added as `authorization` gRPC request header -* Remaining HTTP header keys are prefixed with `Grpc-Metadata-` and added with their values to gRPC request header +* Remaining Permanent HTTP header keys (as specified by the IANA [here](http://www.iana.org/assignments/message-headers/message-headers.xhtml) are prefixed with `grpcgateway-` and added with their values to gRPC request header +* HTTP headers that start with 'Grpc-Metadata-' are mapped to gRPC metadata (prefixed with `grpcgateway-`) * While configurable, the default {un,}marshaling uses [jsonpb](https://godoc.org/github.com/golang/protobuf/jsonpb) with `OrigName: true`. # Contribution diff --git a/examples/integration_test.go b/examples/integration_test.go index ba011497b6a..826fbddc119 100644 --- a/examples/integration_test.go +++ b/examples/integration_test.go @@ -128,10 +128,10 @@ func testEchoBody(t *testing.T) { } if got, want := resp.Header.Get("Grpc-Metadata-Foo"), "foo1"; got != want { - t.Errorf("Grpc-Header-Foo was %q, wanted %q", got, want) + t.Errorf("Grpc-Metadata-Foo was %q, wanted %q", got, want) } if got, want := resp.Header.Get("Grpc-Metadata-Bar"), "bar1"; got != want { - t.Errorf("Grpc-Header-Bar was %q, wanted %q", got, want) + t.Errorf("Grpc-Metadata-Bar was %q, wanted %q", got, want) } if got, want := resp.Trailer.Get("Grpc-Trailer-Foo"), "foo2"; got != want { @@ -369,7 +369,7 @@ func testABEBulkCreate(t *testing.T) { } if got, want := resp.Header.Get("Grpc-Metadata-Count"), fmt.Sprintf("%d", count); got != want { - t.Errorf("Grpc-Header-Count was %q, wanted %q", got, want) + t.Errorf("Grpc-Metadata-Count was %q, wanted %q", got, want) } if got, want := resp.Trailer.Get("Grpc-Trailer-Foo"), "foo2"; got != want { @@ -518,7 +518,7 @@ func testABEList(t *testing.T) { value := resp.Header.Get("Grpc-Metadata-Count") if value == "" { - t.Errorf("Grpc-Header-Count should not be empty") + t.Errorf("Grpc-Metadata-Count should not be empty") } count, err := strconv.Atoi(value) diff --git a/runtime/context.go b/runtime/context.go index f248c738b23..98eeb44c9a1 100644 --- a/runtime/context.go +++ b/runtime/context.go @@ -15,12 +15,17 @@ import ( "google.golang.org/grpc/metadata" ) -// MetadataHeaderPrefix is prepended to HTTP headers in order to convert them to -// gRPC metadata for incoming requests processed by grpc-gateway +// MetadataHeaderPrefix is the http prefix that represents custom metadata +// parameters to or from a gRPC call. const MetadataHeaderPrefix = "Grpc-Metadata-" + +// MetadataPrefix is the prefix for grpc-gateway supplied custom metadata fields. +const MetadataPrefix = "grpcgateway-" + // MetadataTrailerPrefix is prepended to gRPC metadata as it is converted to // HTTP headers in a response handled by grpc-gateway const MetadataTrailerPrefix = "Grpc-Trailer-" + const metadataGrpcTimeout = "Grpc-Timeout" const xForwardedFor = "X-Forwarded-For" @@ -52,8 +57,12 @@ func AnnotateContext(ctx context.Context, req *http.Request) (context.Context, e for key, vals := range req.Header { for _, val := range vals { - if key == "Authorization" { + // For backwards-compatibility, pass through 'authorization' header with no prefix. + if strings.ToLower(key) == "authorization" { pairs = append(pairs, "authorization", val) + } + if isPermanentHTTPHeader(key) { + pairs = append(pairs, strings.ToLower(fmt.Sprintf("%s%s", MetadataPrefix, key)), val) continue } if strings.HasPrefix(key, MetadataHeaderPrefix) { @@ -141,3 +150,38 @@ func timeoutUnitToDuration(u uint8) (d time.Duration, ok bool) { } return } + +// isPermanentHTTPHeader checks whether hdr belongs to the list of +// permenant request headers maintained by IANA. +// http://www.iana.org/assignments/message-headers/message-headers.xml +func isPermanentHTTPHeader(hdr string) bool { + switch hdr { + case + "Accept", + "Accept-Charset", + "Accept-Language", + "Accept-Ranges", + "Authorization", + "Cache-Control", + "Content-Type", + "Cookie", + "Date", + "Expect", + "From", + "Host", + "If-Match", + "If-Modified-Since", + "If-None-Match", + "If-Schedule-Tag-Match", + "If-Unmodified-Since", + "Max-Forwards", + "Origin", + "Pragma", + "Referer", + "User-Agent", + "Via", + "Warning": + return true + } + return false +} diff --git a/runtime/context_test.go b/runtime/context_test.go index abc1873fad9..2f3b02393db 100644 --- a/runtime/context_test.go +++ b/runtime/context_test.go @@ -51,14 +51,17 @@ func TestAnnotateContext_ForwardsGrpcMetadata(t *testing.T) { return } md, ok := metadata.FromContext(annotated) - if got, want := len(md), emptyForwardMetaCount+3; !ok || got != want { - t.Errorf("Expected %d metadata items in context; got %d", got, want) + if got, want := len(md), emptyForwardMetaCount+4; !ok || got != want { + t.Errorf("metadata items in context = %d want %d: %v", got, want, md) } if got, want := md["foobar"], []string{"Value1"}; !reflect.DeepEqual(got, want) { - t.Errorf(`md["foobar"] = %q; want %q`, got, want) + t.Errorf(`md["grpcgateway-foobar"] = %q; want %q`, got, want) } if got, want := md["foo-baz"], []string{"Value2", "Value3"}; !reflect.DeepEqual(got, want) { - t.Errorf(`md["foo-baz"] = %q want %q`, got, want) + t.Errorf(`md["grpcgateway-foo-baz"] = %q want %q`, got, want) + } + if got, want := md["grpcgateway-authorization"], []string{"Token 1234567890"}; !reflect.DeepEqual(got, want) { + t.Errorf(`md["grpcgateway-authorization"] = %q want %q`, got, want) } if got, want := md["authorization"], []string{"Token 1234567890"}; !reflect.DeepEqual(got, want) { t.Errorf(`md["authorization"] = %q want %q`, got, want)