Skip to content

Commit

Permalink
Add support to forward grpc binary metadata
Browse files Browse the repository at this point in the history
Fixes #218
  • Loading branch information
timonwong authored and johanbrandhorst committed Aug 29, 2018
1 parent e679739 commit 8976e60
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 5 deletions.
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) {
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

0 comments on commit 8976e60

Please sign in to comment.