Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to forward grpc binary metadata #737

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions runtime/context.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package runtime

import (
"context"
"encoding/base64"
"fmt"
"net"
"net/http"
"net/textproto"
"strconv"
"strings"
"time"

"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
Expand All @@ -28,6 +30,7 @@ const MetadataPrefix = "grpcgateway-"
const MetadataTrailerPrefix = "Grpc-Trailer-"

const metadataGrpcTimeout = "Grpc-Timeout"
const metadataHeaderBinarySuffix = "-Bin"

const xForwardedFor = "X-Forwarded-For"
const xForwardedHost = "X-Forwarded-Host"
Expand All @@ -38,6 +41,14 @@ var (
DefaultContextTimeout = 0 * time.Second
)

func decodeBinHeader(v string) ([]byte, error) {
if len(v)%4 == 0 {
// Input was padded, or padding was not necessary.
return base64.StdEncoding.DecodeString(v)
}
return base64.RawStdEncoding.DecodeString(v)
}

/*
AnnotateContext adds context information such as metadata from the request.
Expand All @@ -58,11 +69,22 @@ func AnnotateContext(ctx context.Context, mux *ServeMux, req *http.Request) (con

for key, vals := range req.Header {
for _, val := range vals {
key = textproto.CanonicalMIMEHeaderKey(key)
// For backwards-compatibility, pass through 'authorization' header with no prefix.
if strings.ToLower(key) == "authorization" {
if key == "Authorization" {
pairs = append(pairs, "authorization", val)
}
if h, ok := mux.incomingHeaderMatcher(key); ok {
// Handles "-bin" metadata in grpc, since grpc will do another base64
// encode before sending to server, we need to decode it first.
if strings.HasSuffix(key, metadataHeaderBinarySuffix) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand the context here, but should there be a TrimSuffix somewhere? Or do we intentionally preserve the -bin?

Copy link
Contributor Author

@timonwong timonwong Aug 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b, err := decodeBinHeader(val)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid binary header %s: %s", key, err)
}

val = string(b)
}
pairs = append(pairs, h, val)
}
}
Expand Down
27 changes: 26 additions & 1 deletion runtime/context_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package runtime_test

import (
"context"
"encoding/base64"
"net/http"
"reflect"
"testing"
"time"

"context"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"google.golang.org/grpc/metadata"
)
Expand Down Expand Up @@ -68,6 +69,30 @@ func TestAnnotateContext_ForwardsGrpcMetadata(t *testing.T) {
}
}

func TestAnnotateContext_ForwardGrpcBinaryMetadata(t *testing.T) {
ctx := context.Background()
request, err := http.NewRequest("GET", "http://www.example.com", nil)
if err != nil {
t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://www.example.com", err)
}

binData := []byte("\x00test-binary-data")
request.Header.Add("Grpc-Metadata-Test-Bin", base64.StdEncoding.EncodeToString(binData))

annotated, err := runtime.AnnotateContext(ctx, runtime.NewServeMux(), request)
if err != nil {
t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
return
}
md, ok := metadata.FromOutgoingContext(annotated)
if !ok || len(md) != emptyForwardMetaCount+1 {
t.Errorf("Expected %d metadata items in context; got %v", emptyForwardMetaCount+1, md)
}
if got, want := md["test-bin"], []string{string(binData)}; !reflect.DeepEqual(got, want) {
t.Errorf(`md["test-bin"] = %q want %q`, got, want)
}
}

func TestAnnotateContext_XForwardedFor(t *testing.T) {
ctx := context.Background()
request, err := http.NewRequest("GET", "http://bar.foo.example.com", nil)
Expand Down
2 changes: 0 additions & 2 deletions runtime/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package runtime
import (
"fmt"
"net/http"
"net/textproto"
"strings"
"context"

Expand Down Expand Up @@ -51,7 +50,6 @@ type HeaderMatcherFunc func(string) (string, bool)
// keys (as specified by the IANA) to gRPC context with grpcgateway- prefix. HTTP headers that start with
// 'Grpc-Metadata-' are mapped to gRPC metadata after removing prefix 'Grpc-Metadata-'.
func DefaultHeaderMatcher(key string) (string, bool) {
key = textproto.CanonicalMIMEHeaderKey(key)
if isPermanentHTTPHeader(key) {
return MetadataPrefix + key, true
} else if strings.HasPrefix(key, MetadataHeaderPrefix) {
Expand Down