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)