Mercurial > hg > nginx
comparison src/http/v2/ngx_http_v2.c @ 6566:ce94f07d5082
HTTP/2: implemented preread buffer for request body (closes #959).
Previously, the stream's window was kept zero in order to prevent a client
from sending the request body before it was requested (see 887cca40ba6a for
details). Until such initial window was acknowledged all requests with
data were rejected (see 0aa07850922f for details).
That approach revealed a number of problems:
1. Some clients (notably MS IE/Edge, Safari, iOS applications) show an error
or even crash if a stream is rejected;
2. This requires at least one RTT for every request with body before the
client receives window update and able to send data.
To overcome these problems the new directive "http2_body_preread_size" is
introduced. It sets the initial window and configures a special per stream
preread buffer that is used to save all incoming data before the body is
requested and processed.
If the directive's value is lower than the default initial window (65535),
as previously, all streams with data will be rejected until the new window
is acknowledged. Otherwise, no special processing is used and all requests
with data are welcome right from the connection start.
The default value is chosen to be 64k, which is bigger than the default
initial window. Setting it to zero is fully complaint to the previous
behavior.
author | Valentin Bartenev <vbart@nginx.com> |
---|---|
date | Tue, 24 May 2016 17:37:52 +0300 |
parents | 9070ba416284 |
children | bc6fd7afeed6 |
comparison
equal
deleted
inserted
replaced
6565:3af0e65a461a | 6566:ce94f07d5082 |
---|---|
45 #define NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING 0x5 | 45 #define NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING 0x5 |
46 | 46 |
47 #define NGX_HTTP_V2_FRAME_BUFFER_SIZE 24 | 47 #define NGX_HTTP_V2_FRAME_BUFFER_SIZE 24 |
48 | 48 |
49 #define NGX_HTTP_V2_DEFAULT_FRAME_SIZE (1 << 14) | 49 #define NGX_HTTP_V2_DEFAULT_FRAME_SIZE (1 << 14) |
50 | |
51 #define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) | |
52 #define NGX_HTTP_V2_DEFAULT_WINDOW 65535 | |
53 | |
54 #define NGX_HTTP_V2_INITIAL_WINDOW 0 | |
55 | 50 |
56 #define NGX_HTTP_V2_ROOT (void *) -1 | 51 #define NGX_HTTP_V2_ROOT (void *) -1 |
57 | 52 |
58 | 53 |
59 static void ngx_http_v2_read_handler(ngx_event_t *rev); | 54 static void ngx_http_v2_read_handler(ngx_event_t *rev); |
877 } | 872 } |
878 | 873 |
879 return ngx_http_v2_state_skip_padded(h2c, pos, end); | 874 return ngx_http_v2_state_skip_padded(h2c, pos, end); |
880 } | 875 } |
881 | 876 |
882 stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG; | |
883 | |
884 h2c->state.stream = stream; | 877 h2c->state.stream = stream; |
885 | 878 |
886 return ngx_http_v2_state_read_data(h2c, pos, end); | 879 return ngx_http_v2_state_read_data(h2c, pos, end); |
887 } | 880 } |
888 | 881 |
889 | 882 |
890 static u_char * | 883 static u_char * |
891 ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, | 884 ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, |
892 u_char *end) | 885 u_char *end) |
893 { | 886 { |
894 size_t size; | 887 size_t size; |
895 ngx_int_t rc; | 888 ngx_buf_t *buf; |
896 ngx_uint_t last; | 889 ngx_int_t rc; |
897 ngx_http_v2_stream_t *stream; | 890 ngx_http_request_t *r; |
891 ngx_http_v2_stream_t *stream; | |
892 ngx_http_v2_srv_conf_t *h2scf; | |
898 | 893 |
899 stream = h2c->state.stream; | 894 stream = h2c->state.stream; |
900 | 895 |
901 if (stream == NULL) { | 896 if (stream == NULL) { |
902 return ngx_http_v2_state_skip_padded(h2c, pos, end); | 897 return ngx_http_v2_state_skip_padded(h2c, pos, end); |
911 | 906 |
912 size = end - pos; | 907 size = end - pos; |
913 | 908 |
914 if (size >= h2c->state.length) { | 909 if (size >= h2c->state.length) { |
915 size = h2c->state.length; | 910 size = h2c->state.length; |
916 last = stream->in_closed; | 911 stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG; |
917 | 912 } |
918 } else { | 913 |
919 last = 0; | 914 r = stream->request; |
920 } | 915 |
921 | 916 if (r->request_body) { |
922 rc = ngx_http_v2_process_request_body(stream->request, pos, size, last); | 917 rc = ngx_http_v2_process_request_body(r, pos, size, stream->in_closed); |
923 | 918 |
924 if (rc != NGX_OK) { | 919 if (rc != NGX_OK) { |
925 stream->skip_data = 1; | 920 stream->skip_data = 1; |
926 ngx_http_finalize_request(stream->request, rc); | 921 ngx_http_finalize_request(r, rc); |
922 } | |
923 | |
924 } else if (size) { | |
925 buf = stream->preread; | |
926 | |
927 if (buf == NULL) { | |
928 h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); | |
929 | |
930 buf = ngx_create_temp_buf(r->pool, h2scf->preread_size); | |
931 if (buf == NULL) { | |
932 return ngx_http_v2_connection_error(h2c, | |
933 NGX_HTTP_V2_INTERNAL_ERROR); | |
934 } | |
935 | |
936 stream->preread = buf; | |
937 } | |
938 | |
939 if (size > (size_t) (buf->end - buf->last)) { | |
940 ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0, | |
941 "http2 preread buffer overflow"); | |
942 return ngx_http_v2_connection_error(h2c, | |
943 NGX_HTTP_V2_INTERNAL_ERROR); | |
944 } | |
945 | |
946 buf->last = ngx_cpymem(buf->last, pos, size); | |
927 } | 947 } |
928 | 948 |
929 pos += size; | 949 pos += size; |
930 h2c->state.length -= size; | 950 h2c->state.length -= size; |
931 | 951 |
1056 | 1076 |
1057 status = NGX_HTTP_V2_REFUSED_STREAM; | 1077 status = NGX_HTTP_V2_REFUSED_STREAM; |
1058 goto rst_stream; | 1078 goto rst_stream; |
1059 } | 1079 } |
1060 | 1080 |
1061 if (!h2c->settings_ack && !(h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG)) | 1081 if (!h2c->settings_ack |
1082 && !(h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG) | |
1083 && h2scf->preread_size < NGX_HTTP_V2_DEFAULT_WINDOW) | |
1062 { | 1084 { |
1063 ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, | 1085 ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, |
1064 "client sent stream with data " | 1086 "client sent stream with data " |
1065 "before settings were acknowledged"); | 1087 "before settings were acknowledged"); |
1066 | 1088 |
2432 buf->last = ngx_http_v2_write_uint32(buf->last, | 2454 buf->last = ngx_http_v2_write_uint32(buf->last, |
2433 h2scf->concurrent_streams); | 2455 h2scf->concurrent_streams); |
2434 | 2456 |
2435 buf->last = ngx_http_v2_write_uint16(buf->last, | 2457 buf->last = ngx_http_v2_write_uint16(buf->last, |
2436 NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING); | 2458 NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING); |
2437 buf->last = ngx_http_v2_write_uint32(buf->last, | 2459 buf->last = ngx_http_v2_write_uint32(buf->last, h2scf->preread_size); |
2438 NGX_HTTP_V2_INITIAL_WINDOW); | |
2439 | 2460 |
2440 buf->last = ngx_http_v2_write_uint16(buf->last, | 2461 buf->last = ngx_http_v2_write_uint16(buf->last, |
2441 NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING); | 2462 NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING); |
2442 buf->last = ngx_http_v2_write_uint32(buf->last, | 2463 buf->last = ngx_http_v2_write_uint32(buf->last, |
2443 NGX_HTTP_V2_MAX_FRAME_SIZE); | 2464 NGX_HTTP_V2_MAX_FRAME_SIZE); |
2641 ngx_event_t *rev, *wev; | 2662 ngx_event_t *rev, *wev; |
2642 ngx_connection_t *fc; | 2663 ngx_connection_t *fc; |
2643 ngx_http_log_ctx_t *ctx; | 2664 ngx_http_log_ctx_t *ctx; |
2644 ngx_http_request_t *r; | 2665 ngx_http_request_t *r; |
2645 ngx_http_v2_stream_t *stream; | 2666 ngx_http_v2_stream_t *stream; |
2667 ngx_http_v2_srv_conf_t *h2scf; | |
2646 ngx_http_core_srv_conf_t *cscf; | 2668 ngx_http_core_srv_conf_t *cscf; |
2647 | 2669 |
2648 fc = h2c->free_fake_connections; | 2670 fc = h2c->free_fake_connections; |
2649 | 2671 |
2650 if (fc) { | 2672 if (fc) { |
2754 r->stream = stream; | 2776 r->stream = stream; |
2755 | 2777 |
2756 stream->request = r; | 2778 stream->request = r; |
2757 stream->connection = h2c; | 2779 stream->connection = h2c; |
2758 | 2780 |
2781 h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); | |
2782 | |
2759 stream->send_window = h2c->init_window; | 2783 stream->send_window = h2c->init_window; |
2760 stream->recv_window = NGX_HTTP_V2_INITIAL_WINDOW; | 2784 stream->recv_window = h2scf->preread_size; |
2761 | 2785 |
2762 h2c->processing++; | 2786 h2c->processing++; |
2763 | 2787 |
2764 return stream; | 2788 return stream; |
2765 } | 2789 } |
3409 ngx_int_t | 3433 ngx_int_t |
3410 ngx_http_v2_read_request_body(ngx_http_request_t *r, | 3434 ngx_http_v2_read_request_body(ngx_http_request_t *r, |
3411 ngx_http_client_body_handler_pt post_handler) | 3435 ngx_http_client_body_handler_pt post_handler) |
3412 { | 3436 { |
3413 off_t len; | 3437 off_t len; |
3438 size_t size; | |
3439 ngx_buf_t *buf; | |
3440 ngx_int_t rc; | |
3414 ngx_http_v2_stream_t *stream; | 3441 ngx_http_v2_stream_t *stream; |
3442 ngx_http_v2_srv_conf_t *h2scf; | |
3415 ngx_http_request_body_t *rb; | 3443 ngx_http_request_body_t *rb; |
3416 ngx_http_core_loc_conf_t *clcf; | 3444 ngx_http_core_loc_conf_t *clcf; |
3417 ngx_http_v2_connection_t *h2c; | 3445 ngx_http_v2_connection_t *h2c; |
3418 | 3446 |
3419 stream = r->stream; | 3447 stream = r->stream; |
3442 rb->rest = 1; | 3470 rb->rest = 1; |
3443 rb->post_handler = post_handler; | 3471 rb->post_handler = post_handler; |
3444 | 3472 |
3445 r->request_body = rb; | 3473 r->request_body = rb; |
3446 | 3474 |
3475 h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); | |
3447 clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); | 3476 clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); |
3448 | 3477 |
3449 len = r->headers_in.content_length_n; | 3478 len = r->headers_in.content_length_n; |
3450 | 3479 |
3451 if (r->request_body_no_buffering && !stream->in_closed) { | 3480 if (r->request_body_no_buffering && !stream->in_closed) { |
3452 r->request_body_in_file_only = 0; | |
3453 | 3481 |
3454 if (len < 0 || len > (off_t) clcf->client_body_buffer_size) { | 3482 if (len < 0 || len > (off_t) clcf->client_body_buffer_size) { |
3455 len = clcf->client_body_buffer_size; | 3483 len = clcf->client_body_buffer_size; |
3456 } | 3484 } |
3457 | 3485 |
3486 /* | |
3487 * We need a room to store data up to the stream's initial window size, | |
3488 * at least until this window will be exhausted. | |
3489 */ | |
3490 | |
3491 if (len < (off_t) h2scf->preread_size) { | |
3492 len = h2scf->preread_size; | |
3493 } | |
3494 | |
3458 if (len > NGX_HTTP_V2_MAX_WINDOW) { | 3495 if (len > NGX_HTTP_V2_MAX_WINDOW) { |
3459 len = NGX_HTTP_V2_MAX_WINDOW; | 3496 len = NGX_HTTP_V2_MAX_WINDOW; |
3460 } | 3497 } |
3461 } | 3498 |
3462 | 3499 rb->buf = ngx_create_temp_buf(r->pool, (size_t) len); |
3463 if (len >= 0 && len <= (off_t) clcf->client_body_buffer_size | 3500 |
3464 && !r->request_body_in_file_only) | 3501 } else if (len >= 0 && len <= (off_t) clcf->client_body_buffer_size |
3502 && !r->request_body_in_file_only) | |
3465 { | 3503 { |
3466 rb->buf = ngx_create_temp_buf(r->pool, (size_t) len); | 3504 rb->buf = ngx_create_temp_buf(r->pool, (size_t) len); |
3467 | 3505 |
3468 } else { | 3506 } else { |
3469 rb->buf = ngx_calloc_buf(r->pool); | 3507 rb->buf = ngx_calloc_buf(r->pool); |
3476 if (rb->buf == NULL) { | 3514 if (rb->buf == NULL) { |
3477 stream->skip_data = 1; | 3515 stream->skip_data = 1; |
3478 return NGX_HTTP_INTERNAL_SERVER_ERROR; | 3516 return NGX_HTTP_INTERNAL_SERVER_ERROR; |
3479 } | 3517 } |
3480 | 3518 |
3519 buf = stream->preread; | |
3520 | |
3481 if (stream->in_closed) { | 3521 if (stream->in_closed) { |
3482 r->request_body_no_buffering = 0; | 3522 r->request_body_no_buffering = 0; |
3523 | |
3524 if (buf) { | |
3525 rc = ngx_http_v2_process_request_body(r, buf->pos, | |
3526 buf->last - buf->pos, 1); | |
3527 ngx_pfree(r->pool, buf->start); | |
3528 return rc; | |
3529 } | |
3530 | |
3483 return ngx_http_v2_process_request_body(r, NULL, 0, 1); | 3531 return ngx_http_v2_process_request_body(r, NULL, 0, 1); |
3484 } | 3532 } |
3485 | 3533 |
3486 if (len) { | 3534 if (buf) { |
3487 if (r->request_body_no_buffering) { | 3535 rc = ngx_http_v2_process_request_body(r, buf->pos, |
3488 stream->recv_window = (size_t) len; | 3536 buf->last - buf->pos, 0); |
3489 | 3537 |
3490 } else { | 3538 ngx_pfree(r->pool, buf->start); |
3491 stream->no_flow_control = 1; | 3539 |
3492 stream->recv_window = NGX_HTTP_V2_MAX_WINDOW; | 3540 if (rc != NGX_OK) { |
3493 } | 3541 stream->skip_data = 1; |
3494 | 3542 return rc; |
3495 if (ngx_http_v2_send_window_update(stream->connection, stream->node->id, | 3543 } |
3496 stream->recv_window) | 3544 } |
3545 | |
3546 if (r->request_body_no_buffering) { | |
3547 size = len - h2scf->preread_size; | |
3548 | |
3549 } else { | |
3550 stream->no_flow_control = 1; | |
3551 size = NGX_HTTP_V2_MAX_WINDOW - stream->recv_window; | |
3552 } | |
3553 | |
3554 if (size) { | |
3555 if (ngx_http_v2_send_window_update(stream->connection, | |
3556 stream->node->id, size) | |
3497 == NGX_ERROR) | 3557 == NGX_ERROR) |
3498 { | 3558 { |
3499 stream->skip_data = 1; | 3559 stream->skip_data = 1; |
3500 return NGX_HTTP_INTERNAL_SERVER_ERROR; | 3560 return NGX_HTTP_INTERNAL_SERVER_ERROR; |
3501 } | 3561 } |
3506 if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { | 3566 if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { |
3507 stream->skip_data = 1; | 3567 stream->skip_data = 1; |
3508 return NGX_HTTP_INTERNAL_SERVER_ERROR; | 3568 return NGX_HTTP_INTERNAL_SERVER_ERROR; |
3509 } | 3569 } |
3510 } | 3570 } |
3511 } | 3571 |
3512 | 3572 stream->recv_window += size; |
3513 ngx_add_timer(r->connection->read, clcf->client_body_timeout); | 3573 } |
3574 | |
3575 if (!buf) { | |
3576 ngx_add_timer(r->connection->read, clcf->client_body_timeout); | |
3577 } | |
3514 | 3578 |
3515 r->read_event_handler = ngx_http_v2_read_client_request_body_handler; | 3579 r->read_event_handler = ngx_http_v2_read_client_request_body_handler; |
3516 r->write_event_handler = ngx_http_request_empty_handler; | 3580 r->write_event_handler = ngx_http_request_empty_handler; |
3517 | 3581 |
3518 return NGX_AGAIN; | 3582 return NGX_AGAIN; |
3527 ngx_int_t rc; | 3591 ngx_int_t rc; |
3528 ngx_connection_t *fc; | 3592 ngx_connection_t *fc; |
3529 ngx_http_request_body_t *rb; | 3593 ngx_http_request_body_t *rb; |
3530 ngx_http_core_loc_conf_t *clcf; | 3594 ngx_http_core_loc_conf_t *clcf; |
3531 | 3595 |
3596 fc = r->connection; | |
3532 rb = r->request_body; | 3597 rb = r->request_body; |
3533 | |
3534 if (rb == NULL) { | |
3535 return NGX_OK; | |
3536 } | |
3537 | |
3538 fc = r->connection; | |
3539 buf = rb->buf; | 3598 buf = rb->buf; |
3540 | 3599 |
3541 if (size) { | 3600 if (size) { |
3542 if (buf->sync) { | 3601 if (buf->sync) { |
3543 buf->pos = buf->start = pos; | 3602 buf->pos = buf->start = pos; |
3787 | 3846 |
3788 if (h2c->state.stream == stream) { | 3847 if (h2c->state.stream == stream) { |
3789 window -= h2c->state.length; | 3848 window -= h2c->state.length; |
3790 } | 3849 } |
3791 | 3850 |
3792 if (window == stream->recv_window) { | 3851 if (window <= stream->recv_window) { |
3852 if (window < stream->recv_window) { | |
3853 ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, | |
3854 "http2 negative window update"); | |
3855 stream->skip_data = 1; | |
3856 return NGX_HTTP_INTERNAL_SERVER_ERROR; | |
3857 } | |
3858 | |
3793 return NGX_AGAIN; | 3859 return NGX_AGAIN; |
3794 } | 3860 } |
3795 | 3861 |
3796 if (ngx_http_v2_send_window_update(h2c, stream->node->id, | 3862 if (ngx_http_v2_send_window_update(h2c, stream->node->id, |
3797 window - stream->recv_window) | 3863 window - stream->recv_window) |