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")
103 request.RemoteAddr = "192.0.2.200:12345"
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
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
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")
303 request.RemoteAddr = "192.0.2.200:12345"
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
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
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
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