Mercurial > hg > nginx
comparison src/http/v2/ngx_http_v2_filter_module.c @ 6246:257b51c37c5a
The HTTP/2 implementation (RFC 7240, 7241).
The SPDY support is removed, as it's incompatible with the new module.
author | Valentin Bartenev <vbart@nginx.com> |
---|---|
date | Fri, 11 Sep 2015 20:13:06 +0300 |
parents | |
children | cbb8c32f78b5 |
comparison
equal
deleted
inserted
replaced
6245:3cf25d33886a | 6246:257b51c37c5a |
---|---|
1 | |
2 /* | |
3 * Copyright (C) Nginx, Inc. | |
4 * Copyright (C) Valentin V. Bartenev | |
5 */ | |
6 | |
7 | |
8 #include <ngx_config.h> | |
9 #include <ngx_core.h> | |
10 #include <ngx_http.h> | |
11 #include <nginx.h> | |
12 #include <ngx_http_v2_module.h> | |
13 | |
14 | |
15 #define ngx_http_v2_integer_octets(v) (((v) + 127) / 128) | |
16 | |
17 #define ngx_http_v2_literal_size(h) \ | |
18 (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) | |
19 | |
20 #define ngx_http_v2_indexed(i) (128 + (i)) | |
21 #define ngx_http_v2_inc_indexed(i) (64 + (i)) | |
22 | |
23 | |
24 #define NGX_HTTP_V2_STATUS_INDEX 8 | |
25 #define NGX_HTTP_V2_STATUS_200_INDEX 8 | |
26 #define NGX_HTTP_V2_STATUS_204_INDEX 9 | |
27 #define NGX_HTTP_V2_STATUS_206_INDEX 10 | |
28 #define NGX_HTTP_V2_STATUS_304_INDEX 11 | |
29 #define NGX_HTTP_V2_STATUS_400_INDEX 12 | |
30 #define NGX_HTTP_V2_STATUS_404_INDEX 13 | |
31 #define NGX_HTTP_V2_STATUS_500_INDEX 14 | |
32 | |
33 #define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 | |
34 #define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 | |
35 #define NGX_HTTP_V2_DATE_INDEX 33 | |
36 #define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 | |
37 #define NGX_HTTP_V2_LOCATION_INDEX 46 | |
38 #define NGX_HTTP_V2_SERVER_INDEX 54 | |
39 #define NGX_HTTP_V2_VARY_INDEX 59 | |
40 | |
41 | |
42 static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, | |
43 ngx_uint_t value); | |
44 static void ngx_http_v2_write_headers_head(u_char *pos, size_t length, | |
45 ngx_uint_t sid, ngx_uint_t end_headers, ngx_uint_t end_stream); | |
46 static void ngx_http_v2_write_continuation_head(u_char *pos, size_t length, | |
47 ngx_uint_t sid, ngx_uint_t end_headers); | |
48 | |
49 static ngx_chain_t *ngx_http_v2_send_chain(ngx_connection_t *fc, | |
50 ngx_chain_t *in, off_t limit); | |
51 | |
52 static ngx_chain_t *ngx_http_v2_filter_get_shadow( | |
53 ngx_http_v2_stream_t *stream, ngx_buf_t *buf, off_t offset, off_t size); | |
54 static ngx_http_v2_out_frame_t *ngx_http_v2_filter_get_data_frame( | |
55 ngx_http_v2_stream_t *stream, size_t len, ngx_chain_t *first, | |
56 ngx_chain_t *last); | |
57 | |
58 static ngx_inline ngx_int_t ngx_http_v2_flow_control( | |
59 ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream); | |
60 static void ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c, | |
61 ngx_http_v2_stream_t *stream); | |
62 | |
63 static ngx_inline ngx_int_t ngx_http_v2_filter_send( | |
64 ngx_connection_t *fc, ngx_http_v2_stream_t *stream); | |
65 | |
66 static ngx_int_t ngx_http_v2_headers_frame_handler( | |
67 ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); | |
68 static ngx_int_t ngx_http_v2_data_frame_handler( | |
69 ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); | |
70 static ngx_inline void ngx_http_v2_handle_frame( | |
71 ngx_http_v2_stream_t *stream, ngx_http_v2_out_frame_t *frame); | |
72 static ngx_inline void ngx_http_v2_handle_stream( | |
73 ngx_http_v2_connection_t *h2c, ngx_http_v2_stream_t *stream); | |
74 | |
75 static void ngx_http_v2_filter_cleanup(void *data); | |
76 | |
77 static ngx_int_t ngx_http_v2_filter_init(ngx_conf_t *cf); | |
78 | |
79 | |
80 static ngx_http_module_t ngx_http_v2_filter_module_ctx = { | |
81 NULL, /* preconfiguration */ | |
82 ngx_http_v2_filter_init, /* postconfiguration */ | |
83 | |
84 NULL, /* create main configuration */ | |
85 NULL, /* init main configuration */ | |
86 | |
87 NULL, /* create server configuration */ | |
88 NULL, /* merge server configuration */ | |
89 | |
90 NULL, /* create location configuration */ | |
91 NULL /* merge location configuration */ | |
92 }; | |
93 | |
94 | |
95 ngx_module_t ngx_http_v2_filter_module = { | |
96 NGX_MODULE_V1, | |
97 &ngx_http_v2_filter_module_ctx, /* module context */ | |
98 NULL, /* module directives */ | |
99 NGX_HTTP_MODULE, /* module type */ | |
100 NULL, /* init master */ | |
101 NULL, /* init module */ | |
102 NULL, /* init process */ | |
103 NULL, /* init thread */ | |
104 NULL, /* exit thread */ | |
105 NULL, /* exit process */ | |
106 NULL, /* exit master */ | |
107 NGX_MODULE_V1_PADDING | |
108 }; | |
109 | |
110 | |
111 static ngx_http_output_header_filter_pt ngx_http_next_header_filter; | |
112 | |
113 | |
114 static ngx_int_t | |
115 ngx_http_v2_header_filter(ngx_http_request_t *r) | |
116 { | |
117 u_char status, *p, *head; | |
118 size_t len, rest, frame_size; | |
119 ngx_buf_t *b; | |
120 ngx_str_t host, location; | |
121 ngx_uint_t i, port, continuation; | |
122 ngx_chain_t *cl; | |
123 ngx_list_part_t *part; | |
124 ngx_table_elt_t *header; | |
125 ngx_connection_t *fc; | |
126 ngx_http_cleanup_t *cln; | |
127 ngx_http_v2_stream_t *stream; | |
128 ngx_http_v2_out_frame_t *frame; | |
129 ngx_http_core_loc_conf_t *clcf; | |
130 ngx_http_core_srv_conf_t *cscf; | |
131 struct sockaddr_in *sin; | |
132 #if (NGX_HAVE_INET6) | |
133 struct sockaddr_in6 *sin6; | |
134 #endif | |
135 u_char addr[NGX_SOCKADDR_STRLEN]; | |
136 | |
137 | |
138 if (!r->stream) { | |
139 return ngx_http_next_header_filter(r); | |
140 } | |
141 | |
142 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | |
143 "http2 header filter"); | |
144 | |
145 if (r->header_sent) { | |
146 return NGX_OK; | |
147 } | |
148 | |
149 r->header_sent = 1; | |
150 | |
151 if (r != r->main) { | |
152 return NGX_OK; | |
153 } | |
154 | |
155 if (r->method == NGX_HTTP_HEAD) { | |
156 r->header_only = 1; | |
157 } | |
158 | |
159 switch (r->headers_out.status) { | |
160 | |
161 case NGX_HTTP_OK: | |
162 status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_200_INDEX); | |
163 break; | |
164 | |
165 case NGX_HTTP_NO_CONTENT: | |
166 r->header_only = 1; | |
167 | |
168 ngx_str_null(&r->headers_out.content_type); | |
169 | |
170 r->headers_out.content_length = NULL; | |
171 r->headers_out.content_length_n = -1; | |
172 | |
173 r->headers_out.last_modified_time = -1; | |
174 r->headers_out.last_modified = NULL; | |
175 | |
176 status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_204_INDEX); | |
177 break; | |
178 | |
179 case NGX_HTTP_PARTIAL_CONTENT: | |
180 status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_206_INDEX); | |
181 break; | |
182 | |
183 case NGX_HTTP_NOT_MODIFIED: | |
184 r->header_only = 1; | |
185 status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_304_INDEX); | |
186 break; | |
187 | |
188 default: | |
189 r->headers_out.last_modified_time = -1; | |
190 r->headers_out.last_modified = NULL; | |
191 | |
192 switch (r->headers_out.status) { | |
193 | |
194 case NGX_HTTP_BAD_REQUEST: | |
195 status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_400_INDEX); | |
196 break; | |
197 | |
198 case NGX_HTTP_NOT_FOUND: | |
199 status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_404_INDEX); | |
200 break; | |
201 | |
202 case NGX_HTTP_INTERNAL_SERVER_ERROR: | |
203 status = ngx_http_v2_indexed(NGX_HTTP_V2_STATUS_500_INDEX); | |
204 break; | |
205 | |
206 default: | |
207 status = 0; | |
208 } | |
209 } | |
210 | |
211 len = status ? 1 : 1 + ngx_http_v2_literal_size("418"); | |
212 | |
213 clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); | |
214 | |
215 if (r->headers_out.server == NULL) { | |
216 len += 1 + clcf->server_tokens ? ngx_http_v2_literal_size(NGINX_VER) | |
217 : ngx_http_v2_literal_size("nginx"); | |
218 } | |
219 | |
220 if (r->headers_out.date == NULL) { | |
221 len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT"); | |
222 } | |
223 | |
224 if (r->headers_out.content_type.len) { | |
225 len += NGX_HTTP_V2_INT_OCTETS + r->headers_out.content_type.len; | |
226 | |
227 if (r->headers_out.content_type_len == r->headers_out.content_type.len | |
228 && r->headers_out.charset.len) | |
229 { | |
230 len += sizeof("; charset=") - 1 + r->headers_out.charset.len; | |
231 } | |
232 } | |
233 | |
234 if (r->headers_out.content_length == NULL | |
235 && r->headers_out.content_length_n >= 0) | |
236 { | |
237 len += 1 + ngx_http_v2_integer_octets(NGX_OFF_T_LEN) + NGX_OFF_T_LEN; | |
238 } | |
239 | |
240 if (r->headers_out.last_modified == NULL | |
241 && r->headers_out.last_modified_time != -1) | |
242 { | |
243 len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT"); | |
244 } | |
245 | |
246 fc = r->connection; | |
247 | |
248 if (r->headers_out.location && r->headers_out.location->value.len) { | |
249 | |
250 if (r->headers_out.location->value.data[0] == '/') { | |
251 if (clcf->server_name_in_redirect) { | |
252 cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); | |
253 host = cscf->server_name; | |
254 | |
255 } else if (r->headers_in.server.len) { | |
256 host = r->headers_in.server; | |
257 | |
258 } else { | |
259 host.len = NGX_SOCKADDR_STRLEN; | |
260 host.data = addr; | |
261 | |
262 if (ngx_connection_local_sockaddr(fc, &host, 0) != NGX_OK) { | |
263 return NGX_ERROR; | |
264 } | |
265 } | |
266 | |
267 switch (fc->local_sockaddr->sa_family) { | |
268 | |
269 #if (NGX_HAVE_INET6) | |
270 case AF_INET6: | |
271 sin6 = (struct sockaddr_in6 *) fc->local_sockaddr; | |
272 port = ntohs(sin6->sin6_port); | |
273 break; | |
274 #endif | |
275 #if (NGX_HAVE_UNIX_DOMAIN) | |
276 case AF_UNIX: | |
277 port = 0; | |
278 break; | |
279 #endif | |
280 default: /* AF_INET */ | |
281 sin = (struct sockaddr_in *) fc->local_sockaddr; | |
282 port = ntohs(sin->sin_port); | |
283 break; | |
284 } | |
285 | |
286 location.len = sizeof("https://") - 1 + host.len | |
287 + r->headers_out.location->value.len; | |
288 | |
289 if (clcf->port_in_redirect) { | |
290 | |
291 #if (NGX_HTTP_SSL) | |
292 if (fc->ssl) | |
293 port = (port == 443) ? 0 : port; | |
294 else | |
295 #endif | |
296 port = (port == 80) ? 0 : port; | |
297 | |
298 } else { | |
299 port = 0; | |
300 } | |
301 | |
302 if (port) { | |
303 location.len += sizeof(":65535") - 1; | |
304 } | |
305 | |
306 location.data = ngx_pnalloc(r->pool, location.len); | |
307 if (location.data == NULL) { | |
308 return NGX_ERROR; | |
309 } | |
310 | |
311 p = ngx_cpymem(location.data, "http", sizeof("http") - 1); | |
312 | |
313 #if (NGX_HTTP_SSL) | |
314 if (fc->ssl) { | |
315 *p++ = 's'; | |
316 } | |
317 #endif | |
318 | |
319 *p++ = ':'; *p++ = '/'; *p++ = '/'; | |
320 p = ngx_cpymem(p, host.data, host.len); | |
321 | |
322 if (port) { | |
323 p = ngx_sprintf(p, ":%ui", port); | |
324 } | |
325 | |
326 p = ngx_cpymem(p, r->headers_out.location->value.data, | |
327 r->headers_out.location->value.len); | |
328 | |
329 /* update r->headers_out.location->value for possible logging */ | |
330 | |
331 r->headers_out.location->value.len = p - location.data; | |
332 r->headers_out.location->value.data = location.data; | |
333 ngx_str_set(&r->headers_out.location->key, "Location"); | |
334 } | |
335 | |
336 r->headers_out.location->hash = 0; | |
337 | |
338 len += 1 + NGX_HTTP_V2_INT_OCTETS + r->headers_out.location->value.len; | |
339 } | |
340 | |
341 #if (NGX_HTTP_GZIP) | |
342 if (r->gzip_vary) { | |
343 if (clcf->gzip_vary) { | |
344 len += 1 + ngx_http_v2_literal_size("Accept-Encoding"); | |
345 | |
346 } else { | |
347 r->gzip_vary = 0; | |
348 } | |
349 } | |
350 #endif | |
351 | |
352 part = &r->headers_out.headers.part; | |
353 header = part->elts; | |
354 | |
355 for (i = 0; /* void */; i++) { | |
356 | |
357 if (i >= part->nelts) { | |
358 if (part->next == NULL) { | |
359 break; | |
360 } | |
361 | |
362 part = part->next; | |
363 header = part->elts; | |
364 i = 0; | |
365 } | |
366 | |
367 if (header[i].hash == 0) { | |
368 continue; | |
369 } | |
370 | |
371 if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) { | |
372 ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0, | |
373 "too long response header name: \"%V\"", | |
374 &header[i].key); | |
375 return NGX_ERROR; | |
376 } | |
377 | |
378 if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) { | |
379 ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0, | |
380 "too long response header value: \"%V: %V\"", | |
381 &header[i].key, &header[i].value); | |
382 return NGX_ERROR; | |
383 } | |
384 | |
385 len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len | |
386 + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; | |
387 } | |
388 | |
389 stream = r->stream; | |
390 frame_size = stream->connection->frame_size; | |
391 | |
392 len += NGX_HTTP_V2_FRAME_HEADER_SIZE | |
393 * ((len + frame_size - 1) / frame_size); | |
394 | |
395 b = ngx_create_temp_buf(r->pool, len); | |
396 if (b == NULL) { | |
397 return NGX_ERROR; | |
398 } | |
399 | |
400 b->last_buf = r->header_only; | |
401 | |
402 b->last += NGX_HTTP_V2_FRAME_HEADER_SIZE; | |
403 | |
404 if (status) { | |
405 *b->last++ = status; | |
406 | |
407 } else { | |
408 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); | |
409 *b->last++ = 3; | |
410 b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); | |
411 } | |
412 | |
413 if (r->headers_out.server == NULL) { | |
414 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SERVER_INDEX); | |
415 | |
416 if (clcf->server_tokens) { | |
417 *b->last++ = sizeof(NGINX_VER) - 1; | |
418 b->last = ngx_cpymem(b->last, NGINX_VER, sizeof(NGINX_VER) - 1); | |
419 | |
420 } else { | |
421 *b->last++ = sizeof("nginx") - 1; | |
422 b->last = ngx_cpymem(b->last, "nginx", sizeof("nginx") - 1); | |
423 } | |
424 } | |
425 | |
426 if (r->headers_out.date == NULL) { | |
427 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_DATE_INDEX); | |
428 *b->last++ = (u_char) ngx_cached_http_time.len; | |
429 | |
430 b->last = ngx_cpymem(b->last, ngx_cached_http_time.data, | |
431 ngx_cached_http_time.len); | |
432 } | |
433 | |
434 if (r->headers_out.content_type.len) { | |
435 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_TYPE_INDEX); | |
436 | |
437 if (r->headers_out.content_type_len == r->headers_out.content_type.len | |
438 && r->headers_out.charset.len) | |
439 { | |
440 *b->last = 0; | |
441 b->last = ngx_http_v2_write_int(b->last, ngx_http_v2_prefix(7), | |
442 r->headers_out.content_type.len | |
443 + sizeof("; charset=") - 1 | |
444 + r->headers_out.charset.len); | |
445 | |
446 p = b->last; | |
447 | |
448 b->last = ngx_cpymem(p, r->headers_out.content_type.data, | |
449 r->headers_out.content_type.len); | |
450 | |
451 b->last = ngx_cpymem(b->last, "; charset=", | |
452 sizeof("; charset=") - 1); | |
453 | |
454 b->last = ngx_cpymem(b->last, r->headers_out.charset.data, | |
455 r->headers_out.charset.len); | |
456 | |
457 /* update r->headers_out.content_type for possible logging */ | |
458 | |
459 r->headers_out.content_type.len = b->last - p; | |
460 r->headers_out.content_type.data = p; | |
461 | |
462 } else { | |
463 *b->last = 0; | |
464 b->last = ngx_http_v2_write_int(b->last, ngx_http_v2_prefix(7), | |
465 r->headers_out.content_type.len); | |
466 b->last = ngx_cpymem(b->last, r->headers_out.content_type.data, | |
467 r->headers_out.content_type.len); | |
468 } | |
469 } | |
470 | |
471 if (r->headers_out.content_length == NULL | |
472 && r->headers_out.content_length_n >= 0) | |
473 { | |
474 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_CONTENT_LENGTH_INDEX); | |
475 | |
476 p = b->last; | |
477 b->last = ngx_sprintf(b->last + 1, "%O", | |
478 r->headers_out.content_length_n); | |
479 *p = (u_char) (b->last - p - 1); | |
480 } | |
481 | |
482 if (r->headers_out.last_modified == NULL | |
483 && r->headers_out.last_modified_time != -1) | |
484 { | |
485 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LAST_MODIFIED_INDEX); | |
486 | |
487 p = b->last; | |
488 b->last = ngx_http_time(b->last + 1, r->headers_out.last_modified_time); | |
489 *p = (u_char) (b->last - p - 1); | |
490 } | |
491 | |
492 if (r->headers_out.location && r->headers_out.location->value.len) { | |
493 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_LOCATION_INDEX); | |
494 | |
495 *b->last = 0; | |
496 b->last = ngx_http_v2_write_int(b->last, ngx_http_v2_prefix(7), | |
497 r->headers_out.location->value.len); | |
498 b->last = ngx_cpymem(b->last, r->headers_out.location->value.data, | |
499 r->headers_out.location->value.len); | |
500 } | |
501 | |
502 #if (NGX_HTTP_GZIP) | |
503 if (r->gzip_vary) { | |
504 *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_VARY_INDEX); | |
505 *b->last++ = sizeof("Accept-Encoding") - 1; | |
506 b->last = ngx_cpymem(b->last, "Accept-Encoding", | |
507 sizeof("Accept-Encoding") - 1); | |
508 } | |
509 #endif | |
510 | |
511 continuation = 0; | |
512 head = b->pos; | |
513 | |
514 len = b->last - head - NGX_HTTP_V2_FRAME_HEADER_SIZE; | |
515 rest = frame_size - len; | |
516 | |
517 part = &r->headers_out.headers.part; | |
518 header = part->elts; | |
519 | |
520 for (i = 0; /* void */; i++) { | |
521 | |
522 if (i >= part->nelts) { | |
523 if (part->next == NULL) { | |
524 break; | |
525 } | |
526 | |
527 part = part->next; | |
528 header = part->elts; | |
529 i = 0; | |
530 } | |
531 | |
532 if (header[i].hash == 0) { | |
533 continue; | |
534 } | |
535 | |
536 len = 1 + NGX_HTTP_V2_INT_OCTETS * 2 | |
537 + header[i].key.len | |
538 + header[i].value.len; | |
539 | |
540 if (len > rest) { | |
541 len = b->last - head - NGX_HTTP_V2_FRAME_HEADER_SIZE; | |
542 | |
543 if (continuation) { | |
544 ngx_http_v2_write_continuation_head(head, len, | |
545 stream->node->id, 0); | |
546 } else { | |
547 continuation = 1; | |
548 ngx_http_v2_write_headers_head(head, len, stream->node->id, 0, | |
549 r->header_only); | |
550 } | |
551 | |
552 rest = frame_size; | |
553 head = b->last; | |
554 | |
555 b->last += NGX_HTTP_V2_FRAME_HEADER_SIZE; | |
556 } | |
557 | |
558 p = b->last; | |
559 | |
560 *p++ = 0; | |
561 | |
562 *p = 0; | |
563 p = ngx_http_v2_write_int(p, ngx_http_v2_prefix(7), header[i].key.len); | |
564 ngx_strlow(p, header[i].key.data, header[i].key.len); | |
565 p += header[i].key.len; | |
566 | |
567 *p = 0; | |
568 p = ngx_http_v2_write_int(p, ngx_http_v2_prefix(7), | |
569 header[i].value.len); | |
570 p = ngx_cpymem(p, header[i].value.data, header[i].value.len); | |
571 | |
572 rest -= p - b->last; | |
573 b->last = p; | |
574 } | |
575 | |
576 len = b->last - head - NGX_HTTP_V2_FRAME_HEADER_SIZE; | |
577 | |
578 if (continuation) { | |
579 ngx_http_v2_write_continuation_head(head, len, stream->node->id, 1); | |
580 | |
581 } else { | |
582 ngx_http_v2_write_headers_head(head, len, stream->node->id, 1, | |
583 r->header_only); | |
584 } | |
585 | |
586 cl = ngx_alloc_chain_link(r->pool); | |
587 if (cl == NULL) { | |
588 return NGX_ERROR; | |
589 } | |
590 | |
591 cl->buf = b; | |
592 cl->next = NULL; | |
593 | |
594 frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t)); | |
595 if (frame == NULL) { | |
596 return NGX_ERROR; | |
597 } | |
598 | |
599 frame->first = cl; | |
600 frame->last = cl; | |
601 frame->handler = ngx_http_v2_headers_frame_handler; | |
602 frame->stream = stream; | |
603 frame->length = b->last - b->pos - NGX_HTTP_V2_FRAME_HEADER_SIZE; | |
604 frame->blocked = 1; | |
605 frame->fin = r->header_only; | |
606 | |
607 ngx_log_debug3(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, | |
608 "http2:%ui create HEADERS frame %p: len:%uz", | |
609 stream->node->id, frame, frame->length); | |
610 | |
611 ngx_http_v2_queue_blocked_frame(stream->connection, frame); | |
612 | |
613 cln = ngx_http_cleanup_add(r, 0); | |
614 if (cln == NULL) { | |
615 return NGX_ERROR; | |
616 } | |
617 | |
618 cln->handler = ngx_http_v2_filter_cleanup; | |
619 cln->data = stream; | |
620 | |
621 stream->queued = 1; | |
622 | |
623 fc->send_chain = ngx_http_v2_send_chain; | |
624 fc->need_last_buf = 1; | |
625 | |
626 return ngx_http_v2_filter_send(fc, stream); | |
627 } | |
628 | |
629 | |
630 static u_char * | |
631 ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) | |
632 { | |
633 if (value < prefix) { | |
634 *pos++ |= value; | |
635 return pos; | |
636 } | |
637 | |
638 *pos++ |= prefix; | |
639 value -= prefix; | |
640 | |
641 while (value >= 128) { | |
642 *pos++ = value % 128 + 128; | |
643 value /= 128; | |
644 } | |
645 | |
646 *pos++ = (u_char) value; | |
647 | |
648 return pos; | |
649 } | |
650 | |
651 | |
652 static void | |
653 ngx_http_v2_write_headers_head(u_char *pos, size_t length, ngx_uint_t sid, | |
654 ngx_uint_t end_headers, ngx_uint_t end_stream) | |
655 { | |
656 u_char flags; | |
657 | |
658 pos = ngx_http_v2_write_len_and_type(pos, length, | |
659 NGX_HTTP_V2_HEADERS_FRAME); | |
660 | |
661 flags = NGX_HTTP_V2_NO_FLAG; | |
662 | |
663 if (end_headers) { | |
664 flags |= NGX_HTTP_V2_END_HEADERS_FLAG; | |
665 } | |
666 | |
667 if (end_stream) { | |
668 flags |= NGX_HTTP_V2_END_STREAM_FLAG; | |
669 } | |
670 | |
671 *pos++ = flags; | |
672 | |
673 (void) ngx_http_v2_write_sid(pos, sid); | |
674 } | |
675 | |
676 | |
677 static void | |
678 ngx_http_v2_write_continuation_head(u_char *pos, size_t length, ngx_uint_t sid, | |
679 ngx_uint_t end_headers) | |
680 { | |
681 pos = ngx_http_v2_write_len_and_type(pos, length, | |
682 NGX_HTTP_V2_CONTINUATION_FRAME); | |
683 | |
684 *pos++ = end_headers ? NGX_HTTP_V2_END_HEADERS_FLAG : NGX_HTTP_V2_NO_FLAG; | |
685 | |
686 (void) ngx_http_v2_write_sid(pos, sid); | |
687 } | |
688 | |
689 | |
690 static ngx_chain_t * | |
691 ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) | |
692 { | |
693 off_t size, offset; | |
694 size_t rest, frame_size; | |
695 ngx_chain_t *cl, *out, **ln; | |
696 ngx_http_request_t *r; | |
697 ngx_http_v2_stream_t *stream; | |
698 ngx_http_v2_loc_conf_t *h2lcf; | |
699 ngx_http_v2_out_frame_t *frame; | |
700 ngx_http_v2_connection_t *h2c; | |
701 | |
702 r = fc->data; | |
703 stream = r->stream; | |
704 | |
705 #if (NGX_SUPPRESS_WARN) | |
706 size = 0; | |
707 #endif | |
708 | |
709 while (in) { | |
710 size = ngx_buf_size(in->buf); | |
711 | |
712 if (size || in->buf->last_buf) { | |
713 break; | |
714 } | |
715 | |
716 in = in->next; | |
717 } | |
718 | |
719 if (in == NULL) { | |
720 | |
721 if (stream->queued) { | |
722 fc->write->delayed = 1; | |
723 } else { | |
724 fc->buffered &= ~NGX_HTTP_V2_BUFFERED; | |
725 } | |
726 | |
727 return NULL; | |
728 } | |
729 | |
730 h2c = stream->connection; | |
731 | |
732 if (size && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) { | |
733 fc->write->delayed = 1; | |
734 return in; | |
735 } | |
736 | |
737 if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) { | |
738 cl = ngx_alloc_chain_link(r->pool); | |
739 if (cl == NULL) { | |
740 return NGX_CHAIN_ERROR; | |
741 } | |
742 | |
743 cl->buf = in->buf; | |
744 in->buf = cl->buf->shadow; | |
745 | |
746 offset = ngx_buf_in_memory(in->buf) | |
747 ? (cl->buf->pos - in->buf->pos) | |
748 : (cl->buf->file_pos - in->buf->file_pos); | |
749 | |
750 cl->next = stream->free_bufs; | |
751 stream->free_bufs = cl; | |
752 | |
753 } else { | |
754 offset = 0; | |
755 } | |
756 | |
757 if (limit == 0 || limit > (off_t) h2c->send_window) { | |
758 limit = h2c->send_window; | |
759 } | |
760 | |
761 if (limit > stream->send_window) { | |
762 limit = (stream->send_window > 0) ? stream->send_window : 0; | |
763 } | |
764 | |
765 h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module); | |
766 | |
767 frame_size = (h2lcf->chunk_size < h2c->frame_size) | |
768 ? h2lcf->chunk_size : h2c->frame_size; | |
769 | |
770 #if (NGX_SUPPRESS_WARN) | |
771 cl = NULL; | |
772 #endif | |
773 | |
774 for ( ;; ) { | |
775 if ((off_t) frame_size > limit) { | |
776 frame_size = (size_t) limit; | |
777 } | |
778 | |
779 ln = &out; | |
780 rest = frame_size; | |
781 | |
782 while ((off_t) rest >= size) { | |
783 | |
784 if (offset) { | |
785 cl = ngx_http_v2_filter_get_shadow(stream, in->buf, | |
786 offset, size); | |
787 if (cl == NULL) { | |
788 return NGX_CHAIN_ERROR; | |
789 } | |
790 | |
791 offset = 0; | |
792 | |
793 } else { | |
794 cl = ngx_alloc_chain_link(r->pool); | |
795 if (cl == NULL) { | |
796 return NGX_CHAIN_ERROR; | |
797 } | |
798 | |
799 cl->buf = in->buf; | |
800 } | |
801 | |
802 *ln = cl; | |
803 ln = &cl->next; | |
804 | |
805 rest -= (size_t) size; | |
806 in = in->next; | |
807 | |
808 if (in == NULL) { | |
809 frame_size -= rest; | |
810 rest = 0; | |
811 break; | |
812 } | |
813 | |
814 size = ngx_buf_size(in->buf); | |
815 } | |
816 | |
817 if (rest) { | |
818 cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, rest); | |
819 if (cl == NULL) { | |
820 return NGX_CHAIN_ERROR; | |
821 } | |
822 | |
823 cl->buf->flush = 0; | |
824 cl->buf->last_buf = 0; | |
825 | |
826 *ln = cl; | |
827 | |
828 offset += rest; | |
829 size -= rest; | |
830 } | |
831 | |
832 frame = ngx_http_v2_filter_get_data_frame(stream, frame_size, out, cl); | |
833 if (frame == NULL) { | |
834 return NGX_CHAIN_ERROR; | |
835 } | |
836 | |
837 ngx_http_v2_queue_frame(h2c, frame); | |
838 | |
839 h2c->send_window -= frame_size; | |
840 | |
841 stream->send_window -= frame_size; | |
842 stream->queued++; | |
843 | |
844 if (in == NULL) { | |
845 break; | |
846 } | |
847 | |
848 limit -= frame_size; | |
849 | |
850 if (limit == 0) { | |
851 break; | |
852 } | |
853 } | |
854 | |
855 if (offset) { | |
856 cl = ngx_http_v2_filter_get_shadow(stream, in->buf, offset, size); | |
857 if (cl == NULL) { | |
858 return NGX_CHAIN_ERROR; | |
859 } | |
860 | |
861 in->buf = cl->buf; | |
862 ngx_free_chain(r->pool, cl); | |
863 } | |
864 | |
865 if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) { | |
866 return NGX_CHAIN_ERROR; | |
867 } | |
868 | |
869 if (in && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) { | |
870 fc->write->delayed = 1; | |
871 } | |
872 | |
873 return in; | |
874 } | |
875 | |
876 | |
877 static ngx_chain_t * | |
878 ngx_http_v2_filter_get_shadow(ngx_http_v2_stream_t *stream, ngx_buf_t *buf, | |
879 off_t offset, off_t size) | |
880 { | |
881 ngx_buf_t *chunk; | |
882 ngx_chain_t *cl; | |
883 | |
884 cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_bufs); | |
885 if (cl == NULL) { | |
886 return NULL; | |
887 } | |
888 | |
889 chunk = cl->buf; | |
890 | |
891 ngx_memcpy(chunk, buf, sizeof(ngx_buf_t)); | |
892 | |
893 chunk->tag = (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow; | |
894 chunk->shadow = buf; | |
895 | |
896 if (ngx_buf_in_memory(chunk)) { | |
897 chunk->pos += offset; | |
898 chunk->last = chunk->pos + size; | |
899 } | |
900 | |
901 if (chunk->in_file) { | |
902 chunk->file_pos += offset; | |
903 chunk->file_last = chunk->file_pos + size; | |
904 } | |
905 | |
906 return cl; | |
907 } | |
908 | |
909 | |
910 static ngx_http_v2_out_frame_t * | |
911 ngx_http_v2_filter_get_data_frame(ngx_http_v2_stream_t *stream, | |
912 size_t len, ngx_chain_t *first, ngx_chain_t *last) | |
913 { | |
914 u_char flags; | |
915 ngx_buf_t *buf; | |
916 ngx_chain_t *cl; | |
917 ngx_http_v2_out_frame_t *frame; | |
918 | |
919 | |
920 frame = stream->free_frames; | |
921 | |
922 if (frame) { | |
923 stream->free_frames = frame->next; | |
924 | |
925 } else { | |
926 frame = ngx_palloc(stream->request->pool, | |
927 sizeof(ngx_http_v2_out_frame_t)); | |
928 if (frame == NULL) { | |
929 return NULL; | |
930 } | |
931 } | |
932 | |
933 flags = last->buf->last_buf ? NGX_HTTP_V2_END_STREAM_FLAG : 0; | |
934 | |
935 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, | |
936 "http2:%ui create DATA frame %p: len:%uz flags:%ui", | |
937 stream->node->id, frame, len, (ngx_uint_t) flags); | |
938 | |
939 cl = ngx_chain_get_free_buf(stream->request->pool, | |
940 &stream->free_data_headers); | |
941 if (cl == NULL) { | |
942 return NULL; | |
943 } | |
944 | |
945 buf = cl->buf; | |
946 | |
947 if (!buf->start) { | |
948 buf->start = ngx_palloc(stream->request->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE); | |
949 if (buf->start == NULL) { | |
950 return NULL; | |
951 } | |
952 | |
953 buf->end = buf->start + NGX_HTTP_V2_FRAME_HEADER_SIZE; | |
954 buf->last = buf->end; | |
955 | |
956 buf->tag = (ngx_buf_tag_t) &ngx_http_v2_filter_get_data_frame; | |
957 buf->memory = 1; | |
958 } | |
959 | |
960 buf->pos = buf->start; | |
961 buf->last = buf->pos; | |
962 | |
963 buf->last = ngx_http_v2_write_len_and_type(buf->last, len, | |
964 NGX_HTTP_V2_DATA_FRAME); | |
965 *buf->last++ = flags; | |
966 | |
967 buf->last = ngx_http_v2_write_sid(buf->last, stream->node->id); | |
968 | |
969 cl->next = first; | |
970 first = cl; | |
971 | |
972 last->buf->flush = 1; | |
973 | |
974 frame->first = first; | |
975 frame->last = last; | |
976 frame->handler = ngx_http_v2_data_frame_handler; | |
977 frame->stream = stream; | |
978 frame->length = len; | |
979 frame->blocked = 0; | |
980 frame->fin = last->buf->last_buf; | |
981 | |
982 return frame; | |
983 } | |
984 | |
985 | |
986 static ngx_inline ngx_int_t | |
987 ngx_http_v2_filter_send(ngx_connection_t *fc, ngx_http_v2_stream_t *stream) | |
988 { | |
989 stream->blocked = 1; | |
990 | |
991 if (ngx_http_v2_send_output_queue(stream->connection) == NGX_ERROR) { | |
992 fc->error = 1; | |
993 return NGX_ERROR; | |
994 } | |
995 | |
996 stream->blocked = 0; | |
997 | |
998 if (stream->queued) { | |
999 fc->buffered |= NGX_HTTP_V2_BUFFERED; | |
1000 fc->write->delayed = 1; | |
1001 return NGX_AGAIN; | |
1002 } | |
1003 | |
1004 fc->buffered &= ~NGX_HTTP_V2_BUFFERED; | |
1005 | |
1006 return NGX_OK; | |
1007 } | |
1008 | |
1009 | |
1010 static ngx_inline ngx_int_t | |
1011 ngx_http_v2_flow_control(ngx_http_v2_connection_t *h2c, | |
1012 ngx_http_v2_stream_t *stream) | |
1013 { | |
1014 if (stream->send_window <= 0) { | |
1015 stream->exhausted = 1; | |
1016 return NGX_DECLINED; | |
1017 } | |
1018 | |
1019 if (h2c->send_window == 0) { | |
1020 ngx_http_v2_waiting_queue(h2c, stream); | |
1021 return NGX_DECLINED; | |
1022 } | |
1023 | |
1024 return NGX_OK; | |
1025 } | |
1026 | |
1027 | |
1028 static void | |
1029 ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c, | |
1030 ngx_http_v2_stream_t *stream) | |
1031 { | |
1032 ngx_queue_t *q; | |
1033 ngx_http_v2_stream_t *s; | |
1034 | |
1035 if (stream->handled) { | |
1036 return; | |
1037 } | |
1038 | |
1039 stream->handled = 1; | |
1040 | |
1041 for (q = ngx_queue_last(&h2c->waiting); | |
1042 q != ngx_queue_sentinel(&h2c->waiting); | |
1043 q = ngx_queue_prev(q)) | |
1044 { | |
1045 s = ngx_queue_data(q, ngx_http_v2_stream_t, queue); | |
1046 | |
1047 if (s->node->rank < stream->node->rank | |
1048 || (s->node->rank == stream->node->rank | |
1049 && s->node->rel_weight >= stream->node->rel_weight)) | |
1050 { | |
1051 break; | |
1052 } | |
1053 } | |
1054 | |
1055 ngx_queue_insert_after(q, &stream->queue); | |
1056 } | |
1057 | |
1058 | |
1059 | |
1060 static ngx_int_t | |
1061 ngx_http_v2_headers_frame_handler(ngx_http_v2_connection_t *h2c, | |
1062 ngx_http_v2_out_frame_t *frame) | |
1063 { | |
1064 ngx_buf_t *buf; | |
1065 ngx_http_v2_stream_t *stream; | |
1066 | |
1067 buf = frame->first->buf; | |
1068 | |
1069 if (buf->pos != buf->last) { | |
1070 return NGX_AGAIN; | |
1071 } | |
1072 | |
1073 stream = frame->stream; | |
1074 | |
1075 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, | |
1076 "http2:%ui HEADERS frame %p was sent", | |
1077 stream->node->id, frame); | |
1078 | |
1079 ngx_free_chain(stream->request->pool, frame->first); | |
1080 | |
1081 ngx_http_v2_handle_frame(stream, frame); | |
1082 | |
1083 ngx_http_v2_handle_stream(h2c, stream); | |
1084 | |
1085 return NGX_OK; | |
1086 } | |
1087 | |
1088 | |
1089 static ngx_int_t | |
1090 ngx_http_v2_data_frame_handler(ngx_http_v2_connection_t *h2c, | |
1091 ngx_http_v2_out_frame_t *frame) | |
1092 { | |
1093 ngx_buf_t *buf; | |
1094 ngx_chain_t *cl, *ln; | |
1095 ngx_http_v2_stream_t *stream; | |
1096 | |
1097 stream = frame->stream; | |
1098 | |
1099 cl = frame->first; | |
1100 | |
1101 if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_data_frame) { | |
1102 | |
1103 if (cl->buf->pos != cl->buf->last) { | |
1104 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, | |
1105 "http2:%ui DATA frame %p was sent partially", | |
1106 stream->node->id, frame); | |
1107 | |
1108 return NGX_AGAIN; | |
1109 } | |
1110 | |
1111 ln = cl->next; | |
1112 | |
1113 cl->next = stream->free_data_headers; | |
1114 stream->free_data_headers = cl; | |
1115 | |
1116 if (cl == frame->last) { | |
1117 goto done; | |
1118 } | |
1119 | |
1120 cl = ln; | |
1121 } | |
1122 | |
1123 for ( ;; ) { | |
1124 if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) { | |
1125 buf = cl->buf->shadow; | |
1126 | |
1127 if (ngx_buf_in_memory(buf)) { | |
1128 buf->pos = cl->buf->pos; | |
1129 } | |
1130 | |
1131 if (buf->in_file) { | |
1132 buf->file_pos = cl->buf->file_pos; | |
1133 } | |
1134 } | |
1135 | |
1136 if (ngx_buf_size(cl->buf) != 0) { | |
1137 | |
1138 if (cl != frame->first) { | |
1139 frame->first = cl; | |
1140 ngx_http_v2_handle_stream(h2c, stream); | |
1141 } | |
1142 | |
1143 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, | |
1144 "http2:%ui DATA frame %p was sent partially", | |
1145 stream->node->id, frame); | |
1146 | |
1147 return NGX_AGAIN; | |
1148 } | |
1149 | |
1150 ln = cl->next; | |
1151 | |
1152 if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) { | |
1153 cl->next = stream->free_bufs; | |
1154 stream->free_bufs = cl; | |
1155 | |
1156 } else { | |
1157 ngx_free_chain(stream->request->pool, cl); | |
1158 } | |
1159 | |
1160 if (cl == frame->last) { | |
1161 goto done; | |
1162 } | |
1163 | |
1164 cl = ln; | |
1165 } | |
1166 | |
1167 done: | |
1168 | |
1169 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, | |
1170 "http2:%ui DATA frame %p was sent", | |
1171 stream->node->id, frame); | |
1172 | |
1173 stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE; | |
1174 | |
1175 ngx_http_v2_handle_frame(stream, frame); | |
1176 | |
1177 ngx_http_v2_handle_stream(h2c, stream); | |
1178 | |
1179 return NGX_OK; | |
1180 } | |
1181 | |
1182 | |
1183 static ngx_inline void | |
1184 ngx_http_v2_handle_frame(ngx_http_v2_stream_t *stream, | |
1185 ngx_http_v2_out_frame_t *frame) | |
1186 { | |
1187 ngx_http_request_t *r; | |
1188 | |
1189 r = stream->request; | |
1190 | |
1191 r->connection->sent += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length; | |
1192 | |
1193 if (frame->fin) { | |
1194 stream->out_closed = 1; | |
1195 } | |
1196 | |
1197 frame->next = stream->free_frames; | |
1198 stream->free_frames = frame; | |
1199 | |
1200 stream->queued--; | |
1201 } | |
1202 | |
1203 | |
1204 static ngx_inline void | |
1205 ngx_http_v2_handle_stream(ngx_http_v2_connection_t *h2c, | |
1206 ngx_http_v2_stream_t *stream) | |
1207 { | |
1208 ngx_event_t *wev; | |
1209 | |
1210 if (stream->handled || stream->blocked || stream->exhausted) { | |
1211 return; | |
1212 } | |
1213 | |
1214 wev = stream->request->connection->write; | |
1215 | |
1216 /* | |
1217 * This timer can only be set if the stream was delayed because of rate | |
1218 * limit. In that case the event should be triggered by the timer. | |
1219 */ | |
1220 | |
1221 if (!wev->timer_set) { | |
1222 wev->delayed = 0; | |
1223 | |
1224 stream->handled = 1; | |
1225 ngx_queue_insert_tail(&h2c->posted, &stream->queue); | |
1226 } | |
1227 } | |
1228 | |
1229 | |
1230 static void | |
1231 ngx_http_v2_filter_cleanup(void *data) | |
1232 { | |
1233 ngx_http_v2_stream_t *stream = data; | |
1234 | |
1235 size_t window; | |
1236 ngx_http_v2_out_frame_t *frame, **fn; | |
1237 ngx_http_v2_connection_t *h2c; | |
1238 | |
1239 if (stream->handled) { | |
1240 stream->handled = 0; | |
1241 ngx_queue_remove(&stream->queue); | |
1242 } | |
1243 | |
1244 if (stream->queued == 0) { | |
1245 return; | |
1246 } | |
1247 | |
1248 window = 0; | |
1249 h2c = stream->connection; | |
1250 fn = &h2c->last_out; | |
1251 | |
1252 for ( ;; ) { | |
1253 frame = *fn; | |
1254 | |
1255 if (frame == NULL) { | |
1256 break; | |
1257 } | |
1258 | |
1259 if (frame->stream == stream && !frame->blocked) { | |
1260 *fn = frame->next; | |
1261 | |
1262 window += frame->length; | |
1263 | |
1264 if (--stream->queued == 0) { | |
1265 break; | |
1266 } | |
1267 | |
1268 continue; | |
1269 } | |
1270 | |
1271 fn = &frame->next; | |
1272 } | |
1273 | |
1274 if (h2c->send_window == 0 && window && !ngx_queue_empty(&h2c->waiting)) { | |
1275 ngx_queue_add(&h2c->posted, &h2c->waiting); | |
1276 ngx_queue_init(&h2c->waiting); | |
1277 } | |
1278 | |
1279 h2c->send_window += window; | |
1280 } | |
1281 | |
1282 | |
1283 static ngx_int_t | |
1284 ngx_http_v2_filter_init(ngx_conf_t *cf) | |
1285 { | |
1286 ngx_http_next_header_filter = ngx_http_top_header_filter; | |
1287 ngx_http_top_header_filter = ngx_http_v2_header_filter; | |
1288 | |
1289 return NGX_OK; | |
1290 } |