...

Source file src/github.com/grpc-ecosystem/grpc-gateway/runtime/context_test.go

Documentation: github.com/grpc-ecosystem/grpc-gateway/runtime

     1  package runtime_test
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"net/http"
     7  	"reflect"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/grpc-ecosystem/grpc-gateway/runtime"
    12  	"google.golang.org/grpc/metadata"
    13  )
    14  
    15  const (
    16  	emptyForwardMetaCount = 1
    17  )
    18  
    19  func TestAnnotateContext_WorksWithEmpty(t *testing.T) {
    20  	ctx := context.Background()
    21  
    22  	request, err := http.NewRequest("GET", "http://www.example.com", nil)
    23  	if err != nil {
    24  		t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://www.example.com", err)
    25  	}
    26  	request.Header.Add("Some-Irrelevant-Header", "some value")
    27  	annotated, err := runtime.AnnotateContext(ctx, runtime.NewServeMux(), request)
    28  	if err != nil {
    29  		t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
    30  		return
    31  	}
    32  	md, ok := metadata.FromOutgoingContext(annotated)
    33  	if !ok || len(md) != emptyForwardMetaCount {
    34  		t.Errorf("Expected %d metadata items in context; got %v", emptyForwardMetaCount, md)
    35  	}
    36  }
    37  
    38  func TestAnnotateContext_ForwardsGrpcMetadata(t *testing.T) {
    39  	ctx := context.Background()
    40  	request, err := http.NewRequest("GET", "http://www.example.com", nil)
    41  	if err != nil {
    42  		t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://www.example.com", err)
    43  	}
    44  	request.Header.Add("Some-Irrelevant-Header", "some value")
    45  	request.Header.Add("Grpc-Metadata-FooBar", "Value1")
    46  	request.Header.Add("Grpc-Metadata-Foo-BAZ", "Value2")
    47  	request.Header.Add("Grpc-Metadata-foo-bAz", "Value3")
    48  	request.Header.Add("Authorization", "Token 1234567890")
    49  	annotated, err := runtime.AnnotateContext(ctx, runtime.NewServeMux(), request)
    50  	if err != nil {
    51  		t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
    52  		return
    53  	}
    54  	md, ok := metadata.FromOutgoingContext(annotated)
    55  	if got, want := len(md), emptyForwardMetaCount+4; !ok || got != want {
    56  		t.Errorf("metadata items in context = %d want %d: %v", got, want, md)
    57  	}
    58  	if got, want := md["foobar"], []string{"Value1"}; !reflect.DeepEqual(got, want) {
    59  		t.Errorf(`md["grpcgateway-foobar"] = %q; want %q`, got, want)
    60  	}
    61  	if got, want := md["foo-baz"], []string{"Value2", "Value3"}; !reflect.DeepEqual(got, want) {
    62  		t.Errorf(`md["grpcgateway-foo-baz"] = %q want %q`, got, want)
    63  	}
    64  	if got, want := md["grpcgateway-authorization"], []string{"Token 1234567890"}; !reflect.DeepEqual(got, want) {
    65  		t.Errorf(`md["grpcgateway-authorization"] = %q want %q`, got, want)
    66  	}
    67  	if got, want := md["authorization"], []string{"Token 1234567890"}; !reflect.DeepEqual(got, want) {
    68  		t.Errorf(`md["authorization"] = %q want %q`, got, want)
    69  	}
    70  }
    71  
    72  func TestAnnotateContext_ForwardGrpcBinaryMetadata(t *testing.T) {
    73  	ctx := context.Background()
    74  	request, err := http.NewRequest("GET", "http://www.example.com", nil)
    75  	if err != nil {
    76  		t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://www.example.com", err)
    77  	}
    78  
    79  	binData := []byte("\x00test-binary-data")
    80  	request.Header.Add("Grpc-Metadata-Test-Bin", base64.StdEncoding.EncodeToString(binData))
    81  
    82  	annotated, err := runtime.AnnotateContext(ctx, runtime.NewServeMux(), request)
    83  	if err != nil {
    84  		t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
    85  		return
    86  	}
    87  	md, ok := metadata.FromOutgoingContext(annotated)
    88  	if !ok || len(md) != emptyForwardMetaCount+1 {
    89  		t.Errorf("Expected %d metadata items in context; got %v", emptyForwardMetaCount+1, md)
    90  	}
    91  	if got, want := md["test-bin"], []string{string(binData)}; !reflect.DeepEqual(got, want) {
    92  		t.Errorf(`md["test-bin"] = %q want %q`, got, want)
    93  	}
    94  }
    95  
    96  func TestAnnotateContext_XForwardedFor(t *testing.T) {
    97  	ctx := context.Background()
    98  	request, err := http.NewRequest("GET", "http://bar.foo.example.com", nil)
    99  	if err != nil {
   100  		t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://bar.foo.example.com", err)
   101  	}
   102  	request.Header.Add("X-Forwarded-For", "192.0.2.100") // client
   103  	request.RemoteAddr = "192.0.2.200:12345"             // proxy
   104  
   105  	annotated, err := runtime.AnnotateContext(ctx, runtime.NewServeMux(), request)
   106  	if err != nil {
   107  		t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
   108  		return
   109  	}
   110  	md, ok := metadata.FromOutgoingContext(annotated)
   111  	if !ok || len(md) != emptyForwardMetaCount+1 {
   112  		t.Errorf("Expected %d metadata items in context; got %v", emptyForwardMetaCount+1, md)
   113  	}
   114  	if got, want := md["x-forwarded-host"], []string{"bar.foo.example.com"}; !reflect.DeepEqual(got, want) {
   115  		t.Errorf(`md["host"] = %v; want %v`, got, want)
   116  	}
   117  	// Note: it must be in order client, proxy1, proxy2
   118  	if got, want := md["x-forwarded-for"], []string{"192.0.2.100, 192.0.2.200"}; !reflect.DeepEqual(got, want) {
   119  		t.Errorf(`md["x-forwarded-for"] = %v want %v`, got, want)
   120  	}
   121  }
   122  
   123  func TestAnnotateContext_SupportsTimeouts(t *testing.T) {
   124  	ctx := context.Background()
   125  	request, err := http.NewRequest("GET", "http://example.com", nil)
   126  	if err != nil {
   127  		t.Fatalf(`http.NewRequest("GET", "http://example.com", nil failed with %v; want success`, err)
   128  	}
   129  	annotated, err := runtime.AnnotateContext(ctx, runtime.NewServeMux(), request)
   130  	if err != nil {
   131  		t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
   132  		return
   133  	}
   134  	if _, ok := annotated.Deadline(); ok {
   135  		// no deadline by default
   136  		t.Errorf("annotated.Deadline() = _, true; want _, false")
   137  	}
   138  
   139  	const acceptableError = 50 * time.Millisecond
   140  	runtime.DefaultContextTimeout = 10 * time.Second
   141  	annotated, err = runtime.AnnotateContext(ctx, runtime.NewServeMux(), request)
   142  	if err != nil {
   143  		t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
   144  		return
   145  	}
   146  	deadline, ok := annotated.Deadline()
   147  	if !ok {
   148  		t.Errorf("annotated.Deadline() = _, false; want _, true")
   149  	}
   150  	if got, want := deadline.Sub(time.Now()), runtime.DefaultContextTimeout; got-want > acceptableError || got-want < -acceptableError {
   151  		t.Errorf("deadline.Sub(time.Now()) = %v; want %v; with error %v", got, want, acceptableError)
   152  	}
   153  
   154  	for _, spec := range []struct {
   155  		timeout string
   156  		want    time.Duration
   157  	}{
   158  		{
   159  			timeout: "17H",
   160  			want:    17 * time.Hour,
   161  		},
   162  		{
   163  			timeout: "19M",
   164  			want:    19 * time.Minute,
   165  		},
   166  		{
   167  			timeout: "23S",
   168  			want:    23 * time.Second,
   169  		},
   170  		{
   171  			timeout: "1009m",
   172  			want:    1009 * time.Millisecond,
   173  		},
   174  		{
   175  			timeout: "1000003u",
   176  			want:    1000003 * time.Microsecond,
   177  		},
   178  		{
   179  			timeout: "100000007n",
   180  			want:    100000007 * time.Nanosecond,
   181  		},
   182  	} {
   183  		request.Header.Set("Grpc-Timeout", spec.timeout)
   184  		annotated, err = runtime.AnnotateContext(ctx, runtime.NewServeMux(), request)
   185  		if err != nil {
   186  			t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
   187  			return
   188  		}
   189  		deadline, ok := annotated.Deadline()
   190  		if !ok {
   191  			t.Errorf("annotated.Deadline() = _, false; want _, true; timeout = %q", spec.timeout)
   192  		}
   193  		if got, want := deadline.Sub(time.Now()), spec.want; got-want > acceptableError || got-want < -acceptableError {
   194  			t.Errorf("deadline.Sub(time.Now()) = %v; want %v; with error %v; timeout= %q", got, want, acceptableError, spec.timeout)
   195  		}
   196  	}
   197  }
   198  func TestAnnotateContext_SupportsCustomAnnotators(t *testing.T) {
   199  	md1 := func(context.Context, *http.Request) metadata.MD { return metadata.New(map[string]string{"foo": "bar"}) }
   200  	md2 := func(context.Context, *http.Request) metadata.MD { return metadata.New(map[string]string{"baz": "qux"}) }
   201  	expected := metadata.New(map[string]string{"foo": "bar", "baz": "qux"})
   202  	request, err := http.NewRequest("GET", "http://example.com", nil)
   203  	if err != nil {
   204  		t.Fatalf(`http.NewRequest("GET", "http://example.com", nil failed with %v; want success`, err)
   205  	}
   206  	annotated, err := runtime.AnnotateContext(context.Background(), runtime.NewServeMux(runtime.WithMetadata(md1), runtime.WithMetadata(md2)), request)
   207  	if err != nil {
   208  		t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
   209  		return
   210  	}
   211  	actual, _ := metadata.FromOutgoingContext(annotated)
   212  	for key, e := range expected {
   213  		if a, ok := actual[key]; !ok || !reflect.DeepEqual(e, a) {
   214  			t.Errorf("metadata.MD[%s] = %v; want %v", key, a, e)
   215  		}
   216  	}
   217  }
   218  
   219  func TestAnnotateIncomingContext_WorksWithEmpty(t *testing.T) {
   220  	ctx := context.Background()
   221  
   222  	request, err := http.NewRequest("GET", "http://www.example.com", nil)
   223  	if err != nil {
   224  		t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://www.example.com", err)
   225  	}
   226  	request.Header.Add("Some-Irrelevant-Header", "some value")
   227  	annotated, err := runtime.AnnotateIncomingContext(ctx, runtime.NewServeMux(), request)
   228  	if err != nil {
   229  		t.Errorf("runtime.AnnotateIncomingContext(ctx, %#v) failed with %v; want success", request, err)
   230  		return
   231  	}
   232  	md, ok := metadata.FromIncomingContext(annotated)
   233  	if !ok || len(md) != emptyForwardMetaCount {
   234  		t.Errorf("Expected %d metadata items in context; got %v", emptyForwardMetaCount, md)
   235  	}
   236  }
   237  
   238  func TestAnnotateIncomingContext_ForwardsGrpcMetadata(t *testing.T) {
   239  	ctx := context.Background()
   240  	request, err := http.NewRequest("GET", "http://www.example.com", nil)
   241  	if err != nil {
   242  		t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://www.example.com", err)
   243  	}
   244  	request.Header.Add("Some-Irrelevant-Header", "some value")
   245  	request.Header.Add("Grpc-Metadata-FooBar", "Value1")
   246  	request.Header.Add("Grpc-Metadata-Foo-BAZ", "Value2")
   247  	request.Header.Add("Grpc-Metadata-foo-bAz", "Value3")
   248  	request.Header.Add("Authorization", "Token 1234567890")
   249  	annotated, err := runtime.AnnotateIncomingContext(ctx, runtime.NewServeMux(), request)
   250  	if err != nil {
   251  		t.Errorf("runtime.AnnotateIncomingContext(ctx, %#v) failed with %v; want success", request, err)
   252  		return
   253  	}
   254  	md, ok := metadata.FromIncomingContext(annotated)
   255  	if got, want := len(md), emptyForwardMetaCount+4; !ok || got != want {
   256  		t.Errorf("metadata items in context = %d want %d: %v", got, want, md)
   257  	}
   258  	if got, want := md["foobar"], []string{"Value1"}; !reflect.DeepEqual(got, want) {
   259  		t.Errorf(`md["grpcgateway-foobar"] = %q; want %q`, got, want)
   260  	}
   261  	if got, want := md["foo-baz"], []string{"Value2", "Value3"}; !reflect.DeepEqual(got, want) {
   262  		t.Errorf(`md["grpcgateway-foo-baz"] = %q want %q`, got, want)
   263  	}
   264  	if got, want := md["grpcgateway-authorization"], []string{"Token 1234567890"}; !reflect.DeepEqual(got, want) {
   265  		t.Errorf(`md["grpcgateway-authorization"] = %q want %q`, got, want)
   266  	}
   267  	if got, want := md["authorization"], []string{"Token 1234567890"}; !reflect.DeepEqual(got, want) {
   268  		t.Errorf(`md["authorization"] = %q want %q`, got, want)
   269  	}
   270  }
   271  
   272  func TestAnnotateIncomingContext_ForwardGrpcBinaryMetadata(t *testing.T) {
   273  	ctx := context.Background()
   274  	request, err := http.NewRequest("GET", "http://www.example.com", nil)
   275  	if err != nil {
   276  		t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://www.example.com", err)
   277  	}
   278  
   279  	binData := []byte("\x00test-binary-data")
   280  	request.Header.Add("Grpc-Metadata-Test-Bin", base64.StdEncoding.EncodeToString(binData))
   281  
   282  	annotated, err := runtime.AnnotateIncomingContext(ctx, runtime.NewServeMux(), request)
   283  	if err != nil {
   284  		t.Errorf("runtime.AnnotateIncomingContext(ctx, %#v) failed with %v; want success", request, err)
   285  		return
   286  	}
   287  	md, ok := metadata.FromIncomingContext(annotated)
   288  	if !ok || len(md) != emptyForwardMetaCount+1 {
   289  		t.Errorf("Expected %d metadata items in context; got %v", emptyForwardMetaCount+1, md)
   290  	}
   291  	if got, want := md["test-bin"], []string{string(binData)}; !reflect.DeepEqual(got, want) {
   292  		t.Errorf(`md["test-bin"] = %q want %q`, got, want)
   293  	}
   294  }
   295  
   296  func TestAnnotateIncomingContext_XForwardedFor(t *testing.T) {
   297  	ctx := context.Background()
   298  	request, err := http.NewRequest("GET", "http://bar.foo.example.com", nil)
   299  	if err != nil {
   300  		t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://bar.foo.example.com", err)
   301  	}
   302  	request.Header.Add("X-Forwarded-For", "192.0.2.100") // client
   303  	request.RemoteAddr = "192.0.2.200:12345"             // proxy
   304  
   305  	annotated, err := runtime.AnnotateIncomingContext(ctx, runtime.NewServeMux(), request)
   306  	if err != nil {
   307  		t.Errorf("runtime.AnnotateIncomingContext(ctx, %#v) failed with %v; want success", request, err)
   308  		return
   309  	}
   310  	md, ok := metadata.FromIncomingContext(annotated)
   311  	if !ok || len(md) != emptyForwardMetaCount+1 {
   312  		t.Errorf("Expected %d metadata items in context; got %v", emptyForwardMetaCount+1, md)
   313  	}
   314  	if got, want := md["x-forwarded-host"], []string{"bar.foo.example.com"}; !reflect.DeepEqual(got, want) {
   315  		t.Errorf(`md["host"] = %v; want %v`, got, want)
   316  	}
   317  	// Note: it must be in order client, proxy1, proxy2
   318  	if got, want := md["x-forwarded-for"], []string{"192.0.2.100, 192.0.2.200"}; !reflect.DeepEqual(got, want) {
   319  		t.Errorf(`md["x-forwarded-for"] = %v want %v`, got, want)
   320  	}
   321  }
   322  
   323  func TestAnnotateIncomingContext_SupportsTimeouts(t *testing.T) {
   324  	// While run all test, TestAnnotateContext_SupportsTimeouts() will change the DefaultContextTimeout, so reset it to zero.
   325  	runtime.DefaultContextTimeout = 0 * time.Second
   326  
   327  	ctx := context.Background()
   328  	request, err := http.NewRequest("GET", "http://example.com", nil)
   329  	if err != nil {
   330  		t.Fatalf(`http.NewRequest("GET", "http://example.com", nil failed with %v; want success`, err)
   331  	}
   332  	annotated, err := runtime.AnnotateIncomingContext(ctx, runtime.NewServeMux(), request)
   333  	if err != nil {
   334  		t.Errorf("runtime.AnnotateIncomingContext(ctx, %#v) failed with %v; want success", request, err)
   335  		return
   336  	}
   337  	if _, ok := annotated.Deadline(); ok {
   338  		// no deadline by default
   339  		t.Errorf("annotated.Deadline() = _, true; want _, false")
   340  	}
   341  
   342  	const acceptableError = 50 * time.Millisecond
   343  	runtime.DefaultContextTimeout = 10 * time.Second
   344  	annotated, err = runtime.AnnotateIncomingContext(ctx, runtime.NewServeMux(), request)
   345  	if err != nil {
   346  		t.Errorf("runtime.AnnotateIncomingContext(ctx, %#v) failed with %v; want success", request, err)
   347  		return
   348  	}
   349  	deadline, ok := annotated.Deadline()
   350  	if !ok {
   351  		t.Errorf("annotated.Deadline() = _, false; want _, true")
   352  	}
   353  	if got, want := deadline.Sub(time.Now()), runtime.DefaultContextTimeout; got-want > acceptableError || got-want < -acceptableError {
   354  		t.Errorf("deadline.Sub(time.Now()) = %v; want %v; with error %v", got, want, acceptableError)
   355  	}
   356  
   357  	for _, spec := range []struct {
   358  		timeout string
   359  		want    time.Duration
   360  	}{
   361  		{
   362  			timeout: "17H",
   363  			want:    17 * time.Hour,
   364  		},
   365  		{
   366  			timeout: "19M",
   367  			want:    19 * time.Minute,
   368  		},
   369  		{
   370  			timeout: "23S",
   371  			want:    23 * time.Second,
   372  		},
   373  		{
   374  			timeout: "1009m",
   375  			want:    1009 * time.Millisecond,
   376  		},
   377  		{
   378  			timeout: "1000003u",
   379  			want:    1000003 * time.Microsecond,
   380  		},
   381  		{
   382  			timeout: "100000007n",
   383  			want:    100000007 * time.Nanosecond,
   384  		},
   385  	} {
   386  		request.Header.Set("Grpc-Timeout", spec.timeout)
   387  		annotated, err = runtime.AnnotateIncomingContext(ctx, runtime.NewServeMux(), request)
   388  		if err != nil {
   389  			t.Errorf("runtime.AnnotateIncomingContext(ctx, %#v) failed with %v; want success", request, err)
   390  			return
   391  		}
   392  		deadline, ok := annotated.Deadline()
   393  		if !ok {
   394  			t.Errorf("annotated.Deadline() = _, false; want _, true; timeout = %q", spec.timeout)
   395  		}
   396  		if got, want := deadline.Sub(time.Now()), spec.want; got-want > acceptableError || got-want < -acceptableError {
   397  			t.Errorf("deadline.Sub(time.Now()) = %v; want %v; with error %v; timeout= %q", got, want, acceptableError, spec.timeout)
   398  		}
   399  	}
   400  }
   401  func TestAnnotateIncomingContext_SupportsCustomAnnotators(t *testing.T) {
   402  	md1 := func(context.Context, *http.Request) metadata.MD { return metadata.New(map[string]string{"foo": "bar"}) }
   403  	md2 := func(context.Context, *http.Request) metadata.MD { return metadata.New(map[string]string{"baz": "qux"}) }
   404  	expected := metadata.New(map[string]string{"foo": "bar", "baz": "qux"})
   405  	request, err := http.NewRequest("GET", "http://example.com", nil)
   406  	if err != nil {
   407  		t.Fatalf(`http.NewRequest("GET", "http://example.com", nil failed with %v; want success`, err)
   408  	}
   409  	annotated, err := runtime.AnnotateIncomingContext(context.Background(), runtime.NewServeMux(runtime.WithMetadata(md1), runtime.WithMetadata(md2)), request)
   410  	if err != nil {
   411  		t.Errorf("runtime.AnnotateIncomingContext(ctx, %#v) failed with %v; want success", request, err)
   412  		return
   413  	}
   414  	actual, _ := metadata.FromIncomingContext(annotated)
   415  	for key, e := range expected {
   416  		if a, ok := actual[key]; !ok || !reflect.DeepEqual(e, a) {
   417  			t.Errorf("metadata.MD[%s] = %v; want %v", key, a, e)
   418  		}
   419  	}
   420  }
   421  

View as plain text