comparison grpc.t @ 1303:42577a840a7d

Tests: grpc module tests.
author Sergey Kandaurov <pluknet@nginx.com>
date Tue, 20 Mar 2018 18:56:33 +0300
parents
children 6f95c0ed2335
comparison
equal deleted inserted replaced
1302:beed8146d045 1303:42577a840a7d
1 #!/usr/bin/perl
2
3 # (C) Sergey Kandaurov
4 # (C) Nginx, Inc.
5
6 # Tests for grpc backend.
7
8 ###############################################################################
9
10 use warnings;
11 use strict;
12
13 use Test::More;
14
15 BEGIN { use FindBin; chdir($FindBin::Bin); }
16
17 use lib 'lib';
18 use Test::Nginx;
19 use Test::Nginx::HTTP2;
20
21 ###############################################################################
22
23 select STDERR; $| = 1;
24 select STDOUT; $| = 1;
25
26 my $t = Test::Nginx->new()->has(qw/http proxy rewrite http_v2 grpc/)
27 ->has(qw/upstream_keepalive/);
28
29 $t->write_file_expand('nginx.conf', <<'EOF');
30
31 %%TEST_GLOBALS%%
32
33 daemon off;
34
35 events {
36 }
37
38 http {
39 %%TEST_GLOBALS_HTTP%%
40
41 upstream u {
42 server 127.0.0.1:8081;
43 keepalive 1;
44 }
45
46 server {
47 listen 127.0.0.1:8080 http2;
48 server_name localhost;
49
50 http2_max_field_size 128k;
51 http2_max_header_size 128k;
52 http2_body_preread_size 128k;
53
54 location / {
55 grpc_pass grpc://127.0.0.1:8081;
56
57 if ($arg_if) {
58 # nothing
59 }
60
61 limit_except GET {
62 # nothing
63 }
64 }
65
66 location /KeepAlive {
67 grpc_pass u;
68 }
69
70 location /LongHeader {
71 grpc_pass 127.0.0.1:8081;
72 grpc_set_header X-LongHeader $arg_h;
73 }
74
75 location /LongField {
76 grpc_pass 127.0.0.1:8081;
77 grpc_buffer_size 65k;
78 }
79
80 location /SetHost {
81 grpc_pass 127.0.0.1:8081;
82 grpc_set_header Host custom;
83 }
84
85 location /SetArgs {
86 grpc_pass 127.0.0.1:8081;
87 set $args $arg_c;
88 }
89 }
90 }
91
92 EOF
93
94 $t->try_run('no grpc')->plan(97);
95
96 ###############################################################################
97
98 my $p = port(8081);
99 my $f = grpc();
100
101 my $frames = $f->{http_start}('/SayHello');
102 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
103 is($frame->{flags}, 4, 'request - HEADERS flags');
104 ok((my $sid = $frame->{sid}) % 2, 'request - HEADERS sid odd');
105 is($frame->{headers}{':method'}, 'POST', 'request - method');
106 is($frame->{headers}{':scheme'}, 'http', 'request - scheme');
107 is($frame->{headers}{':path'}, '/SayHello', 'request - path');
108 is($frame->{headers}{':authority'}, "127.0.0.1:$p", 'request - authority');
109 is($frame->{headers}{'content-type'}, 'application/grpc',
110 'request - content type');
111 is($frame->{headers}{te}, 'trailers', 'request - te');
112
113 $frames = $f->{data}('Hello');
114 ($frame) = grep { $_->{type} eq "SETTINGS" } @$frames;
115 is($frame->{flags}, 1, 'request - SETTINGS ack');
116 is($frame->{sid}, 0, 'request - SETTINGS sid');
117 is($frame->{length}, 0, 'request - SETTINGS length');
118
119 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
120 is($frame->{data}, 'Hello', 'request - DATA');
121 is($frame->{length}, 5, 'request - DATA length');
122 is($frame->{flags}, 1, 'request - DATA flags');
123 is($frame->{sid}, $sid, 'request - DATA sid match');
124
125 $frames = $f->{http_end}();
126 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
127 is($frame->{flags}, 4, 'response - HEADERS flags');
128 is($frame->{sid}, 1, 'response - HEADERS sid');
129 is($frame->{headers}{':status'}, '200', 'response - status');
130 is($frame->{headers}{'content-type'}, 'application/grpc',
131 'response - content type');
132 ok($frame->{headers}{server}, 'response - server');
133 ok($frame->{headers}{date}, 'response - date');
134 ok(my $c = $frame->{headers}{'x-connection'}, 'response - connection');
135
136 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
137 is($frame->{data}, 'Hello world', 'response - DATA');
138 is($frame->{length}, 11, 'response - DATA length');
139 is($frame->{flags}, 0, 'response - DATA flags');
140 is($frame->{sid}, 1, 'response - DATA sid');
141
142 (undef, $frame) = grep { $_->{type} eq "HEADERS" } @$frames;
143 is($frame->{flags}, 5, 'response - trailers flags');
144 is($frame->{sid}, 1, 'response - trailers sid');
145 is($frame->{headers}{'grpc-message'}, '', 'response - trailers message');
146 is($frame->{headers}{'grpc-status'}, '0', 'response - trailers status');
147
148 # next request is on a new backend connection, no sid incremented
149
150 $frames = $f->{http_start}('/SayHello');
151 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
152 is($frame->{sid}, $sid, 'request 2 - HEADERS sid again');
153 $f->{data}('Hello');
154 $frames = $f->{http_end}();
155 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
156 cmp_ok($frame->{headers}{'x-connection'}, '>', $c, 'response 2 - connection');
157
158 # upstream keepalive
159
160 $frames = $f->{http_start}('/KeepAlive');
161 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
162 is($frame->{sid}, $sid, 'keepalive - HEADERS sid');
163 $f->{data}('Hello');
164 $frames = $f->{http_end}();
165 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
166 ok($c = $frame->{headers}{'x-connection'}, 'keepalive - connection');
167
168 $frames = $f->{http_start}('/KeepAlive', reuse => 1);
169 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
170 cmp_ok($frame->{sid}, '>', $sid, 'keepalive - HEADERS sid next');
171 $f->{data}('Hello');
172 $frames = $f->{http_end}();
173 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
174 is($frame->{headers}{'x-connection'}, $c, 'keepalive - connection reuse');
175
176 # various header compression formats
177
178 $f->{http_start}('/SayHello');
179 $f->{data}('Hello');
180 $frames = $f->{http_end}(mode => 3);
181 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
182 is($frame->{headers}{':status'}, '200', 'without indexing');
183 is($frame->{headers}{'content-type'}, 'application/grpc',
184 'without indexing 2');
185
186 $f->{http_start}('/SayHello');
187 $f->{data}('Hello');
188 $frames = $f->{http_end}(mode => 4);
189 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
190 is($frame->{headers}{':status'}, '200', 'without indexing new');
191 is($frame->{headers}{'content-type'}, 'application/grpc',
192 'without indexing new 2');
193
194 $f->{http_start}('/SayHello');
195 $f->{data}('Hello');
196 $frames = $f->{http_end}(mode => 5);
197 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
198 is($frame->{headers}{':status'}, '200', 'never indexed');
199 is($frame->{headers}{'content-type'}, 'application/grpc',
200 'never indexed 2');
201
202 $f->{http_start}('/SayHello');
203 $f->{data}('Hello');
204 $frames = $f->{http_end}(mode => 6);
205 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
206 is($frame->{headers}{':status'}, '200', 'never indexed new');
207 is($frame->{headers}{'content-type'}, 'application/grpc',
208 'never indexed new 2');
209
210 # padding & priority
211
212 $f->{http_start}('/SayHello');
213 $f->{data}('Hello');
214 $frames = $f->{http_end}(padding => 7);
215 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
216 is($frame->{headers}{':status'}, '200', 'padding');
217
218 $f->{http_start}('/SayHello');
219 $f->{data}('Hello');
220 $frames = $f->{http_end}(prio => 137, dep => 0x01020304);
221 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
222 is($frame->{headers}{':status'}, '200', 'priority');
223
224 $f->{http_start}('/SayHello');
225 $f->{data}('Hello');
226 $frames = $f->{http_end}(padding => 7, prio => 137, dep => 0x01020304);
227 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
228 is($frame->{headers}{':status'}, '200', 'padding priority');
229
230 SKIP: {
231 skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE};
232
233 $f->{http_start}('/SaySplit');
234 $f->{data}('Hello');
235 $frames = $f->{http_end}(padding => 7, prio => 137, dep => 0x01020304,
236 split => [(map{1}(1..20)), 30], split_delay => 0.1);
237 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
238 is($frame->{headers}{':status'}, '200', 'padding priority split');
239
240 }
241
242 # grpc error, no empty data frame expected
243
244 $f->{http_start}('/SayHello');
245 $f->{data}('Hello');
246 $frames = $f->{http_err}();
247 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
248 is($frame->{flags}, 5, 'grpc error - HEADERS flags');
249 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
250 ok(!$frame, 'grpc error - no DATA frame');
251
252 # continuation from backend, expect parts assembled
253
254 $f->{http_start}('/SayHello');
255 $f->{data}('Hello');
256 $frames = $f->{continuation}();
257 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
258 is($frame->{flags}, 4, 'continuation - HEADERS flags');
259 is($frame->{headers}{':status'}, '200', 'continuation - status');
260 is($frame->{headers}{'content-type'}, 'application/grpc',
261 'continuation - content type');
262
263 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
264 is($frame->{data}, 'Hello world', 'continuation - DATA');
265 is($frame->{length}, 11, 'continuation - DATA length');
266 is($frame->{flags}, 0, 'continuation - DATA flags');
267
268 (undef, $frame) = grep { $_->{type} eq "HEADERS" } @$frames;
269 is($frame->{flags}, 5, 'continuation - trailers flags');
270 is($frame->{headers}{'grpc-message'}, '', 'continuation - trailers message');
271 is($frame->{headers}{'grpc-status'}, '0', 'continuation - trailers status');
272
273 # continuation from backend, header split
274
275 $f->{http_start}('/SayHello');
276 $f->{data}('Hello');
277 $frames = $f->{http_end}(mode => 6, continuation => [map { 1 } (1 .. 42)]);
278 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
279 is($frame->{headers}{':status'}, '200', 'continuation - header split');
280
281 # continuation to backend
282
283 $frames = $f->{http_start}('/LongHeader?h=' . ('Z' x 31337));
284 @$frames = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
285 is(@$frames, 4, 'continuation - frames');
286
287 $frame = shift @$frames;
288 is($frame->{type}, 'HEADERS', 'continuation - HEADERS');
289 is($frame->{length}, 16384, 'continuation - HEADERS length');
290 is($frame->{flags}, 1, 'continuation - HEADERS flags');
291 ok($frame->{sid}, 'continuation - HEADERS sid');
292
293 $frame = shift @$frames;
294 is($frame->{type}, 'CONTINUATION', 'continuation - CONTINUATION');
295 is($frame->{length}, 16384, 'continuation - CONTINUATION length');
296 is($frame->{flags}, 0, 'continuation - CONTINUATION flags');
297 ok($frame->{sid}, 'continuation - CONTINUATION sid');
298
299 $frame = shift @$frames;
300 is($frame->{type}, 'CONTINUATION', 'continuation - CONTINUATION 2');
301 is($frame->{length}, 16384, 'continuation - CONTINUATION 2 length');
302 is($frame->{flags}, 0, 'continuation - CONTINUATION 2 flags');
303
304 $frame = shift @$frames;
305 is($frame->{type}, 'CONTINUATION', 'continuation - CONTINUATION n');
306 cmp_ok($frame->{length}, '<', 16384, 'continuation - CONTINUATION n length');
307 is($frame->{flags}, 4, 'continuation - CONTINUATION n flags');
308 is($frame->{headers}{':path'}, '/LongHeader?h=' . 'Z' x 31337,
309 'continuation - path');
310 is($frame->{headers}{'x-longheader'}, 'Z' x 31337, 'continuation - header');
311
312 $f->{http_end}();
313
314 # long header field
315
316 $f->{http_start}('/LongField');
317 $f->{data}('Hello');
318 $frames = $f->{field_len}(2**7);
319 ($frame) = grep { $_->{flags} & 0x4 } @$frames;
320 is($frame->{headers}{'x' x 2**7}, 'y' x 2**7, 'long header field 1');
321
322 $f->{http_start}('/LongField');
323 $f->{data}('Hello');
324 $frames = $f->{field_len}(2**8);
325 ($frame) = grep { $_->{flags} & 0x4 } @$frames;
326 is($frame->{headers}{'x' x 2**8}, 'y' x 2**8, 'long header field 2');
327
328 $f->{http_start}('/LongField');
329 $f->{data}('Hello');
330 $frames = $f->{field_len}(2**15);
331 ($frame) = grep { $_->{flags} & 0x4 } @$frames;
332 is($frame->{headers}{'x' x 2**15}, 'y' x 2**15, 'long header field 3');
333
334 # flow control
335
336 $f->{http_start}('/FlowControl');
337 $frames = $f->{data_len}(('Hello' x 13000) . ('x' x 550), 65535);
338 my $sum = eval join '+', map { $_->{type} eq "DATA" && $_->{length} } @$frames;
339 is($sum, 65535, 'flow control - iws length');
340
341 $f->{update}(10);
342 $f->{update_sid}(10);
343
344 $frames = $f->{data_len}(undef, 10);
345 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
346 is($frame->{length}, 10, 'flow control - update length');
347 is($frame->{flags}, 0, 'flow control - update flags');
348
349 $f->{update_sid}(10);
350 $f->{update}(10);
351
352 $frames = $f->{data_len}(undef, 5);
353 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
354 is($frame->{length}, 5, 'flow control - rest length');
355 is($frame->{flags}, 1, 'flow control - rest flags');
356
357 $f->{http_end}();
358
359 # DATA padding
360
361 $f->{http_start}('/SayPadding');
362 $f->{data}('Hello');
363 $frames = $f->{http_end}(body_padding => 42);
364 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
365 is($frame->{data}, 'Hello world', 'response - DATA');
366 is($frame->{length}, 11, 'response - DATA length');
367 is($frame->{flags}, 0, 'response - DATA flags');
368
369 # :authority inheritance
370
371 $frames = $f->{http_start}('/SayHello?if=1');
372 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
373 is($frame->{headers}{':authority'}, "127.0.0.1:$p", 'authority in if');
374 $f->{data}('Hello');
375 $f->{http_end}();
376
377 # misc tests
378
379 $frames = $f->{http_start}('/SetHost');
380 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
381 ok(!$frame->{headers}{':authority'}, 'set host - authority');
382 is($frame->{headers}{'host'}, 'custom', 'set host - host');
383 $f->{data}('Hello');
384 $f->{http_end}();
385
386 $frames = $f->{http_start}('/SetArgs?f');
387 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
388 is($frame->{headers}{':path'}, '/SetArgs', 'set args');
389 $f->{data}('Hello');
390 $f->{http_end}();
391
392 $frames = $f->{http_start}('/SetArgs?c=1');
393 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
394 is($frame->{headers}{':path'}, '/SetArgs?1', 'set args len');
395 $f->{data}('Hello');
396 $f->{http_end}();
397
398 $frames = $f->{http_start}('/SetArgs esc');
399 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
400 is($frame->{headers}{':path'}, '/SetArgs%20esc', 'uri escape');
401 $f->{data}('Hello');
402 $f->{http_end}();
403
404 $frames = $f->{http_start}('/');
405 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
406 is($frame->{headers}{':path'}, '/', 'root index');
407 $f->{data}('Hello');
408 $f->{http_end}();
409
410 $frames = $f->{http_start}('/', method => 'GET');
411 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
412 is($frame->{headers}{':method'}, 'GET', 'method get');
413 $f->{data}('Hello');
414 $f->{http_end}();
415
416 $frames = $f->{http_start}('/', method => 'HEAD');
417 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
418 is($frame->{headers}{':method'}, 'HEAD', 'method head');
419 $f->{data}('Hello');
420 $f->{http_end}();
421
422 ###############################################################################
423
424 sub grpc {
425 my ($server, $client, $f, $s, $c, $sid, $uri);
426 my $n = 0;
427
428 $server = IO::Socket::INET->new(
429 Proto => 'tcp',
430 LocalHost => '127.0.0.1',
431 LocalPort => $p,
432 Listen => 5,
433 Reuse => 1
434 )
435 or die "Can't create listening socket: $!\n";
436
437 $f->{http_start} = sub {
438 ($uri, my %extra) = @_;
439 my $body_more = 1 if $uri !~ /LongHeader/;
440 my $meth = $extra{method} || 'POST';
441 $s = Test::Nginx::HTTP2->new() if !defined $s;
442 $s->new_stream({ body_more => $body_more, headers => [
443 { name => ':method', value => $meth, mode => !!$meth },
444 { name => ':scheme', value => 'http', mode => 0 },
445 { name => ':path', value => $uri, },
446 { name => ':authority', value => 'localhost' },
447 { name => 'content-type', value => 'application/grpc' },
448 { name => 'te', value => 'trailers', mode => 2 }]});
449
450 if (!$extra{reuse}) {
451 $client = $server->accept() or return;
452 log2c("(new connection $client)");
453 $n++;
454
455 $client->sysread(my $buf, 24) == 24 or return; # preface
456
457 $c = Test::Nginx::HTTP2->new(1, socket => $client,
458 pure => 1, preface => "") or return;
459 }
460
461 my $frames = $c->read(all => [{ fin => 4 }]);
462
463 if (!$extra{reuse}) {
464 $c->h2_settings(0);
465 $c->h2_settings(1);
466 }
467
468 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
469 $sid = $frame->{sid};
470 return $frames;
471 };
472 $f->{data} = sub {
473 my ($body, %extra) = @_;
474 $s->h2_body($body, { %extra });
475 return $c->read(all => [{ sid => $sid,
476 length => length($body) }]);
477 };
478 $f->{data_len} = sub {
479 my ($body, $len) = @_;
480 $s->h2_body($body) if defined $body;
481 return $c->read(all => [{ sid => $sid, length => $len }]);
482 };
483 $f->{update} = sub {
484 $c->h2_window(shift);
485 };
486 $f->{update_sid} = sub {
487 $c->h2_window(shift, $sid);
488 };
489 $f->{http_end} = sub {
490 my (%extra) = @_;
491 $c->new_stream({ body_more => 1, %extra, headers => [
492 { name => ':status', value => '200',
493 mode => $extra{mode} || 0 },
494 { name => 'content-type', value => 'application/grpc',
495 mode => $extra{mode} || 1, huff => 1 },
496 { name => 'x-connection', value => $n,
497 mode => 2, huff => 1 },
498 ]}, $sid);
499 $c->h2_body('Hello world', { body_more => 1,
500 body_padding => $extra{body_padding} });
501 $c->new_stream({ headers => [
502 { name => 'grpc-status', value => '0',
503 mode => 2, huff => 1 },
504 { name => 'grpc-message', value => '',
505 mode => 2, huff => 1 },
506 ]}, $sid);
507
508 return $s->read(all => [{ fin => 1 }]);
509 };
510 $f->{http_err} = sub {
511 $c->new_stream({ headers => [
512 { name => ':status', value => '200', mode => 0 },
513 { name => 'content-type', value => 'application/grpc',
514 mode => 1, huff => 1 },
515 { name => 'grpc-status', value => '12',
516 mode => 2, huff => 1 },
517 { name => 'grpc-message', value => 'unknown service',
518 mode => 2, huff => 1 },
519 ]}, $sid);
520
521 return $s->read(all => [{ fin => 1 }]);
522 };
523 $f->{continuation} = sub {
524 $c->new_stream({ continuation => 1, body_more => 1, headers => [
525 { name => ':status', value => '200', mode => 0 },
526 ]}, $sid);
527 $c->h2_continue($sid, { continuation => 1, headers => [
528 { name => 'content-type', value => 'application/grpc',
529 mode => 1, huff => 1 },
530 ]});
531 $c->h2_continue($sid, { headers => [
532 # an empty CONTINUATION frame is legitimate
533 ]});
534 $c->h2_body('Hello world', { body_more => 1 });
535 $c->new_stream({ continuation => 1, headers => [
536 { name => 'grpc-status', value => '0',
537 mode => 2, huff => 1 },
538 ]}, $sid);
539 $c->h2_continue($sid, { headers => [
540 { name => 'grpc-message', value => '',
541 mode => 2, huff => 1 },
542 ]});
543
544 return $s->read(all => [{ fin => 1 }]);
545 };
546 $f->{field_len} = sub {
547 my ($len) = @_;
548 $c->new_stream({ continuation => [map {2**14} (0..$len/2**13)],
549 body_more => 1, headers => [
550 { name => ':status', value => '200', mode => 0 },
551 { name => 'content-type', value => 'application/grpc',
552 mode => 1, huff => 1 },
553 { name => 'x' x $len, value => 'y' x $len, mode => 6 },
554 ]}, $sid);
555 $c->h2_body('Hello world', { body_more => 1 });
556 $c->new_stream({ headers => [
557 { name => 'grpc-status', value => '0',
558 mode => 2, huff => 1 },
559 { name => 'grpc-message', value => '',
560 mode => 2, huff => 1 },
561 ]}, $sid);
562
563 return $s->read(all => [{ fin => 1 }]);
564 };
565 return $f;
566 }
567
568 sub log2i { Test::Nginx::log_core('|| <<', @_); }
569 sub log2o { Test::Nginx::log_core('|| >>', @_); }
570 sub log2c { Test::Nginx::log_core('||', @_); }
571
572 ###############################################################################