Changeset 6566:ce94f07d5082 in nginx


Ignore:
Timestamp:
05/24/16 14:37:52 (19 months ago)
Author:
Valentin Bartenev <vbart@…>
Branch:
default
Message:

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;
  1. 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.

Location:
src/http/v2
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • src/http/v2/ngx_http_v2.c

    r6520 r6566  
    4848
    4949#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
    5550
    5651#define NGX_HTTP_V2_ROOT                         (void *) -1
     
    880875    }
    881876
    882     stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG;
    883 
    884877    h2c->state.stream = stream;
    885878
     
    892885    u_char *end)
    893886{
    894     size_t                 size;
    895     ngx_int_t              rc;
    896     ngx_uint_t             last;
    897     ngx_http_v2_stream_t  *stream;
     887    size_t                   size;
     888    ngx_buf_t               *buf;
     889    ngx_int_t                rc;
     890    ngx_http_request_t      *r;
     891    ngx_http_v2_stream_t    *stream;
     892    ngx_http_v2_srv_conf_t  *h2scf;
    898893
    899894    stream = h2c->state.stream;
     
    914909    if (size >= h2c->state.length) {
    915910        size = h2c->state.length;
    916         last = stream->in_closed;
    917 
    918     } else {
    919         last = 0;
    920     }
    921 
    922     rc = ngx_http_v2_process_request_body(stream->request, pos, size, last);
    923 
    924     if (rc != NGX_OK) {
    925         stream->skip_data = 1;
    926         ngx_http_finalize_request(stream->request, rc);
     911        stream->in_closed  = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG;
     912    }
     913
     914    r = stream->request;
     915
     916    if (r->request_body) {
     917        rc = ngx_http_v2_process_request_body(r, pos, size, stream->in_closed);
     918
     919        if (rc != NGX_OK) {
     920            stream->skip_data = 1;
     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);
    927947    }
    928948
     
    10591079    }
    10601080
    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)
    10621084    {
    10631085        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
     
    24352457        buf->last = ngx_http_v2_write_uint16(buf->last,
    24362458                                         NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING);
    2437         buf->last = ngx_http_v2_write_uint32(buf->last,
    2438                                              NGX_HTTP_V2_INITIAL_WINDOW);
     2459        buf->last = ngx_http_v2_write_uint32(buf->last, h2scf->preread_size);
    24392460
    24402461        buf->last = ngx_http_v2_write_uint16(buf->last,
     
    26442665    ngx_http_request_t        *r;
    26452666    ngx_http_v2_stream_t      *stream;
     2667    ngx_http_v2_srv_conf_t    *h2scf;
    26462668    ngx_http_core_srv_conf_t  *cscf;
    26472669
     
    27572779    stream->connection = h2c;
    27582780
     2781    h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module);
     2782
    27592783    stream->send_window = h2c->init_window;
    2760     stream->recv_window = NGX_HTTP_V2_INITIAL_WINDOW;
     2784    stream->recv_window = h2scf->preread_size;
    27612785
    27622786    h2c->processing++;
     
    34123436{
    34133437    off_t                      len;
     3438    size_t                     size;
     3439    ngx_buf_t                 *buf;
     3440    ngx_int_t                  rc;
    34143441    ngx_http_v2_stream_t      *stream;
     3442    ngx_http_v2_srv_conf_t    *h2scf;
    34153443    ngx_http_request_body_t   *rb;
    34163444    ngx_http_core_loc_conf_t  *clcf;
     
    34453473    r->request_body = rb;
    34463474
     3475    h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module);
    34473476    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
    34483477
     
    34503479
    34513480    if (r->request_body_no_buffering && !stream->in_closed) {
    3452         r->request_body_in_file_only = 0;
    34533481
    34543482        if (len < 0 || len > (off_t) clcf->client_body_buffer_size) {
     
    34563484        }
    34573485
     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
    34583495        if (len > NGX_HTTP_V2_MAX_WINDOW) {
    34593496            len = NGX_HTTP_V2_MAX_WINDOW;
    34603497        }
    3461     }
    3462 
    3463     if (len >= 0 && len <= (off_t) clcf->client_body_buffer_size
    3464         && !r->request_body_in_file_only)
     3498
     3499        rb->buf = ngx_create_temp_buf(r->pool, (size_t) len);
     3500
     3501    } else if (len >= 0 && len <= (off_t) clcf->client_body_buffer_size
     3502               && !r->request_body_in_file_only)
    34653503    {
    34663504        rb->buf = ngx_create_temp_buf(r->pool, (size_t) len);
     
    34793517    }
    34803518
     3519    buf = stream->preread;
     3520
    34813521    if (stream->in_closed) {
    34823522        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
    34833531        return ngx_http_v2_process_request_body(r, NULL, 0, 1);
    34843532    }
    34853533
    3486     if (len) {
    3487         if (r->request_body_no_buffering) {
    3488             stream->recv_window = (size_t) len;
    3489 
    3490         } else {
    3491             stream->no_flow_control = 1;
    3492             stream->recv_window = NGX_HTTP_V2_MAX_WINDOW;
    3493         }
    3494 
    3495         if (ngx_http_v2_send_window_update(stream->connection, stream->node->id,
    3496                                            stream->recv_window)
     3534    if (buf) {
     3535        rc = ngx_http_v2_process_request_body(r, buf->pos,
     3536                                              buf->last - buf->pos, 0);
     3537
     3538        ngx_pfree(r->pool, buf->start);
     3539
     3540        if (rc != NGX_OK) {
     3541            stream->skip_data = 1;
     3542            return rc;
     3543        }
     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)
    34973557            == NGX_ERROR)
    34983558        {
     
    35093569            }
    35103570        }
    3511     }
    3512 
    3513     ngx_add_timer(r->connection->read, clcf->client_body_timeout);
     3571
     3572        stream->recv_window += size;
     3573    }
     3574
     3575    if (!buf) {
     3576        ngx_add_timer(r->connection->read, clcf->client_body_timeout);
     3577    }
    35143578
    35153579    r->read_event_handler = ngx_http_v2_read_client_request_body_handler;
     
    35303594    ngx_http_core_loc_conf_t  *clcf;
    35313595
     3596    fc = r->connection;
    35323597    rb = r->request_body;
    3533 
    3534     if (rb == NULL) {
    3535         return NGX_OK;
    3536     }
    3537 
    3538     fc = r->connection;
    35393598    buf = rb->buf;
    35403599
     
    37903849    }
    37913850
    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
    37933859        return NGX_AGAIN;
    37943860    }
  • src/http/v2/ngx_http_v2.h

    r6514 r6566  
    4646#define NGX_HTTP_V2_PADDED_FLAG          0x08
    4747#define NGX_HTTP_V2_PRIORITY_FLAG        0x20
     48
     49#define NGX_HTTP_V2_MAX_WINDOW           ((1U << 31) - 1)
     50#define NGX_HTTP_V2_DEFAULT_WINDOW       65535
    4851
    4952
     
    175178    size_t                           recv_window;
    176179
     180    ngx_buf_t                       *preread;
     181
    177182    ngx_http_v2_out_frame_t         *free_frames;
    178183    ngx_chain_t                     *free_frame_headers;
  • src/http/v2/ngx_http_v2_module.c

    r6474 r6566  
    3131    void *data);
    3232static char *ngx_http_v2_pool_size(ngx_conf_t *cf, void *post, void *data);
     33static char *ngx_http_v2_preread_size(ngx_conf_t *cf, void *post, void *data);
    3334static char *ngx_http_v2_streams_index_mask(ngx_conf_t *cf, void *post,
    3435    void *data);
     
    4243static ngx_conf_post_t  ngx_http_v2_pool_size_post =
    4344    { ngx_http_v2_pool_size };
     45static ngx_conf_post_t  ngx_http_v2_preread_size_post =
     46    { ngx_http_v2_preread_size };
    4447static ngx_conf_post_t  ngx_http_v2_streams_index_mask_post =
    4548    { ngx_http_v2_streams_index_mask };
     
    8487      offsetof(ngx_http_v2_srv_conf_t, max_header_size),
    8588      NULL },
     89
     90    { ngx_string("http2_body_preread_size"),
     91      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
     92      ngx_conf_set_size_slot,
     93      NGX_HTTP_SRV_CONF_OFFSET,
     94      offsetof(ngx_http_v2_srv_conf_t, preread_size),
     95      &ngx_http_v2_preread_size_post },
    8696
    8797    { ngx_string("http2_streams_index_size"),
     
    317327    h2scf->max_header_size = NGX_CONF_UNSET_SIZE;
    318328
     329    h2scf->preread_size = NGX_CONF_UNSET_SIZE;
     330
    319331    h2scf->streams_index_mask = NGX_CONF_UNSET_UINT;
    320332
     
    342354                              16384);
    343355
     356    ngx_conf_merge_size_value(conf->preread_size, prev->preread_size, 65536);
     357
    344358    ngx_conf_merge_uint_value(conf->streams_index_mask,
    345359                              prev->streams_index_mask, 32 - 1);
     
    421435
    422436static char *
     437ngx_http_v2_preread_size(ngx_conf_t *cf, void *post, void *data)
     438{
     439    size_t *sp = data;
     440
     441    if (*sp > NGX_HTTP_V2_MAX_WINDOW) {
     442        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
     443                           "the maximum body preread buffer size is %uz",
     444                           NGX_HTTP_V2_MAX_WINDOW);
     445
     446        return NGX_CONF_ERROR;
     447    }
     448
     449    return NGX_CONF_OK;
     450}
     451
     452
     453static char *
    423454ngx_http_v2_streams_index_mask(ngx_conf_t *cf, void *post, void *data)
    424455{
  • src/http/v2/ngx_http_v2_module.h

    r6246 r6566  
    2626    size_t                          max_field_size;
    2727    size_t                          max_header_size;
     28    size_t                          preread_size;
    2829    ngx_uint_t                      streams_index_mask;
    2930    ngx_msec_t                      recv_timeout;
Note: See TracChangeset for help on using the changeset viewer.