comparison lib/Test/Nginx/HTTP2.pm @ 949:c657aaffdaa8

Tests: style.
author Sergey Kandaurov <pluknet@nginx.com>
date Fri, 17 Jun 2016 11:41:30 +0300
parents 4dc302d8e04f
children 9361c7eddfc1
comparison
equal deleted inserted replaced
948:4dc302d8e04f 949:c657aaffdaa8
29 7 => { name => 'GOAWAY', value => \&goaway }, 29 7 => { name => 'GOAWAY', value => \&goaway },
30 8 => { name => 'WINDOW_UPDATE', value => \&window_update }, 30 8 => { name => 'WINDOW_UPDATE', value => \&window_update },
31 9 => { name => 'CONTINUATION', value => \&headers }, 31 9 => { name => 'CONTINUATION', value => \&headers },
32 ); 32 );
33 33
34 sub new {
35 my $class = shift;
36 my ($port, %extra) = @_;
37
38 my $s = $extra{socket} || new_socket($port, %extra);
39 my $preface = $extra{preface}
40 || 'PRI * HTTP/2.0' . CRLF . CRLF . 'SM' . CRLF . CRLF;
41
42 if ($extra{proxy}) {
43 raw_write($s, $extra{proxy});
44 }
45
46 # preface
47
48 raw_write($s, $preface);
49
50 my $self = bless {
51 socket => $s, last_stream => -1,
52 dynamic_encode => [ static_table() ],
53 dynamic_decode => [ static_table() ],
54 static_table_size => scalar @{[static_table()]},
55 iws => 65535, conn_window => 65535, streams => {}
56 }, $class;
57
58 return $self if $extra{pure};
59
60 # update windows, if any
61
62 my $frames = $self->read(all => [
63 { type => 'WINDOW_UPDATE' },
64 { type => 'SETTINGS'}
65 ]);
66
67 # 6.5.3. Settings Synchronization
68
69 if (grep { $_->{type} eq "SETTINGS" && $_->{flags} == 0 } @$frames) {
70 $self->h2_settings(1);
71 }
72
73 return $self;
74 }
75
34 sub h2_ping { 76 sub h2_ping {
35 my ($sess, $payload) = @_; 77 my ($self, $payload) = @_;
36 78
37 raw_write($sess->{socket}, pack("x2C2x5a8", 8, 0x6, $payload)); 79 raw_write($self->{socket}, pack("x2C2x5a8", 8, 0x6, $payload));
38 } 80 }
39 81
40 sub h2_rst { 82 sub h2_rst {
41 my ($sess, $stream, $error) = @_; 83 my ($self, $stream, $error) = @_;
42 84
43 raw_write($sess->{socket}, pack("x2C2xNN", 4, 0x3, $stream, $error)); 85 raw_write($self->{socket}, pack("x2C2xNN", 4, 0x3, $stream, $error));
44 } 86 }
45 87
46 sub h2_goaway { 88 sub h2_goaway {
47 my ($sess, $stream, $lstream, $err, $debug, %extra) = @_; 89 my ($self, $stream, $lstream, $err, $debug, %extra) = @_;
48 $debug = '' unless defined $debug; 90 $debug = '' unless defined $debug;
49 my $len = defined $extra{len} ? $extra{len} : 8 + length($debug); 91 my $len = defined $extra{len} ? $extra{len} : 8 + length($debug);
50 my $buf = pack("x2C2xN3A*", $len, 0x7, $stream, $lstream, $err, $debug); 92 my $buf = pack("x2C2xN3A*", $len, 0x7, $stream, $lstream, $err, $debug);
51 93
52 my @bufs = map { 94 my @bufs = map {
53 raw_write($sess->{socket}, substr $buf, 0, $_, ""); 95 raw_write($self->{socket}, substr $buf, 0, $_, "");
54 select undef, undef, undef, 0.2; 96 select undef, undef, undef, 0.2;
55 } @{$extra{split}}; 97 } @{$extra{split}};
56 98
57 raw_write($sess->{socket}, $buf); 99 raw_write($self->{socket}, $buf);
58 } 100 }
59 101
60 sub h2_priority { 102 sub h2_priority {
61 my ($sess, $w, $stream, $dep, %extra) = @_; 103 my ($self, $w, $stream, $dep, %extra) = @_;
62 104
63 $stream = 0 unless defined $stream; 105 $stream = 0 unless defined $stream;
64 $dep = 0 unless defined $dep; 106 $dep = 0 unless defined $dep;
65 $dep |= $extra{excl} << 31 if exists $extra{excl}; 107 $dep |= $extra{excl} << 31 if exists $extra{excl};
66 raw_write($sess->{socket}, pack("x2C2xNNC", 5, 0x2, $stream, $dep, $w)); 108 raw_write($self->{socket}, pack("x2C2xNNC", 5, 0x2, $stream, $dep, $w));
67 } 109 }
68 110
69 sub h2_window { 111 sub h2_window {
70 my ($sess, $win, $stream) = @_; 112 my ($self, $win, $stream) = @_;
71 113
72 $stream = 0 unless defined $stream; 114 $stream = 0 unless defined $stream;
73 raw_write($sess->{socket}, pack("x2C2xNN", 4, 0x8, $stream, $win)); 115 raw_write($self->{socket}, pack("x2C2xNN", 4, 0x8, $stream, $win));
74 } 116 }
75 117
76 sub h2_settings { 118 sub h2_settings {
77 my ($sess, $ack, %extra) = @_; 119 my ($self, $ack, %extra) = @_;
78 120
79 my $len = 6 * keys %extra; 121 my $len = 6 * keys %extra;
80 my $buf = pack_length($len) . pack "CCx4", 0x4, $ack ? 0x1 : 0x0; 122 my $buf = pack_length($len) . pack "CCx4", 0x4, $ack ? 0x1 : 0x0;
81 $buf .= join '', map { pack "nN", $_, $extra{$_} } keys %extra; 123 $buf .= join '', map { pack "nN", $_, $extra{$_} } keys %extra;
82 raw_write($sess->{socket}, $buf); 124 raw_write($self->{socket}, $buf);
83 } 125 }
84 126
85 sub h2_unknown { 127 sub h2_unknown {
86 my ($sess, $payload) = @_; 128 my ($self, $payload) = @_;
87 129
88 my $buf = pack_length(length($payload)) . pack("Cx5a*", 0xa, $payload); 130 my $buf = pack_length(length($payload)) . pack("Cx5a*", 0xa, $payload);
89 raw_write($sess->{socket}, $buf); 131 raw_write($self->{socket}, $buf);
90 } 132 }
91 133
92 sub h2_continue { 134 sub h2_continue {
93 my ($ctx, $stream, $uri) = @_; 135 my ($ctx, $stream, $uri) = @_;
94 136
95 $uri->{h2_continue} = 1; 137 $uri->{h2_continue} = 1;
96 return new_stream($ctx, $uri, $stream); 138 return new_stream($ctx, $uri, $stream);
97 } 139 }
98 140
99 sub h2_body { 141 sub h2_body {
100 my ($sess, $body, $extra) = @_; 142 my ($self, $body, $extra) = @_;
101 $extra = {} unless defined $extra; 143 $extra = {} unless defined $extra;
102 144
103 my $len = length $body; 145 my $len = length $body;
104 my $sid = $sess->{last_stream}; 146 my $sid = $self->{last_stream};
105 147
106 if ($len > $sess->{conn_window} || $len > $sess->{streams}{$sid}) { 148 if ($len > $self->{conn_window} || $len > $self->{streams}{$sid}) {
107 $sess->read(all => [{ type => 'WINDOW_UPDATE' }]); 149 $self->read(all => [{ type => 'WINDOW_UPDATE' }]);
108 } 150 }
109 151
110 if ($len > $sess->{conn_window} || $len > $sess->{streams}{$sid}) { 152 if ($len > $self->{conn_window} || $len > $self->{streams}{$sid}) {
111 return; 153 return;
112 } 154 }
113 155
114 $sess->{conn_window} -= $len; 156 $self->{conn_window} -= $len;
115 $sess->{streams}{$sid} -= $len; 157 $self->{streams}{$sid} -= $len;
116 158
117 my $buf; 159 my $buf;
118 160
119 my $split = ref $extra->{body_split} && $extra->{body_split} || []; 161 my $split = ref $extra->{body_split} && $extra->{body_split} || [];
120 for (@$split) { 162 for (@$split) {
121 $buf .= pack_body($sess, substr($body, 0, $_, ""), 0x0, $extra); 163 $buf .= pack_body($self, substr($body, 0, $_, ""), 0x0, $extra);
122 } 164 }
123 165
124 $buf .= pack_body($sess, $body, 0x1, $extra) if defined $body; 166 $buf .= pack_body($self, $body, 0x1, $extra) if defined $body;
125 167
126 $split = ref $extra->{split} && $extra->{split} || []; 168 $split = ref $extra->{split} && $extra->{split} || [];
127 for (@$split) { 169 for (@$split) {
128 raw_write($sess->{socket}, substr($buf, 0, $_, "")); 170 raw_write($self->{socket}, substr($buf, 0, $_, ""));
129 return if $extra->{abort}; 171 return if $extra->{abort};
130 select undef, undef, undef, ($extra->{split_delay} || 0.2); 172 select undef, undef, undef, ($extra->{split_delay} || 0.2);
131 } 173 }
132 174
133 raw_write($sess->{socket}, $buf); 175 raw_write($self->{socket}, $buf);
134 } 176 }
177
178 sub new_stream {
179 my ($self, $uri, $stream) = @_;
180 my ($input, $buf);
181 my ($d, $status);
182
183 $self->{headers} = '';
184
185 my $host = $uri->{host} || 'localhost';
186 my $method = $uri->{method} || 'GET';
187 my $scheme = $uri->{scheme} || 'http';
188 my $path = $uri->{path} || '/';
189 my $headers = $uri->{headers};
190 my $body = $uri->{body};
191 my $prio = $uri->{prio};
192 my $dep = $uri->{dep};
193
194 my $pad = defined $uri->{padding} ? $uri->{padding} : 0;
195 my $padlen = defined $uri->{padding} ? 1 : 0;
196
197 my $type = defined $uri->{h2_continue} ? 0x9 : 0x1;
198 my $flags = defined $uri->{continuation} ? 0x0 : 0x4;
199 $flags |= 0x1 unless defined $body || defined $uri->{body_more};
200 $flags |= 0x8 if $padlen;
201 $flags |= 0x20 if defined $dep || defined $prio;
202
203 if ($stream) {
204 $self->{last_stream} = $stream;
205 } else {
206 $self->{last_stream} += 2;
207 $self->{streams}{$self->{last_stream}} = $self->{iws};
208 }
209
210 $buf = pack("xxx"); # Length stub
211 $buf .= pack("CC", $type, $flags); # END_HEADERS
212 $buf .= pack("N", $self->{last_stream}); # Stream-ID
213
214 $dep = 0 if defined $prio and not defined $dep;
215 $prio = 16 if defined $dep and not defined $prio;
216
217 unless ($headers) {
218 $input = hpack($self, ":method", $method);
219 $input .= hpack($self, ":scheme", $scheme);
220 $input .= hpack($self, ":path", $path);
221 $input .= hpack($self, ":authority", $host);
222 $input .= hpack($self, "content-length", length($body))
223 if $body;
224
225 } else {
226 $input = join '', map {
227 hpack($self, $_->{name}, $_->{value},
228 mode => $_->{mode}, huff => $_->{huff})
229 } @$headers if $headers;
230 }
231
232 $input = pack("B*", '001' . ipack(5, $uri->{table_size})) . $input
233 if defined $uri->{table_size};
234
235 my $split = ref $uri->{continuation} && $uri->{continuation} || [];
236 my @input = map { substr $input, 0, $_, "" } @$split;
237 push @input, $input;
238
239 # set length, attach headers, padding, priority
240
241 my $hlen = length($input[0]) + $pad + $padlen;
242 $hlen += 5 if $flags & 0x20;
243 $buf |= pack_length($hlen);
244
245 $buf .= pack 'C', $pad if $padlen; # Pad Length?
246 $buf .= pack 'NC', $dep, $prio if $flags & 0x20;
247 $buf .= $input[0];
248 $buf .= (pack 'C', 0) x $pad if $padlen; # Padding
249
250 shift @input;
251
252 while (@input) {
253 $input = shift @input;
254 $flags = @input ? 0x0 : 0x4;
255 $buf .= pack_length(length($input));
256 $buf .= pack("CC", 0x9, $flags);
257 $buf .= pack("N", $self->{last_stream});
258 $buf .= $input;
259 }
260
261 $split = ref $uri->{body_split} && $uri->{body_split} || [];
262 for (@$split) {
263 $buf .= pack_body($self, substr($body, 0, $_, ""), 0x0, $uri);
264 }
265
266 $buf .= pack_body($self, $body, 0x1, $uri) if defined $body;
267
268 $split = ref $uri->{split} && $uri->{split} || [];
269 for (@$split) {
270 raw_write($self->{socket}, substr($buf, 0, $_, ""));
271 goto done if $uri->{abort};
272 select undef, undef, undef, ($uri->{split_delay} || 0.2);
273 }
274
275 raw_write($self->{socket}, $buf);
276 done:
277 return $self->{last_stream};
278 }
279
280 sub read {
281 my ($self, %extra) = @_;
282 my (@got);
283 my $s = $self->{socket};
284 my $buf = '';
285 my $wait = $extra{wait};
286
287 local $Data::Dumper::Terse = 1;
288
289 while (1) {
290 $buf = raw_read($s, $buf, 9, $wait);
291 last if length $buf < 9;
292
293 my $length = unpack_length($buf);
294 my $type = unpack('x3C', $buf);
295 my $flags = unpack('x4C', $buf);
296
297 my $stream = unpack "x5 B32", $buf;
298 substr($stream, 0, 1) = 0;
299 $stream = unpack("N", pack("B32", $stream));
300
301 $buf = raw_read($s, $buf, $length + 9, $wait);
302 last if length($buf) < $length + 9;
303
304 $buf = substr($buf, 9);
305
306 my $frame = $cframe{$type}{value}($self, $buf, $length, $flags,
307 $stream);
308 $frame->{length} = $length;
309 $frame->{type} = $cframe{$type}{name};
310 $frame->{flags} = $flags;
311 $frame->{sid} = $stream;
312 push @got, $frame;
313
314 Test::Nginx::log_core('||', $_) for split "\n", Dumper $frame;
315
316 $buf = substr($buf, $length);
317
318 last unless $extra{all} && test_fin($got[-1], $extra{all});
319 };
320 return \@got;
321 }
322
323 ###############################################################################
135 324
136 sub pack_body { 325 sub pack_body {
137 my ($ctx, $body, $flags, $extra) = @_; 326 my ($ctx, $body, $flags, $extra) = @_;
138 327
139 my $pad = defined $extra->{body_padding} ? $extra->{body_padding} : 0; 328 my $pad = defined $extra->{body_padding} ? $extra->{body_padding} : 0;
148 $buf .= $body; 337 $buf .= $body;
149 $buf .= pack "x$pad" if $padlen; # DATA Padding 338 $buf .= pack "x$pad" if $padlen; # DATA Padding
150 return $buf; 339 return $buf;
151 } 340 }
152 341
153 sub new_stream {
154 my ($ctx, $uri, $stream) = @_;
155 my ($input, $buf);
156 my ($d, $status);
157
158 $ctx->{headers} = '';
159
160 my $host = $uri->{host} || 'localhost';
161 my $method = $uri->{method} || 'GET';
162 my $scheme = $uri->{scheme} || 'http';
163 my $path = $uri->{path} || '/';
164 my $headers = $uri->{headers};
165 my $body = $uri->{body};
166 my $prio = $uri->{prio};
167 my $dep = $uri->{dep};
168
169 my $pad = defined $uri->{padding} ? $uri->{padding} : 0;
170 my $padlen = defined $uri->{padding} ? 1 : 0;
171
172 my $type = defined $uri->{h2_continue} ? 0x9 : 0x1;
173 my $flags = defined $uri->{continuation} ? 0x0 : 0x4;
174 $flags |= 0x1 unless defined $body || defined $uri->{body_more};
175 $flags |= 0x8 if $padlen;
176 $flags |= 0x20 if defined $dep || defined $prio;
177
178 if ($stream) {
179 $ctx->{last_stream} = $stream;
180 } else {
181 $ctx->{last_stream} += 2;
182 $ctx->{streams}{$ctx->{last_stream}} = $ctx->{iws};
183 }
184
185 $buf = pack("xxx"); # Length stub
186 $buf .= pack("CC", $type, $flags); # END_HEADERS
187 $buf .= pack("N", $ctx->{last_stream}); # Stream-ID
188
189 $dep = 0 if defined $prio and not defined $dep;
190 $prio = 16 if defined $dep and not defined $prio;
191
192 unless ($headers) {
193 $input = hpack($ctx, ":method", $method);
194 $input .= hpack($ctx, ":scheme", $scheme);
195 $input .= hpack($ctx, ":path", $path);
196 $input .= hpack($ctx, ":authority", $host);
197 $input .= hpack($ctx, "content-length", length($body)) if $body;
198
199 } else {
200 $input = join '', map {
201 hpack($ctx, $_->{name}, $_->{value},
202 mode => $_->{mode}, huff => $_->{huff})
203 } @$headers if $headers;
204 }
205
206 $input = pack("B*", '001' . ipack(5, $uri->{table_size})) . $input
207 if defined $uri->{table_size};
208
209 my $split = ref $uri->{continuation} && $uri->{continuation} || [];
210 my @input = map { substr $input, 0, $_, "" } @$split;
211 push @input, $input;
212
213 # set length, attach headers, padding, priority
214
215 my $hlen = length($input[0]) + $pad + $padlen;
216 $hlen += 5 if $flags & 0x20;
217 $buf |= pack_length($hlen);
218
219 $buf .= pack 'C', $pad if $padlen; # Pad Length?
220 $buf .= pack 'NC', $dep, $prio if $flags & 0x20;
221 $buf .= $input[0];
222 $buf .= (pack 'C', 0) x $pad if $padlen; # Padding
223
224 shift @input;
225
226 while (@input) {
227 $input = shift @input;
228 $flags = @input ? 0x0 : 0x4;
229 $buf .= pack_length(length($input));
230 $buf .= pack("CC", 0x9, $flags);
231 $buf .= pack("N", $ctx->{last_stream});
232 $buf .= $input;
233 }
234
235 $split = ref $uri->{body_split} && $uri->{body_split} || [];
236 for (@$split) {
237 $buf .= pack_body($ctx, substr($body, 0, $_, ""), 0x0, $uri);
238 }
239
240 $buf .= pack_body($ctx, $body, 0x1, $uri) if defined $body;
241
242 $split = ref $uri->{split} && $uri->{split} || [];
243 for (@$split) {
244 raw_write($ctx->{socket}, substr($buf, 0, $_, ""));
245 goto done if $uri->{abort};
246 select undef, undef, undef, ($uri->{split_delay} || 0.2);
247 }
248
249 raw_write($ctx->{socket}, $buf);
250 done:
251 return $ctx->{last_stream};
252 }
253
254 sub read {
255 my ($sess, %extra) = @_;
256 my (@got);
257 my $s = $sess->{socket};
258 my $buf = '';
259 my $wait = $extra{wait};
260
261 local $Data::Dumper::Terse = 1;
262
263 while (1) {
264 $buf = raw_read($s, $buf, 9, \&log_in, $wait);
265 last if length $buf < 9;
266
267 my $length = unpack_length($buf);
268 my $type = unpack('x3C', $buf);
269 my $flags = unpack('x4C', $buf);
270
271 my $stream = unpack "x5 B32", $buf;
272 substr($stream, 0, 1) = 0;
273 $stream = unpack("N", pack("B32", $stream));
274
275 $buf = raw_read($s, $buf, $length + 9, \&log_in, $wait);
276 last if length($buf) < $length + 9;
277
278 $buf = substr($buf, 9);
279
280 my $frame = $cframe{$type}{value}($sess, $buf, $length, $flags,
281 $stream);
282 $frame->{length} = $length;
283 $frame->{type} = $cframe{$type}{name};
284 $frame->{flags} = $flags;
285 $frame->{sid} = $stream;
286 push @got, $frame;
287
288 Test::Nginx::log_core('||', $_) for split "\n", Dumper $frame;
289
290 $buf = substr($buf, $length);
291
292 last unless $extra{all} && test_fin($got[-1], $extra{all});
293 };
294 return \@got;
295 }
296
297 sub test_fin { 342 sub test_fin {
298 my ($frame, $all) = @_; 343 my ($frame, $all) = @_;
299 my @test = @{$all}; 344 my @test = @{$all};
300 345
301 # wait for the specified DATA length 346 # wait for the specified DATA length
400 sub unpack_length { 445 sub unpack_length {
401 unpack 'N', pack 'xc3', unpack 'c3', $_[0]; 446 unpack 'N', pack 'xc3', unpack 'c3', $_[0];
402 } 447 }
403 448
404 sub raw_read { 449 sub raw_read {
405 my ($s, $buf, $len, $log, $timo) = @_; 450 my ($s, $buf, $len, $timo) = @_;
406 $log = \&log_in unless defined $log;
407 $timo = 3 unless $timo; 451 $timo = 3 unless $timo;
408 my $got = ''; 452 my $got = '';
409 453
410 while (length($buf) < $len && IO::Select->new($s)->can_read($timo)) { 454 while (length($buf) < $len && IO::Select->new($s)->can_read($timo)) {
411 $s->sysread($got, 16384) or last; 455 $s->sysread($got, 16384) or last;
412 $log->($got); 456 log_in($got);
413 $buf .= $got; 457 $buf .= $got;
414 } 458 }
415 return $buf; 459 return $buf;
416 } 460 }
417 461
425 my $n = $s->syswrite($message); 469 my $n = $s->syswrite($message);
426 last unless $n; 470 last unless $n;
427 $message = substr($message, $n); 471 $message = substr($message, $n);
428 last unless length $message; 472 last unless length $message;
429 } 473 }
430 }
431
432 sub new {
433 my $class = shift;
434 my ($port, %extra) = @_;
435
436 my $s = $extra{socket} || new_socket($port, %extra);
437 my $preface = $extra{preface}
438 || 'PRI * HTTP/2.0' . CRLF . CRLF . 'SM' . CRLF . CRLF;
439
440 if ($extra{proxy}) {
441 raw_write($s, $extra{proxy});
442 }
443
444 # preface
445
446 raw_write($s, $preface);
447
448 my $ctx = { socket => $s, last_stream => -1,
449 dynamic_encode => [ static_table() ],
450 dynamic_decode => [ static_table() ],
451 static_table_size => scalar @{[static_table()]},
452 iws => 65535, conn_window => 65535, streams => {}};
453 bless $ctx, $class;
454
455 return $ctx if $extra{pure};
456
457 # update windows, if any
458
459 my $frames = $ctx->read(all => [
460 { type => 'WINDOW_UPDATE' },
461 { type => 'SETTINGS'}
462 ]);
463
464 # 6.5.3. Settings Synchronization
465
466 if (grep { $_->{type} eq "SETTINGS" && $_->{flags} == 0 } @$frames) {
467 h2_settings($ctx, 1);
468 }
469
470 return $ctx;
471 } 474 }
472 475
473 sub new_socket { 476 sub new_socket {
474 my ($port, %extra) = @_; 477 my ($port, %extra) = @_;
475 my $npn = $extra{'npn'}; 478 my $npn = $extra{'npn'};