From is151320 at fhstp.ac.at Sun Sep 2 18:44:19 2018 From: is151320 at fhstp.ac.at (Slawitscheck Stefan) Date: Sun, 2 Sep 2018 20:44:19 +0200 Subject: NGX error logging - Race condition possible? Message-ID: <1787612529.1551.1535913859725@sxalumni.fhstp.ac.at> Dear Development Team, I have one question about the logging mechanism of nginx with more worker processes under Windows. In ngx_log.c is the function ngx_write_console() which calls the systemcall WriteFile(). How is do you ensures that there is no race condition while using this function to write into the error.log file with multiple working processes? >From my understanding, each worker process writes into the error.log file by itself. So it could be that there are two processes (worker) trying to write at the same time?So usually you need some locking, semaphore, mutex or something like that? If you ensure that there will be no race condition, how do you guarantee it? I am looking forward to your answer. Kind regards,Stefan -------------- next part -------------- An HTML attachment was scrubbed... URL: From igor at sysoev.ru Mon Sep 3 06:14:30 2018 From: igor at sysoev.ru (Igor Sysoev) Date: Mon, 3 Sep 2018 09:14:30 +0300 Subject: NGX error logging - Race condition possible? In-Reply-To: <1787612529.1551.1535913859725@sxalumni.fhstp.ac.at> References: <1787612529.1551.1535913859725@sxalumni.fhstp.ac.at> Message-ID: <5481C4C5-BE27-4E20-B7DD-5D33798339D3@sysoev.ru> > On 2 Sep 2018, at 21:44, Slawitscheck Stefan via nginx-devel wrote: > > Dear Development Team, > > I have one question about the logging mechanism of nginx with more worker processes under Windows. > > In ngx_log.c is the function ngx_write_console() which calls the systemcall WriteFile(). > > How is do you ensures that there is no race condition while using this function to write into the error.log file with multiple working processes? > > From my understanding, each worker process writes into the error.log file by itself. So it could be that there are two processes (worker) trying to write at the same time? > So usually you need some locking, semaphore, mutex or something like that? > > If you ensure that there will be no race condition, how do you guarantee it? > > I am looking forward to your answer. It should be guaranteed by CreateFile options: FILE_APPEND_DATA | SYNCHRONIZE. -- Igor Sysoev http://nginx.com From xeioex at nginx.com Mon Sep 3 12:53:11 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Mon, 03 Sep 2018 12:53:11 +0000 Subject: [njs] Fixed compilation with gcc 4.4. Message-ID: details: http://hg.nginx.org/njs/rev/1a02c6a3bdd9 branches: changeset: 598:1a02c6a3bdd9 user: Dmitry Volyntsev date: Mon Sep 03 14:29:52 2018 +0300 description: Fixed compilation with gcc 4.4. This fixes #49 issue on Github. diffstat: nxt/nxt_strtod.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (11 lines): diff -r 25d3bb391edb -r 1a02c6a3bdd9 nxt/nxt_strtod.c --- a/nxt/nxt_strtod.c Fri Aug 31 16:55:35 2018 +0300 +++ b/nxt/nxt_strtod.c Mon Sep 03 14:29:52 2018 +0300 @@ -111,6 +111,7 @@ nxt_adjust_pow10(int exp) return nxt_diyfp(nxt_uint64(0x98968000, 00000000), -40); default: nxt_unreachable(); + return nxt_diyfp(0, 0); } } From mdounin at mdounin.ru Mon Sep 3 16:09:03 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 3 Sep 2018 19:09:03 +0300 Subject: cache: move open to thread pool In-Reply-To: References: <20180808181653.GX56558@mdounin.ru> <20180810113946.GG56558@mdounin.ru> Message-ID: <20180903160903.GI56558@mdounin.ru> Hello! On Mon, Aug 27, 2018 at 02:38:36PM -0700, Ka-Hing Cheung via nginx-devel wrote: > Thanks for all the feedback! This is an updated version of the patch. > There may still be some coding style issues (which will be addressed), > but should address most of the comments other than these two: > > > - The code bypasses open file cache, and uses a direct call > > in the http cache code instead. While it might be ok in your > > setup, it looks like an incomplete solution from the generic point > > of view. A better solution would be to introduce a generic > > interface in ngx_open_cached_file() to allow use of thread > > pools. > > The complications with this just didn't seem worth it. aio_open makes > the most impact if a large number of distinct files need to be opened > and that's when open_file_cache is last effectiv. Instead I made > aio_open to only take effect if open_file_cache is off. The biggest problem with in-place solution is that it requires in-place coding for all the places where we want to use aio_open. Currently we use ngx_open_cached_file() to open files everywhere, and it would be a good idea to preserve it as a starting point for all file open operations. This should be beneficial even if aio_open is only used when open file cache is actually disabled. And I'm actually fine with implementing this only when open file cache is disabled - but implementing this in ngx_open_cached_file(), to make it easier to add more places which use aio_open, and also makeing it possible to further improve things by making it compatible with open_file_cache. Adding a code path somewhere in ngx_open_file_wrapper() should be simple enough. Note that probably there is no need to cover stat() as well, as well as to address various symlinks cases - I think it would be fine if aio_open will only work when there is no need to disable symlinks, and will only work for open(), as stat() after an open is expected to be fast. > > - The code calls ngx_open_and_stat_file() whithin a thread, which > > is relatively complex function never designed to be thread safe. > > While it looks like currently it is, a better solution would be to > > introduce a separate simple function to execute within a thread, > > similar to ngx_thread_read_handler(). > > I kept using ngx_open_and_stat_file() as we are looking into moving > other types of open into thread pool, so we will probably need to have > similar logic anyway. The problem is that ngx_open_and_stat_file() is never coded to be thread safe, and this is expected to cause problems sooner or later - as we've seen in the previous version of your patch, which tried to use pool allocations. A separate simple function is needed to avoid such problems - and to make it explicitly obvious that the code is expected to be thread safe. > > commit 3c67f1d972404bda7713839186a91cb18dbc96be > Author: Ka-Hing Cheung > Date: Fri Aug 3 13:37:58 2018 -0700 > > move open to thread pool > > At cloudflare we found that open() can block a long time, especially > at p99 and p999. Moving it to thread pool improved overall p99 by 6x > or so during peak time. > > This introduces an aio_open option. When set to 'on' the open will be > done in a thread pool. open_file_cache has to be disabled for aio_open > to take effect. > > thread pool does increase CPU usage somewhat but we have not measured > difference in CPU usage for stock "aio threads" compared to after this > patch. > > Only the cache hit open() is moved to thread pool. > > diff --git src/core/ngx_open_file_cache.c src/core/ngx_open_file_cache.c > index b23ee78d..9177ebfd 100644 > --- src/core/ngx_open_file_cache.c > +++ src/core/ngx_open_file_cache.c > @@ -35,8 +35,6 @@ static ngx_fd_t ngx_open_file_wrapper(ngx_str_t *name, > ngx_int_t access, ngx_log_t *log); > static ngx_int_t ngx_file_info_wrapper(ngx_str_t *name, > ngx_open_file_info_t *of, ngx_file_info_t *fi, ngx_log_t *log); > -static ngx_int_t ngx_open_and_stat_file(ngx_str_t *name, > - ngx_open_file_info_t *of, ngx_log_t *log); > static void ngx_open_file_add_event(ngx_open_file_cache_t *cache, > ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log); > static void ngx_open_file_cleanup(void *data); > @@ -836,7 +834,7 @@ ngx_file_info_wrapper(ngx_str_t *name, > ngx_open_file_info_t *of, > } > > > -static ngx_int_t > +ngx_int_t > ngx_open_and_stat_file(ngx_str_t *name, ngx_open_file_info_t *of, > ngx_log_t *log) > { > diff --git src/core/ngx_open_file_cache.h src/core/ngx_open_file_cache.h > index d119c129..144744c0 100644 > --- src/core/ngx_open_file_cache.h > +++ src/core/ngx_open_file_cache.h > @@ -7,6 +7,7 @@ > > #include > #include > +#include > > > #ifndef _NGX_OPEN_FILE_CACHE_H_INCLUDED_ > @@ -124,6 +125,8 @@ ngx_open_file_cache_t > *ngx_open_file_cache_init(ngx_pool_t *pool, > ngx_uint_t max, time_t inactive); > ngx_int_t ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name, > ngx_open_file_info_t *of, ngx_pool_t *pool); > +ngx_int_t ngx_open_and_stat_file(ngx_str_t *name, > + ngx_open_file_info_t *of, ngx_log_t *log); > > > #endif /* _NGX_OPEN_FILE_CACHE_H_INCLUDED_ */ > diff --git src/http/ngx_http_cache.h src/http/ngx_http_cache.h > index f9e96640..8940405a 100644 > --- src/http/ngx_http_cache.h > +++ src/http/ngx_http_cache.h > @@ -97,6 +97,7 @@ struct ngx_http_cache_s { > > #if (NGX_THREADS || NGX_COMPAT) > ngx_thread_task_t *thread_task; > + ngx_int_t open_rv; > #endif > > ngx_msec_t lock_timeout; > @@ -114,6 +115,9 @@ struct ngx_http_cache_s { > unsigned exists:1; > unsigned temp_file:1; > unsigned purged:1; > +#if (NGX_THREADS || NGX_COMPAT) > + unsigned opening:1; > +#endif > unsigned reading:1; > unsigned secondary:1; > unsigned background:1; > diff --git src/http/ngx_http_core_module.c src/http/ngx_http_core_module.c > index c57ec00c..1c7b26c2 100644 > --- src/http/ngx_http_core_module.c > +++ src/http/ngx_http_core_module.c > @@ -420,6 +420,13 @@ static ngx_command_t ngx_http_core_commands[] = { > offsetof(ngx_http_core_loc_conf_t, aio_write), > NULL }, > > + { ngx_string("aio_open"), > + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, > + ngx_conf_set_flag_slot, > + NGX_HTTP_LOC_CONF_OFFSET, > + offsetof(ngx_http_core_loc_conf_t, aio_open), > + NULL }, > + > { ngx_string("read_ahead"), > NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, > ngx_conf_set_size_slot, > @@ -3380,6 +3387,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf) > clcf->subrequest_output_buffer_size = NGX_CONF_UNSET_SIZE; > clcf->aio = NGX_CONF_UNSET; > clcf->aio_write = NGX_CONF_UNSET; > + clcf->aio_open = NGX_CONF_UNSET; > #if (NGX_THREADS) > clcf->thread_pool = NGX_CONF_UNSET_PTR; > clcf->thread_pool_value = NGX_CONF_UNSET_PTR; > @@ -3605,6 +3613,7 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, > void *parent, void *child) > (size_t) ngx_pagesize); > ngx_conf_merge_value(conf->aio, prev->aio, NGX_HTTP_AIO_OFF); > ngx_conf_merge_value(conf->aio_write, prev->aio_write, 0); > + ngx_conf_merge_value(conf->aio_open, prev->aio_open, 0); > #if (NGX_THREADS) > ngx_conf_merge_ptr_value(conf->thread_pool, prev->thread_pool, NULL); > ngx_conf_merge_ptr_value(conf->thread_pool_value, prev->thread_pool_value, > diff --git src/http/ngx_http_core_module.h src/http/ngx_http_core_module.h > index 4c6da7c0..8498eb63 100644 > --- src/http/ngx_http_core_module.h > +++ src/http/ngx_http_core_module.h > @@ -382,6 +382,7 @@ struct ngx_http_core_loc_conf_s { > ngx_flag_t sendfile; /* sendfile */ > ngx_flag_t aio; /* aio */ > ngx_flag_t aio_write; /* aio_write */ > + ngx_flag_t aio_open; /* aio_open */ > ngx_flag_t tcp_nopush; /* tcp_nopush */ > ngx_flag_t tcp_nodelay; /* tcp_nodelay */ > ngx_flag_t reset_timedout_connection; /* reset_timedout_connection */ > diff --git src/http/ngx_http_file_cache.c src/http/ngx_http_file_cache.c > index 56866fa4..10a29d93 100644 > --- src/http/ngx_http_file_cache.c > +++ src/http/ngx_http_file_cache.c > @@ -18,6 +18,8 @@ static void > ngx_http_file_cache_lock_wait(ngx_http_request_t *r, > ngx_http_cache_t *c); > static ngx_int_t ngx_http_file_cache_read(ngx_http_request_t *r, > ngx_http_cache_t *c); > +static ngx_int_t ngx_http_file_cache_aio_open(ngx_http_request_t *r, > + ngx_http_cache_t *c, ngx_int_t rv); > static ssize_t ngx_http_file_cache_aio_read(ngx_http_request_t *r, > ngx_http_cache_t *c); > #if (NGX_HAVE_FILE_AIO) > @@ -268,9 +270,7 @@ ngx_http_file_cache_open(ngx_http_request_t *r) > ngx_uint_t test; > ngx_http_cache_t *c; > ngx_pool_cleanup_t *cln; > - ngx_open_file_info_t of; > ngx_http_file_cache_t *cache; > - ngx_http_core_loc_conf_t *clcf; > > c = r->cache; > > @@ -278,6 +278,12 @@ ngx_http_file_cache_open(ngx_http_request_t *r) > return NGX_AGAIN; > } > > +#if (NGX_THREADS) > + if (c->opening) { > + return ngx_http_file_cache_aio_open(r, c, c->open_rv); > + } > +#endif > + While this looks slightly better than in the previous iteration, this still leaves a lot to be desired. In particular, this still uses duplicate "rv == NGX_DECLINED" checks. > if (c->reading) { > return ngx_http_file_cache_read(r, c); > } > @@ -339,58 +345,10 @@ ngx_http_file_cache_open(ngx_http_request_t *r) > return NGX_ERROR; > } > > - if (!test) { > - goto done; > - } > - > - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); > - > - ngx_memzero(&of, sizeof(ngx_open_file_info_t)); > - > - of.uniq = c->uniq; > - of.valid = clcf->open_file_cache_valid; > - of.min_uses = clcf->open_file_cache_min_uses; > - of.events = clcf->open_file_cache_events; > - of.directio = NGX_OPEN_FILE_DIRECTIO_OFF; > - of.read_ahead = clcf->read_ahead; > - > - if (ngx_open_cached_file(clcf->open_file_cache, &c->file.name, > &of, r->pool) > - != NGX_OK) > - { > - switch (of.err) { > - > - case 0: > - return NGX_ERROR; > - > - case NGX_ENOENT: > - case NGX_ENOTDIR: > - goto done; > - > - default: > - ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err, > - ngx_open_file_n " \"%s\" failed", c->file.name.data); > - return NGX_ERROR; > - } > + if (test) { > + return ngx_http_file_cache_aio_open(r, c, rv); > } > > - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > - "http file cache fd: %d", of.fd); > - > - c->file.fd = of.fd; > - c->file.log = r->connection->log; > - c->uniq = of.uniq; > - c->length = of.size; > - c->fs_size = (of.fs_size + cache->bsize - 1) / cache->bsize; > - > - c->buf = ngx_create_temp_buf(r->pool, c->body_start); > - if (c->buf == NULL) { > - return NGX_ERROR; > - } > - > - return ngx_http_file_cache_read(r, c); > - > -done: > - > if (rv == NGX_DECLINED) { > return ngx_http_file_cache_lock(r, c); > } > @@ -398,7 +356,6 @@ done: > return rv; > } > > - > static ngx_int_t Unrelated change and a style issue. As previously pointed out, there should be two empty lines between functions. > ngx_http_file_cache_lock(ngx_http_request_t *r, ngx_http_cache_t *c) > { > @@ -663,6 +620,106 @@ ngx_http_file_cache_read(ngx_http_request_t *r, > ngx_http_cache_t *c) > } > > > +static ngx_int_t > +ngx_http_file_cache_aio_open(ngx_http_request_t *r, ngx_http_cache_t *c, > + ngx_int_t rv) > +{ > + ngx_int_t rc; > + ngx_open_file_info_t of = { 0 }; Style: assignments should be written separately, except some specific cases where one assignment is allowed in a separate variables block. > + ngx_http_file_cache_t *cache; > + ngx_http_core_loc_conf_t *clcf; > + > + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); > + > + of.uniq = c->uniq; > + of.valid = clcf->open_file_cache_valid; > + of.min_uses = clcf->open_file_cache_min_uses; > + of.events = clcf->open_file_cache_events; > + of.directio = NGX_OPEN_FILE_DIRECTIO_OFF; > + of.read_ahead = clcf->read_ahead; > + > +#if (NGX_THREADS) > + if (clcf->aio == NGX_HTTP_AIO_THREADS && clcf->aio_open && > + clcf->open_file_cache == NULL) { Style: as long as #if contains something complex, it should be separated with empty lines. Style: the "&&" shouldn't be left hanging at the end of line, it should be wrapped to the next line instead. Style: as long as "if" condition is on multiple lines, "{" should be on its own line. > + > + c->file.thread_task = c->thread_task; > + c->file.thread_handler = ngx_http_cache_thread_handler; > + c->file.thread_ctx = r; > + c->open_rv = rv; > + > + rc = ngx_thread_open(&c->file, &of, r->pool); > + > + c->opening = (rc == NGX_AGAIN); > + c->thread_task = c->file.thread_task; > + > + if (rc == NGX_AGAIN) { > + return rc; > + } else if (of.fd != NGX_INVALID_FILE) { > + ngx_pool_cleanup_t *cln; > + ngx_pool_cleanup_file_t *clnf; > + > + cln = ngx_pool_cleanup_add(r->pool, > sizeof(ngx_pool_cleanup_file_t)); > + if (cln == NULL) { > + ngx_close_file(of.fd); > + goto done; > + } > + > + cln->handler = ngx_pool_cleanup_file; > + clnf = cln->data; > + > + clnf->fd = of.fd; > + clnf->name = r->cache->file.name.data; > + clnf->log = r->connection->log; > + goto post_open; > + } > + } > +#endif > + > + rc = ngx_open_cached_file(clcf->open_file_cache, &c->file.name, > &of, r->pool); > + > +#if (NGX_THREADS) > +post_open: > +#endif As previously suggested, all this logic should be hidden in the ngx_open_cached_file() function instead. You may want to take a look at how it is done in ngx_write_chain_to_temp_file() for an example. > + > + if (rc != NGX_OK) { > + switch (of.err) { > + case NGX_OK: > + return NGX_ERROR; > + case NGX_ENOENT: > + case NGX_ENOTDIR: > + goto done; > + > + default: > + ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err, > + ngx_open_file_n " \"%s\" failed", c->file.name.data); > + return NGX_ERROR; > + } Style: you may want to take a look at the original code to find out how to put additional empty lines. > + } > + > + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, > + "http file cache fd: %d", of.fd); > + > + cache = c->file_cache; > + c->file.fd = of.fd; > + c->file.log = r->connection->log; > + c->uniq = of.uniq; > + c->length = of.size; > + c->fs_size = (of.fs_size + cache->bsize - 1) / cache->bsize; > + > + c->buf = ngx_create_temp_buf(r->pool, c->body_start); > + if (c->buf == NULL) { > + return NGX_ERROR; > + } > + > + return ngx_http_file_cache_read(r, c); > + > +done: > + if (rv == NGX_DECLINED) { > + return ngx_http_file_cache_lock(r, c); > + } > + return rv; Style: see above, this needs more empty lines. Also, it might be a good idea to keep the "rv == NGX_DECLINED" / ngx_http_file_cache_lock() processing in a single place. > +} > + > static ssize_t Style: as previously pointed out, there should be two empty lines between functions. > ngx_http_file_cache_aio_read(ngx_http_request_t *r, ngx_http_cache_t *c) > { > @@ -751,30 +808,14 @@ ngx_http_cache_aio_event_handler(ngx_event_t *ev) > static ngx_int_t > ngx_http_cache_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) > { > - ngx_str_t name; > ngx_thread_pool_t *tp; > ngx_http_request_t *r; > - ngx_http_core_loc_conf_t *clcf; > > r = file->thread_ctx; > > - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); > - tp = clcf->thread_pool; > - > + tp = ngx_http_request_get_thread_pool(r); > if (tp == NULL) { > - if (ngx_http_complex_value(r, clcf->thread_pool_value, &name) > - != NGX_OK) > - { > - return NGX_ERROR; > - } > - > - tp = ngx_thread_pool_get((ngx_cycle_t *) ngx_cycle, &name); > - > - if (tp == NULL) { > - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, > - "thread pool \"%V\" not found", &name); > - return NGX_ERROR; > - } > + return NGX_ERROR; > } > > task->event.data = r; This change looks unrelated, as ngx_http_request_get_thread_pool() is never used elsewhere in this patch. > diff --git src/http/ngx_http_request.c src/http/ngx_http_request.c > index 97900915..228cda3d 100644 > --- src/http/ngx_http_request.c > +++ src/http/ngx_http_request.c > @@ -3700,3 +3700,39 @@ ngx_http_log_error_handler(ngx_http_request_t > *r, ngx_http_request_t *sr, > > return buf; > } > + > + > +#if (NGX_THREADS) > + > +ngx_thread_pool_t * > +ngx_http_request_get_thread_pool(ngx_http_request_t *r) > +{ > + ngx_str_t name; > + ngx_thread_pool_t *tp; > + ngx_http_core_loc_conf_t *clcf; > + > + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); > + tp = clcf->thread_pool; > + > + if (tp == NULL) { > + if (ngx_http_complex_value(r, clcf->thread_pool_value, &name) > + != NGX_OK) > + { > + return NULL; > + } > + > + tp = ngx_thread_pool_get((ngx_cycle_t *) ngx_cycle, &name); > + > + if (tp == NULL) { > + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, > + "thread pool \"%V\" not found", &name); > + return NULL; > + } > + > + clcf->thread_pool = tp; > + } > + > + return tp; > +} > + > +#endif > diff --git src/http/ngx_http_request.h src/http/ngx_http_request.h > index 6bfff96e..12df9c76 100644 > --- src/http/ngx_http_request.h > +++ src/http/ngx_http_request.h > @@ -605,4 +605,10 @@ extern ngx_http_header_out_t ngx_http_headers_out[]; > ((ngx_http_log_ctx_t *) log->data)->current_request = r > > > +#if (NGX_THREADS) > +typedef struct ngx_thread_pool_s ngx_thread_pool_t; > + > +ngx_thread_pool_t *ngx_http_request_get_thread_pool(ngx_http_request_t *r); > +#endif > + > #endif /* _NGX_HTTP_REQUEST_H_INCLUDED_ */ > diff --git src/os/unix/ngx_files.c src/os/unix/ngx_files.c > index 482d3276..3260c2aa 100644 > --- src/os/unix/ngx_files.c > +++ src/os/unix/ngx_files.c > @@ -88,15 +88,94 @@ typedef struct { > > size_t nbytes; > ngx_err_t err; > +} ngx_thread_file_read_ctx_t; > + > +typedef struct { > + ngx_str_t name; > + ngx_pool_t *pool; As previously explained, you cannot use the request pool in a thread. As such, this field is not needed. > + ngx_open_file_info_t of; > + ngx_int_t rv; > +} ngx_thread_file_open_ctx_t; > + > +typedef struct { > + union { > + ngx_thread_file_open_ctx_t open; > + ngx_thread_file_read_ctx_t read; > + }; > } ngx_thread_file_ctx_t; Please keep things in a single structure instead. This will allow to avoid many coresponding changes in the file. > > > +static void > +ngx_thread_open_handler(void *data, ngx_log_t *log) > +{ > + ngx_int_t rc; > + ngx_open_file_info_t *of; > + ngx_thread_file_open_ctx_t *ctx; > + > + ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "thread open handler"); > + > + ctx = data; > + of = &ctx->of; > + > + of->fd = NGX_INVALID_FILE; > + > + rc = ngx_open_and_stat_file(&ctx->name, of, log); > + if (rc != NGX_OK && of->err == NGX_OK) { > + of->err = rc; > + } > +} > + > + > +ngx_int_t > +ngx_thread_open(ngx_file_t *file, ngx_open_file_info_t *of, > + ngx_pool_t *pool) > +{ > + ngx_thread_task_t *task; > + ngx_thread_file_open_ctx_t *ctx; > + > + ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0, > + "thread open: %s", file->name.data); > + > + task = file->thread_task; > + > + if (task == NULL) { > + task = ngx_thread_task_alloc(pool, sizeof(ngx_thread_file_ctx_t)); > + if (task == NULL) { > + return NGX_ERROR; > + } > + > + file->thread_task = task; > + } > + > + ctx = task->ctx; > + > + if (task->event.complete) { > + task->event.complete = 0; > + *of = ctx->of; > + > + return of->err; > + } > + > + task->handler = ngx_thread_open_handler; > + > + ctx->pool = pool; > + ctx->name = file->name; > + ctx->of = *of; > + > + if (file->thread_handler(task, file) != NGX_OK) { > + return NGX_ERROR; > + } > + > + return NGX_AGAIN; > +} > + > + > ssize_t > ngx_thread_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset, > ngx_pool_t *pool) > { > ngx_thread_task_t *task; > - ngx_thread_file_ctx_t *ctx; > + ngx_thread_file_read_ctx_t *ctx; > > ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0, > "thread read: %d, %p, %uz, %O", > @@ -155,7 +234,7 @@ ngx_thread_read(ngx_file_t *file, u_char *buf, > size_t size, off_t offset, > static void > ngx_thread_read_handler(void *data, ngx_log_t *log) > { > - ngx_thread_file_ctx_t *ctx = data; > + ngx_thread_file_read_ctx_t *ctx = data; > > ssize_t n; > > @@ -478,7 +557,7 @@ ngx_thread_write_chain_to_file(ngx_file_t *file, > ngx_chain_t *cl, off_t offset, > ngx_pool_t *pool) > { > ngx_thread_task_t *task; > - ngx_thread_file_ctx_t *ctx; > + ngx_thread_file_read_ctx_t *ctx; > > ngx_log_debug3(NGX_LOG_DEBUG_CORE, file->log, 0, > "thread write chain: %d, %p, %O", > @@ -488,7 +567,7 @@ ngx_thread_write_chain_to_file(ngx_file_t *file, > ngx_chain_t *cl, off_t offset, > > if (task == NULL) { > task = ngx_thread_task_alloc(pool, > - sizeof(ngx_thread_file_ctx_t)); > + sizeof(ngx_thread_file_read_ctx_t)); > if (task == NULL) { > return NGX_ERROR; > } > @@ -536,7 +615,7 @@ ngx_thread_write_chain_to_file(ngx_file_t *file, > ngx_chain_t *cl, off_t offset, > static void > ngx_thread_write_chain_to_file_handler(void *data, ngx_log_t *log) > { > - ngx_thread_file_ctx_t *ctx = data; > + ngx_thread_file_read_ctx_t *ctx = data; > > #if (NGX_HAVE_PWRITEV) > > diff --git src/os/unix/ngx_files.h src/os/unix/ngx_files.h > index 07872b13..2659c0cb 100644 > --- src/os/unix/ngx_files.h > +++ src/os/unix/ngx_files.h > @@ -12,11 +12,11 @@ > #include > #include > > - Unrelated (and an incorrect from style point of view) change. > typedef int ngx_fd_t; > typedef struct stat ngx_file_info_t; > typedef ino_t ngx_file_uniq_t; > > +#include > This introduces a dependency of low-level code from high-level OS-independant structures in ngx_open_file_cache.h. Instead, some low-level interface should be used. > typedef struct { > u_char *name; > @@ -385,6 +385,8 @@ extern ngx_uint_t ngx_file_aio; > #endif > > #if (NGX_THREADS) > +ngx_int_t ngx_thread_open(ngx_file_t *file, ngx_open_file_info_t *of, > + ngx_pool_t *pool); > ssize_t ngx_thread_read(ngx_file_t *file, u_char *buf, size_t size, > off_t offset, ngx_pool_t *pool); > ssize_t ngx_thread_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > http://mailman.nginx.org/mailman/listinfo/nginx-devel -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Mon Sep 3 16:31:59 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 03 Sep 2018 16:31:59 +0000 Subject: [nginx] Uwsgi: style. Message-ID: details: http://hg.nginx.org/nginx/rev/b9b3518fa93b branches: changeset: 7347:b9b3518fa93b user: Maxim Dounin date: Mon Sep 03 19:17:01 2018 +0300 description: Uwsgi: style. diffstat: src/http/modules/ngx_http_uwsgi_module.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (14 lines): diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -954,8 +954,8 @@ ngx_http_uwsgi_create_request(ngx_http_r #if 0 /* allow custom uwsgi packet */ if (len > 0 && len < 2) { - ngx_log_error (NGX_LOG_ALERT, r->connection->log, 0, - "uwsgi request is too little: %uz", len); + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "uwsgi request is too little: %uz", len); return NGX_ERROR; } #endif From mdounin at mdounin.ru Mon Sep 3 16:32:01 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 03 Sep 2018 16:32:01 +0000 Subject: [nginx] Uwsgi: added a check on maximum uwsgi request size. Message-ID: details: http://hg.nginx.org/nginx/rev/f6e7831a17d4 branches: changeset: 7348:f6e7831a17d4 user: Maxim Dounin date: Mon Sep 03 19:17:06 2018 +0300 description: Uwsgi: added a check on maximum uwsgi request size. Requested by Chris Caputo. diffstat: src/http/modules/ngx_http_uwsgi_module.c | 6 ++++++ 1 files changed, 6 insertions(+), 0 deletions(-) diffs (16 lines): diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -960,6 +960,12 @@ ngx_http_uwsgi_create_request(ngx_http_r } #endif + if (len > 65535) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "uwsgi request is too big: %uz", len); + return NGX_ERROR; + } + b = ngx_create_temp_buf(r->pool, len + 4); if (b == NULL) { return NGX_ERROR; From mdounin at mdounin.ru Mon Sep 3 16:33:33 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 3 Sep 2018 19:33:33 +0300 Subject: [PATCH] uwsgi - prevent protocol overflow In-Reply-To: References: Message-ID: <20180903163333.GK56558@mdounin.ru> Hello! On Wed, Aug 22, 2018 at 11:57:02PM +0000, Chris Caputo wrote: > At present, ngx_http_uwsgi_module.c's ngx_http_uwsgi_create_request() has > nothing to stop it from dispatching a request exceeding what is possible > per the uwsgi protocol: > > https://uwsgi-docs.readthedocs.io/en/latest/Protocol.html > > The limit is 65,535 (0xffff) and when a request exceeds that size, this > function is currently just overflowing, with the uwsgi handler receiving a > large buffer with a length that doesn't match it. > > Would someone review and help me get the below code to be accepted? > > Thank you, > Chris > > --- ngx_http_uwsgi_module.c.original 2018-08-22 23:41:16.309151481 +0000 > +++ ngx_http_uwsgi_module.c 2018-08-22 23:43:39.546795158 +0000 > @@ -960,6 +960,13 @@ > } > #endif > > + /* enforce uwsgi protocol max len of uint16 */ > + if (len > 0xffff) { > + ngx_log_error (NGX_LOG_ALERT, r->connection->log, 0, > + "uwsgi request is too large for uwsgi protocol: %uz", len); > + return NGX_ERROR; > + } > + > b = ngx_create_temp_buf(r->pool, len + 4); > if (b == NULL) { > return NGX_ERROR; Thanks, I've pushed a slightly simplier patch: http://hg.nginx.org/nginx/rev/f6e7831a17d4 -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Mon Sep 3 16:39:44 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 03 Sep 2018 16:39:44 +0000 Subject: [nginx] gRPC: improved keepalive handling. Message-ID: details: http://hg.nginx.org/nginx/rev/f6047a579ca1 branches: changeset: 7349:f6047a579ca1 user: Maxim Dounin date: Mon Sep 03 19:34:01 2018 +0300 description: gRPC: improved keepalive handling. The code is now able to parse additional control frames after the response is received, and can send control frames as well. This fixes keepalive problems as observed with grpc-c, which can send window update and ping frames after the response, see http://mailman.nginx.org/pipermail/nginx/2018-August/056620.html. diffstat: src/http/modules/ngx_http_grpc_module.c | 100 +++++++++++++++++++++---------- 1 files changed, 67 insertions(+), 33 deletions(-) diffs (173 lines): diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -111,6 +111,7 @@ typedef struct { unsigned output_closed:1; unsigned parsing_headers:1; unsigned end_stream:1; + unsigned done:1; unsigned status:1; ngx_http_request_t *request; @@ -1074,6 +1075,7 @@ ngx_http_grpc_reinit_request(ngx_http_re ctx->output_closed = 0; ctx->parsing_headers = 0; ctx->end_stream = 0; + ctx->done = 0; ctx->status = 0; ctx->connection = NULL; @@ -1093,6 +1095,7 @@ ngx_http_grpc_body_output_filter(void *d ngx_int_t rc; ngx_uint_t next, last; ngx_chain_t *cl, *out, **ll; + ngx_http_upstream_t *u; ngx_http_grpc_ctx_t *ctx; ngx_http_grpc_frame_t *f; @@ -1407,6 +1410,28 @@ ngx_http_grpc_body_output_filter(void *d rc = NGX_AGAIN; } + if (ctx->done) { + + /* + * We have already got the response and were sending some additional + * control frames. Even if there is still something unsent, stop + * here anyway. + */ + + u = r->upstream; + u->length = 0; + + if (ctx->in == NULL + && ctx->out == NULL + && ctx->output_closed + && ctx->state == ngx_http_grpc_st_start) + { + u->keepalive = 1; + } + + ngx_post_event(u->peer.connection->read, &ngx_posted_events); + } + return rc; } @@ -1832,6 +1857,33 @@ ngx_http_grpc_filter(void *data, ssize_t rc = ngx_http_grpc_parse_frame(r, ctx, b); if (rc == NGX_AGAIN) { + + if (ctx->done) { + + /* + * We have finished parsing the response and the + * remaining control frames. If there are unsent + * control frames, post a write event to send them. + */ + + if (ctx->out) { + ngx_post_event(u->peer.connection->write, + &ngx_posted_events); + return NGX_AGAIN; + } + + u->length = 0; + + if (ctx->in == NULL + && ctx->output_closed + && ctx->state == ngx_http_grpc_st_start) + { + u->keepalive = 1; + } + + break; + } + return NGX_AGAIN; } @@ -1898,6 +1950,13 @@ ngx_http_grpc_filter(void *data, ssize_t return NGX_ERROR; } + if (ctx->stream_id && ctx->done) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent frame for closed stream %ui", + ctx->stream_id); + return NGX_ERROR; + } + ctx->padding = 0; } @@ -1914,17 +1973,7 @@ ngx_http_grpc_filter(void *data, ssize_t ctx->state = ngx_http_grpc_st_start; if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) { - u->length = 0; - - if (ctx->in == NULL - && ctx->out == NULL - && ctx->output_closed - && b->last == b->pos) - { - u->keepalive = 1; - } - - break; + ctx->done = 1; } continue; @@ -2094,17 +2143,8 @@ ngx_http_grpc_filter(void *data, ssize_t "grpc trailer done"); if (ctx->end_stream) { - u->length = 0; - - if (ctx->in == NULL - && ctx->out == NULL - && ctx->output_closed - && b->last == b->pos) - { - u->keepalive = 1; - } - - return NGX_OK; + ctx->done = 1; + break; } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, @@ -2121,6 +2161,10 @@ ngx_http_grpc_filter(void *data, ssize_t return NGX_ERROR; } + if (rc == NGX_HTTP_PARSE_HEADER_DONE) { + continue; + } + /* rc == NGX_AGAIN */ if (ctx->rest == 0) { @@ -2237,17 +2281,7 @@ ngx_http_grpc_filter(void *data, ssize_t ctx->state = ngx_http_grpc_st_start; if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) { - u->length = 0; - - if (ctx->in == NULL - && ctx->out == NULL - && ctx->output_closed - && b->last == b->pos) - { - u->keepalive = 1; - } - - break; + ctx->done = 1; } } From mdounin at mdounin.ru Mon Sep 3 16:39:45 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 03 Sep 2018 16:39:45 +0000 Subject: [nginx] gRPC: disabled keepalive when sending control frames was blocked. Message-ID: details: http://hg.nginx.org/nginx/rev/67c6cb7f477c branches: changeset: 7350:67c6cb7f477c user: Maxim Dounin date: Mon Sep 03 19:34:02 2018 +0300 description: gRPC: disabled keepalive when sending control frames was blocked. If sending request body was not completed (u->request_body_sent is not set), the upstream keepalive module won't save such a connection. However, it is theoretically possible (though highly unlikely) that sending of some control frames can be blocked after the request body was sent. The ctx->output_blocked flag introduced to disable keepalive in such cases. diffstat: src/http/modules/ngx_http_grpc_module.c | 12 ++++++++++++ 1 files changed, 12 insertions(+), 0 deletions(-) diffs (57 lines): diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -109,6 +109,7 @@ typedef struct { unsigned header_sent:1; unsigned output_closed:1; + unsigned output_blocked:1; unsigned parsing_headers:1; unsigned end_stream:1; unsigned done:1; @@ -1073,6 +1074,7 @@ ngx_http_grpc_reinit_request(ngx_http_re ctx->state = 0; ctx->header_sent = 0; ctx->output_closed = 0; + ctx->output_blocked = 0; ctx->parsing_headers = 0; ctx->end_stream = 0; ctx->done = 0; @@ -1410,6 +1412,13 @@ ngx_http_grpc_body_output_filter(void *d rc = NGX_AGAIN; } + if (rc == NGX_AGAIN) { + ctx->output_blocked = 1; + + } else { + ctx->output_blocked = 0; + } + if (ctx->done) { /* @@ -1424,6 +1433,7 @@ ngx_http_grpc_body_output_filter(void *d if (ctx->in == NULL && ctx->out == NULL && ctx->output_closed + && !ctx->output_blocked && ctx->state == ngx_http_grpc_st_start) { u->keepalive = 1; @@ -1774,6 +1784,7 @@ ngx_http_grpc_process_header(ngx_http_re if (ctx->in == NULL && ctx->out == NULL && ctx->output_closed + && !ctx->output_blocked && b->last == b->pos) { u->keepalive = 1; @@ -1876,6 +1887,7 @@ ngx_http_grpc_filter(void *data, ssize_t if (ctx->in == NULL && ctx->output_closed + && !ctx->output_blocked && ctx->state == ngx_http_grpc_st_start) { u->keepalive = 1; From kahing at cloudflare.com Tue Sep 4 23:58:05 2018 From: kahing at cloudflare.com (Ka-Hing Cheung) Date: Tue, 4 Sep 2018 16:58:05 -0700 Subject: cache: move open to thread pool In-Reply-To: <20180903160903.GI56558@mdounin.ru> References: <20180808181653.GX56558@mdounin.ru> <20180810113946.GG56558@mdounin.ru> <20180903160903.GI56558@mdounin.ru> Message-ID: On Mon, Sep 3, 2018 at 9:09 AM, Maxim Dounin wrote: > The biggest problem with in-place solution is that it requires > in-place coding for all the places where we want to use aio_open. > Currently we use ngx_open_cached_file() to open files everywhere, > and it would be a good idea to preserve it as a starting point for > all file open operations. > > This should be beneficial even if aio_open is only used when open > file cache is actually disabled. And I'm actually fine with > implementing this only when open file cache is disabled - but > implementing this in ngx_open_cached_file(), to make it easier to > add more places which use aio_open, and also makeing it possible > to further improve things by making it compatible with > open_file_cache. The most difficult thing to switch to threaded open isn't which method to call, but to expect NGX_AGAIN and have the necessary bookkeeping in place to resume, so imo this is a pretty weak argument. > > Adding a code path somewhere in ngx_open_file_wrapper() should be > simple enough. Note that probably there is no need to cover > stat() as well, as well as to address various symlinks cases - I > think it would be fine if aio_open will only work when there is no > need to disable symlinks, and will only work for open(), as stat() > after an open is expected to be fast. A problem is that someone need to own the thread task (which for other aio is owned by ngx_file, and someone needs to own the ngx_file). ngx_open_cached_file doesn't care about anything beyond the file name. That can of course change, but changing API is subjective and difficult for me as an external contributor to decide on. I can try to find time to do the change if you want to comment on how it should look like. >> > - The code calls ngx_open_and_stat_file() whithin a thread, which >> > is relatively complex function never designed to be thread safe. >> > While it looks like currently it is, a better solution would be to >> > introduce a separate simple function to execute within a thread, >> > similar to ngx_thread_read_handler(). >> >> I kept using ngx_open_and_stat_file() as we are looking into moving >> other types of open into thread pool, so we will probably need to have >> similar logic anyway. > > The problem is that ngx_open_and_stat_file() is never coded to be > thread safe, and this is expected to cause problems sooner or switched to use ngx_open_file_wrapper > > While this looks slightly better than in the previous iteration, > this still leaves a lot to be desired. In particular, this still > uses duplicate "rv == NGX_DECLINED" checks. I poked around at it and thinks this one line duplication is the cleanest. ngx_http_file_cache_read can also return DECLINED and cleaning up nginx return code seems to be beyond the scope of this work. > >> + ngx_open_file_info_t of; >> + ngx_int_t rv; >> +} ngx_thread_file_open_ctx_t; >> + >> +typedef struct { >> + union { >> + ngx_thread_file_open_ctx_t open; >> + ngx_thread_file_read_ctx_t read; >> + }; >> } ngx_thread_file_ctx_t; > > Please keep things in a single structure instead. This will allow > to avoid many coresponding changes in the file. Is 72 bytes per request not worth saving? >> typedef int ngx_fd_t; >> typedef struct stat ngx_file_info_t; >> typedef ino_t ngx_file_uniq_t; >> >> +#include >> > > This introduces a dependency of low-level code from high-level > OS-independant structures in ngx_open_file_cache.h. Instead, some > low-level interface should be used. Do you have a more concrete suggestion? Addressed the other style and extraneous changes. - Ka-Hing commit f20eb2dc3f60c4360cc13101d15e94e5471027b6 Author: Ka-Hing Cheung Date: Fri Aug 3 13:37:58 2018 -0700 move open to thread pool At cloudflare we found that open() can block a long time, especially at p99 and p999. Moving it to thread pool improved overall p99 by 6x or so during peak time. This introduces an aio_open option. When set to 'on' the open will be done in a thread pool. open_file_cache has to be disabled for aio_open to take effect. thread pool does increase CPU usage somewhat but we have not measured difference in CPU usage for stock "aio threads" compared to after this patch. Only the cache hit open() is moved to thread pool. diff --git src/core/ngx_open_file_cache.c src/core/ngx_open_file_cache.c index b23ee78d..36da0218 100644 --- src/core/ngx_open_file_cache.c +++ src/core/ngx_open_file_cache.c @@ -30,9 +30,6 @@ static ngx_int_t ngx_file_o_path_info(ngx_fd_t fd, ngx_file_info_t *fi, ngx_log_t *log); #endif #endif -static ngx_fd_t ngx_open_file_wrapper(ngx_str_t *name, - ngx_open_file_info_t *of, ngx_int_t mode, ngx_int_t create, - ngx_int_t access, ngx_log_t *log); static ngx_int_t ngx_file_info_wrapper(ngx_str_t *name, ngx_open_file_info_t *of, ngx_file_info_t *fi, ngx_log_t *log); static ngx_int_t ngx_open_and_stat_file(ngx_str_t *name, @@ -610,7 +607,7 @@ ngx_file_o_path_info(ngx_fd_t fd, ngx_file_info_t *fi, ngx_log_t *log) #endif /* NGX_HAVE_OPENAT */ -static ngx_fd_t +ngx_fd_t ngx_open_file_wrapper(ngx_str_t *name, ngx_open_file_info_t *of, ngx_int_t mode, ngx_int_t create, ngx_int_t access, ngx_log_t *log) { diff --git src/core/ngx_open_file_cache.h src/core/ngx_open_file_cache.h index d119c129..9c1c6bdc 100644 --- src/core/ngx_open_file_cache.h +++ src/core/ngx_open_file_cache.h @@ -7,6 +7,7 @@ #include #include +#include #ifndef _NGX_OPEN_FILE_CACHE_H_INCLUDED_ @@ -124,6 +125,8 @@ ngx_open_file_cache_t *ngx_open_file_cache_init(ngx_pool_t *pool, ngx_uint_t max, time_t inactive); ngx_int_t ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name, ngx_open_file_info_t *of, ngx_pool_t *pool); +ngx_fd_t ngx_open_file_wrapper(ngx_str_t *name, ngx_open_file_info_t *of, + ngx_int_t mode, ngx_int_t create, ngx_int_t access, ngx_log_t *log); #endif /* _NGX_OPEN_FILE_CACHE_H_INCLUDED_ */ diff --git src/http/ngx_http_cache.h src/http/ngx_http_cache.h index f9e96640..8940405a 100644 --- src/http/ngx_http_cache.h +++ src/http/ngx_http_cache.h @@ -97,6 +97,7 @@ struct ngx_http_cache_s { #if (NGX_THREADS || NGX_COMPAT) ngx_thread_task_t *thread_task; + ngx_int_t open_rv; #endif ngx_msec_t lock_timeout; @@ -114,6 +115,9 @@ struct ngx_http_cache_s { unsigned exists:1; unsigned temp_file:1; unsigned purged:1; +#if (NGX_THREADS || NGX_COMPAT) + unsigned opening:1; +#endif unsigned reading:1; unsigned secondary:1; unsigned background:1; diff --git src/http/ngx_http_core_module.c src/http/ngx_http_core_module.c index c57ec00c..1c7b26c2 100644 --- src/http/ngx_http_core_module.c +++ src/http/ngx_http_core_module.c @@ -420,6 +420,13 @@ static ngx_command_t ngx_http_core_commands[] = { offsetof(ngx_http_core_loc_conf_t, aio_write), NULL }, + { ngx_string("aio_open"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_core_loc_conf_t, aio_open), + NULL }, + { ngx_string("read_ahead"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -3380,6 +3387,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf) clcf->subrequest_output_buffer_size = NGX_CONF_UNSET_SIZE; clcf->aio = NGX_CONF_UNSET; clcf->aio_write = NGX_CONF_UNSET; + clcf->aio_open = NGX_CONF_UNSET; #if (NGX_THREADS) clcf->thread_pool = NGX_CONF_UNSET_PTR; clcf->thread_pool_value = NGX_CONF_UNSET_PTR; @@ -3605,6 +3613,7 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) (size_t) ngx_pagesize); ngx_conf_merge_value(conf->aio, prev->aio, NGX_HTTP_AIO_OFF); ngx_conf_merge_value(conf->aio_write, prev->aio_write, 0); + ngx_conf_merge_value(conf->aio_open, prev->aio_open, 0); #if (NGX_THREADS) ngx_conf_merge_ptr_value(conf->thread_pool, prev->thread_pool, NULL); ngx_conf_merge_ptr_value(conf->thread_pool_value, prev->thread_pool_value, diff --git src/http/ngx_http_core_module.h src/http/ngx_http_core_module.h index 4c6da7c0..8498eb63 100644 --- src/http/ngx_http_core_module.h +++ src/http/ngx_http_core_module.h @@ -382,6 +382,7 @@ struct ngx_http_core_loc_conf_s { ngx_flag_t sendfile; /* sendfile */ ngx_flag_t aio; /* aio */ ngx_flag_t aio_write; /* aio_write */ + ngx_flag_t aio_open; /* aio_open */ ngx_flag_t tcp_nopush; /* tcp_nopush */ ngx_flag_t tcp_nodelay; /* tcp_nodelay */ ngx_flag_t reset_timedout_connection; /* reset_timedout_connection */ diff --git src/http/ngx_http_file_cache.c src/http/ngx_http_file_cache.c index 56866fa4..894ac70b 100644 --- src/http/ngx_http_file_cache.c +++ src/http/ngx_http_file_cache.c @@ -18,6 +18,8 @@ static void ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c); static ngx_int_t ngx_http_file_cache_read(ngx_http_request_t *r, ngx_http_cache_t *c); +static ngx_int_t ngx_http_file_cache_aio_open(ngx_http_request_t *r, + ngx_http_cache_t *c, ngx_int_t rv); static ssize_t ngx_http_file_cache_aio_read(ngx_http_request_t *r, ngx_http_cache_t *c); #if (NGX_HAVE_FILE_AIO) @@ -268,9 +270,7 @@ ngx_http_file_cache_open(ngx_http_request_t *r) ngx_uint_t test; ngx_http_cache_t *c; ngx_pool_cleanup_t *cln; - ngx_open_file_info_t of; ngx_http_file_cache_t *cache; - ngx_http_core_loc_conf_t *clcf; c = r->cache; @@ -278,6 +278,12 @@ ngx_http_file_cache_open(ngx_http_request_t *r) return NGX_AGAIN; } +#if (NGX_THREADS) + if (c->opening) { + return ngx_http_file_cache_aio_open(r, c, c->open_rv); + } +#endif + if (c->reading) { return ngx_http_file_cache_read(r, c); } @@ -339,58 +345,10 @@ ngx_http_file_cache_open(ngx_http_request_t *r) return NGX_ERROR; } - if (!test) { - goto done; - } - - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - - ngx_memzero(&of, sizeof(ngx_open_file_info_t)); - - of.uniq = c->uniq; - of.valid = clcf->open_file_cache_valid; - of.min_uses = clcf->open_file_cache_min_uses; - of.events = clcf->open_file_cache_events; - of.directio = NGX_OPEN_FILE_DIRECTIO_OFF; - of.read_ahead = clcf->read_ahead; - - if (ngx_open_cached_file(clcf->open_file_cache, &c->file.name, &of, r->pool) - != NGX_OK) - { - switch (of.err) { - - case 0: - return NGX_ERROR; - - case NGX_ENOENT: - case NGX_ENOTDIR: - goto done; - - default: - ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err, - ngx_open_file_n " \"%s\" failed", c->file.name.data); - return NGX_ERROR; - } + if (test) { + return ngx_http_file_cache_aio_open(r, c, rv); } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http file cache fd: %d", of.fd); - - c->file.fd = of.fd; - c->file.log = r->connection->log; - c->uniq = of.uniq; - c->length = of.size; - c->fs_size = (of.fs_size + cache->bsize - 1) / cache->bsize; - - c->buf = ngx_create_temp_buf(r->pool, c->body_start); - if (c->buf == NULL) { - return NGX_ERROR; - } - - return ngx_http_file_cache_read(r, c); - -done: - if (rv == NGX_DECLINED) { return ngx_http_file_cache_lock(r, c); } @@ -398,7 +356,6 @@ done: return rv; } - static ngx_int_t ngx_http_file_cache_lock(ngx_http_request_t *r, ngx_http_cache_t *c) { @@ -663,6 +620,114 @@ ngx_http_file_cache_read(ngx_http_request_t *r, ngx_http_cache_t *c) } +static ngx_int_t +ngx_http_file_cache_aio_open(ngx_http_request_t *r, ngx_http_cache_t *c, + ngx_int_t rv) +{ + ngx_int_t rc; + ngx_open_file_info_t of; + ngx_http_file_cache_t *cache; + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + ngx_memzero(&of, sizeof(of)); + + of.uniq = c->uniq; + of.valid = clcf->open_file_cache_valid; + of.min_uses = clcf->open_file_cache_min_uses; + of.events = clcf->open_file_cache_events; + of.directio = NGX_OPEN_FILE_DIRECTIO_OFF; + of.read_ahead = clcf->read_ahead; + +#if (NGX_THREADS) + + if (clcf->aio == NGX_HTTP_AIO_THREADS && clcf->aio_open + && clcf->open_file_cache == NULL) + { + c->file.thread_task = c->thread_task; + c->file.thread_handler = ngx_http_cache_thread_handler; + c->file.thread_ctx = r; + c->open_rv = rv; + + rc = ngx_thread_open(&c->file, &of, r->pool); + + c->opening = (rc == NGX_AGAIN); + c->thread_task = c->file.thread_task; + + if (rc == NGX_AGAIN) { + return rc; + } else if (of.fd != NGX_INVALID_FILE) { + ngx_pool_cleanup_t *cln; + ngx_pool_cleanup_file_t *clnf; + + cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_pool_cleanup_file_t)); + if (cln == NULL) { + ngx_close_file(of.fd); + goto done; + } + + cln->handler = ngx_pool_cleanup_file; + clnf = cln->data; + + clnf->fd = of.fd; + clnf->name = r->cache->file.name.data; + clnf->log = r->connection->log; + goto post_open; + } + } + +#endif + + rc = ngx_open_cached_file(clcf->open_file_cache, &c->file.name, &of, r->pool); + +#if (NGX_THREADS) +post_open: +#endif + + if (rc != NGX_OK) { + switch (of.err) { + + case NGX_OK: + return NGX_ERROR; + + case NGX_ENOENT: + case NGX_ENOTDIR: + goto done; + + default: + ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err, + ngx_open_file_n " \"%s\" failed", c->file.name.data); + return NGX_ERROR; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http file cache fd: %d", of.fd); + + cache = c->file_cache; + c->file.fd = of.fd; + c->file.log = r->connection->log; + c->uniq = of.uniq; + c->length = of.size; + c->fs_size = (of.fs_size + cache->bsize - 1) / cache->bsize; + + c->buf = ngx_create_temp_buf(r->pool, c->body_start); + if (c->buf == NULL) { + return NGX_ERROR; + } + + return ngx_http_file_cache_read(r, c); + +done: + if (rv == NGX_DECLINED) { + return ngx_http_file_cache_lock(r, c); + } + + return rv; +} + + static ssize_t ngx_http_file_cache_aio_read(ngx_http_request_t *r, ngx_http_cache_t *c) { @@ -775,6 +840,8 @@ ngx_http_cache_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) "thread pool \"%V\" not found", &name); return NGX_ERROR; } + + clcf->thread_pool = tp; } task->event.data = r; diff --git src/os/unix/ngx_files.c src/os/unix/ngx_files.c index 482d3276..286f7d7f 100644 --- src/os/unix/ngx_files.c +++ src/os/unix/ngx_files.c @@ -88,15 +88,88 @@ typedef struct { size_t nbytes; ngx_err_t err; +} ngx_thread_file_read_ctx_t; + +typedef struct { + ngx_str_t name; + ngx_open_file_info_t of; + ngx_int_t rv; +} ngx_thread_file_open_ctx_t; + +typedef struct { + union { + ngx_thread_file_open_ctx_t open; + ngx_thread_file_read_ctx_t read; + }; } ngx_thread_file_ctx_t; +static void +ngx_thread_open_handler(void *data, ngx_log_t *log) +{ + ngx_open_file_info_t *of; + ngx_thread_file_open_ctx_t *ctx; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "thread open handler"); + + ctx = data; + of = &ctx->of; + + of->fd = ngx_open_file_wrapper(&ctx->name, of, + NGX_FILE_RDONLY|NGX_FILE_NONBLOCK, + NGX_FILE_OPEN, 0, log); +} + + +ngx_int_t +ngx_thread_open(ngx_file_t *file, ngx_open_file_info_t *of, + ngx_pool_t *pool) +{ + ngx_thread_task_t *task; + ngx_thread_file_open_ctx_t *ctx; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0, + "thread open: %s", file->name.data); + + task = file->thread_task; + + if (task == NULL) { + task = ngx_thread_task_alloc(pool, sizeof(ngx_thread_file_ctx_t)); + if (task == NULL) { + return NGX_ERROR; + } + + file->thread_task = task; + } + + ctx = task->ctx; + + if (task->event.complete) { + task->event.complete = 0; + *of = ctx->of; + + return of->err; + } + + task->handler = ngx_thread_open_handler; + + ctx->name = file->name; + ctx->of = *of; + + if (file->thread_handler(task, file) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_AGAIN; +} + + ssize_t ngx_thread_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset, ngx_pool_t *pool) { ngx_thread_task_t *task; - ngx_thread_file_ctx_t *ctx; + ngx_thread_file_read_ctx_t *ctx; ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0, "thread read: %d, %p, %uz, %O", @@ -155,7 +228,7 @@ ngx_thread_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset, static void ngx_thread_read_handler(void *data, ngx_log_t *log) { - ngx_thread_file_ctx_t *ctx = data; + ngx_thread_file_read_ctx_t *ctx = data; ssize_t n; @@ -478,7 +551,7 @@ ngx_thread_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, off_t offset, ngx_pool_t *pool) { ngx_thread_task_t *task; - ngx_thread_file_ctx_t *ctx; + ngx_thread_file_read_ctx_t *ctx; ngx_log_debug3(NGX_LOG_DEBUG_CORE, file->log, 0, "thread write chain: %d, %p, %O", @@ -488,7 +561,7 @@ ngx_thread_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, off_t offset, if (task == NULL) { task = ngx_thread_task_alloc(pool, - sizeof(ngx_thread_file_ctx_t)); + sizeof(ngx_thread_file_read_ctx_t)); if (task == NULL) { return NGX_ERROR; } @@ -536,7 +609,7 @@ ngx_thread_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, off_t offset, static void ngx_thread_write_chain_to_file_handler(void *data, ngx_log_t *log) { - ngx_thread_file_ctx_t *ctx = data; + ngx_thread_file_read_ctx_t *ctx = data; #if (NGX_HAVE_PWRITEV) diff --git src/os/unix/ngx_files.h src/os/unix/ngx_files.h index 07872b13..c7b826e7 100644 --- src/os/unix/ngx_files.h +++ src/os/unix/ngx_files.h @@ -17,6 +17,7 @@ typedef int ngx_fd_t; typedef struct stat ngx_file_info_t; typedef ino_t ngx_file_uniq_t; +#include typedef struct { u_char *name; @@ -385,6 +386,8 @@ extern ngx_uint_t ngx_file_aio; #endif #if (NGX_THREADS) +ngx_int_t ngx_thread_open(ngx_file_t *file, ngx_open_file_info_t *of, + ngx_pool_t *pool); ssize_t ngx_thread_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset, ngx_pool_t *pool); ssize_t ngx_thread_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, From dnj0496 at gmail.com Sat Sep 8 05:54:09 2018 From: dnj0496 at gmail.com (Dk Jack) Date: Fri, 7 Sep 2018 22:54:09 -0700 Subject: rewrite q. Message-ID: Hi, >From within my module, I'd like rewrite the URL. Especially, the host portion of the URL. In the nginx devel guide it talks about nginx internal redirect but that's only affecting the uri portion of the url. I'd like to do what proxy_pass does but from within my module. It'd help me immensely, ff someone can direct me to some documentation or an example of how this can be accomplish this. Thanks. regards, Dk. -------------- next part -------------- An HTML attachment was scrubbed... URL: From mdounin at mdounin.ru Mon Sep 10 17:29:00 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 10 Sep 2018 17:29:00 +0000 Subject: [nginx] Lingering close changed to handle NGX_AGAIN. Message-ID: details: http://hg.nginx.org/nginx/rev/2b5528023f6b branches: changeset: 7351:2b5528023f6b user: Maxim Dounin date: Mon Sep 10 18:57:13 2018 +0300 description: Lingering close changed to handle NGX_AGAIN. The "do { c->recv() } while (c->read->ready)" form used in the ngx_http_lingering_close_handler() is not really correct, as for example with SSL c->read->ready may be still set when returning NGX_AGAIN due to SSL_ERROR_WANT_WRITE. Therefore the above might be an infinite loop. This doesn't really matter in lingering close, as we shutdown write side of the socket anyway and also disable renegotiation (and even without shutdown and with renegotiation it requires using very large certificate chain and tuning socket buffers to trigger SSL_ERROR_WANT_WRITE). But for the sake of correctness added an NGX_AGAIN check. diffstat: src/http/ngx_http_request.c | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-) diffs (14 lines): diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -3311,6 +3311,10 @@ ngx_http_lingering_close_handler(ngx_eve ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "lingering read: %z", n); + if (n == NGX_AGAIN) { + break; + } + if (n == NGX_ERROR || n == 0) { ngx_http_close_request(r, 0); return; From mdounin at mdounin.ru Mon Sep 10 17:29:02 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 10 Sep 2018 17:29:02 +0000 Subject: [nginx] SSL: corrected SSL_ERROR_WANT_WRITE / SSL_ERROR_WANT_READ logging. Message-ID: details: http://hg.nginx.org/nginx/rev/0de0b16a551c branches: changeset: 7352:0de0b16a551c user: Maxim Dounin date: Mon Sep 10 18:57:19 2018 +0300 description: SSL: corrected SSL_ERROR_WANT_WRITE / SSL_ERROR_WANT_READ logging. While SSL_read() most likely to return SSL_ERROR_WANT_WRITE (and SSL_write() accordingly SSL_ERROR_WANT_READ) during an SSL renegotiation, it is not necessary mean that a renegotiation was started. In particular, it can never happen during a renegotiation or can happen multiple times during a renegotiation. Because of the above, misleading "peer started SSL renegotiation" info messages were replaced with "SSL_read: want write" and "SSL_write: want read" debug ones. Additionally, "SSL write handler" and "SSL read handler" are now logged by the SSL write and read handlers, to make it easier to understand that temporary SSL handlers are called instead of normal handlers. diffstat: src/event/ngx_event_openssl.c | 12 ++++++++---- 1 files changed, 8 insertions(+), 4 deletions(-) diffs (43 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -1681,8 +1681,8 @@ ngx_ssl_handle_recv(ngx_connection_t *c, if (sslerr == SSL_ERROR_WANT_WRITE) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "peer started SSL renegotiation"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_read: want write"); c->write->ready = 0; @@ -1724,6 +1724,8 @@ ngx_ssl_write_handler(ngx_event_t *wev) c = wev->data; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL write handler"); + c->read->handler(c->read); } @@ -1938,8 +1940,8 @@ ngx_ssl_write(ngx_connection_t *c, u_cha if (sslerr == SSL_ERROR_WANT_READ) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "peer started SSL renegotiation"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_write: want read"); c->read->ready = 0; @@ -1977,6 +1979,8 @@ ngx_ssl_read_handler(ngx_event_t *rev) c = rev->data; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL read handler"); + c->write->handler(c->write); } From mdounin at mdounin.ru Mon Sep 10 17:29:03 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 10 Sep 2018 17:29:03 +0000 Subject: [nginx] SSL: restore handlers after blocking. Message-ID: details: http://hg.nginx.org/nginx/rev/87d2ea860f38 branches: changeset: 7353:87d2ea860f38 user: Maxim Dounin date: Mon Sep 10 18:57:39 2018 +0300 description: SSL: restore handlers after blocking. It is possible that after SSL_read() will return SSL_ERROR_WANT_WRITE, further calls will return SSL_ERROR_WANT_READ without reading any application data. We have to call ngx_handle_write_event() and switch back to normal write handling much like we do if there are some application data, or the write there will be reported again and again. Similarly, we have to switch back to normal read handling if there is saved read handler and SSL_write() returns SSL_ERROR_WANT_WRITE. diffstat: src/event/ngx_event_openssl.c | 28 ++++++++++++++++++++++++++++ 1 files changed, 28 insertions(+), 0 deletions(-) diffs (45 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -1675,6 +1675,20 @@ ngx_ssl_handle_recv(ngx_connection_t *c, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); if (sslerr == SSL_ERROR_WANT_READ) { + + if (c->ssl->saved_write_handler) { + + c->write->handler = c->ssl->saved_write_handler; + c->ssl->saved_write_handler = NULL; + c->write->ready = 1; + + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_post_event(c->write, &ngx_posted_events); + } + c->read->ready = 0; return NGX_AGAIN; } @@ -1934,6 +1948,20 @@ ngx_ssl_write(ngx_connection_t *c, u_cha ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); if (sslerr == SSL_ERROR_WANT_WRITE) { + + if (c->ssl->saved_read_handler) { + + c->read->handler = c->ssl->saved_read_handler; + c->ssl->saved_read_handler = NULL; + c->read->ready = 1; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_post_event(c->read, &ngx_posted_events); + } + c->write->ready = 0; return NGX_AGAIN; } From dnj0496 at gmail.com Tue Sep 11 05:58:29 2018 From: dnj0496 at gmail.com (Dk Jack) Date: Mon, 10 Sep 2018 22:58:29 -0700 Subject: body capture and redirect Message-ID: Hi, In my module, I am trying to forward the request to my server based on the content of the request body. To acheive this, I've added a body capture filter to capture the body. My code is something like this... static ngx_int_t nginx_inspect_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ... // extract body if (if_content_of_interest_in_body(body, body_length)) { ngx_str_t uri = ngx_string("/my_location"); ngx_http_internal_redirect(r, &url, NULL); ngx_http_finalize_request(r, NGX_DONE); return NGX_DONE; } ... } I have the following conf for '/my_location': server { ... location / { ... } location /my_location { proxy_pass http://myserver; } } However, I am running into an issue with my code. The request seems to get forwarded to my server like I expected. However, my connection seems to hang. Looks like the server seems to be waiting to read more data from nginx. When I interrupt my server (ctrl-c; its a simple python server), it sort breaks out of the read loop and a response is returned. Sending the same request to my server without sending it through my module in nginx, behaves correctly. Could someone more experienced in nginx, point out what I am doing wrong? Is redirect allowed from a body filter handler? Thanks for your help in advance. Regards, Dk. -------------- next part -------------- An HTML attachment was scrubbed... URL: From xeioex at nginx.com Tue Sep 11 12:36:07 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 11 Sep 2018 12:36:07 +0000 Subject: [njs] Added njs_vm_object_alloc(). Message-ID: details: http://hg.nginx.org/njs/rev/48267f0ebab3 branches: changeset: 600:48267f0ebab3 user: Dmitry Volyntsev date: Tue Sep 11 15:34:48 2018 +0300 description: Added njs_vm_object_alloc(). diffstat: njs/njs.c | 72 +++++++++++++++++++++++++++++++++ njs/njs.h | 2 + njs/test/njs_unit_test.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 174 insertions(+), 1 deletions(-) diffs (213 lines): diff -r 39c741a9994c -r 48267f0ebab3 njs/njs.c --- a/njs/njs.c Tue Sep 11 15:34:25 2018 +0300 +++ b/njs/njs.c Tue Sep 11 15:34:48 2018 +0300 @@ -686,6 +686,78 @@ njs_ret_t njs_vm_retval_to_ext_string(nj } +njs_ret_t +njs_vm_object_alloc(njs_vm_t *vm, njs_value_t *retval, ...) +{ + va_list args; + nxt_int_t ret; + njs_ret_t rc; + njs_value_t *name, *value; + njs_object_t *object; + njs_object_prop_t *prop; + nxt_lvlhsh_query_t lhq; + + object = njs_object_alloc(vm); + if (nxt_slow_path(object == NULL)) { + return NJS_ERROR; + } + + rc = NJS_ERROR; + + va_start(args, retval); + + for ( ;; ) { + name = va_arg(args, njs_value_t *); + if (name == NULL) { + break; + } + + value = va_arg(args, njs_value_t *); + if (value == NULL) { + njs_type_error(vm, "missed value for a key"); + goto done; + } + + if (nxt_slow_path(!njs_is_string(name))) { + njs_type_error(vm, "prop name is not a string"); + goto done; + } + + lhq.replace = 0; + lhq.pool = vm->mem_cache_pool; + lhq.proto = &njs_object_hash_proto; + + njs_string_get(name, &lhq.key); + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); + + prop = njs_object_prop_alloc(vm, name, value, 1); + if (nxt_slow_path(prop == NULL)) { + goto done; + } + + lhq.value = prop; + + ret = nxt_lvlhsh_insert(&object->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_internal_error(vm, NULL); + goto done; + } + } + + rc = NJS_OK; + + retval->data.u.object = object; + retval->type = NJS_OBJECT; + retval->data.truth = 1; + +done: + + va_end(args); + + return rc; +} + + njs_value_t * njs_vm_object_prop(njs_vm_t *vm, const njs_value_t *value, const nxt_str_t *key) { diff -r 39c741a9994c -r 48267f0ebab3 njs/njs.h --- a/njs/njs.h Tue Sep 11 15:34:25 2018 +0300 +++ b/njs/njs.h Tue Sep 11 15:34:48 2018 +0300 @@ -227,6 +227,8 @@ NXT_EXPORT nxt_int_t njs_value_is_functi NXT_EXPORT njs_ret_t njs_vm_value_dump(njs_vm_t *vm, nxt_str_t *retval, const njs_value_t *value, nxt_uint_t indent); +NXT_EXPORT njs_ret_t njs_vm_object_alloc(njs_vm_t *vm, njs_value_t *retval, + ...); NXT_EXPORT njs_value_t *njs_vm_object_prop(njs_vm_t *vm, const njs_value_t *value, const nxt_str_t *key); diff -r 39c741a9994c -r 48267f0ebab3 njs/test/njs_unit_test.c --- a/njs/test/njs_unit_test.c Tue Sep 11 15:34:25 2018 +0300 +++ b/njs/test/njs_unit_test.c Tue Sep 11 15:34:48 2018 +0300 @@ -10300,9 +10300,103 @@ done: } +static nxt_int_t +njs_vm_object_alloc_test(njs_vm_t * vm, nxt_bool_t disassemble, + nxt_bool_t verbose) +{ + njs_ret_t ret; + njs_value_t args[2], obj; + + static const njs_value_t num_key = njs_string("num"); + static const njs_value_t bool_key = njs_string("bool"); + + njs_value_number_set(njs_argument(&args, 0), 1); + njs_value_boolean_set(njs_argument(&args, 1), 0); + + ret = njs_vm_object_alloc(vm, &obj, NULL); + if (ret != NJS_OK) { + return NXT_ERROR; + } + + ret = njs_vm_object_alloc(vm, &obj, &num_key, NULL); + if (ret == NJS_OK) { + return NXT_ERROR; + } + + ret = njs_vm_object_alloc(vm, &obj, &num_key, &args[0], NULL); + if (ret != NJS_OK) { + return NXT_ERROR; + } + + ret = njs_vm_object_alloc(vm, &obj, &num_key, &args[0], &bool_key, &args[1], + NULL); + if (ret != NJS_OK) { + return NXT_ERROR; + } + + return NXT_OK; +} + + +typedef struct { + nxt_int_t (*test)(njs_vm_t *, nxt_bool_t, nxt_bool_t); + nxt_str_t name; +} njs_api_test_t; + + +static nxt_int_t +njs_api_test(nxt_bool_t disassemble, nxt_bool_t verbose) +{ + njs_vm_t *vm; + nxt_int_t ret, rc; + nxt_uint_t i; + njs_vm_opt_t options; + njs_api_test_t *test; + + static njs_api_test_t njs_api_test[] = + { + { njs_vm_object_alloc_test, + nxt_string("njs_vm_object_alloc_test") } + }; + + rc = NXT_ERROR; + + vm = NULL; + memset(&options, 0, sizeof(njs_vm_opt_t)); + + for (i = 0; i < nxt_nitems(njs_api_test); i++) { + test = &njs_api_test[i]; + + vm = njs_vm_create(&options); + if (vm == NULL) { + printf("njs_vm_create() failed\n"); + goto done; + } + + ret = test->test(vm, disassemble, verbose); + if (nxt_slow_path(ret != NXT_OK)) { + printf("njs_api_test: \"%.*s\" test failed\n", + (int) test->name.length, test->name.start); + goto done; + } + } + + rc = NXT_OK; + +done: + + if (vm != NULL) { + njs_vm_destroy(vm); + } + + return rc; +} + + int nxt_cdecl main(int argc, char **argv) { + nxt_int_t ret; nxt_bool_t disassemble, verbose; disassemble = 0; @@ -10324,5 +10418,10 @@ main(int argc, char **argv) } } - return njs_unit_test(disassemble, verbose); + ret = njs_unit_test(disassemble, verbose); + if (ret != NXT_OK) { + return ret; + } + + return njs_api_test(disassemble, verbose); } From xeioex at nginx.com Tue Sep 11 12:36:07 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 11 Sep 2018 12:36:07 +0000 Subject: [njs] Allowing to create repeatable events. Message-ID: details: http://hg.nginx.org/njs/rev/39c741a9994c branches: changeset: 599:39c741a9994c user: Dmitry Volyntsev date: Tue Sep 11 15:34:25 2018 +0300 description: Allowing to create repeatable events. njs_vm_add_event() prototype is extended to allow creating oneshot vs repeatable events. diffstat: nginx/ngx_http_js_module.c | 2 +- njs/njs.c | 11 +++++++++-- njs/njs.h | 2 +- njs/njs_event.h | 1 + njs/njs_time.c | 1 + 5 files changed, 13 insertions(+), 4 deletions(-) diffs (81 lines): diff -r 1a02c6a3bdd9 -r 39c741a9994c nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Mon Sep 03 14:29:52 2018 +0300 +++ b/nginx/ngx_http_js_module.c Tue Sep 11 15:34:25 2018 +0300 @@ -2074,7 +2074,7 @@ ngx_http_js_subrequest(ngx_http_request_ return NJS_ERROR; } - vm_event = njs_vm_add_event(ctx->vm, callback, NULL, NULL); + vm_event = njs_vm_add_event(ctx->vm, callback, 1, NULL, NULL); if (vm_event == NULL) { njs_vm_error(ctx->vm, "internal error"); return NJS_ERROR; diff -r 1a02c6a3bdd9 -r 39c741a9994c njs/njs.c --- a/njs/njs.c Mon Sep 03 14:29:52 2018 +0300 +++ b/njs/njs.c Tue Sep 11 15:34:25 2018 +0300 @@ -486,7 +486,7 @@ njs_vm_call(njs_vm_t *vm, njs_function_t njs_vm_event_t -njs_vm_add_event(njs_vm_t *vm, njs_function_t *function, +njs_vm_add_event(njs_vm_t *vm, njs_function_t *function, nxt_uint_t once, njs_host_event_t host_ev, njs_event_destructor destructor) { njs_event_t *event; @@ -499,6 +499,7 @@ njs_vm_add_event(njs_vm_t *vm, njs_funct event->host_event = host_ev; event->destructor = destructor; event->function = function; + event->once = once; event->posted = 0; event->nargs = 0; event->args = NULL; @@ -633,7 +634,13 @@ njs_vm_handle_events(njs_vm_t *vm) ev = nxt_queue_link_data(link, njs_event_t, link); - njs_del_event(vm, ev, NJS_EVENT_DELETE); + if (ev->once) { + njs_del_event(vm, ev, NJS_EVENT_DELETE); + + } else { + ev->posted = 0; + nxt_queue_remove(&ev->link); + } ret = njs_vm_call(vm, ev->function, ev->args, ev->nargs); diff -r 1a02c6a3bdd9 -r 39c741a9994c njs/njs.h --- a/njs/njs.h Mon Sep 03 14:29:52 2018 +0300 +++ b/njs/njs.h Tue Sep 11 15:34:25 2018 +0300 @@ -164,7 +164,7 @@ NXT_EXPORT nxt_int_t njs_vm_call(njs_vm_ const njs_value_t *args, nxt_uint_t nargs); NXT_EXPORT njs_vm_event_t njs_vm_add_event(njs_vm_t *vm, - njs_function_t *function, njs_host_event_t host_ev, + njs_function_t *function, nxt_uint_t once, njs_host_event_t host_ev, njs_event_destructor destructor); NXT_EXPORT void njs_vm_del_event(njs_vm_t *vm, njs_vm_event_t vm_event); NXT_EXPORT nxt_int_t njs_vm_pending(njs_vm_t *vm); diff -r 1a02c6a3bdd9 -r 39c741a9994c njs/njs_event.h --- a/njs/njs_event.h Mon Sep 03 14:29:52 2018 +0300 +++ b/njs/njs_event.h Tue Sep 11 15:34:25 2018 +0300 @@ -26,6 +26,7 @@ typedef struct { nxt_queue_link_t link; unsigned posted:1; + unsigned once:1; } njs_event_t; diff -r 1a02c6a3bdd9 -r 39c741a9994c njs/njs_time.c --- a/njs/njs_time.c Mon Sep 03 14:29:52 2018 +0300 +++ b/njs/njs_time.c Tue Sep 11 15:34:25 2018 +0300 @@ -48,6 +48,7 @@ njs_set_timeout(njs_vm_t *vm, njs_value_ event->destructor = ops->clear_timer; event->function = args[1].data.u.function; event->nargs = (nargs >= 3) ? nargs - 3 : 0; + event->once = 1; event->posted = 0; if (event->nargs != 0) { From xeioex at nginx.com Tue Sep 11 12:36:08 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 11 Sep 2018 12:36:08 +0000 Subject: [njs] Fixed macro for aligned size of njs_frame_t struct. Message-ID: details: http://hg.nginx.org/njs/rev/c2cddf3b97b7 branches: changeset: 602:c2cddf3b97b7 user: Dmitry Volyntsev date: Tue Sep 11 15:35:27 2018 +0300 description: Fixed macro for aligned size of njs_frame_t struct. NJS_FRAME_SIZE did not take into account the variable length of closures array. This can result in overlapping addresses for native_frame->arguments and frame->closures[n], diffstat: njs/njs_function.c | 8 ++++---- njs/njs_function.h | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diffs (41 lines): diff -r bbec3cdb747b -r c2cddf3b97b7 njs/njs_function.c --- a/njs/njs_function.c Tue Sep 11 15:34:50 2018 +0300 +++ b/njs/njs_function.c Tue Sep 11 15:35:27 2018 +0300 @@ -166,10 +166,9 @@ njs_function_frame(njs_vm_t *vm, njs_fun closures = lambda->nesting + lambda->block_closures; - size = NJS_FRAME_SIZE + size = njs_frame_size(closures) + (function->args_offset + max_args) * sizeof(njs_value_t) - + lambda->local_size - + closures * sizeof(njs_closure_t *); + + lambda->local_size; native_frame = njs_function_frame_alloc(vm, size); if (nxt_slow_path(native_frame == NULL)) { @@ -182,7 +181,8 @@ njs_function_frame(njs_vm_t *vm, njs_fun /* Function arguments. */ - value = (njs_value_t *) ((u_char *) native_frame + NJS_FRAME_SIZE); + value = (njs_value_t *) ((u_char *) native_frame + + njs_frame_size(closures)); native_frame->arguments = value; bound = function->bound; diff -r bbec3cdb747b -r c2cddf3b97b7 njs/njs_function.h --- a/njs/njs_function.h Tue Sep 11 15:34:50 2018 +0300 +++ b/njs/njs_function.h Tue Sep 11 15:35:27 2018 +0300 @@ -45,8 +45,9 @@ struct njs_function_lambda_s { nxt_align_size(sizeof(njs_native_frame_t), sizeof(njs_value_t)) /* The frame size must be aligned to njs_value_t. */ -#define NJS_FRAME_SIZE \ - nxt_align_size(sizeof(njs_frame_t), sizeof(njs_value_t)) +#define njs_frame_size(closures) \ + nxt_align_size(sizeof(njs_frame_t) + closures * sizeof(njs_closure_t *), \ + sizeof(njs_value_t)) /* The retval field is not used in the global frame. */ #define NJS_GLOBAL_FRAME_SIZE \ From xeioex at nginx.com Tue Sep 11 12:36:07 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 11 Sep 2018 12:36:07 +0000 Subject: [njs] Nginx stream module refactored. Message-ID: details: http://hg.nginx.org/njs/rev/bbec3cdb747b branches: changeset: 601:bbec3cdb747b user: Dmitry Volyntsev date: Tue Sep 11 15:34:50 2018 +0300 description: Nginx stream module refactored. The module handlers are refactored to make them similar to the HTTP ones. This change is not backward compatible with the existing njs code. To allow asynchronous operations the model of evaluation of njs handlers is changed. A handler function (which is set for a corresponding nginx directive) is invoked only once. It can register a callback if more data is necessary, by calling s.on(, ). Available events: upload - new data from a client. download - new data from an upstream. Callback prototype: callback(data, flags). data - string. flags - object. Available properties: last - boolean, data is a last buffer. A callback can be deregistered by s.off(). Return codes are replaced with special methods: s.allow(), s.deny(), s.done(). s.done([code]) (s.OK), can be used to return arbitrary code. s.allow() (s.OK) s.deny() (s.ABORT) In addition, s.decline() method is added to allow handlers to stop processing of the current handler and pass control to the next handler of the current phase. A handler is expected to invoke one of these methods when the processing is done. For example js_preread can wait for additional data by registering a callback which will be called whenever new incoming data appears. function js_preread(s) { s.on('upload', function(data, flags) { // process data // to proceed to the next phase s.done() // to proceed to next handler // of the current phase s.decline() }) } js_filter handler is refactored. 1) The current data chunk is moved from s.buffer to the callback argument. 2) s.fromUpstream is removed. 3) The properties related to the current data chunk (s.eof) are put into the second callback argument. s.eof is renamed to flags.last. 3) s.send(data[, opts]) is added to replace s.buffer = data; opts - object, can be used to override nginx buffer flags derived from an incoming data chunk buffer. It can contain boolean flags: last, flush. 4) data toward corresponding direction is not forwarded if a callback is active, a callback is expected to call s.send(data, flags) if it wants to pass data as is. 5) s.send() can be called multiple times per callback invocation. function stream_filter(s) { s.on('upload', function (data, flags) { // process a data chunk from a client // stop filtering data s.off('upload') }) s.on('download', function (data, flags) { // process data from upstream // send my_data as the last buffer. s.send(my_data, {last:1}); }) } diffstat: nginx/ngx_stream_js_module.c | 933 +++++++++++++++++++++++++++++------------- 1 files changed, 635 insertions(+), 298 deletions(-) diffs (truncated from 1184 to 1000 lines): diff -r 48267f0ebab3 -r bbec3cdb747b nginx/ngx_stream_js_module.c --- a/nginx/ngx_stream_js_module.c Tue Sep 11 15:34:48 2018 +0300 +++ b/nginx/ngx_stream_js_module.c Tue Sep 11 15:34:50 2018 +0300 @@ -1,6 +1,7 @@ /* * Copyright (C) Roman Arutyunyan + * Copyright (C) Dmitry Volyntsev * Copyright (C) NGINX, Inc. */ @@ -26,16 +27,29 @@ typedef struct { typedef struct { - njs_vm_t *vm; - ngx_log_t *log; - njs_opaque_value_t arg; - ngx_buf_t *buf; - ngx_chain_t *free; - ngx_chain_t *busy; + njs_vm_t *vm; + ngx_log_t *log; + njs_opaque_value_t args[3]; + ngx_buf_t *buf; + ngx_chain_t **last_out; + ngx_chain_t *free; + ngx_chain_t *busy; + ngx_stream_session_t *session; + ngx_int_t status; + njs_vm_event_t upload_event; + njs_vm_event_t download_event; + unsigned from_upstream:1; + unsigned filter:1; + unsigned in_progress:1; +} ngx_stream_js_ctx_t; + + +typedef struct { ngx_stream_session_t *session; - unsigned from_upstream:1; - unsigned filter:1; -} ngx_stream_js_ctx_t; + njs_vm_event_t vm_event; + void *unused; + ngx_int_t ident; +} ngx_stream_js_event_t; static ngx_int_t ngx_stream_js_access_handler(ngx_stream_session_t *s); @@ -49,17 +63,24 @@ static ngx_int_t ngx_stream_js_variable( static ngx_int_t ngx_stream_js_init_vm(ngx_stream_session_t *s); static void ngx_stream_js_cleanup_ctx(void *data); static void ngx_stream_js_cleanup_vm(void *data); +static njs_ret_t ngx_stream_js_buffer_arg(ngx_stream_session_t *s, + njs_value_t *buffer); +static njs_ret_t ngx_stream_js_flags_arg(ngx_stream_session_t *s, + njs_value_t *flags); +static njs_vm_event_t *ngx_stream_js_event(ngx_stream_session_t *s, + nxt_str_t *event); static njs_ret_t ngx_stream_js_ext_get_remote_address(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); -static njs_ret_t ngx_stream_js_ext_get_eof(njs_vm_t *vm, njs_value_t *value, - void *obj, uintptr_t data); -static njs_ret_t ngx_stream_js_ext_get_from_upstream(njs_vm_t *vm, - njs_value_t *value, void *obj, uintptr_t data); -static njs_ret_t ngx_stream_js_ext_get_buffer(njs_vm_t *vm, njs_value_t *value, - void *obj, uintptr_t data); -static njs_ret_t ngx_stream_js_ext_set_buffer(njs_vm_t *vm, void *obj, - uintptr_t data, nxt_str_t *value); + +static njs_ret_t ngx_stream_js_ext_done(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t ngx_stream_js_ext_deny(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t ngx_stream_js_ext_decline(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t ngx_stream_js_ext_set_status(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, ngx_int_t status); static njs_ret_t ngx_stream_js_ext_log(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); @@ -69,11 +90,23 @@ static njs_ret_t ngx_stream_js_ext_error nxt_uint_t nargs, njs_index_t unused); static njs_ret_t ngx_stream_js_ext_log_core(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, ngx_uint_t level); +static njs_ret_t ngx_stream_js_ext_on(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t ngx_stream_js_ext_off(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t ngx_stream_js_ext_send(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); static njs_ret_t ngx_stream_js_ext_get_variable(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); -static njs_ret_t ngx_stream_js_ext_get_code(njs_vm_t *vm, - njs_value_t *value, void *obj, uintptr_t data); + +static njs_host_event_t ngx_stream_js_set_timer(njs_external_ptr_t external, + uint64_t delay, njs_vm_event_t vm_event); +static void ngx_stream_js_clear_timer(njs_external_ptr_t external, + njs_host_event_t event); +static void ngx_stream_js_timer_handler(ngx_event_t *ev); +static void ngx_stream_js_handle_event(ngx_stream_session_t *s, + njs_vm_event_t vm_event, njs_value_t *args, nxt_uint_t nargs); static char *ngx_stream_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -169,43 +202,6 @@ static njs_external_t ngx_stream_js_ext NULL, 0 }, - { nxt_string("eof"), - NJS_EXTERN_PROPERTY, - NULL, - 0, - ngx_stream_js_ext_get_eof, - NULL, - NULL, - NULL, - NULL, - NULL, - 0 }, - - { nxt_string("fromUpstream"), - NJS_EXTERN_PROPERTY, - NULL, - 0, - ngx_stream_js_ext_get_from_upstream, - NULL, - NULL, - NULL, - NULL, - NULL, - 0 }, - - { nxt_string("buffer"), - NJS_EXTERN_PROPERTY, - NULL, - 0, - ngx_stream_js_ext_get_buffer, - ngx_stream_js_ext_set_buffer, - NULL, - NULL, - NULL, - NULL, - 0 }, - - { nxt_string("variables"), NJS_EXTERN_OBJECT, NULL, @@ -218,6 +214,54 @@ static njs_external_t ngx_stream_js_ext NULL, 0 }, + { nxt_string("allow"), + NJS_EXTERN_METHOD, + NULL, + 0, + NULL, + NULL, + NULL, + NULL, + NULL, + ngx_stream_js_ext_done, + 0 }, + + { nxt_string("deny"), + NJS_EXTERN_METHOD, + NULL, + 0, + NULL, + NULL, + NULL, + NULL, + NULL, + ngx_stream_js_ext_deny, + 0 }, + + { nxt_string("decline"), + NJS_EXTERN_METHOD, + NULL, + 0, + NULL, + NULL, + NULL, + NULL, + NULL, + ngx_stream_js_ext_decline, + 0 }, + + { nxt_string("done"), + NJS_EXTERN_METHOD, + NULL, + 0, + NULL, + NULL, + NULL, + NULL, + NULL, + ngx_stream_js_ext_done, + 0 }, + { nxt_string("log"), NJS_EXTERN_METHOD, NULL, @@ -254,65 +298,42 @@ static njs_external_t ngx_stream_js_ext ngx_stream_js_ext_error, 0 }, - { nxt_string("OK"), - NJS_EXTERN_PROPERTY, + { nxt_string("on"), + NJS_EXTERN_METHOD, NULL, 0, - ngx_stream_js_ext_get_code, - NULL, - NULL, - NULL, - NULL, - NULL, - -NGX_OK }, - - { nxt_string("DECLINED"), - NJS_EXTERN_PROPERTY, - NULL, - 0, - ngx_stream_js_ext_get_code, NULL, NULL, NULL, NULL, NULL, - -NGX_DECLINED }, + ngx_stream_js_ext_on, + 0 }, - { nxt_string("AGAIN"), - NJS_EXTERN_PROPERTY, + { nxt_string("off"), + NJS_EXTERN_METHOD, NULL, 0, - ngx_stream_js_ext_get_code, NULL, NULL, NULL, NULL, NULL, - -NGX_AGAIN }, + ngx_stream_js_ext_off, + 0 }, - { nxt_string("ERROR"), - NJS_EXTERN_PROPERTY, + { nxt_string("send"), + NJS_EXTERN_METHOD, NULL, 0, - ngx_stream_js_ext_get_code, NULL, NULL, NULL, NULL, NULL, - -NGX_ERROR }, + ngx_stream_js_ext_send, + 0 }, - { nxt_string("ABORT"), - NJS_EXTERN_PROPERTY, - NULL, - 0, - ngx_stream_js_ext_get_code, - NULL, - NULL, - NULL, - NULL, - NULL, - -NGX_ABORT }, }; @@ -332,13 +353,18 @@ static njs_external_t ngx_stream_js_ext }; +static njs_vm_ops_t ngx_stream_js_ops = { + ngx_stream_js_set_timer, + ngx_stream_js_clear_timer +}; + + static ngx_stream_filter_pt ngx_stream_next_filter; static ngx_int_t ngx_stream_js_access_handler(ngx_stream_session_t *s) { - ngx_int_t rc; ngx_stream_js_srv_conf_t *jscf; ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, @@ -346,15 +372,7 @@ ngx_stream_js_access_handler(ngx_stream_ jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - rc = ngx_stream_js_phase_handler(s, &jscf->access); - - if (rc == NGX_ABORT) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "access forbidden by js"); - rc = NGX_STREAM_FORBIDDEN; - } - - return rc; + return ngx_stream_js_phase_handler(s, &jscf->access); } @@ -375,76 +393,111 @@ ngx_stream_js_preread_handler(ngx_stream static ngx_int_t ngx_stream_js_phase_handler(ngx_stream_session_t *s, ngx_str_t *name) { - nxt_str_t fname, value, exception; - ngx_int_t rc; - njs_function_t *func; - ngx_connection_t *c; - ngx_stream_js_ctx_t *ctx; + nxt_str_t fname, exception; + njs_ret_t ret; + ngx_int_t rc; + njs_function_t *func; + ngx_connection_t *c; + ngx_stream_js_ctx_t *ctx; if (name->len == 0) { return NGX_DECLINED; } - c = s->connection; - rc = ngx_stream_js_init_vm(s); if (rc != NGX_OK) { return rc; } + c = s->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "http js phase call \"%V\"", name); + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - fname.start = name->data; - fname.length = name->len; + if (!ctx->in_progress) { + fname.start = name->data; + fname.length = name->len; + + func = njs_vm_function(ctx->vm, &fname); - func = njs_vm_function(ctx->vm, &fname); + if (func == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "js function \"%V\" not found", name); + return NGX_ERROR; + } - if (func == NULL) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, "js function \"%V\" not found", - name); - return NGX_ERROR; - } + /* + * status is expected to be overriden by allow(), deny(), decline() or + * done() methods. + */ - if (njs_vm_call(ctx->vm, func, njs_value_arg(&ctx->arg), 1) != NJS_OK) { - njs_vm_retval_to_ext_string(ctx->vm, &exception); + ctx->status = NGX_ERROR; - ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %*s", - exception.length, exception.start); - - return NGX_ERROR; + ret = njs_vm_call(ctx->vm, func, njs_value_arg(&ctx->args), 1); + if (ret != NJS_OK) { + goto exception; + } } - if (njs_value_is_void(njs_vm_retval(ctx->vm))) { - return NGX_OK; - } + if (ctx->upload_event != NULL) { + ret = ngx_stream_js_buffer_arg(s, njs_value_arg(&ctx->args[1])); + if (ret != NJS_OK) { + goto exception; + } - if (njs_vm_retval_to_ext_string(ctx->vm, &value) != NJS_OK) { - return NGX_ERROR; + ret = ngx_stream_js_flags_arg(s, njs_value_arg(&ctx->args[2])); + if (ret != NJS_OK) { + goto exception; + } + + njs_vm_post_event(ctx->vm, ctx->upload_event, + njs_value_arg(&ctx->args[1]), 2); + + rc = njs_vm_run(ctx->vm); + if (rc == NJS_ERROR) { + goto exception; + } } - ngx_log_debug2(NGX_LOG_DEBUG_STREAM, c->log, 0, "js return value: \"%*s\"", - value.length, value.start); - - rc = ngx_atoi(value.start, value.length); + if (njs_vm_pending(ctx->vm)) { + ctx->in_progress = 1; + rc = ctx->upload_event ? NGX_AGAIN : NGX_DONE; - if (rc == NGX_ERROR) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "unexpected js return code: \"%*s\"", - value.length, value.start); - return NGX_ERROR; + } else { + ctx->in_progress = 0; + rc = ctx->status; } - return -rc; + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream js phase rc: %i", + rc); + + return rc; + +exception: + + njs_vm_retval_to_ext_string(ctx->vm, &exception); + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %*s", + exception.length, exception.start); + + return NGX_ERROR; } +#define ngx_stream_event(from_upstream) \ + (from_upstream ? ctx->download_event : ctx->upload_event) + + static ngx_int_t ngx_stream_js_body_filter(ngx_stream_session_t *s, ngx_chain_t *in, ngx_uint_t from_upstream) { - nxt_str_t name, value, exception; + nxt_str_t name, exception; + njs_ret_t ret; ngx_int_t rc; - ngx_chain_t *out, *cl, **ll; + ngx_chain_t *out, *cl; njs_function_t *func; ngx_connection_t *c; ngx_stream_js_ctx_t *ctx; @@ -472,83 +525,88 @@ ngx_stream_js_body_filter(ngx_stream_ses ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - ctx->filter = 1; + if (!ctx->filter) { + name.start = jscf->filter.data; + name.length = jscf->filter.len; - name.start = jscf->filter.data; - name.length = jscf->filter.len; + func = njs_vm_function(ctx->vm, &name); - func = njs_vm_function(ctx->vm, &name); + if (func == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "js function \"%V\" not found", &jscf->filter); + return NGX_ERROR; + } - if (func == NULL) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, "js function \"%V\" not found", - &jscf->filter); - return NGX_ERROR; + ret = njs_vm_call(ctx->vm, func, njs_value_arg(&ctx->args), 1); + if (ret != NJS_OK) { + goto exception; + } } + ctx->filter = 1; ctx->from_upstream = from_upstream; - ll = &out; + ctx->last_out = &out; while (in) { ctx->buf = in->buf; - if (njs_vm_call(ctx->vm, func, njs_value_arg(&ctx->arg), 1) != NJS_OK) { - njs_vm_retval_to_ext_string(ctx->vm, &exception); + if (ngx_stream_event(from_upstream) != NULL) { + ret = ngx_stream_js_buffer_arg(s, njs_value_arg(&ctx->args[1])); + if (ret != NJS_OK) { + goto exception; + } - ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %*s", - exception.length, exception.start); + ret = ngx_stream_js_flags_arg(s, njs_value_arg(&ctx->args[2])); + if (ret != NJS_OK) { + goto exception; + } - return NGX_ERROR; - } + njs_vm_post_event(ctx->vm, ngx_stream_event(from_upstream), + njs_value_arg(&ctx->args[1]), 2); - if (!njs_value_is_void(njs_vm_retval(ctx->vm))) { - if (njs_vm_retval_to_ext_string(ctx->vm, &value) != NJS_OK) { + rc = njs_vm_run(ctx->vm); + if (rc == NJS_ERROR) { + goto exception; + } + + } else { + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_STREAM, c->log, 0, - "js return value: \"%*s\"", - value.length, value.start); - - if (value.length) { - rc = ngx_atoi(value.start, value.length); - - if (rc != NGX_OK && rc != -NGX_ERROR) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "unexpected js return code: \"%*s\"", - value.length, value.start); - return NGX_ERROR; - } - - rc = -rc; + cl->buf = ctx->buf; - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - } + *ctx->last_out = cl; + ctx->last_out = &cl->next; } - cl = ngx_alloc_chain_link(c->pool); - if (cl == NULL) { - return NGX_ERROR; - } - - cl->buf = ctx->buf; - - *ll = cl; - ll = &cl->next; - in = in->next; } - *ll = NULL; + *ctx->last_out = NULL; + + if (out != NULL) { + rc = ngx_stream_next_filter(s, out, from_upstream); - rc = ngx_stream_next_filter(s, out, from_upstream); + ngx_chain_update_chains(c->pool, &ctx->free, &ctx->busy, &out, + (ngx_buf_tag_t) &ngx_stream_js_module); - ngx_chain_update_chains(c->pool, &ctx->free, &ctx->busy, &out, - (ngx_buf_tag_t) &ngx_stream_js_module); + } else { + rc = NGX_OK; + } return rc; + +exception: + + njs_vm_retval_to_ext_string(ctx->vm, &exception); + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "js exception: %*s", + exception.length, exception.start); + + return NGX_ERROR; } @@ -593,7 +651,7 @@ ngx_stream_js_variable(ngx_stream_sessio pending = njs_vm_pending(ctx->vm); - if (njs_vm_call(ctx->vm, func, njs_value_arg(&ctx->arg), 1) != NJS_OK) { + if (njs_vm_call(ctx->vm, func, njs_value_arg(&ctx->args), 1) != NJS_OK) { njs_vm_retval_to_ext_string(ctx->vm, &exception); ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, @@ -676,8 +734,8 @@ ngx_stream_js_init_vm(ngx_stream_session return NGX_ERROR; } - rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->arg), jmcf->proto, - s); + rc = njs_vm_external_create(ctx->vm, njs_value_arg(&ctx->args[0]), + jmcf->proto, s); if (rc != NXT_OK) { return NGX_ERROR; } @@ -709,6 +767,103 @@ ngx_stream_js_cleanup_vm(void *data) static njs_ret_t +ngx_stream_js_buffer_arg(ngx_stream_session_t *s, njs_value_t *buffer) +{ + size_t len; + u_char *p; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_stream_js_ctx_t *ctx; + + c = s->connection; + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); + + b = ctx->filter ? ctx->buf : c->buffer; + + len = b ? b->last - b->pos : 0; + + p = njs_string_alloc(ctx->vm, buffer, len, 0); + if (p == NULL) { + return NJS_ERROR; + } + + if (len) { + ngx_memcpy(p, b->pos, len); + } + + return NJS_OK; +} + + + +static njs_ret_t +ngx_stream_js_flags_arg(ngx_stream_session_t *s, njs_value_t *flags) +{ + ngx_buf_t *b; + ngx_connection_t *c; + njs_opaque_value_t last_key; + njs_opaque_value_t values[1]; + ngx_stream_js_ctx_t *ctx; + + static const nxt_str_t last_str = nxt_string("last"); + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); + + njs_string_create(ctx->vm, njs_value_arg(&last_key), last_str.start, + last_str.length, 0); + + c = s->connection; + + b = ctx->filter ? ctx->buf : c->buffer; + njs_value_boolean_set(njs_value_arg(&values[0]), b && b->last_buf); + + return njs_vm_object_alloc(ctx->vm, flags, + njs_value_arg(&last_key), + njs_value_arg(&values[0]), NULL); +} + + +static njs_vm_event_t * +ngx_stream_js_event(ngx_stream_session_t *s, nxt_str_t *event) +{ + ngx_uint_t i, n; + ngx_stream_js_ctx_t *ctx; + + static const nxt_str_t events[] = { + nxt_string("upload"), + nxt_string("download") + }; + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); + + i = 0; + n = sizeof(events) / sizeof(events[0]); + + while (i < n) { + if (event->length == events[i].length + && ngx_memcmp(event->start, events[i].start, event->length) == 0) + { + break; + } + + i++; + } + + if (i == n) { + njs_vm_error(ctx->vm, "unknown event \"%.*s\"", (int) event->length, + event->start); + return NULL; + } + + if (i == 0) { + return &ctx->upload_event; + } + + return &ctx->download_event; +} + + +static njs_ret_t ngx_stream_js_ext_get_remote_address(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data) { @@ -723,131 +878,74 @@ ngx_stream_js_ext_get_remote_address(njs static njs_ret_t -ngx_stream_js_ext_get_eof(njs_vm_t *vm, njs_value_t *value, void *obj, - uintptr_t data) +ngx_stream_js_ext_done(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused) { - ngx_buf_t *b; - ngx_connection_t *c; - ngx_stream_js_ctx_t *ctx; - ngx_stream_session_t *s; - - s = (ngx_stream_session_t *) obj; - c = s->connection; - ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - - b = ctx->filter ? ctx->buf : c->buffer; - - njs_value_boolean_set(value, b && b->last_buf); - - return NJS_OK; + return ngx_stream_js_ext_set_status(vm, args, nargs, NGX_OK); } static njs_ret_t -ngx_stream_js_ext_get_from_upstream(njs_vm_t *vm, njs_value_t *value, void *obj, - uintptr_t data) +ngx_stream_js_ext_deny(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused) { - ngx_stream_js_ctx_t *ctx; - ngx_stream_session_t *s; + return ngx_stream_js_ext_set_status(vm, args, nxt_min(nargs, 1), + NGX_STREAM_FORBIDDEN); +} - s = (ngx_stream_session_t *) obj; - ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - njs_value_boolean_set(value, ctx->from_upstream); - - return NJS_OK; +static njs_ret_t +ngx_stream_js_ext_decline(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused) +{ + return ngx_stream_js_ext_set_status(vm, args, nxt_min(nargs, 1), + NGX_DECLINED); } static njs_ret_t -ngx_stream_js_ext_get_buffer(njs_vm_t *vm, njs_value_t *value, void *obj, - uintptr_t data) +ngx_stream_js_ext_set_status(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + ngx_int_t status) { - size_t len; - u_char *p; - ngx_buf_t *b; - ngx_connection_t *c; - ngx_stream_js_ctx_t *ctx; - ngx_stream_session_t *s; - - s = (ngx_stream_session_t *) obj; - c = s->connection; - ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - - b = ctx->filter ? ctx->buf : c->buffer; - - len = b ? b->last - b->pos : 0; - - p = njs_string_alloc(vm, value, len, 0); - if (p == NULL) { - return NJS_ERROR; - } - - if (len) { - ngx_memcpy(p, b->pos, len); - } - - return NJS_OK; -} - - -static njs_ret_t -ngx_stream_js_ext_set_buffer(njs_vm_t *vm, void *obj, uintptr_t data, - nxt_str_t *value) -{ - ngx_buf_t *b; - ngx_chain_t *cl; - ngx_connection_t *c; + const njs_value_t *code; ngx_stream_js_ctx_t *ctx; ngx_stream_session_t *s; - s = (ngx_stream_session_t *) obj; - c = s->connection; - ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - - ngx_log_debug2(NGX_LOG_DEBUG_STREAM, c->log, 0, - "stream js set buffer \"%*s\"", value->length, value->start); - - if (!ctx->filter) { - ngx_log_error(NGX_LOG_WARN, c->log, 0, - "cannot set buffer in this handler"); - return NJS_OK; - } - - cl = ngx_chain_get_free_buf(c->pool, &ctx->free); - if (cl == NULL) { + s = njs_vm_external(vm, njs_arg(args, nargs, 0)); + if (nxt_slow_path(s == NULL)) { return NJS_ERROR; } - b = cl->buf; - - ngx_free_chain(c->pool, cl); - - b->last_buf = ctx->buf->last_buf; - b->memory = (value->length ? 1 : 0); - b->sync = (value->length ? 0 : 1); - b->tag = (ngx_buf_tag_t) &ngx_stream_js_module; - - b->start = value->start; - b->end = value->start + value->length; - b->pos = b->start; - b->last = b->end; - - if (ctx->buf->tag != (ngx_buf_tag_t) &ngx_stream_js_module) { - ctx->buf->pos = ctx->buf->last; - - } else { - cl = ngx_alloc_chain_link(c->pool); - if (cl == NULL) { + if (nargs > 1) { + code = njs_arg(args, nargs, 1); + if (!njs_value_is_valid_number(code)) { + njs_vm_error(vm, "code is not a number"); return NJS_ERROR; } - cl->buf = ctx->buf; - cl->next = ctx->free; - ctx->free = cl; + status = njs_value_number(code); + if (status < NGX_ABORT || status > NGX_STREAM_SERVICE_UNAVAILABLE) { + njs_vm_error(vm, "code is out of range"); + return NJS_ERROR; + } } - ctx->buf = b; + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream js set status: %i", status); + + ctx->status = status; + + if (ctx->upload_event != NULL) { + njs_vm_del_event(ctx->vm, ctx->upload_event); + ctx->upload_event = NULL; + } + + if (ctx->download_event != NULL) { + njs_vm_del_event(ctx->vm, ctx->download_event); + ctx->download_event = NULL; + } return NJS_OK; } @@ -911,6 +1009,166 @@ ngx_stream_js_ext_log_core(njs_vm_t *vm, static njs_ret_t +ngx_stream_js_ext_on(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + nxt_str_t name; + njs_vm_event_t *event; + const njs_value_t *callback; + ngx_stream_session_t *s; + + s = njs_vm_external(vm, njs_arg(args, nargs, 0)); + if (nxt_slow_path(s == NULL)) { + return NJS_ERROR; + } + + if (njs_vm_value_to_ext_string(vm, &name, njs_arg(args, nargs, 1), 0) + == NJS_ERROR) + { + njs_vm_error(vm, "failed to convert event arg"); + return NJS_ERROR; + } + + callback = njs_arg(args, nargs, 2); + if (!njs_value_is_function(callback)) { + njs_vm_error(vm, "callback is not a function"); + return NJS_ERROR; + } + + event = ngx_stream_js_event(s, &name); + if (event == NULL) { + return NJS_ERROR; + } + + if (*event != NULL) { + njs_vm_error(vm, "event handler \"%.*s\" is already set", + (int) name.length, name.start); + return NJS_ERROR; + } + + *event = njs_vm_add_event(vm, njs_value_function(callback), 0, NULL, NULL); + if (*event == NULL) { + njs_vm_error(vm, "internal error"); + return NJS_ERROR; + } + + return NJS_OK; +} + + +static njs_ret_t +ngx_stream_js_ext_off(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + nxt_str_t name; + njs_vm_event_t *event; + ngx_stream_session_t *s; + + s = njs_vm_external(vm, njs_arg(args, nargs, 0)); + if (nxt_slow_path(s == NULL)) { + return NJS_ERROR; + } + + if (njs_vm_value_to_ext_string(vm, &name, njs_arg(args, nargs, 1), 0) + == NJS_ERROR) + { + njs_vm_error(vm, "failed to convert event arg"); + return NJS_ERROR; + } + + event = ngx_stream_js_event(s, &name); + if (event == NULL) { + return NJS_ERROR; + } + + *event = NULL; + + return NJS_OK; +} + + +static njs_ret_t +ngx_stream_js_ext_send(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + unsigned last_buf, flush; + nxt_str_t buffer; From mdounin at mdounin.ru Tue Sep 11 12:55:37 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 11 Sep 2018 15:55:37 +0300 Subject: body capture and redirect In-Reply-To: References: Message-ID: <20180911125536.GN56558@mdounin.ru> Hello! On Mon, Sep 10, 2018 at 10:58:29PM -0700, Dk Jack wrote: > Hi, > In my module, I am trying to forward the request to my server based on the > content of the request body. To acheive this, I've added a body capture > filter to capture the body. My code is something like this... > > static ngx_int_t > nginx_inspect_body_filter(ngx_http_request_t *r, ngx_chain_t *in) > { > > ... // extract body > if (if_content_of_interest_in_body(body, body_length)) { > ngx_str_t uri = ngx_string("/my_location"); > ngx_http_internal_redirect(r, &url, NULL); > ngx_http_finalize_request(r, NGX_DONE); > return NGX_DONE; > } > ... > } > > I have the following conf for '/my_location': > > server { > ... > location / { > ... > } > location /my_location { > proxy_pass http://myserver; > } > } > > However, I am running into an issue with my code. The request seems to get > forwarded to my server like I expected. However, my connection seems to > hang. Looks like the server seems to be waiting to read more data from > nginx. When I interrupt my server (ctrl-c; its a simple python server), it > sort breaks out of the read loop and a response is returned. Sending the > same request to my server without sending it through my module in nginx, > behaves correctly. > > Could someone more experienced in nginx, point out what I am doing wrong? > Is redirect allowed from a body filter handler? Thanks for your help in > advance. Assuming the "nginx_inspect_body_filter" function is installed into the request body filter chain via the ngx_http_top_request_body_filter, what you are trying to do is wrong. You cannot stop reading the request body at arbitrary time and switch to different processing. This will leave the body half-read, in an inconsistent state, and this in turn will cause various problem during further processing. That is, connection hang you see is an expected result. In the request body filter you have to either return a fatal error, or raise some internal flag for your processing, and then act on this flag after the body is fully read. -- Maxim Dounin http://mdounin.ru/ From dnj0496 at gmail.com Tue Sep 11 14:52:54 2018 From: dnj0496 at gmail.com (Dk Jack) Date: Tue, 11 Sep 2018 07:52:54 -0700 Subject: body capture and redirect In-Reply-To: <20180911125536.GN56558@mdounin.ru> References: <20180911125536.GN56558@mdounin.ru> Message-ID: <683B16FB-7BDE-48C5-AEE1-B1EDF067FF1E@gmail.com> Hi Maxim, Thanks for answering. Yes, my filter is linked in the chain. I am taking the action after reading the entire body, otherwise, I won?t be able to perform my check. My server seems to be waiting for some data from nginx. It works as expected if I don?t do internal redirect. I suspect it has something to do with internal redirect and body capture. Thanks > On Sep 11, 2018, at 5:55 AM, Maxim Dounin wrote: > > Hello! > >> On Mon, Sep 10, 2018 at 10:58:29PM -0700, Dk Jack wrote: >> >> Hi, >> In my module, I am trying to forward the request to my server based on the >> content of the request body. To acheive this, I've added a body capture >> filter to capture the body. My code is something like this... >> >> static ngx_int_t >> nginx_inspect_body_filter(ngx_http_request_t *r, ngx_chain_t *in) >> { >> >> ... // extract body >> if (if_content_of_interest_in_body(body, body_length)) { >> ngx_str_t uri = ngx_string("/my_location"); >> ngx_http_internal_redirect(r, &url, NULL); >> ngx_http_finalize_request(r, NGX_DONE); >> return NGX_DONE; >> } >> ... >> } >> >> I have the following conf for '/my_location': >> >> server { >> ... >> location / { >> ... >> } >> location /my_location { >> proxy_pass http://myserver; >> } >> } >> >> However, I am running into an issue with my code. The request seems to get >> forwarded to my server like I expected. However, my connection seems to >> hang. Looks like the server seems to be waiting to read more data from >> nginx. When I interrupt my server (ctrl-c; its a simple python server), it >> sort breaks out of the read loop and a response is returned. Sending the >> same request to my server without sending it through my module in nginx, >> behaves correctly. >> >> Could someone more experienced in nginx, point out what I am doing wrong? >> Is redirect allowed from a body filter handler? Thanks for your help in >> advance. > > Assuming the "nginx_inspect_body_filter" function is installed > into the request body filter chain via the > ngx_http_top_request_body_filter, what you are trying to do is > wrong. > > You cannot stop reading the request body at arbitrary time and > switch to different processing. This will leave the body > half-read, in an inconsistent state, and this in turn will cause > various problem during further processing. That is, connection > hang you see is an expected result. > > In the request body filter you have to either return a fatal > error, or raise some internal flag for your processing, and then > act on this flag after the body is fully read. > > -- > Maxim Dounin > http://mdounin.ru/ > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > http://mailman.nginx.org/mailman/listinfo/nginx-devel From dnj0496 at gmail.com Tue Sep 11 19:28:52 2018 From: dnj0496 at gmail.com (Dk Jack) Date: Tue, 11 Sep 2018 12:28:52 -0700 Subject: body capture and redirect In-Reply-To: <20180911125536.GN56558@mdounin.ru> References: <20180911125536.GN56558@mdounin.ru> Message-ID: Hello Maxim, On your last suggestion... "In the request body filter you have to either return a fatal error, or raise some internal flag for your processing, and then act on this flag after the body is fully read." How can I be certain when my body is fully read? Currently, when my body filter is invoked, I am walking the chain of buffers and saving it to a temporary buffer to perform my lookup. That is why I am little confused by your statement.Thanks for all your help. regards, dk On Tue, Sep 11, 2018 at 5:55 AM Maxim Dounin wrote: > Hello! > > On Mon, Sep 10, 2018 at 10:58:29PM -0700, Dk Jack wrote: > > > Hi, > > In my module, I am trying to forward the request to my server based on > the > > content of the request body. To acheive this, I've added a body capture > > filter to capture the body. My code is something like this... > > > > static ngx_int_t > > nginx_inspect_body_filter(ngx_http_request_t *r, ngx_chain_t *in) > > { > > > > ... // extract body > > if (if_content_of_interest_in_body(body, body_length)) { > > ngx_str_t uri = ngx_string("/my_location"); > > ngx_http_internal_redirect(r, &url, NULL); > > ngx_http_finalize_request(r, NGX_DONE); > > return NGX_DONE; > > } > > ... > > } > > > > I have the following conf for '/my_location': > > > > server { > > ... > > location / { > > ... > > } > > location /my_location { > > proxy_pass http://myserver; > > } > > } > > > > However, I am running into an issue with my code. The request seems to > get > > forwarded to my server like I expected. However, my connection seems to > > hang. Looks like the server seems to be waiting to read more data from > > nginx. When I interrupt my server (ctrl-c; its a simple python server), > it > > sort breaks out of the read loop and a response is returned. Sending the > > same request to my server without sending it through my module in nginx, > > behaves correctly. > > > > Could someone more experienced in nginx, point out what I am doing wrong? > > Is redirect allowed from a body filter handler? Thanks for your help in > > advance. > > Assuming the "nginx_inspect_body_filter" function is installed > into the request body filter chain via the > ngx_http_top_request_body_filter, what you are trying to do is > wrong. > > You cannot stop reading the request body at arbitrary time and > switch to different processing. This will leave the body > half-read, in an inconsistent state, and this in turn will cause > various problem during further processing. That is, connection > hang you see is an expected result. > > In the request body filter you have to either return a fatal > error, or raise some internal flag for your processing, and then > act on this flag after the body is fully read. > > -- > Maxim Dounin > http://mdounin.ru/ > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > http://mailman.nginx.org/mailman/listinfo/nginx-devel > -------------- next part -------------- An HTML attachment was scrubbed... URL: From mdounin at mdounin.ru Wed Sep 12 14:59:30 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Wed, 12 Sep 2018 17:59:30 +0300 Subject: body capture and redirect In-Reply-To: References: <20180911125536.GN56558@mdounin.ru> Message-ID: <20180912145930.GS56558@mdounin.ru> Hello! On Tue, Sep 11, 2018 at 12:28:52PM -0700, Dk Jack wrote: > On your last suggestion... > > "In the request body filter you have to either return a fatal error, or > raise some internal flag for your processing, and then > act on this flag after the body is fully read." > > How can I be certain when my body is fully read? Currently, when my body > filter is invoked, > I am walking the chain of buffers and saving it to a temporary buffer to > perform my lookup. > That is why I am little confused by your statement.Thanks for all your help. By writing "act on this flag after the body is fully read" I mean doing something only when the request body reading is complete, and the post_handler was called by the request body reading code to indicate this and resume request processing. Also, just to make things more clear: a request body filter is only allowed to return NGX_OK or an NGX_HTTP_* error. Trying to return anything else will cause problems. -- Maxim Dounin http://mdounin.ru/ From xeioex at nginx.com Thu Sep 13 14:54:26 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 13 Sep 2018 14:54:26 +0000 Subject: [njs] Introduced sandboxing mode. Message-ID: details: http://hg.nginx.org/njs/rev/a92ae4986bc0 branches: changeset: 603:a92ae4986bc0 user: Dmitry Volyntsev date: Thu Sep 13 15:52:17 2018 +0300 description: Introduced sandboxing mode. Thanks to David CARLIER. diffstat: njs/njs.c | 15 ++++++--------- njs/njs.h | 1 + njs/njs_builtin.c | 4 ++++ njs/njs_generator.c | 2 +- njs/njs_module.c | 16 ++++++++++++++++ njs/njs_module.h | 1 + njs/njs_parser.c | 4 ++-- njs/njs_shell.c | 7 +++++++ njs/njs_time.c | 2 +- njs/njs_variable.c | 4 ++-- njs/njs_vm.h | 4 +--- njs/test/njs_expect_test.exp | 37 +++++++++++++++++++++++++++++++++++-- 12 files changed, 77 insertions(+), 20 deletions(-) diffs (307 lines): diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/njs.c --- a/njs/njs.c Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/njs.c Thu Sep 13 15:52:17 2018 +0300 @@ -110,6 +110,8 @@ njs_vm_create(njs_vm_opt_t *options) return NULL; } + vm->options = *options; + if (options->shared != NULL) { vm->shared = options->shared; @@ -160,15 +162,11 @@ njs_vm_create(njs_vm_opt_t *options) nxt_lvlhsh_init(&vm->externals_hash); nxt_lvlhsh_init(&vm->external_prototypes_hash); - vm->ops = options->ops; - vm->trace.level = NXT_LEVEL_TRACE; vm->trace.size = 2048; vm->trace.handler = njs_parser_trace_handler; vm->trace.data = vm; - vm->trailer = options->trailer; - if (options->backtrace) { debug = nxt_array_create(4, sizeof(njs_function_debug_t), &njs_array_mem_proto, @@ -180,8 +178,7 @@ njs_vm_create(njs_vm_opt_t *options) vm->debug = debug; } - vm->accumulative = options->accumulative; - if (vm->accumulative) { + if (options->accumulative) { ret = njs_vm_init(vm); if (nxt_slow_path(ret != NXT_OK)) { return NULL; @@ -232,7 +229,7 @@ njs_vm_compile(njs_vm_t *vm, u_char **st return NJS_ERROR; } - if (vm->parser != NULL && !vm->accumulative) { + if (vm->parser != NULL && !vm->options.accumulative) { return NJS_ERROR; } @@ -307,7 +304,7 @@ njs_vm_clone(njs_vm_t *vm, njs_external_ nxt_thread_log_debug("CLONE:"); - if (vm->accumulative) { + if (vm->options.accumulative) { return NULL; } @@ -347,7 +344,7 @@ njs_vm_clone(njs_vm_t *vm, njs_external_ nvm->external_objects = externals; - nvm->ops = vm->ops; + nvm->options = vm->options; nvm->current = vm->current; diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/njs.h --- a/njs/njs.h Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/njs.h Thu Sep 13 15:52:17 2018 +0300 @@ -145,6 +145,7 @@ typedef struct { uint8_t trailer; /* 1 bit */ uint8_t accumulative; /* 1 bit */ uint8_t backtrace; /* 1 bit */ + uint8_t sandbox; /* 1 bit */ } njs_vm_opt_t; diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/njs_builtin.c --- a/njs/njs_builtin.c Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/njs_builtin.c Thu Sep 13 15:52:17 2018 +0300 @@ -256,6 +256,10 @@ njs_builtin_objects_create(njs_vm_t *vm) lhq.pool = vm->mem_cache_pool; for (i = NJS_MODULE_FS; i < NJS_MODULE_MAX; i++) { + if (vm->options.sandbox && !njs_sandbox_module(i)) { + continue; + } + module = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_module_t)); if (nxt_slow_path(module == NULL)) { return NJS_ERROR; diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/njs_generator.c --- a/njs/njs_generator.c Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/njs_generator.c Thu Sep 13 15:52:17 2018 +0300 @@ -2558,7 +2558,7 @@ njs_generator_temp_index_get(njs_vm_t *v scope = scope->parent; } - if (vm->accumulative && scope->type == NJS_SCOPE_GLOBAL) { + if (vm->options.accumulative && scope->type == NJS_SCOPE_GLOBAL) { /* * When non-clonable VM runs in accumulative mode * all global variables are allocated in absolute scope diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/njs_module.c --- a/njs/njs_module.c Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/njs_module.c Thu Sep 13 15:52:17 2018 +0300 @@ -74,3 +74,19 @@ const njs_object_init_t njs_require_fun NULL, 0, }; + + +nxt_bool_t +njs_sandbox_module(enum njs_module_e module) +{ + switch (module) { + case NJS_MODULE_CRYPTO: + return 1; + + case NJS_MODULE_FS: + default: + break; + } + + return 0; +} diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/njs_module.h --- a/njs/njs_module.h Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/njs_module.h Thu Sep 13 15:52:17 2018 +0300 @@ -15,6 +15,7 @@ typedef struct { njs_ret_t njs_module_require(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); +nxt_bool_t njs_sandbox_module(enum njs_module_e module); extern const nxt_lvlhsh_proto_t njs_modules_hash_proto; diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/njs_parser.c --- a/njs/njs_parser.c Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/njs_parser.c Thu Sep 13 15:52:17 2018 +0300 @@ -134,7 +134,7 @@ njs_parser(njs_vm_t *vm, njs_parser_t *p return NULL; } - if (token == NJS_TOKEN_CLOSE_BRACE && vm->trailer) { + if (token == NJS_TOKEN_CLOSE_BRACE && vm->options.trailer) { parser->lexer->start--; break; } @@ -351,7 +351,7 @@ njs_parser_statement(njs_vm_t *vm, njs_p return njs_parser_block_statement(vm, parser); case NJS_TOKEN_CLOSE_BRACE: - if (vm->trailer) { + if (vm->options.trailer) { parser->node = NULL; nxt_thread_log_debug("BLOCK END"); return token; diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/njs_shell.c --- a/njs/njs_shell.c Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/njs_shell.c Thu Sep 13 15:52:17 2018 +0300 @@ -31,6 +31,7 @@ typedef struct { nxt_int_t version; nxt_int_t disassemble; nxt_int_t interactive; + nxt_int_t sandbox; } njs_opts_t; @@ -146,6 +147,7 @@ main(int argc, char **argv) vm_options.accumulative = 1; vm_options.backtrace = 1; + vm_options.sandbox = opts.sandbox; if (opts.interactive) { ret = njs_interactive_shell(&opts, &vm_options); @@ -170,6 +172,7 @@ njs_get_options(njs_opts_t *opts, int ar "Options:\n" " -v print njs version and exit.\n" " -d print disassembled code.\n" + " -s sandbox mode.\n" " | - run code from a file or stdin.\n"; ret = NXT_DONE; @@ -201,6 +204,10 @@ njs_get_options(njs_opts_t *opts, int ar opts->version = 1; break; + case 's': + opts->sandbox = 1; + break; + default: fprintf(stderr, "Unknown argument: \"%s\" " "try \"%s -h\" for available options\n", argv[i], argv[0]); diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/njs_time.c --- a/njs/njs_time.c Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/njs_time.c Thu Sep 13 15:52:17 2018 +0300 @@ -28,7 +28,7 @@ njs_set_timeout(njs_vm_t *vm, njs_value_ return NJS_ERROR; } - ops = vm->ops; + ops = vm->options.ops; if (nxt_slow_path(ops == NULL)) { njs_internal_error(vm, "not supported by host environment"); return NJS_ERROR; diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/njs_variable.c --- a/njs/njs_variable.c Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/njs_variable.c Thu Sep 13 15:52:17 2018 +0300 @@ -123,7 +123,7 @@ njs_variable_add(njs_vm_t *vm, njs_parse return var; } - lhq.replace = vm->accumulative; + lhq.replace = vm->options.accumulative; lhq.value = var; lhq.pool = vm->mem_cache_pool; @@ -389,7 +389,7 @@ njs_variable_get(njs_vm_t *vm, njs_parse goto not_found; } - if (vm->accumulative && vs.scope->type == NJS_SCOPE_GLOBAL) { + if (vm->options.accumulative && vs.scope->type == NJS_SCOPE_GLOBAL) { /* * When non-clonable VM runs in accumulative mode all * global variables should be allocated in absolute scope diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/njs_vm.h --- a/njs/njs_vm.h Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/njs_vm.h Thu Sep 13 15:52:17 2018 +0300 @@ -1036,7 +1036,7 @@ struct njs_vm_s { nxt_lvlhsh_t events_hash; nxt_queue_t posted_events; - njs_vm_ops_t *ops; + njs_vm_opt_t options; /* * The prototypes and constructors arrays must be together because @@ -1073,8 +1073,6 @@ struct njs_vm_s { nxt_array_t *backtrace; njs_trap_t trap:8; - uint8_t trailer; /* 1 bit */ - uint8_t accumulative; /* 1 bit */ }; diff -r c2cddf3b97b7 -r a92ae4986bc0 njs/test/njs_expect_test.exp --- a/njs/test/njs_expect_test.exp Tue Sep 11 15:35:27 2018 +0300 +++ b/njs/test/njs_expect_test.exp Thu Sep 13 15:52:17 2018 +0300 @@ -3,8 +3,15 @@ # Copyright (C) NGINX, Inc. # -proc njs_test {body} { - spawn -nottycopy njs +proc njs_test {body {opts ""}} { + + if {$opts eq ""} { + spawn -nottycopy njs + + } else { + spawn -nottycopy njs $opts + } + expect -re "interactive njs \\d+\.\\d+\.\\d+\r\n\r" expect "v. -> the properties and prototype methods of v.\r type console.help() for more information\r @@ -23,6 +30,12 @@ type console.help() for more information expect eof } +proc njs_run {opts output} { + spawn -nottycopy njs $opts + expect -re $output + expect eof +} + njs_test { {"njs.version\r\n" "njs.version\r\n\*\.\*\.\*"} @@ -490,3 +503,23 @@ njs_test { {"fs.readFileSync('njs_test_file2')\r\n" "'ABCABC'\r\n>> "} } + +# CLI OPTIONS + +# version + +njs_run "-v" "\\d+\.\\d+\.\\d+" + +# disassemble + +njs_test { + {"1+1\r\n" + "00000 ADD*\r\n00040 STOP*\r\n\r\n2"} +} "-d" + +# sandboxing + +njs_test { + {"var fs = require('fs')\r\n" + "Error: Cannot find module 'fs'\r\n"} +} "-s" From xeioex at nginx.com Thu Sep 13 17:19:45 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 13 Sep 2018 17:19:45 +0000 Subject: [njs] Added njs_uint32_to_string(). Message-ID: details: http://hg.nginx.org/njs/rev/c4f9a4948697 branches: changeset: 604:c4f9a4948697 user: Dmitry Volyntsev date: Thu Sep 13 18:46:02 2018 +0300 description: Added njs_uint32_to_string(). diffstat: njs/njs_event.c | 6 +----- njs/njs_json.c | 16 +++------------- njs/njs_number.h | 12 ++++++++++++ njs/njs_object.c | 6 +----- 4 files changed, 17 insertions(+), 23 deletions(-) diffs (153 lines): diff -r a92ae4986bc0 -r c4f9a4948697 njs/njs_event.c --- a/njs/njs_event.c Thu Sep 13 15:52:17 2018 +0300 +++ b/njs/njs_event.c Thu Sep 13 18:46:02 2018 +0300 @@ -6,7 +6,6 @@ #include #include -#include static nxt_int_t njs_event_hash_test(nxt_lvlhsh_query_t *lhq, void *data); @@ -44,13 +43,10 @@ njs_event_hash_test(nxt_lvlhsh_query_t * nxt_int_t njs_add_event(njs_vm_t *vm, njs_event_t *event) { - size_t size; nxt_int_t ret; nxt_lvlhsh_query_t lhq; - size = snprintf((char *) njs_string_short_start(&event->id), - NJS_STRING_SHORT, "%u", vm->event_id++); - njs_string_short_set(&event->id, size, size); + njs_uint32_to_string(&event->id, vm->event_id++); njs_string_get(&event->id, &lhq.key); lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); diff -r a92ae4986bc0 -r c4f9a4948697 njs/njs_json.c --- a/njs/njs_json.c Thu Sep 13 15:52:17 2018 +0300 +++ b/njs/njs_json.c Thu Sep 13 18:46:02 2018 +0300 @@ -8,7 +8,6 @@ #include #include #include -#include #include @@ -1036,7 +1035,6 @@ memory_error: static njs_ret_t njs_json_parse_continuation_apply(njs_vm_t *vm, njs_json_parse_t *parse) { - size_t size; njs_value_t arguments[3]; njs_json_state_t *state; @@ -1053,9 +1051,7 @@ njs_json_parse_continuation_apply(njs_vm break; case NJS_JSON_ARRAY_START: - size = snprintf((char *) njs_string_short_start(&arguments[1]), - NJS_STRING_SHORT, "%u", state->index); - njs_string_short_set(&arguments[1], size, size); + njs_uint32_to_string(&arguments[1], state->index); arguments[2] = state->value.data.u.array->start[state->index]; state->type = NJS_JSON_ARRAY_REPLACED; @@ -1455,7 +1451,6 @@ static njs_ret_t njs_json_stringify_to_json(njs_vm_t *vm, njs_json_stringify_t* stringify, njs_function_t *function, njs_value_t *key, njs_value_t *value) { - size_t size; njs_value_t arguments[2]; njs_json_state_t *state; @@ -1482,9 +1477,7 @@ njs_json_stringify_to_json(njs_vm_t *vm, case NJS_JSON_ARRAY_START: case NJS_JSON_ARRAY_CONTINUE: - size = snprintf((char *) njs_string_short_start(&arguments[1]), - NJS_STRING_SHORT, "%u", state->index - 1); - njs_string_short_set(&arguments[1], size, size); + njs_uint32_to_string(&arguments[1], state->index - 1); state->type = NJS_JSON_ARRAY_TO_JSON_REPLACED; break; @@ -1504,7 +1497,6 @@ static njs_ret_t njs_json_stringify_replacer(njs_vm_t *vm, njs_json_stringify_t* stringify, njs_value_t *key, njs_value_t *value) { - size_t size; njs_value_t arguments[3]; njs_json_state_t *state; @@ -1526,9 +1518,7 @@ njs_json_stringify_replacer(njs_vm_t *vm case NJS_JSON_ARRAY_START: case NJS_JSON_ARRAY_CONTINUE: case NJS_JSON_ARRAY_TO_JSON_REPLACED: - size = snprintf((char *) njs_string_short_start(&arguments[1]), - NJS_STRING_SHORT, "%u", state->index - 1); - njs_string_short_set(&arguments[1], size, size); + njs_uint32_to_string(&arguments[1], state->index - 1); arguments[2] = *value; state->type = NJS_JSON_ARRAY_REPLACED; diff -r a92ae4986bc0 -r c4f9a4948697 njs/njs_number.h --- a/njs/njs_number.h Thu Sep 13 15:52:17 2018 +0300 +++ b/njs/njs_number.h Thu Sep 13 18:46:02 2018 +0300 @@ -9,6 +9,7 @@ #include +#include uint32_t njs_value_to_index(const njs_value_t *value); @@ -56,6 +57,17 @@ njs_char_to_hex(u_char c) } +nxt_inline void +njs_uint32_to_string(njs_value_t *value, uint32_t u32) +{ + size_t size; + + size = snprintf((char *) njs_string_short_start(value), + NJS_STRING_SHORT, "%u", u32); + njs_string_short_set(value, size, size); +} + + extern const njs_object_init_t njs_number_constructor_init; extern const njs_object_init_t njs_number_prototype_init; diff -r a92ae4986bc0 -r c4f9a4948697 njs/njs_object.c --- a/njs/njs_object.c Thu Sep 13 15:52:17 2018 +0300 +++ b/njs/njs_object.c Thu Sep 13 18:46:02 2018 +0300 @@ -5,7 +5,6 @@ */ #include -#include #include @@ -651,7 +650,6 @@ njs_object_keys(njs_vm_t *vm, njs_value_ njs_array_t * njs_object_keys_array(njs_vm_t *vm, const njs_value_t *object) { - size_t size; uint32_t i, n, keys_length, array_length; njs_value_t *value; njs_array_t *keys, *array; @@ -704,9 +702,7 @@ njs_object_keys_array(njs_vm_t *vm, cons * The maximum array index is 4294967294, so * it can be stored as a short string inside value. */ - size = snprintf((char *) njs_string_short_start(value), - NJS_STRING_SHORT, "%u", i); - njs_string_short_set(value, size, size); + njs_uint32_to_string(value, i); } } From mdounin at mdounin.ru Thu Sep 13 18:10:23 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Thu, 13 Sep 2018 21:10:23 +0300 Subject: cache: move open to thread pool In-Reply-To: References: <20180808181653.GX56558@mdounin.ru> <20180810113946.GG56558@mdounin.ru> <20180903160903.GI56558@mdounin.ru> Message-ID: <20180913181023.GW56558@mdounin.ru> Hello! On Tue, Sep 04, 2018 at 04:58:05PM -0700, Ka-Hing Cheung via nginx-devel wrote: > On Mon, Sep 3, 2018 at 9:09 AM, Maxim Dounin wrote: > > The biggest problem with in-place solution is that it requires > > in-place coding for all the places where we want to use aio_open. > > Currently we use ngx_open_cached_file() to open files everywhere, > > and it would be a good idea to preserve it as a starting point for > > all file open operations. > > > > This should be beneficial even if aio_open is only used when open > > file cache is actually disabled. And I'm actually fine with > > implementing this only when open file cache is disabled - but > > implementing this in ngx_open_cached_file(), to make it easier to > > add more places which use aio_open, and also makeing it possible > > to further improve things by making it compatible with > > open_file_cache. > > The most difficult thing to switch to threaded open isn't which method > to call, but to expect NGX_AGAIN and have the necessary bookkeeping in > place to resume, so imo this is a pretty weak argument. Sure. But adding an alternative interface and dozens of lines of the otherwise non-needed code everywhere we want to support aio_open is something we can easily avoid, and we should do it. > > Adding a code path somewhere in ngx_open_file_wrapper() should be > > simple enough. Note that probably there is no need to cover > > stat() as well, as well as to address various symlinks cases - I > > think it would be fine if aio_open will only work when there is no > > need to disable symlinks, and will only work for open(), as stat() > > after an open is expected to be fast. > > A problem is that someone need to own the thread task (which for other > aio is owned by ngx_file, and someone needs to own the ngx_file). > ngx_open_cached_file doesn't care about anything beyond the file name. > That can of course change, but changing API is subjective and > difficult for me as an external contributor to decide on. I can try to > find time to do the change if you want to comment on how it should > look like. The ngx_open_file_cache() function takes a ngx_open_file_info_t structure with various arguments, and it would be logical to extend this structure to keep thread task as well. > >> > - The code calls ngx_open_and_stat_file() whithin a thread, which > >> > is relatively complex function never designed to be thread safe. > >> > While it looks like currently it is, a better solution would be to > >> > introduce a separate simple function to execute within a thread, > >> > similar to ngx_thread_read_handler(). > >> > >> I kept using ngx_open_and_stat_file() as we are looking into moving > >> other types of open into thread pool, so we will probably need to have > >> similar logic anyway. > > > > The problem is that ngx_open_and_stat_file() is never coded to be > > thread safe, and this is expected to cause problems sooner or > > switched to use ngx_open_file_wrapper You probably didn't get the point. You shouldn't use existing functions - all them were not designed to be thread safe. Instead, write a simple function using only low-level calls. Please take a look at ngx_thread_read_handler() for an example. > > While this looks slightly better than in the previous iteration, > > this still leaves a lot to be desired. In particular, this still > > uses duplicate "rv == NGX_DECLINED" checks. > > I poked around at it and thinks this one line duplication is the > cleanest. ngx_http_file_cache_read can also return DECLINED and > cleaning up nginx return code seems to be beyond the scope of this > work. We can live with some duplication, but the whole thing needs to be improved somehow to be more readable. The thing which bugs me most is an opaque "c->open_rv" field, which is also passed as a separate argument into the ngx_http_file_cache_aio_open() function. Using some more descriptive name and saving it always to avoid separate argument might be a way to go. > >> + ngx_open_file_info_t of; > >> + ngx_int_t rv; > >> +} ngx_thread_file_open_ctx_t; > >> + > >> +typedef struct { > >> + union { > >> + ngx_thread_file_open_ctx_t open; > >> + ngx_thread_file_read_ctx_t read; > >> + }; > >> } ngx_thread_file_ctx_t; > > > > Please keep things in a single structure instead. This will allow > > to avoid many coresponding changes in the file. > > Is 72 bytes per request not worth saving? To save bytes, consider re-thinking the data used for open (and you anyway has to do it when re-implementing the in-thread code as a simple function using only low-level calls). Just a file name, fd, and a return code should be enough here - and most of these fields are already available in the existing ngx_thread_file_ctx_t structure. > >> typedef int ngx_fd_t; > >> typedef struct stat ngx_file_info_t; > >> typedef ino_t ngx_file_uniq_t; > >> > >> +#include > >> > > > > This introduces a dependency of low-level code from high-level > > OS-independant structures in ngx_open_file_cache.h. Instead, some > > low-level interface should be used. > > Do you have a more concrete suggestion? See above. [...] > diff --git src/core/ngx_open_file_cache.h src/core/ngx_open_file_cache.h > index d119c129..9c1c6bdc 100644 > --- src/core/ngx_open_file_cache.h > +++ src/core/ngx_open_file_cache.h > @@ -7,6 +7,7 @@ > > #include > #include > +#include > > > #ifndef _NGX_OPEN_FILE_CACHE_H_INCLUDED_ This looks like an unrelated change. [...] > @@ -398,7 +356,6 @@ done: > return rv; > } > > - > static ngx_int_t > ngx_http_file_cache_lock(ngx_http_request_t *r, ngx_http_cache_t *c) > { As previously pointed out, this is an unrelated change and a style issue. [...] -- Maxim Dounin http://mdounin.ru/ From xeioex at nginx.com Fri Sep 14 11:19:23 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Fri, 14 Sep 2018 11:19:23 +0000 Subject: [njs] Fixed njs_string_slice(). Message-ID: details: http://hg.nginx.org/njs/rev/85f57cc123c2 branches: changeset: 605:85f57cc123c2 user: Dmitry Volyntsev date: Fri Sep 14 14:19:03 2018 +0300 description: Fixed njs_string_slice(). dst retval argument was ignored. diffstat: njs/njs_string.c | 6 +++--- njs/njs_string.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diffs (37 lines): diff -r c4f9a4948697 -r 85f57cc123c2 njs/njs_string.c --- a/njs/njs_string.c Thu Sep 13 18:46:02 2018 +0300 +++ b/njs/njs_string.c Fri Sep 14 14:19:03 2018 +0300 @@ -1288,7 +1288,7 @@ njs_string_slice_args(njs_slice_prop_t * nxt_noinline njs_ret_t njs_string_slice(njs_vm_t *vm, njs_value_t *dst, - const njs_string_prop_t *string, njs_slice_prop_t *slice) + const njs_string_prop_t *string, const njs_slice_prop_t *slice) { size_t size, n, length; const u_char *p, *start, *end; @@ -1325,10 +1325,10 @@ njs_string_slice(njs_vm_t *vm, njs_value } if (nxt_fast_path(size != 0)) { - return njs_string_new(vm, &vm->retval, start, size, length); + return njs_string_new(vm, dst, start, size, length); } - vm->retval = njs_string_empty; + *dst = njs_string_empty; return NXT_OK; } diff -r c4f9a4948697 -r 85f57cc123c2 njs/njs_string.h --- a/njs/njs_string.h Thu Sep 13 18:46:02 2018 +0300 +++ b/njs/njs_string.h Fri Sep 14 14:19:03 2018 +0300 @@ -146,7 +146,7 @@ njs_ret_t njs_string_constructor(njs_vm_ nxt_bool_t njs_string_eq(const njs_value_t *val1, const njs_value_t *val2); nxt_int_t njs_string_cmp(const njs_value_t *val1, const njs_value_t *val2); njs_ret_t njs_string_slice(njs_vm_t *vm, njs_value_t *dst, - const njs_string_prop_t *string, njs_slice_prop_t *slice); + const njs_string_prop_t *string, const njs_slice_prop_t *slice); const u_char *njs_string_offset(const u_char *start, const u_char *end, size_t index); nxt_noinline uint32_t njs_string_index(njs_string_prop_t *string, From johan.ekenlycka at tui.se Mon Sep 17 13:05:41 2018 From: johan.ekenlycka at tui.se (Johan Ekenlycka) Date: Mon, 17 Sep 2018 13:05:41 +0000 Subject: Upstream with zone directive Message-ID: Hi! I am using ngx_lua_upstream module in OpenResty, which is calling ngx_http_get_module_main_conf(r, ngx_http_upstream_module). For some reason, I think the peer data is not correct when the zone directive is used in an upstream. But I am not really sure where in the source the config is parsed and peer/server data is populated. Can someone please get me some pointers to where I should look or even better if somebody could reaffirm and check the problem. Br, Johan -------------- next part -------------- An HTML attachment was scrubbed... URL: From xeioex at nginx.com Mon Sep 17 15:48:12 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Mon, 17 Sep 2018 15:48:12 +0000 Subject: [njs] Fixed String.slice() for undefined arguments. Message-ID: details: http://hg.nginx.org/njs/rev/b00d4680635a branches: changeset: 606:b00d4680635a user: Dmitry Volyntsev date: Mon Sep 17 18:47:00 2018 +0300 description: Fixed String.slice() for undefined arguments. diffstat: njs/njs_string.c | 65 ++++++++++++++++++++++++----------------------- njs/test/njs_unit_test.c | 12 ++++++++ 2 files changed, 45 insertions(+), 32 deletions(-) diffs (107 lines): diff -r 85f57cc123c2 -r b00d4680635a njs/njs_string.c --- a/njs/njs_string.c Fri Sep 14 14:19:03 2018 +0300 +++ b/njs/njs_string.c Mon Sep 17 18:47:00 2018 +0300 @@ -1236,48 +1236,49 @@ static nxt_noinline void njs_string_slice_args(njs_slice_prop_t *slice, njs_value_t *args, nxt_uint_t nargs) { - ssize_t start, end, length; + ssize_t start, end, length; + const njs_value_t *value; length = slice->string_length; - start = 0; - - if (nargs > 1) { - start = args[1].data.u.number; + + value = njs_arg(args, nargs, 1); + start = value->data.u.number; + + if (start < 0) { + start += length; if (start < 0) { - start += length; - - if (start < 0) { - start = 0; - } + start = 0; } - - if (start >= length) { - start = 0; - length = 0; + } + + if (start >= length) { + start = 0; + length = 0; + + } else { + if (!njs_is_void(njs_arg(args, nargs, 2))) { + value = njs_arg(args, nargs, 2); + end = value->data.u.number; } else { end = length; - - if (nargs > 2) { - end = args[2].data.u.number; - - if (end < 0) { - end += length; - } + } + + if (end < 0) { + end += length; + } + + if (length >= end) { + length = end - start; + + if (length < 0) { + start = 0; + length = 0; } - if (length >= end) { - length = end - start; - - if (length < 0) { - start = 0; - length = 0; - } - - } else { - length -= start; - } + } else { + length -= start; } } diff -r 85f57cc123c2 -r b00d4680635a njs/test/njs_unit_test.c --- a/njs/test/njs_unit_test.c Fri Sep 14 14:19:03 2018 +0300 +++ b/njs/test/njs_unit_test.c Mon Sep 17 18:47:00 2018 +0300 @@ -3794,6 +3794,18 @@ static njs_unit_test_t njs_test[] = { nxt_string("'abcdefgh'.slice(3)"), nxt_string("defgh") }, + { nxt_string("'abcdefgh'.slice(undefined, undefined)"), + nxt_string("abcdefgh") }, + + { nxt_string("'abcdefgh'.slice(undefined)"), + nxt_string("abcdefgh") }, + + { nxt_string("'abcdefgh'.slice(undefined, 1)"), + nxt_string("a") }, + + { nxt_string("'abcdefgh'.slice(3, undefined)"), + nxt_string("defgh") }, + { nxt_string("'abcde'.slice(50)"), nxt_string("") }, From johan.ekenlycka at tui.se Tue Sep 18 11:40:11 2018 From: johan.ekenlycka at tui.se (Johan Ekenlycka) Date: Tue, 18 Sep 2018 11:40:11 +0000 Subject: Fix copy of multiple peers when using upstream zone directive Message-ID: # HG changeset patch # User Johan Ekenlycka # Date 1537270510 -7200 # Tue Sep 18 13:35:10 2018 +0200 # Node ID f6a4b9360aaa102749233e14d4b49120dae46d9b # Parent 87d2ea860f380dc8418c97c0163412f53c2d008e Fix copy of multiple peers when using upstream zone Iteration of destination pointer and next pointer on peers was not performed correctly, resulting in NULL peers when multiple peers in upstream. diff -r 87d2ea860f38 -r f6a4b9360aaa src/http/modules/ngx_http_upstream_zone_module.c --- a/src/http/modules/ngx_http_upstream_zone_module.c Mon Sep 10 18:57:39 2018 +0300 +++ b/src/http/modules/ngx_http_upstream_zone_module.c Tue Sep 18 13:35:10 2018 +0200 @@ -187,8 +187,9 @@ ngx_http_upstream_zone_copy_peers(ngx_slab_pool_t *shpool, ngx_http_upstream_srv_conf_t *uscf) { + ngx_uint_t i; ngx_str_t *name; - ngx_http_upstream_rr_peer_t *peer, **peerp; + ngx_http_upstream_rr_peer_t *peer; ngx_http_upstream_rr_peers_t *peers, *backup; peers = ngx_slab_alloc(shpool, sizeof(ngx_http_upstream_rr_peers_t)); @@ -215,14 +216,17 @@ peers->shpool = shpool; - for (peerp = &peers->peer; *peerp; peerp = &peer->next) { + for (i = 0; i < peers->number; i++) { /* pool is unlocked */ - peer = ngx_http_upstream_zone_copy_peer(peers, *peerp); + peer = ngx_http_upstream_zone_copy_peer(peers, &peers->peer[i]); if (peer == NULL) { return NULL; } + if (i > 0) { + peers->peer[i - 1].next = peer; + } - *peerp = peer; + peers->peer[i] = *peer; } if (peers->next == NULL) { @@ -240,14 +244,18 @@ backup->shpool = shpool; - for (peerp = &backup->peer; *peerp; peerp = &peer->next) { + + for (i = 0; i < backup->number; i++) { /* pool is unlocked */ - peer = ngx_http_upstream_zone_copy_peer(backup, *peerp); + peer = ngx_http_upstream_zone_copy_peer(backup, &backup->peer[i]); if (peer == NULL) { return NULL; } + if (i > 0) { + backup->peer[i - 1].next = peer; + } - *peerp = peer; + backup->peer[i] = *peer; } peers->next = backup; -------------- next part -------------- An HTML attachment was scrubbed... URL: From teward at thomas-ward.net Tue Sep 18 12:12:20 2018 From: teward at thomas-ward.net (Thomas Ward) Date: Tue, 18 Sep 2018 08:12:20 -0400 Subject: PCRE2 support? Message-ID: Downstream in Ubuntu, it has been proposed to demote pcre3 and use pcre2 instead as it is newer. https://trac.nginx.org/nginx/ticket/720 shows it was marked 4 years ago that NGINX does not support pcre2.? Are there any plans to use pcre2 instead of pcre3? Thomas Sent from my Sprint Samsung Galaxy S9+. -------------- next part -------------- An HTML attachment was scrubbed... URL: From xeioex at nginx.com Tue Sep 18 12:14:18 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 18 Sep 2018 12:14:18 +0000 Subject: [njs] Version 0.2.4. Message-ID: details: http://hg.nginx.org/njs/rev/3e6c38f64bdb branches: changeset: 607:3e6c38f64bdb user: Dmitry Volyntsev date: Tue Sep 18 15:13:41 2018 +0300 description: Version 0.2.4. diffstat: CHANGES | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 56 insertions(+), 0 deletions(-) diffs (63 lines): diff -r b00d4680635a -r 3e6c38f64bdb CHANGES --- a/CHANGES Mon Sep 17 18:47:00 2018 +0300 +++ b/CHANGES Tue Sep 18 15:13:41 2018 +0300 @@ -1,3 +1,59 @@ + +Changes with njs 0.2.4 18 Aug 2018 + + nginx modules: + + *) Change: stream module handlers are refactored. + + New methods and properties: + s.on(), s.off(), s.allow(), s.done(), s.decline(), + s.deny(). + + Removed properties of Stream object: + s.OK, s.ABORT, s.AGAIN, s.DECLINED, s.ERROR (replaced + with s.allow(), s.done([code]), s.deny()). + + s.buffer (for reading replaced with data argument of + the corresponding callback, for writing use s.send()). + + s.fromUpstream (replaced with a callback for a corresponding + event). + + s.eof (replaced with flags.last). + + Core: + + *) Feature: added Function.prototype.length. + + *) Feature: introduced sandboxing mode. + + *) Improvement: added exception strings where appropriate. + + *) Improvement: improved wording for primitive type conversion + exception. + + *) Bugfix: throwing TypeError for attempts to change frozen + properties. + + *) Bugfix: fixed Object.defineProperty() for existing properties. + + *) Bugfix: respecting the enumerable attribute while iterating + by for in. + + *) Bugfix: respecting writable attribute for property handlers. + + *) Bugfix: fixed exception handling in arguments of a function. + + *) Bugfix: fixed Object.prototype.toString for different + value types. + + *) Bugfix: fixed Object() constructor for object types arguments. + + *) Bugfix: fixed comparison of objects and strings. + + *) Bugfix: fixed String.slice() for undefined arguments. + + *) Bugfix: miscellaneous additional bugs have been fixed. Changes with njs 0.2.3 31 Jul 2018 From xeioex at nginx.com Tue Sep 18 12:14:18 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Tue, 18 Sep 2018 12:14:18 +0000 Subject: [njs] Added tag 0.2.4 for changeset 3e6c38f64bdb Message-ID: details: http://hg.nginx.org/njs/rev/9ed38e1bd40c branches: changeset: 608:9ed38e1bd40c user: Dmitry Volyntsev date: Tue Sep 18 15:14:06 2018 +0300 description: Added tag 0.2.4 for changeset 3e6c38f64bdb diffstat: .hgtags | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (8 lines): diff -r 3e6c38f64bdb -r 9ed38e1bd40c .hgtags --- a/.hgtags Tue Sep 18 15:13:41 2018 +0300 +++ b/.hgtags Tue Sep 18 15:14:06 2018 +0300 @@ -18,3 +18,4 @@ ddd1b2c9c64b2d459e9c399554dfaadcaabcc364 2a0a59728b5f197379ca62a334a516fabd4ea392 0.2.1 4adbb035caa39dae58611a061d78bc974652231e 0.2.2 e83f41520613987542472275d49633a9aa5955d1 0.2.3 +3e6c38f64bdbc53e783813541559034ed6890aee 0.2.4 From mdounin at mdounin.ru Tue Sep 18 15:55:18 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 18 Sep 2018 18:55:18 +0300 Subject: PCRE2 support? In-Reply-To: <20180918121231.843FC2C50D50@mail.nginx.com> References: <20180918121231.843FC2C50D50@mail.nginx.com> Message-ID: <20180918155518.GQ56558@mdounin.ru> Hello! On Tue, Sep 18, 2018 at 08:12:20AM -0400, Thomas Ward wrote: > Downstream in Ubuntu, it has been proposed to demote pcre3 and > use pcre2 instead as it is newer. > https://trac.nginx.org/nginx/ticket/720 shows it was marked 4 > years ago that NGINX does not support pcre2.? Are there any > plans to use pcre2 instead of pcre3? There are no immediate plans. When we last checked, there were no problems with PCRE, but PCRE2 wasn't available in most distributions we support, making the switch mostly meaningless. Also, it looks like PCRE2 is still not supported even by Exim, which is the parent project of PCRE and PCRE2: https://bugs.exim.org/show_bug.cgi?id=1878 As such, adding PCRE2 support to nginx looks premature. -- Maxim Dounin http://mdounin.ru/ From mark.mielke at gmail.com Wed Sep 19 07:19:27 2018 From: mark.mielke at gmail.com (Mark Mielke) Date: Wed, 19 Sep 2018 03:19:27 -0400 Subject: PCRE2 support? In-Reply-To: <20180918155518.GQ56558@mdounin.ru> References: <20180918121231.843FC2C50D50@mail.nginx.com> <20180918155518.GQ56558@mdounin.ru> Message-ID: On Tue, Sep 18, 2018 at 11:55 AM Maxim Dounin wrote: > On Tue, Sep 18, 2018 at 08:12:20AM -0400, Thomas Ward wrote: > > Downstream in Ubuntu, it has been proposed to demote pcre3 and > > use pcre2 instead as it is newer. > > https://trac.nginx.org/nginx/ticket/720 shows it was marked 4 > > years ago that NGINX does not support pcre2. Are there any > > plans to use pcre2 instead of pcre3? > There are no immediate plans. > When we last checked, there were no problems with PCRE, but PCRE2 > wasn't available in most distributions we support, making the > switch mostly meaningless. > I think there have been changes since then. PCRE2 was new in 2015, but isn't new in 2018. RHEL 7 (and clones) include libpcre2. I think this is a pretty wide set of machines right here. I believe Ubuntu 16.04 LTS has libpcre2 which is another wide set. Fedora has had it for a while now. I think that unless you go to older releases, all major distributions should have PCRE2 by now. I build haproxy with PCRE2 support. One that was interesting to me, is that Git recently (April, 2018?) switched to building with PCRE2 by default when "USE_LIBPCRE=Yes" was specified: https://github.com/git/git/commit/cac5351363c5713248b8494e1e58e282c0a5bde7 I'm not sure about meaningless from a functionality perspective. All new features are going into PCRE2. PCRE is a stable branched with only bug fixes and security problems fixed. The newest features, including performance improvements, are all in PCRE2. I believe the reason it is "PCRE2-10" instead of "PCRE-10", is because of API changes, and the opportunity to improve or fix previous API decisions. This then becomes the basis for all future development free of the PCRE API shackles. > > Also, it looks like PCRE2 is still not supported even by Exim, > which is the parent project of PCRE and PCRE2: > > https://bugs.exim.org/show_bug.cgi?id=1878 I don't think Exim should be the measure: "PCRE was originally written for the Exim MTA , but is now used by many high-profile open source projects, including Apache , PHP , KDE , Postfix , and Nmap . PCRE has also found its way into some well known commercial products, like Apple Safari . Some other interesting projects using PCRE include Chicken , Ferite , Onyx , Hypermail , Leafnode , Askemos , Wenlin , and 8th ." This list is also far from complete. PCRE and PCRE2 are widely used, and PCRE2 is where all current improvement is occurring. Anybody waiting for Exim, will probably be among the very last to switch. As such, adding PCRE2 support to nginx looks premature. > Before writing the above, I was debating with myself about when the right time to add support for a new component release is. For some projects and some components, it will be more important than others. For example, OpenSSL 1.1.1 with TLSv1.3 support might be considered premature for some projects to support, and yet Nginx supports this newly released version long before OpenSSL 1.1.1 is readily available in a majority of distributions that you support. I think in this case, OpenSSL 1.1.1 is of strategic importance to Nginx, and this ensured it was on the roadmap. (However, it still seems to be missing an ability to configure the cipher suite... :-) ) I don't know if PCRE2 belongs on this roadmap. I think there have been significant improvements with the JIT compilation that could prove useful for higher performance evaluation of regular expressions with Nginx, but I personally don't have this requirement, so if after considering all of the above - you still felt it was premature, I wouldn't raise any concern personally. I just want to make sure this is an informed position. :-) -- Mark Mielke -------------- next part -------------- An HTML attachment was scrubbed... URL: From pluknet at nginx.com Wed Sep 19 11:35:42 2018 From: pluknet at nginx.com (Sergey Kandaurov) Date: Wed, 19 Sep 2018 14:35:42 +0300 Subject: Upstream with zone directive In-Reply-To: References: Message-ID: <79FE627C-7351-47D3-B660-7B450162964D@nginx.com> > On 17 Sep 2018, at 16:05, Johan Ekenlycka wrote: > > Hi! > > I am using ngx_lua_upstream module in OpenResty, which is calling ngx_http_get_module_main_conf(r, ngx_http_upstream_module). For some reason, I think the peer data is not correct when the zone directive is used in an upstream. But I am not really sure where in the source the config is parsed and peer/server data is populated. > > Can someone please get me some pointers to where I should look or even better if somebody could reaffirm and check the problem. The ngx_lua_upstream module is not adapted to work with peers stored in memory as a linked list, thus it cannot be used with upstream zone that copies peers from configuration to shared memory one by one, thus the peers' layout is effectively changed from an array to a linked list. The problem you observe is that lua iterates peers by index as an array. See this change as a guidance: http://hg.nginx.org/nginx/rev/c44459611d91 -- Sergey Kandaurov From nova at novalinium.com Wed Sep 19 15:41:23 2018 From: nova at novalinium.com (Nova) Date: Wed, 19 Sep 2018 10:41:23 -0500 (EST) Subject: [PATCH] HTTP Special Page backgrounds Message-ID: TZAG, I've run into a compatability issue between Nginx error/special response pages and customized font color and background color in user stylesheets. I use white on black as my default color scheme, and since the Nginx special pages specify bgcolor but not color (font color), this leads to a "blank" white page (with a hr on it). The attached patch presented for review removes Nginx bgcolor on special pages, since this is not necessary to render them as intended without interfering with accessibility settings. Best, Nova -------------- next part -------------- # HG changeset patch # User Nova DasSarma # Date 1537367207 18000 # Wed Sep 19 09:26:47 2018 -0500 # Node ID f359ea07f1d61c284fb54706c881120c3e4628c0 # Parent 87d2ea860f380dc8418c97c0163412f53c2d008e HTTP Special Responses: removed bgcolor attribute on body. bgcolor overrides compatibility settings in browsers and leads to undesirable behavior when the default font color is set to white in the browser, since font-color is not also overridden. diff --git a/src/http/ngx_http_special_response.c b/src/http/ngx_http_special_response.c --- a/src/http/ngx_http_special_response.c +++ b/src/http/ngx_http_special_response.c @@ -60,7 +60,7 @@ static u_char ngx_http_msie_refresh_tail static char ngx_http_error_301_page[] = "" CRLF "301 Moved Permanently" CRLF -"" CRLF +"" CRLF "

301 Moved Permanently

" CRLF ; @@ -68,7 +68,7 @@ static char ngx_http_error_301_page[] = static char ngx_http_error_302_page[] = "" CRLF "302 Found" CRLF -"" CRLF +"" CRLF "

302 Found

" CRLF ; @@ -76,7 +76,7 @@ static char ngx_http_error_302_page[] = static char ngx_http_error_303_page[] = "" CRLF "303 See Other" CRLF -"" CRLF +"" CRLF "

303 See Other

" CRLF ; @@ -84,7 +84,7 @@ static char ngx_http_error_303_page[] = static char ngx_http_error_307_page[] = "" CRLF "307 Temporary Redirect" CRLF -"" CRLF +"" CRLF "

307 Temporary Redirect

" CRLF ; @@ -92,7 +92,7 @@ static char ngx_http_error_307_page[] = static char ngx_http_error_308_page[] = "" CRLF "308 Permanent Redirect" CRLF -"" CRLF +"" CRLF "

308 Permanent Redirect

" CRLF ; @@ -100,7 +100,7 @@ static char ngx_http_error_308_page[] = static char ngx_http_error_400_page[] = "" CRLF "400 Bad Request" CRLF -"" CRLF +"" CRLF "

400 Bad Request

" CRLF ; @@ -108,7 +108,7 @@ static char ngx_http_error_400_page[] = static char ngx_http_error_401_page[] = "" CRLF "401 Authorization Required" CRLF -"" CRLF +"" CRLF "

401 Authorization Required

" CRLF ; @@ -116,7 +116,7 @@ static char ngx_http_error_401_page[] = static char ngx_http_error_402_page[] = "" CRLF "402 Payment Required" CRLF -"" CRLF +"" CRLF "

402 Payment Required

" CRLF ; @@ -124,7 +124,7 @@ static char ngx_http_error_402_page[] = static char ngx_http_error_403_page[] = "" CRLF "403 Forbidden" CRLF -"" CRLF +"" CRLF "

403 Forbidden

" CRLF ; @@ -132,7 +132,7 @@ static char ngx_http_error_403_page[] = static char ngx_http_error_404_page[] = "" CRLF "404 Not Found" CRLF -"" CRLF +"" CRLF "

404 Not Found

" CRLF ; @@ -140,7 +140,7 @@ static char ngx_http_error_404_page[] = static char ngx_http_error_405_page[] = "" CRLF "405 Not Allowed" CRLF -"" CRLF +"" CRLF "

405 Not Allowed

" CRLF ; @@ -148,7 +148,7 @@ static char ngx_http_error_405_page[] = static char ngx_http_error_406_page[] = "" CRLF "406 Not Acceptable" CRLF -"" CRLF +"" CRLF "

406 Not Acceptable

" CRLF ; @@ -156,7 +156,7 @@ static char ngx_http_error_406_page[] = static char ngx_http_error_408_page[] = "" CRLF "408 Request Time-out" CRLF -"" CRLF +"" CRLF "

408 Request Time-out

" CRLF ; @@ -164,7 +164,7 @@ static char ngx_http_error_408_page[] = static char ngx_http_error_409_page[] = "" CRLF "409 Conflict" CRLF -"" CRLF +"" CRLF "

409 Conflict

" CRLF ; @@ -172,7 +172,7 @@ static char ngx_http_error_409_page[] = static char ngx_http_error_410_page[] = "" CRLF "410 Gone" CRLF -"" CRLF +"" CRLF "

410 Gone

" CRLF ; @@ -180,7 +180,7 @@ static char ngx_http_error_410_page[] = static char ngx_http_error_411_page[] = "" CRLF "411 Length Required" CRLF -"" CRLF +"" CRLF "

411 Length Required

" CRLF ; @@ -188,7 +188,7 @@ static char ngx_http_error_411_page[] = static char ngx_http_error_412_page[] = "" CRLF "412 Precondition Failed" CRLF -"" CRLF +"" CRLF "

412 Precondition Failed

" CRLF ; @@ -196,7 +196,7 @@ static char ngx_http_error_412_page[] = static char ngx_http_error_413_page[] = "" CRLF "413 Request Entity Too Large" CRLF -"" CRLF +"" CRLF "

413 Request Entity Too Large

" CRLF ; @@ -204,7 +204,7 @@ static char ngx_http_error_413_page[] = static char ngx_http_error_414_page[] = "" CRLF "414 Request-URI Too Large" CRLF -"" CRLF +"" CRLF "

414 Request-URI Too Large

" CRLF ; @@ -212,7 +212,7 @@ static char ngx_http_error_414_page[] = static char ngx_http_error_415_page[] = "" CRLF "415 Unsupported Media Type" CRLF -"" CRLF +"" CRLF "

415 Unsupported Media Type

" CRLF ; @@ -220,7 +220,7 @@ static char ngx_http_error_415_page[] = static char ngx_http_error_416_page[] = "" CRLF "416 Requested Range Not Satisfiable" CRLF -"" CRLF +"" CRLF "

416 Requested Range Not Satisfiable

" CRLF ; @@ -228,7 +228,7 @@ static char ngx_http_error_416_page[] = static char ngx_http_error_421_page[] = "" CRLF "421 Misdirected Request" CRLF -"" CRLF +"" CRLF "

421 Misdirected Request

" CRLF ; @@ -236,7 +236,7 @@ static char ngx_http_error_421_page[] = static char ngx_http_error_429_page[] = "" CRLF "429 Too Many Requests" CRLF -"" CRLF +"" CRLF "

429 Too Many Requests

" CRLF ; @@ -245,7 +245,7 @@ static char ngx_http_error_494_page[] = "" CRLF "400 Request Header Or Cookie Too Large" CRLF -"" CRLF +"" CRLF "

400 Bad Request

" CRLF "
Request Header Or Cookie Too Large
" CRLF ; @@ -255,7 +255,7 @@ static char ngx_http_error_495_page[] = "" CRLF "400 The SSL certificate error" CRLF -"" CRLF +"" CRLF "

400 Bad Request

" CRLF "
The SSL certificate error
" CRLF ; @@ -265,7 +265,7 @@ static char ngx_http_error_496_page[] = "" CRLF "400 No required SSL certificate was sent" CRLF -"" CRLF +"" CRLF "

400 Bad Request

" CRLF "
No required SSL certificate was sent
" CRLF ; @@ -275,7 +275,7 @@ static char ngx_http_error_497_page[] = "" CRLF "400 The plain HTTP request was sent to HTTPS port" CRLF -"" CRLF +"" CRLF "

400 Bad Request

" CRLF "
The plain HTTP request was sent to HTTPS port
" CRLF ; @@ -284,7 +284,7 @@ CRLF static char ngx_http_error_500_page[] = "" CRLF "500 Internal Server Error" CRLF -"" CRLF +"" CRLF "

500 Internal Server Error

" CRLF ; @@ -292,7 +292,7 @@ static char ngx_http_error_500_page[] = static char ngx_http_error_501_page[] = "" CRLF "501 Not Implemented" CRLF -"" CRLF +"" CRLF "

501 Not Implemented

" CRLF ; @@ -300,7 +300,7 @@ static char ngx_http_error_501_page[] = static char ngx_http_error_502_page[] = "" CRLF "502 Bad Gateway" CRLF -"" CRLF +"" CRLF "

502 Bad Gateway

" CRLF ; @@ -308,7 +308,7 @@ static char ngx_http_error_502_page[] = static char ngx_http_error_503_page[] = "" CRLF "503 Service Temporarily Unavailable" CRLF -"" CRLF +"" CRLF "

503 Service Temporarily Unavailable

" CRLF ; @@ -316,7 +316,7 @@ static char ngx_http_error_503_page[] = static char ngx_http_error_504_page[] = "" CRLF "504 Gateway Time-out" CRLF -"" CRLF +"" CRLF "

504 Gateway Time-out

" CRLF ; @@ -324,7 +324,7 @@ static char ngx_http_error_504_page[] = static char ngx_http_error_505_page[] = "" CRLF "505 HTTP Version Not Supported" CRLF -"" CRLF +"" CRLF "

505 HTTP Version Not Supported

" CRLF ; @@ -332,7 +332,7 @@ static char ngx_http_error_505_page[] = static char ngx_http_error_507_page[] = "" CRLF "507 Insufficient Storage" CRLF -"" CRLF +"" CRLF "

507 Insufficient Storage

" CRLF ; From xeioex at nginx.com Wed Sep 19 16:45:37 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 19 Sep 2018 16:45:37 +0000 Subject: [njs] Version bump. Message-ID: details: http://hg.nginx.org/njs/rev/c989d199095a branches: changeset: 609:c989d199095a user: Dmitry Volyntsev date: Wed Sep 19 14:43:58 2018 +0300 description: Version bump. diffstat: njs/njs.h | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r 9ed38e1bd40c -r c989d199095a njs/njs.h --- a/njs/njs.h Tue Sep 18 15:14:06 2018 +0300 +++ b/njs/njs.h Wed Sep 19 14:43:58 2018 +0300 @@ -11,7 +11,7 @@ #include -#define NJS_VERSION "0.2.4" +#define NJS_VERSION "0.2.5" #include From xeioex at nginx.com Wed Sep 19 16:45:37 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 19 Sep 2018 16:45:37 +0000 Subject: [njs] Fixed Array.prototype.length setter. Message-ID: details: http://hg.nginx.org/njs/rev/da303066654d branches: changeset: 610:da303066654d user: Dmitry Volyntsev date: Wed Sep 19 19:24:05 2018 +0300 description: Fixed Array.prototype.length setter. diffstat: njs/njs_array.c | 4 ++-- njs/test/njs_unit_test.c | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diffs (40 lines): diff -r c989d199095a -r da303066654d njs/njs_array.c --- a/njs/njs_array.c Wed Sep 19 14:43:58 2018 +0300 +++ b/njs/njs_array.c Wed Sep 19 19:24:05 2018 +0300 @@ -377,7 +377,7 @@ njs_array_prototype_length(njs_vm_t *vm, njs_value_t *setval, njs_value_t *retval) { double num; - int32_t size; + int64_t size; uint32_t length; njs_ret_t ret; njs_value_t *val; @@ -399,7 +399,7 @@ njs_array_prototype_length(njs_vm_t *vm, return NJS_ERROR; } - size = (int32_t) (length - array->length); + size = (int64_t) length - array->length; if (size > 0) { ret = njs_array_expand(vm, array, 0, size); diff -r c989d199095a -r da303066654d njs/test/njs_unit_test.c --- a/njs/test/njs_unit_test.c Wed Sep 19 14:43:58 2018 +0300 +++ b/njs/test/njs_unit_test.c Wed Sep 19 19:24:05 2018 +0300 @@ -2790,9 +2790,15 @@ static njs_unit_test_t njs_test[] = { nxt_string("[].length = {}"), nxt_string("RangeError: Invalid array length") }, + { nxt_string("[].length = 2**32 - 1"), + nxt_string("MemoryError") }, + { nxt_string("[].length = 2**32"), nxt_string("RangeError: Invalid array length") }, + { nxt_string("[].length = 2**32 + 1"), + nxt_string("RangeError: Invalid array length") }, + { nxt_string("[].length = -1"), nxt_string("RangeError: Invalid array length") }, From xeioex at nginx.com Wed Sep 19 16:45:37 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 19 Sep 2018 16:45:37 +0000 Subject: [njs] Fixed njs_array_alloc() for length > 2**31. Message-ID: details: http://hg.nginx.org/njs/rev/731e29c2b41e branches: changeset: 611:731e29c2b41e user: Dmitry Volyntsev date: Wed Sep 19 19:24:07 2018 +0300 description: Fixed njs_array_alloc() for length > 2**31. diffstat: njs/njs_array.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r da303066654d -r 731e29c2b41e njs/njs_array.c --- a/njs/njs_array.c Wed Sep 19 19:24:05 2018 +0300 +++ b/njs/njs_array.c Wed Sep 19 19:24:07 2018 +0300 @@ -117,7 +117,7 @@ njs_array_alloc(njs_vm_t *vm, uint32_t l goto memory_error; } - size = length + spare; + size = (size_t) length + spare; if (nxt_slow_path(size * sizeof(njs_value_t) < size)) { goto memory_error; From xeioex at nginx.com Wed Sep 19 16:45:38 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Wed, 19 Sep 2018 16:45:38 +0000 Subject: [njs] Fixed http status and contentType getter. Message-ID: details: http://hg.nginx.org/njs/rev/68a3580688ab branches: changeset: 612:68a3580688ab user: Dmitry Volyntsev date: Wed Sep 19 19:36:00 2018 +0300 description: Fixed http status and contentType getter. Getter are expected to set resulting value to provied argument, not to vm->retval. diffstat: nginx/ngx_http_js_module.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (21 lines): diff -r 731e29c2b41e -r 68a3580688ab nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Wed Sep 19 19:24:07 2018 +0300 +++ b/nginx/ngx_http_js_module.c Wed Sep 19 19:36:00 2018 +0300 @@ -1190,7 +1190,7 @@ ngx_http_js_ext_get_status(njs_vm_t *vm, r = (ngx_http_request_t *) obj; - njs_value_number_set(njs_vm_retval(vm), r->headers_out.status); + njs_value_number_set(value, r->headers_out.status); return NJS_OK; } @@ -1224,7 +1224,7 @@ ngx_http_js_ext_get_content_length(njs_v r = (ngx_http_request_t *) obj; - njs_value_number_set(njs_vm_retval(vm), r->headers_out.content_length_n); + njs_value_number_set(value, r->headers_out.content_length_n); return NJS_OK; } From gmm at csdoc.com Fri Sep 21 11:24:49 2018 From: gmm at csdoc.com (Gena Makhomed) Date: Fri, 21 Sep 2018 14:24:49 +0300 Subject: [PATCH] Allow 1xx 2xx 3xx 4xx 5xx codes in xxxxx_cache_valid directives. Message-ID: # HG changeset patch # User Gena Makhomed # Date 1537528104 -10800 # Fri Sep 21 14:08:24 2018 +0300 # Node ID a7533f3f3138fe5524a0f14293da4149e65b1402 # Parent 87d2ea860f380dc8418c97c0163412f53c2d008e Allow 1xx 2xx 3xx 4xx 5xx codes in xxxxx_cache_valid directives. For example, config fragment fastcgi_cache_valid 200 201 202 203 204 205 206 207 208 226 5m; fastcgi_cache_valid 300 301 302 303 304 305 306 307 308 10m; now can be rewritten as fastcgi_cache_valid 2xx 5m; fastcgi_cache_valid 3xx 10m; diff -r 87d2ea860f38 -r a7533f3f3138 src/http/ngx_http_file_cache.c --- a/src/http/ngx_http_file_cache.c Mon Sep 10 18:57:39 2018 +0300 +++ b/src/http/ngx_http_file_cache.c Fri Sep 21 14:08:24 2018 +0300 @@ -2290,6 +2290,26 @@ return valid[i].valid; } + if (valid[i].status == 1 && status >= 100 && status <= 199) { + return valid[i].valid; + } + + if (valid[i].status == 2 && status >= 200 && status <= 299) { + return valid[i].valid; + } + + if (valid[i].status == 3 && status >= 300 && status <= 399) { + return valid[i].valid; + } + + if (valid[i].status == 4 && status >= 400 && status <= 499) { + return valid[i].valid; + } + + if (valid[i].status == 5 && status >= 500 && status <= 599) { + return valid[i].valid; + } + if (valid[i].status == status) { return valid[i].valid; } @@ -2666,10 +2686,30 @@ status = 0; + } else if(ngx_strcmp(value[i].data, "1xx") == 0) { + + status = 1; + + } else if(ngx_strcmp(value[i].data, "2xx") == 0) { + + status = 2; + + } else if(ngx_strcmp(value[i].data, "3xx") == 0) { + + status = 3; + + } else if(ngx_strcmp(value[i].data, "4xx") == 0) { + + status = 4; + + } else if(ngx_strcmp(value[i].data, "5xx") == 0) { + + status = 5; + } else { status = ngx_atoi(value[i].data, value[i].len); - if (status < 100) { + if (status < 100 || status > 599) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid status \"%V\"", &value[i]); return NGX_CONF_ERROR; From mdounin at mdounin.ru Fri Sep 21 12:58:39 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 21 Sep 2018 15:58:39 +0300 Subject: [PATCH] Allow 1xx 2xx 3xx 4xx 5xx codes in xxxxx_cache_valid directives. In-Reply-To: References: Message-ID: <20180921125839.GJ56558@mdounin.ru> Hello! On Fri, Sep 21, 2018 at 02:24:49PM +0300, Gena Makhomed wrote: > # HG changeset patch > # User Gena Makhomed > # Date 1537528104 -10800 > # Fri Sep 21 14:08:24 2018 +0300 > # Node ID a7533f3f3138fe5524a0f14293da4149e65b1402 > # Parent 87d2ea860f380dc8418c97c0163412f53c2d008e > Allow 1xx 2xx 3xx 4xx 5xx codes in xxxxx_cache_valid directives. > > For example, config fragment > > fastcgi_cache_valid 200 201 202 203 204 205 206 207 208 226 5m; > fastcgi_cache_valid 300 301 302 303 304 305 306 307 308 10m; > > now can be rewritten as > > fastcgi_cache_valid 2xx 5m; > fastcgi_cache_valid 3xx 10m; I cannot say I like this change. Cacheability of various response codes vary widely, and caching then "in bulk" in most cases is a bad idea. In particular, caching of 201 is always wrong as it is only expected to be returned to non-cacheable POST and PUT requests. Caching 206 is wrong unless you use "Ranges" in the cache key. And caching 304 is wrong unless you've laso included If-Modified-Since and If-None-Match headers in the cache key. As such, I would rather refrain from introducing these shortcuts, as they looks rather dangerous for the unwary. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Fri Sep 21 14:35:25 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 21 Sep 2018 14:35:25 +0000 Subject: [nginx] Fixed socket leak with "return 444" in error_page (ticket #274). Message-ID: details: http://hg.nginx.org/nginx/rev/1812f1d79d84 branches: changeset: 7354:1812f1d79d84 user: Maxim Dounin date: Fri Sep 21 15:59:30 2018 +0300 description: Fixed socket leak with "return 444" in error_page (ticket #274). Socket leak was observed in the following configuration: error_page 400 = /close; location = /close { return 444; } The problem is that "return 444" triggers termination of the request, and due to error_page termination thinks that it needs to use a posted request to clear stack. But at the early request processing where 400 errors are generated there are no ngx_http_run_posted_requests() calls, so the request is only terminated after an external event. Variants of the problem include "error_page 497" instead (ticket #695) and various other errors generated during early request processing (405, 414, 421, 494, 495, 496, 501, 505). The same problem can be also triggered with "return 499" and "return 408" as both codes trigger ngx_http_terminate_request(), much like "return 444". To fix this, the patch adds ngx_http_run_posted_requests() calls to ngx_http_process_request_line() and ngx_http_process_request_headers() functions, and to ngx_http_v2_run_request() and ngx_http_v2_push_stream() functions in HTTP/2. Since the ngx_http_process_request() function is now only called via other functions which call ngx_http_run_posted_requests(), the call there is no longer needed and was removed. diffstat: src/http/ngx_http_request.c | 50 +++++++++++++++++++++++--------------------- src/http/v2/ngx_http_v2.c | 18 ++++++++++++--- 2 files changed, 40 insertions(+), 28 deletions(-) diffs (266 lines): diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -959,7 +959,7 @@ ngx_http_process_request_line(ngx_event_ n = ngx_http_read_request_header(r); if (n == NGX_AGAIN || n == NGX_ERROR) { - return; + break; } } @@ -984,7 +984,7 @@ ngx_http_process_request_line(ngx_event_ } if (ngx_http_process_request_uri(r) != NGX_OK) { - return; + break; } if (r->schema_end) { @@ -1003,16 +1003,16 @@ ngx_http_process_request_line(ngx_event_ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent invalid host in request line"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return; + break; } if (rc == NGX_ERROR) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return; + break; } if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { - return; + break; } r->headers_in.server = host; @@ -1024,11 +1024,11 @@ ngx_http_process_request_line(ngx_event_ && ngx_http_set_virtual_server(r, &r->headers_in.server) == NGX_ERROR) { - return; + break; } ngx_http_process_request(r); - return; + break; } @@ -1037,7 +1037,7 @@ ngx_http_process_request_line(ngx_event_ != NGX_OK) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return; + break; } c->log->action = "reading client request headers"; @@ -1045,7 +1045,7 @@ ngx_http_process_request_line(ngx_event_ rev->handler = ngx_http_process_request_headers; ngx_http_process_request_headers(rev); - return; + break; } if (rc != NGX_AGAIN) { @@ -1062,7 +1062,7 @@ ngx_http_process_request_line(ngx_event_ ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); } - return; + break; } /* NGX_AGAIN: a request line parsing is still incomplete */ @@ -1073,7 +1073,7 @@ ngx_http_process_request_line(ngx_event_ if (rv == NGX_ERROR) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return; + break; } if (rv == NGX_DECLINED) { @@ -1083,10 +1083,12 @@ ngx_http_process_request_line(ngx_event_ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent too long URI"); ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE); - return; + break; } } } + + ngx_http_run_posted_requests(c); } @@ -1248,7 +1250,7 @@ ngx_http_process_request_headers(ngx_eve if (rv == NGX_ERROR) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return; + break; } if (rv == NGX_DECLINED) { @@ -1261,7 +1263,7 @@ ngx_http_process_request_headers(ngx_eve "client sent too large request"); ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); - return; + break; } len = r->header_in->end - p; @@ -1276,14 +1278,14 @@ ngx_http_process_request_headers(ngx_eve ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); - return; + break; } } n = ngx_http_read_request_header(r); if (n == NGX_AGAIN || n == NGX_ERROR) { - return; + break; } } @@ -1313,7 +1315,7 @@ ngx_http_process_request_headers(ngx_eve h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return; + break; } h->hash = r->header_hash; @@ -1329,7 +1331,7 @@ ngx_http_process_request_headers(ngx_eve h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); if (h->lowcase_key == NULL) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return; + break; } if (h->key.len == r->lowcase_index) { @@ -1343,7 +1345,7 @@ ngx_http_process_request_headers(ngx_eve h->lowcase_key, h->key.len); if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return; + break; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -1367,12 +1369,12 @@ ngx_http_process_request_headers(ngx_eve rc = ngx_http_process_request_header(r); if (rc != NGX_OK) { - return; + break; } ngx_http_process_request(r); - return; + break; } if (rc == NGX_AGAIN) { @@ -1388,8 +1390,10 @@ ngx_http_process_request_headers(ngx_eve "client sent invalid header line"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return; + break; } + + ngx_http_run_posted_requests(c); } @@ -1944,8 +1948,6 @@ ngx_http_process_request(ngx_http_reques r->read_event_handler = ngx_http_block_reading; ngx_http_handler(r); - - ngx_http_run_posted_requests(c); } diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -2673,11 +2673,13 @@ error: if (rc == NGX_ABORT) { /* header handler has already finalized request */ + ngx_http_run_posted_requests(fc); return NULL; } if (rc == NGX_DECLINED) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + ngx_http_run_posted_requests(fc); return NULL; } @@ -3742,18 +3744,22 @@ ngx_http_v2_construct_cookie_header(ngx_ static void ngx_http_v2_run_request(ngx_http_request_t *r) { + ngx_connection_t *fc; + + fc = r->connection; + if (ngx_http_v2_construct_request_line(r) != NGX_OK) { - return; + goto failed; } if (ngx_http_v2_construct_cookie_header(r) != NGX_OK) { - return; + goto failed; } r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; if (ngx_http_process_request_header(r) != NGX_OK) { - return; + goto failed; } if (r->headers_in.content_length_n > 0 && r->stream->in_closed) { @@ -3763,7 +3769,7 @@ ngx_http_v2_run_request(ngx_http_request r->stream->skip_data = 1; ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return; + goto failed; } if (r->headers_in.content_length_n == -1 && !r->stream->in_closed) { @@ -3771,6 +3777,10 @@ ngx_http_v2_run_request(ngx_http_request } ngx_http_process_request(r); + +failed: + + ngx_http_run_posted_requests(fc); } From mdounin at mdounin.ru Fri Sep 21 14:35:27 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 21 Sep 2018 14:35:27 +0000 Subject: [nginx] Rewrite: removed r->err_status special handling (ticket #1634). Message-ID: details: http://hg.nginx.org/nginx/rev/b64adc956643 branches: changeset: 7355:b64adc956643 user: Maxim Dounin date: Fri Sep 21 15:59:33 2018 +0300 description: Rewrite: removed r->err_status special handling (ticket #1634). Trying to look into r->err_status in the "return" directive makes it behave differently than real errors generated in other parts of the code, and is an endless source of various problems. This behaviour was introduced in 726:7b71936d5299 (0.4.4) with the comment "fix: "return" always overrode "error_page" response code". It is not clear if there were any real cases this was expected to fix, but there are several cases which are broken due to this change, some previously fixed (4147:7f64de1cc2c0). In ticket #1634, the problem is that when r->err_status is set to a non-special status code, it is not possible to return a response by simply returning r->err_status. If this is the case, the only option is to return script's e->status instead. An example configuration: location / { error_page 404 =200 /err502; return 404; } location = /err502 { return 502; } After the change, such a configuration will properly return standard 502 error, much like it happens when a 502 error is generated by proxy_pass. This also fixes the following configuration to properly close connection as clearly requested by "return 444": location / { error_page 404 /close; return 404; } location = /close { return 444; } Previously, this required "error_page 404 = /close;" to work as intended. diffstat: src/http/modules/ngx_http_rewrite_module.c | 10 +--------- 1 files changed, 1 insertions(+), 9 deletions(-) diffs (20 lines): diff --git a/src/http/modules/ngx_http_rewrite_module.c b/src/http/modules/ngx_http_rewrite_module.c --- a/src/http/modules/ngx_http_rewrite_module.c +++ b/src/http/modules/ngx_http_rewrite_module.c @@ -180,15 +180,7 @@ ngx_http_rewrite_handler(ngx_http_reques code(e); } - if (e->status < NGX_HTTP_BAD_REQUEST) { - return e->status; - } - - if (r->err_status == 0) { - return e->status; - } - - return r->err_status; + return e->status; } From gmm at csdoc.com Fri Sep 21 15:35:52 2018 From: gmm at csdoc.com (Gena Makhomed) Date: Fri, 21 Sep 2018 18:35:52 +0300 Subject: [PATCH] Add 307 and 308 to default response codes for xxxxx_cache_valid. In-Reply-To: <20180921125839.GJ56558@mdounin.ru> References: <20180921125839.GJ56558@mdounin.ru> Message-ID: <99493675-b3e3-1ead-134a-52e90132a32a@csdoc.com> On 21.09.2018 15:58, Maxim Dounin wrote: >> Allow 1xx 2xx 3xx 4xx 5xx codes in xxxxx_cache_valid directives. >> >> For example, config fragment >> >> fastcgi_cache_valid 200 201 202 203 204 205 206 207 208 226 5m; >> fastcgi_cache_valid 300 301 302 303 304 305 306 307 308 10m; >> >> now can be rewritten as >> >> fastcgi_cache_valid 2xx 5m; >> fastcgi_cache_valid 3xx 10m; > I cannot say I like this change. Cacheability of various response > codes vary widely, and caching then "in bulk" in most cases is a > bad idea. > > In particular, caching of 201 is always wrong as it is only > expected to be returned to non-cacheable POST and PUT requests. > Caching 206 is wrong unless you use "Ranges" in the cache key. > And caching 304 is wrong unless you've laso included > If-Modified-Since and If-None-Match headers in the cache key. > > As such, I would rather refrain from introducing these shortcuts, > as they looks rather dangerous for the unwary. Ok, thank you for detailed explanation. What about adding 307 and 308 to list of default response codes? # HG changeset patch # User Gena Makhomed # Date 1537543090 -10800 # Fri Sep 21 18:18:10 2018 +0300 # Node ID c23c4b7822ae8b100c0add10d6a334f773f05c80 # Parent 87d2ea860f380dc8418c97c0163412f53c2d008e Add 307 and 308 to default response codes for xxxxx_cache_valid. If only caching time is specified fastcgi_cache_valid 5m; then only 200, 301, 302, 307 and 308 responses are cached. diff -r 87d2ea860f38 -r c23c4b7822ae src/http/ngx_http_file_cache.c --- a/src/http/ngx_http_file_cache.c Mon Sep 10 18:57:39 2018 +0300 +++ b/src/http/ngx_http_file_cache.c Fri Sep 21 18:18:10 2018 +0300 @@ -2624,7 +2624,7 @@ ngx_uint_t i, n; ngx_array_t **a; ngx_http_cache_valid_t *v; - static ngx_uint_t statuses[] = { 200, 301, 302 }; + static ngx_uint_t statuses[] = { 200, 301, 302, 307, 308 }; a = (ngx_array_t **) (p + cmd->offset); @@ -2647,7 +2647,7 @@ if (n == 1) { - for (i = 0; i < 3; i++) { + for (i = 0; i < 5; i++) { v = ngx_array_push(*a); if (v == NULL) { return NGX_CONF_ERROR; @@ -2669,7 +2669,7 @@ } else { status = ngx_atoi(value[i].data, value[i].len); - if (status < 100) { + if (status < 100 || status > 599) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid status \"%V\"", &value[i]); return NGX_CONF_ERROR; From mdounin at mdounin.ru Fri Sep 21 17:32:29 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 21 Sep 2018 17:32:29 +0000 Subject: [nginx] SSL: disabled renegotiation checks with SSL_OP_NO_RENEGOTIATION. Message-ID: details: http://hg.nginx.org/nginx/rev/e3ba4026c02d branches: changeset: 7356:e3ba4026c02d user: Maxim Dounin date: Fri Sep 21 20:31:32 2018 +0300 description: SSL: disabled renegotiation checks with SSL_OP_NO_RENEGOTIATION. Following 7319:dcab86115261, as long as SSL_OP_NO_RENEGOTIATION is defined, it is OpenSSL library responsibility to prevent renegotiation, so the checks are meaningless. Additionally, with TLSv1.3 OpenSSL tends to report SSL_CB_HANDSHAKE_START at various unexpected moments - notably, on KeyUpdate messages and when sending tickets. This change prevents unexpected connection close on KeyUpdate messages and when finishing handshake with upcoming early data changes. diffstat: src/event/ngx_event_openssl.c | 10 ++++++++++ 1 files changed, 10 insertions(+), 0 deletions(-) diffs (55 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -843,6 +843,8 @@ ngx_ssl_info_callback(const ngx_ssl_conn BIO *rbio, *wbio; ngx_connection_t *c; +#ifndef SSL_OP_NO_RENEGOTIATION + if ((where & SSL_CB_HANDSHAKE_START) && SSL_is_server((ngx_ssl_conn_t *) ssl_conn)) { @@ -854,6 +856,8 @@ ngx_ssl_info_callback(const ngx_ssl_conn } } +#endif + if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) { c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); @@ -1391,6 +1395,7 @@ ngx_ssl_handshake(ngx_connection_t *c) c->recv_chain = ngx_ssl_recv_chain; c->send_chain = ngx_ssl_send_chain; +#ifndef SSL_OP_NO_RENEGOTIATION #if OPENSSL_VERSION_NUMBER < 0x10100000L #ifdef SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS @@ -1401,6 +1406,7 @@ ngx_ssl_handshake(ngx_connection_t *c) #endif #endif +#endif return NGX_OK; } @@ -1628,6 +1634,8 @@ ngx_ssl_handle_recv(ngx_connection_t *c, int sslerr; ngx_err_t err; +#ifndef SSL_OP_NO_RENEGOTIATION + if (c->ssl->renegotiation) { /* * disable renegotiation (CVE-2009-3555): @@ -1650,6 +1658,8 @@ ngx_ssl_handle_recv(ngx_connection_t *c, return NGX_ERROR; } +#endif + if (n > 0) { if (c->ssl->saved_write_handler) { From pluknet at nginx.com Fri Sep 21 18:59:38 2018 From: pluknet at nginx.com (Sergey Kandaurov) Date: Fri, 21 Sep 2018 18:59:38 +0000 Subject: [nginx] SSL: support for TLSv1.3 early data with OpenSSL. Message-ID: details: http://hg.nginx.org/nginx/rev/548a63b354a2 branches: changeset: 7357:548a63b354a2 user: Sergey Kandaurov date: Fri Sep 21 20:49:12 2018 +0300 description: SSL: support for TLSv1.3 early data with OpenSSL. In collaboration with Maxim Dounin. diffstat: src/event/ngx_event_openssl.c | 490 ++++++++++++++++++++++++++++++++++++++--- src/event/ngx_event_openssl.h | 5 + 2 files changed, 451 insertions(+), 44 deletions(-) diffs (604 lines): diff -r e3ba4026c02d -r 548a63b354a2 src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c Fri Sep 21 20:31:32 2018 +0300 +++ b/src/event/ngx_event_openssl.c Fri Sep 21 20:49:12 2018 +0300 @@ -26,9 +26,23 @@ static void ngx_ssl_info_callback(const static void ngx_ssl_passwords_cleanup(void *data); static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess); +#ifdef SSL_READ_EARLY_DATA_SUCCESS +static ngx_int_t ngx_ssl_try_early_data(ngx_connection_t *c); +#endif +#if (NGX_DEBUG) +static void ngx_ssl_handshake_log(ngx_connection_t *c); +#endif static void ngx_ssl_handshake_handler(ngx_event_t *ev); +#ifdef SSL_READ_EARLY_DATA_SUCCESS +static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, + size_t size); +#endif static ngx_int_t ngx_ssl_handle_recv(ngx_connection_t *c, int n); static void ngx_ssl_write_handler(ngx_event_t *wev); +#ifdef SSL_READ_EARLY_DATA_SUCCESS +static ssize_t ngx_ssl_write_early(ngx_connection_t *c, u_char *data, + size_t size); +#endif static void ngx_ssl_read_handler(ngx_event_t *rev); static void ngx_ssl_shutdown_handler(ngx_event_t *ev); static void ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, @@ -340,6 +354,10 @@ ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_ SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_COMPRESSION); #endif +#ifdef SSL_OP_NO_ANTI_REPLAY + SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_ANTI_REPLAY); +#endif + #ifdef SSL_MODE_RELEASE_BUFFERS SSL_CTX_set_mode(ssl->ctx, SSL_MODE_RELEASE_BUFFERS); #endif @@ -1185,6 +1203,12 @@ ngx_ssl_early_data(ngx_conf_t *cf, ngx_s SSL_CTX_set_early_data_enabled(ssl->ctx, 1); +#elif defined SSL_READ_EARLY_DATA_SUCCESS + + /* OpenSSL */ + + SSL_CTX_set_max_early_data(ssl->ctx, NGX_SSL_BUFSIZE); + #else ngx_log_error(NGX_LOG_WARN, ssl->log, 0, "\"ssl_early_data\" is not supported on this platform, " @@ -1246,6 +1270,12 @@ ngx_ssl_create_connection(ngx_ssl_t *ssl sc->session_ctx = ssl->ctx; +#ifdef SSL_READ_EARLY_DATA_SUCCESS + if (SSL_CTX_get_max_early_data(ssl->ctx)) { + sc->try_early_data = 1; + } +#endif + sc->connection = SSL_new(ssl->ctx); if (sc->connection == NULL) { @@ -1325,6 +1355,12 @@ ngx_ssl_handshake(ngx_connection_t *c) int n, sslerr; ngx_err_t err; +#ifdef SSL_READ_EARLY_DATA_SUCCESS + if (c->ssl->try_early_data) { + return ngx_ssl_try_early_data(c); + } +#endif + ngx_ssl_clear_error(c->log); n = SSL_do_handshake(c->ssl->connection); @@ -1342,50 +1378,7 @@ ngx_ssl_handshake(ngx_connection_t *c) } #if (NGX_DEBUG) - { - char buf[129], *s, *d; -#if OPENSSL_VERSION_NUMBER >= 0x10000000L - const -#endif - SSL_CIPHER *cipher; - - cipher = SSL_get_current_cipher(c->ssl->connection); - - if (cipher) { - SSL_CIPHER_description(cipher, &buf[1], 128); - - for (s = &buf[1], d = buf; *s; s++) { - if (*s == ' ' && *d == ' ') { - continue; - } - - if (*s == LF || *s == CR) { - continue; - } - - *++d = *s; - } - - if (*d != ' ') { - d++; - } - - *d = '\0'; - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL: %s, cipher: \"%s\"", - SSL_get_version(c->ssl->connection), &buf[1]); - - if (SSL_session_reused(c->ssl->connection)) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL reused session"); - } - - } else { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL no shared ciphers"); - } - } + ngx_ssl_handshake_log(c); #endif c->ssl->handshaked = 1; @@ -1468,6 +1461,173 @@ ngx_ssl_handshake(ngx_connection_t *c) } +#ifdef SSL_READ_EARLY_DATA_SUCCESS + +static ngx_int_t +ngx_ssl_try_early_data(ngx_connection_t *c) +{ + int n, sslerr; + u_char buf; + size_t readbytes; + ngx_err_t err; + + ngx_ssl_clear_error(c->log); + + readbytes = 0; + + n = SSL_read_early_data(c->ssl->connection, &buf, 1, &readbytes); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_read_early_data: %d, %uz", n, readbytes); + + if (n == SSL_READ_EARLY_DATA_FINISH) { + c->ssl->try_early_data = 0; + return ngx_ssl_handshake(c); + } + + if (n == SSL_READ_EARLY_DATA_SUCCESS) { + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + return NGX_ERROR; + } + +#if (NGX_DEBUG) + ngx_ssl_handshake_log(c); +#endif + + c->ssl->try_early_data = 0; + + c->ssl->early_buf = buf; + c->ssl->early_preread = 1; + + c->ssl->handshaked = 1; + c->ssl->in_early = 1; + + c->recv = ngx_ssl_recv; + c->send = ngx_ssl_write; + c->recv_chain = ngx_ssl_recv_chain; + c->send_chain = ngx_ssl_send_chain; + + return NGX_OK; + } + + /* SSL_READ_EARLY_DATA_ERROR */ + + sslerr = SSL_get_error(c->ssl->connection, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); + + if (sslerr == SSL_ERROR_WANT_READ) { + c->read->ready = 0; + c->read->handler = ngx_ssl_handshake_handler; + c->write->handler = ngx_ssl_handshake_handler; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_AGAIN; + } + + if (sslerr == SSL_ERROR_WANT_WRITE) { + c->write->ready = 0; + c->read->handler = ngx_ssl_handshake_handler; + c->write->handler = ngx_ssl_handshake_handler; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_AGAIN; + } + + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + + c->ssl->no_wait_shutdown = 1; + c->ssl->no_send_shutdown = 1; + c->read->eof = 1; + + if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { + ngx_connection_error(c, err, + "peer closed connection in SSL handshake"); + + return NGX_ERROR; + } + + c->read->error = 1; + + ngx_ssl_connection_error(c, sslerr, err, "SSL_read_early_data() failed"); + + return NGX_ERROR; +} + +#endif + + +#if (NGX_DEBUG) + +static void +ngx_ssl_handshake_log(ngx_connection_t *c) +{ + char buf[129], *s, *d; +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + const +#endif + SSL_CIPHER *cipher; + + cipher = SSL_get_current_cipher(c->ssl->connection); + + if (cipher) { + SSL_CIPHER_description(cipher, &buf[1], 128); + + for (s = &buf[1], d = buf; *s; s++) { + if (*s == ' ' && *d == ' ') { + continue; + } + + if (*s == LF || *s == CR) { + continue; + } + + *++d = *s; + } + + if (*d != ' ') { + d++; + } + + *d = '\0'; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL: %s, cipher: \"%s\"", + SSL_get_version(c->ssl->connection), &buf[1]); + + if (SSL_session_reused(c->ssl->connection)) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL reused session"); + } + + } else { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL no shared ciphers"); + } +} + +#endif + + static void ngx_ssl_handshake_handler(ngx_event_t *ev) { @@ -1555,6 +1715,12 @@ ngx_ssl_recv(ngx_connection_t *c, u_char { int n, bytes; +#ifdef SSL_READ_EARLY_DATA_SUCCESS + if (c->ssl->in_early) { + return ngx_ssl_recv_early(c, buf, size); + } +#endif + if (c->ssl->last == NGX_ERROR) { c->read->error = 1; return NGX_ERROR; @@ -1628,6 +1794,123 @@ ngx_ssl_recv(ngx_connection_t *c, u_char } +#ifdef SSL_READ_EARLY_DATA_SUCCESS + +static ssize_t +ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, size_t size) +{ + int n, bytes; + size_t readbytes; + + if (c->ssl->last == NGX_ERROR) { + c->read->error = 1; + return NGX_ERROR; + } + + if (c->ssl->last == NGX_DONE) { + c->read->ready = 0; + c->read->eof = 1; + return 0; + } + + bytes = 0; + + ngx_ssl_clear_error(c->log); + + if (c->ssl->early_preread) { + + if (size == 0) { + c->read->ready = 0; + c->read->eof = 1; + return 0; + } + + *buf = c->ssl->early_buf; + + c->ssl->early_preread = 0; + + bytes = 1; + size -= 1; + buf += 1; + } + + /* + * SSL_read_early_data() may return data in parts, so try to read + * until SSL_read_early_data() would return no data + */ + + for ( ;; ) { + + readbytes = 0; + + n = SSL_read_early_data(c->ssl->connection, buf, size, &readbytes); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_read_early_data: %d, %uz", n, readbytes); + + if (n == SSL_READ_EARLY_DATA_SUCCESS) { + + c->ssl->last = ngx_ssl_handle_recv(c, 1); + + bytes += readbytes; + size -= readbytes; + + if (size == 0) { + c->read->ready = 1; + return bytes; + } + + buf += readbytes; + + continue; + } + + if (n == SSL_READ_EARLY_DATA_FINISH) { + + c->ssl->last = ngx_ssl_handle_recv(c, 1); + c->ssl->in_early = 0; + + if (bytes) { + c->read->ready = 1; + return bytes; + } + + return ngx_ssl_recv(c, buf, size); + } + + /* SSL_READ_EARLY_DATA_ERROR */ + + c->ssl->last = ngx_ssl_handle_recv(c, 0); + + if (bytes) { + if (c->ssl->last != NGX_AGAIN) { + c->read->ready = 1; + } + + return bytes; + } + + switch (c->ssl->last) { + + case NGX_DONE: + c->read->ready = 0; + c->read->eof = 1; + return 0; + + case NGX_ERROR: + c->read->error = 1; + + /* fall through */ + + case NGX_AGAIN: + return c->ssl->last; + } + } +} + +#endif + + static ngx_int_t ngx_ssl_handle_recv(ngx_connection_t *c, int n) { @@ -1923,6 +2206,12 @@ ngx_ssl_write(ngx_connection_t *c, u_cha int n, sslerr; ngx_err_t err; +#ifdef SSL_READ_EARLY_DATA_SUCCESS + if (c->ssl->in_early) { + return ngx_ssl_write_early(c, data, size); + } +#endif + ngx_ssl_clear_error(c->log); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %uz", size); @@ -2010,6 +2299,107 @@ ngx_ssl_write(ngx_connection_t *c, u_cha } +#ifdef SSL_READ_EARLY_DATA_SUCCESS + +ssize_t +ngx_ssl_write_early(ngx_connection_t *c, u_char *data, size_t size) +{ + int n, sslerr; + size_t written; + ngx_err_t err; + + ngx_ssl_clear_error(c->log); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %uz", size); + + written = 0; + + n = SSL_write_early_data(c->ssl->connection, data, size, &written); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_write_early_data: %d, %uz", n, written); + + if (n > 0) { + + if (c->ssl->saved_read_handler) { + + c->read->handler = c->ssl->saved_read_handler; + c->ssl->saved_read_handler = NULL; + c->read->ready = 1; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_post_event(c->read, &ngx_posted_events); + } + + c->sent += written; + + return written; + } + + sslerr = SSL_get_error(c->ssl->connection, n); + + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); + + if (sslerr == SSL_ERROR_WANT_WRITE) { + + if (c->ssl->saved_read_handler) { + + c->read->handler = c->ssl->saved_read_handler; + c->ssl->saved_read_handler = NULL; + c->read->ready = 1; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_post_event(c->read, &ngx_posted_events); + } + + c->write->ready = 0; + return NGX_AGAIN; + } + + if (sslerr == SSL_ERROR_WANT_READ) { + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_write_early_data: want read"); + + c->read->ready = 0; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + /* + * we do not set the timer because there is already + * the write event timer + */ + + if (c->ssl->saved_read_handler == NULL) { + c->ssl->saved_read_handler = c->read->handler; + c->read->handler = ngx_ssl_read_handler; + } + + return NGX_AGAIN; + } + + c->ssl->no_wait_shutdown = 1; + c->ssl->no_send_shutdown = 1; + c->write->error = 1; + + ngx_ssl_connection_error(c, sslerr, err, "SSL_write_early_data() failed"); + + return NGX_ERROR; +} + +#endif + + static void ngx_ssl_read_handler(ngx_event_t *rev) { @@ -3694,9 +4084,21 @@ ngx_ssl_get_early_data(ngx_connection_t s->len = 0; #ifdef SSL_ERROR_EARLY_DATA_REJECTED + + /* BoringSSL */ + if (SSL_in_early_data(c->ssl->connection)) { ngx_str_set(s, "1"); } + +#elif defined SSL_READ_EARLY_DATA_SUCCESS + + /* OpenSSL */ + + if (!SSL_is_init_finished(c->ssl->connection)) { + ngx_str_set(s, "1"); + } + #endif return NGX_OK; diff -r e3ba4026c02d -r 548a63b354a2 src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h Fri Sep 21 20:31:32 2018 +0300 +++ b/src/event/ngx_event_openssl.h Fri Sep 21 20:49:12 2018 +0300 @@ -87,12 +87,17 @@ struct ngx_ssl_connection_s { ngx_event_handler_pt saved_read_handler; ngx_event_handler_pt saved_write_handler; + u_char early_buf; + unsigned handshaked:1; unsigned renegotiation:1; unsigned buffer:1; unsigned no_wait_shutdown:1; unsigned no_send_shutdown:1; unsigned handshake_buffer_set:1; + unsigned try_early_data:1; + unsigned in_early:1; + unsigned early_preread:1; }; From mdounin at mdounin.ru Sat Sep 22 05:59:37 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sat, 22 Sep 2018 05:59:37 +0000 Subject: [nginx] Removed bgcolor attribute on body in error pages and autoindex. Message-ID: details: http://hg.nginx.org/nginx/rev/170922952477 branches: changeset: 7358:170922952477 user: Nova DasSarma date: Wed Sep 19 09:26:47 2018 -0500 description: Removed bgcolor attribute on body in error pages and autoindex. The bgcolor attribute overrides compatibility settings in browsers and leads to undesirable behavior when the default font color is set to white in the browser, since font-color is not also overridden. diffstat: src/http/modules/ngx_http_autoindex_module.c | 2 +- src/http/ngx_http_special_response.c | 68 ++++++++++++++-------------- 2 files changed, 35 insertions(+), 35 deletions(-) diffs (321 lines): diff --git a/src/http/modules/ngx_http_autoindex_module.c b/src/http/modules/ngx_http_autoindex_module.c --- a/src/http/modules/ngx_http_autoindex_module.c +++ b/src/http/modules/ngx_http_autoindex_module.c @@ -452,7 +452,7 @@ ngx_http_autoindex_html(ngx_http_request static u_char header[] = "" CRLF - "" CRLF + "" CRLF "

Index of " ; diff --git a/src/http/ngx_http_special_response.c b/src/http/ngx_http_special_response.c --- a/src/http/ngx_http_special_response.c +++ b/src/http/ngx_http_special_response.c @@ -60,7 +60,7 @@ static u_char ngx_http_msie_refresh_tail static char ngx_http_error_301_page[] = "" CRLF "301 Moved Permanently" CRLF -"" CRLF +"" CRLF "

301 Moved Permanently

" CRLF ; @@ -68,7 +68,7 @@ static char ngx_http_error_301_page[] = static char ngx_http_error_302_page[] = "" CRLF "302 Found" CRLF -"" CRLF +"" CRLF "

302 Found

" CRLF ; @@ -76,7 +76,7 @@ static char ngx_http_error_302_page[] = static char ngx_http_error_303_page[] = "" CRLF "303 See Other" CRLF -"" CRLF +"" CRLF "

303 See Other

" CRLF ; @@ -84,7 +84,7 @@ static char ngx_http_error_303_page[] = static char ngx_http_error_307_page[] = "" CRLF "307 Temporary Redirect" CRLF -"" CRLF +"" CRLF "

307 Temporary Redirect

" CRLF ; @@ -92,7 +92,7 @@ static char ngx_http_error_307_page[] = static char ngx_http_error_308_page[] = "" CRLF "308 Permanent Redirect" CRLF -"" CRLF +"" CRLF "

308 Permanent Redirect

" CRLF ; @@ -100,7 +100,7 @@ static char ngx_http_error_308_page[] = static char ngx_http_error_400_page[] = "" CRLF "400 Bad Request" CRLF -"" CRLF +"" CRLF "

400 Bad Request

" CRLF ; @@ -108,7 +108,7 @@ static char ngx_http_error_400_page[] = static char ngx_http_error_401_page[] = "" CRLF "401 Authorization Required" CRLF -"" CRLF +"" CRLF "

401 Authorization Required

" CRLF ; @@ -116,7 +116,7 @@ static char ngx_http_error_401_page[] = static char ngx_http_error_402_page[] = "" CRLF "402 Payment Required" CRLF -"" CRLF +"" CRLF "

402 Payment Required

" CRLF ; @@ -124,7 +124,7 @@ static char ngx_http_error_402_page[] = static char ngx_http_error_403_page[] = "" CRLF "403 Forbidden" CRLF -"" CRLF +"" CRLF "

403 Forbidden

" CRLF ; @@ -132,7 +132,7 @@ static char ngx_http_error_403_page[] = static char ngx_http_error_404_page[] = "" CRLF "404 Not Found" CRLF -"" CRLF +"" CRLF "

404 Not Found

" CRLF ; @@ -140,7 +140,7 @@ static char ngx_http_error_404_page[] = static char ngx_http_error_405_page[] = "" CRLF "405 Not Allowed" CRLF -"" CRLF +"" CRLF "

405 Not Allowed

" CRLF ; @@ -148,7 +148,7 @@ static char ngx_http_error_405_page[] = static char ngx_http_error_406_page[] = "" CRLF "406 Not Acceptable" CRLF -"" CRLF +"" CRLF "

406 Not Acceptable

" CRLF ; @@ -156,7 +156,7 @@ static char ngx_http_error_406_page[] = static char ngx_http_error_408_page[] = "" CRLF "408 Request Time-out" CRLF -"" CRLF +"" CRLF "

408 Request Time-out

" CRLF ; @@ -164,7 +164,7 @@ static char ngx_http_error_408_page[] = static char ngx_http_error_409_page[] = "" CRLF "409 Conflict" CRLF -"" CRLF +"" CRLF "

409 Conflict

" CRLF ; @@ -172,7 +172,7 @@ static char ngx_http_error_409_page[] = static char ngx_http_error_410_page[] = "" CRLF "410 Gone" CRLF -"" CRLF +"" CRLF "

410 Gone

" CRLF ; @@ -180,7 +180,7 @@ static char ngx_http_error_410_page[] = static char ngx_http_error_411_page[] = "" CRLF "411 Length Required" CRLF -"" CRLF +"" CRLF "

411 Length Required

" CRLF ; @@ -188,7 +188,7 @@ static char ngx_http_error_411_page[] = static char ngx_http_error_412_page[] = "" CRLF "412 Precondition Failed" CRLF -"" CRLF +"" CRLF "

412 Precondition Failed

" CRLF ; @@ -196,7 +196,7 @@ static char ngx_http_error_412_page[] = static char ngx_http_error_413_page[] = "" CRLF "413 Request Entity Too Large" CRLF -"" CRLF +"" CRLF "

413 Request Entity Too Large

" CRLF ; @@ -204,7 +204,7 @@ static char ngx_http_error_413_page[] = static char ngx_http_error_414_page[] = "" CRLF "414 Request-URI Too Large" CRLF -"" CRLF +"" CRLF "

414 Request-URI Too Large

" CRLF ; @@ -212,7 +212,7 @@ static char ngx_http_error_414_page[] = static char ngx_http_error_415_page[] = "" CRLF "415 Unsupported Media Type" CRLF -"" CRLF +"" CRLF "

415 Unsupported Media Type

" CRLF ; @@ -220,7 +220,7 @@ static char ngx_http_error_415_page[] = static char ngx_http_error_416_page[] = "" CRLF "416 Requested Range Not Satisfiable" CRLF -"" CRLF +"" CRLF "

416 Requested Range Not Satisfiable

" CRLF ; @@ -228,7 +228,7 @@ static char ngx_http_error_416_page[] = static char ngx_http_error_421_page[] = "" CRLF "421 Misdirected Request" CRLF -"" CRLF +"" CRLF "

421 Misdirected Request

" CRLF ; @@ -236,7 +236,7 @@ static char ngx_http_error_421_page[] = static char ngx_http_error_429_page[] = "" CRLF "429 Too Many Requests" CRLF -"" CRLF +"" CRLF "

429 Too Many Requests

" CRLF ; @@ -245,7 +245,7 @@ static char ngx_http_error_494_page[] = "" CRLF "400 Request Header Or Cookie Too Large" CRLF -"" CRLF +"" CRLF "

400 Bad Request

" CRLF "
Request Header Or Cookie Too Large
" CRLF ; @@ -255,7 +255,7 @@ static char ngx_http_error_495_page[] = "" CRLF "400 The SSL certificate error" CRLF -"" CRLF +"" CRLF "

400 Bad Request

" CRLF "
The SSL certificate error
" CRLF ; @@ -265,7 +265,7 @@ static char ngx_http_error_496_page[] = "" CRLF "400 No required SSL certificate was sent" CRLF -"" CRLF +"" CRLF "

400 Bad Request

" CRLF "
No required SSL certificate was sent
" CRLF ; @@ -275,7 +275,7 @@ static char ngx_http_error_497_page[] = "" CRLF "400 The plain HTTP request was sent to HTTPS port" CRLF -"" CRLF +"" CRLF "

400 Bad Request

" CRLF "
The plain HTTP request was sent to HTTPS port
" CRLF ; @@ -284,7 +284,7 @@ CRLF static char ngx_http_error_500_page[] = "" CRLF "500 Internal Server Error" CRLF -"" CRLF +"" CRLF "

500 Internal Server Error

" CRLF ; @@ -292,7 +292,7 @@ static char ngx_http_error_500_page[] = static char ngx_http_error_501_page[] = "" CRLF "501 Not Implemented" CRLF -"" CRLF +"" CRLF "

501 Not Implemented

" CRLF ; @@ -300,7 +300,7 @@ static char ngx_http_error_501_page[] = static char ngx_http_error_502_page[] = "" CRLF "502 Bad Gateway" CRLF -"" CRLF +"" CRLF "

502 Bad Gateway

" CRLF ; @@ -308,7 +308,7 @@ static char ngx_http_error_502_page[] = static char ngx_http_error_503_page[] = "" CRLF "503 Service Temporarily Unavailable" CRLF -"" CRLF +"" CRLF "

503 Service Temporarily Unavailable

" CRLF ; @@ -316,7 +316,7 @@ static char ngx_http_error_503_page[] = static char ngx_http_error_504_page[] = "" CRLF "504 Gateway Time-out" CRLF -"" CRLF +"" CRLF "

504 Gateway Time-out

" CRLF ; @@ -324,7 +324,7 @@ static char ngx_http_error_504_page[] = static char ngx_http_error_505_page[] = "" CRLF "505 HTTP Version Not Supported" CRLF -"" CRLF +"" CRLF "

505 HTTP Version Not Supported

" CRLF ; @@ -332,7 +332,7 @@ static char ngx_http_error_505_page[] = static char ngx_http_error_507_page[] = "" CRLF "507 Insufficient Storage" CRLF -"" CRLF +"" CRLF "

507 Insufficient Storage

" CRLF ; From mdounin at mdounin.ru Sat Sep 22 06:00:52 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Sat, 22 Sep 2018 09:00:52 +0300 Subject: [PATCH] HTTP Special Page backgrounds In-Reply-To: References: Message-ID: <20180922060052.GS56558@mdounin.ru> Hello! On Wed, Sep 19, 2018 at 10:41:23AM -0500, Nova wrote: > TZAG, > > I've run into a compatability issue between Nginx error/special response > pages and customized font color and background color in > user stylesheets. I use white on black as my default color scheme, and > since the Nginx special pages specify bgcolor but not color > (font color), this leads to a "blank" white page (with a hr on it). The > attached patch presented for review removes Nginx bgcolor > on special pages, since this is not necessary to render them as intended > without interfering with accessibility settings. > > Best, > > Nova > # HG changeset patch > # User Nova DasSarma > # Date 1537367207 18000 > # Wed Sep 19 09:26:47 2018 -0500 > # Node ID f359ea07f1d61c284fb54706c881120c3e4628c0 > # Parent 87d2ea860f380dc8418c97c0163412f53c2d008e > HTTP Special Responses: removed bgcolor attribute on body. > > bgcolor overrides compatibility settings in browsers and leads to undesirable behavior when the default font color is set to white in the browser, since font-color is not also overridden. [...] Thanks for the patch. I've extended it on the autoindex module where the same problem occurs, and committed it with some minor changes to commit log: http://hg.nginx.org/nginx/rev/170922952477 -- Maxim Dounin http://mdounin.ru/ From elsascarlet at outlook.com Mon Sep 24 08:44:15 2018 From: elsascarlet at outlook.com (Elza Scarlet) Date: Mon, 24 Sep 2018 08:44:15 +0000 Subject: [nginx] Upstream: enable hash balancing method support parameter "backup". Message-ID: Dear Development Team, Thanks all of you make nginx so nice to use. We have a use case, we use hash balancing method to increasing service's cache hit rate, but it doesn't support "backup" parameter, which can be use to make doing online backup server easier. After reading the source code, we found it's very easy to support "backup" parameter when using hash balancing method, so we submit this changes. Hope you can accept this changes. Kind regards, Scarlet -------------- next part -------------- A non-text attachment was scrubbed... Name: 7359.patch Type: application/octet-stream Size: 2495 bytes Desc: 7359.patch URL: From ru at nginx.com Mon Sep 24 11:47:24 2018 From: ru at nginx.com (Ruslan Ermilov) Date: Mon, 24 Sep 2018 14:47:24 +0300 Subject: [nginx] Upstream: enable hash balancing method support parameter "backup". In-Reply-To: References: Message-ID: <20180924114723.GD40268@lo0.su> On Mon, Sep 24, 2018 at 08:44:15AM +0000, Elza Scarlet wrote: > Dear Development Team, > > Thanks all of you make nginx so nice to use. We have a use case, we use hash > balancing method to increasing service's cache hit rate, but it doesn't > support "backup" parameter, which can be use to make doing online backup > server easier. After reading the source code, we found it's very easy to > support "backup" parameter when using hash balancing method, so we submit > this changes. Hope you can accept this changes. > > Kind regards, Scarlet Generally, hash methods don't support the "backup" parameter, but for those who need backup when falling back to round robin, there's a work around: put the "hash" directive after the "server" directives in the "upstream" block. From elsascarlet at outlook.com Mon Sep 24 12:17:34 2018 From: elsascarlet at outlook.com (Elza Scarlet) Date: Mon, 24 Sep 2018 12:17:34 +0000 Subject: [nginx] Upstream: enable hash balancing method support parameter "backup". In-Reply-To: <20180924114723.GD40268@lo0.su> References: , <20180924114723.GD40268@lo0.su> Message-ID: Got it. Thanks a lot. Kind regards, Scarlet From: nginx-devel on behalf of Ruslan Ermilov Sent: September 24, 2018 6:47 AM To: nginx-devel at nginx.org Subject: Re: [nginx] Upstream: enable hash balancing method support parameter "backup". ? On Mon, Sep 24, 2018 at 08:44:15AM +0000, Elza Scarlet wrote: > Dear Development Team, > > Thanks all of you make nginx so nice to use. We have a use case, we use hash > balancing method to increasing service's cache hit rate, but it doesn't > support "backup" parameter,? which can be use to make doing online backup > server easier. After reading the source code, we found it's very easy to > support "backup" parameter when using hash balancing method, so we submit > this changes. Hope you can accept this changes. > > Kind regards, Scarlet Generally, hash methods don't support the "backup" parameter, but for those who need backup when falling back to round robin, there's a work around: put the "hash" directive after the "server" directives in the "upstream" block. _______________________________________________ nginx-devel mailing list nginx-devel at nginx.org http://mailman.nginx.org/mailman/listinfo/nginx-devel From mdounin at mdounin.ru Mon Sep 24 16:25:21 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 24 Sep 2018 19:25:21 +0300 Subject: [PATCH] Add 307 and 308 to default response codes for xxxxx_cache_valid. In-Reply-To: <99493675-b3e3-1ead-134a-52e90132a32a@csdoc.com> References: <20180921125839.GJ56558@mdounin.ru> <99493675-b3e3-1ead-134a-52e90132a32a@csdoc.com> Message-ID: <20180924162520.GZ56558@mdounin.ru> Hello! On Fri, Sep 21, 2018 at 06:35:52PM +0300, Gena Makhomed wrote: > On 21.09.2018 15:58, Maxim Dounin wrote: > > >> Allow 1xx 2xx 3xx 4xx 5xx codes in xxxxx_cache_valid directives. > >> > >> For example, config fragment > >> > >> fastcgi_cache_valid 200 201 202 203 204 205 206 207 208 226 5m; > >> fastcgi_cache_valid 300 301 302 303 304 305 306 307 308 10m; > >> > >> now can be rewritten as > >> > >> fastcgi_cache_valid 2xx 5m; > >> fastcgi_cache_valid 3xx 10m; > > > I cannot say I like this change. Cacheability of various response > > codes vary widely, and caching then "in bulk" in most cases is a > > bad idea. > > > > In particular, caching of 201 is always wrong as it is only > > expected to be returned to non-cacheable POST and PUT requests. > > Caching 206 is wrong unless you use "Ranges" in the cache key. > > And caching 304 is wrong unless you've laso included > > If-Modified-Since and If-None-Match headers in the cache key. > > > > As such, I would rather refrain from introducing these shortcuts, > > as they looks rather dangerous for the unwary. > > Ok, thank you for detailed explanation. > > What about adding 307 and 308 to list of default response codes? I'm not a fan of the "proxy_cache_valid 5m" form for the very same reasons, and would rather avoid changing it. -- Maxim Dounin http://mdounin.ru/ From gmm at csdoc.com Mon Sep 24 17:34:37 2018 From: gmm at csdoc.com (Gena Makhomed) Date: Mon, 24 Sep 2018 20:34:37 +0300 Subject: [PATCH] Cache: status must be less then 599 in *_cache_valid directives. In-Reply-To: <20180924162520.GZ56558@mdounin.ru> References: <20180921125839.GJ56558@mdounin.ru> <99493675-b3e3-1ead-134a-52e90132a32a@csdoc.com> <20180924162520.GZ56558@mdounin.ru> Message-ID: <2e5610b5-d75a-e50e-603f-20f4007dbbb5@csdoc.com> On 24.09.2018 19:25, Maxim Dounin wrote: >> What about adding 307 and 308 to list of default response codes? > I'm not a fan of the "proxy_cache_valid 5m" form for the very same > reasons, and would rather avoid changing it. Ok. What about more strict check for invalid status codes? # HG changeset patch # User Gena Makhomed # Date 1537810006 -10800 # Mon Sep 24 20:26:46 2018 +0300 # Node ID fc6c7e03edaad907d6a85afab009cb5c1fa43c56 # Parent 17092295247709a533acca09f990c13337a24948 Cache: status must be less then 599 in *_cache_valid directives. Previously, configurations with typo, for example fastcgi_cache_valid 200301 302 5m; successfully pass configuration test. Adding check for status codes > 599, and such configurations are now properly rejected. diff -r 170922952477 -r fc6c7e03edaa src/http/ngx_http_file_cache.c --- a/src/http/ngx_http_file_cache.c Wed Sep 19 09:26:47 2018 -0500 +++ b/src/http/ngx_http_file_cache.c Mon Sep 24 20:26:46 2018 +0300 @@ -2669,7 +2669,7 @@ } else { status = ngx_atoi(value[i].data, value[i].len); - if (status < 100) { + if (status < 100 || status > 599) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid status \"%V\"", &value[i]); return NGX_CONF_ERROR; From mdounin at mdounin.ru Mon Sep 24 18:00:01 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 24 Sep 2018 21:00:01 +0300 Subject: [PATCH] Cache: status must be less then 599 in *_cache_valid directives. In-Reply-To: <2e5610b5-d75a-e50e-603f-20f4007dbbb5@csdoc.com> References: <20180921125839.GJ56558@mdounin.ru> <99493675-b3e3-1ead-134a-52e90132a32a@csdoc.com> <20180924162520.GZ56558@mdounin.ru> <2e5610b5-d75a-e50e-603f-20f4007dbbb5@csdoc.com> Message-ID: <20180924180000.GB56558@mdounin.ru> Hello! On Mon, Sep 24, 2018 at 08:34:37PM +0300, Gena Makhomed wrote: > On 24.09.2018 19:25, Maxim Dounin wrote: > > >> What about adding 307 and 308 to list of default response codes? > > > I'm not a fan of the "proxy_cache_valid 5m" form for the very same > > reasons, and would rather avoid changing it. > > Ok. What about more strict check for invalid status codes? > > # HG changeset patch > # User Gena Makhomed > # Date 1537810006 -10800 > # Mon Sep 24 20:26:46 2018 +0300 > # Node ID fc6c7e03edaad907d6a85afab009cb5c1fa43c56 > # Parent 17092295247709a533acca09f990c13337a24948 > Cache: status must be less then 599 in *_cache_valid directives. > > Previously, configurations with typo, for example > > fastcgi_cache_valid 200301 302 5m; > > successfully pass configuration test. Adding check for status > codes > 599, and such configurations are now properly rejected. Have you seen such configurations in the real life, or it is something made-up while looking at the code? > > diff -r 170922952477 -r fc6c7e03edaa src/http/ngx_http_file_cache.c > --- a/src/http/ngx_http_file_cache.c Wed Sep 19 09:26:47 2018 -0500 > +++ b/src/http/ngx_http_file_cache.c Mon Sep 24 20:26:46 2018 +0300 > @@ -2669,7 +2669,7 @@ > } else { > > status = ngx_atoi(value[i].data, value[i].len); > - if (status < 100) { > + if (status < 100 || status > 599) { > ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, > "invalid status \"%V\"", &value[i]); > return NGX_CONF_ERROR; > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > http://mailman.nginx.org/mailman/listinfo/nginx-devel -- Maxim Dounin http://mdounin.ru/ From gmm at csdoc.com Mon Sep 24 19:10:08 2018 From: gmm at csdoc.com (Gena Makhomed) Date: Mon, 24 Sep 2018 22:10:08 +0300 Subject: [PATCH] Cache: status must be less then 599 in *_cache_valid directives. In-Reply-To: <20180924180000.GB56558@mdounin.ru> References: <20180921125839.GJ56558@mdounin.ru> <99493675-b3e3-1ead-134a-52e90132a32a@csdoc.com> <20180924162520.GZ56558@mdounin.ru> <2e5610b5-d75a-e50e-603f-20f4007dbbb5@csdoc.com> <20180924180000.GB56558@mdounin.ru> Message-ID: On 24.09.2018 21:00, Maxim Dounin wrote: >> # HG changeset patch >> # User Gena Makhomed >> # Date 1537810006 -10800 >> # Mon Sep 24 20:26:46 2018 +0300 >> # Node ID fc6c7e03edaad907d6a85afab009cb5c1fa43c56 >> # Parent 17092295247709a533acca09f990c13337a24948 >> Cache: status must be less then 599 in *_cache_valid directives. >> >> Previously, configurations with typo, for example >> >> fastcgi_cache_valid 200301 302 5m; >> >> successfully pass configuration test. Adding check for status >> codes > 599, and such configurations are now properly rejected. > Have you seen such configurations in the real life, or it is > something made-up while looking at the code? While looking at the code and your patch http://hg.nginx.org/nginx/rev/7c614ef3c6ea Did you see in the real life configurations need for *_cache_valid status codes > 599 ? >> diff -r 170922952477 -r fc6c7e03edaa src/http/ngx_http_file_cache.c >> --- a/src/http/ngx_http_file_cache.c Wed Sep 19 09:26:47 2018 -0500 >> +++ b/src/http/ngx_http_file_cache.c Mon Sep 24 20:26:46 2018 +0300 >> @@ -2669,7 +2669,7 @@ >> } else { >> >> status = ngx_atoi(value[i].data, value[i].len); >> - if (status < 100) { >> + if (status < 100 || status > 599) { >> ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, >> "invalid status \"%V\"", &value[i]); >> return NGX_CONF_ERROR; -- Best regards, Gena From mdounin at mdounin.ru Mon Sep 24 23:32:17 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Mon, 24 Sep 2018 23:32:17 +0000 Subject: [nginx] Cache: status must be less then 599 in *_cache_valid directives. Message-ID: details: http://hg.nginx.org/nginx/rev/b5ea47df9bee branches: changeset: 7359:b5ea47df9bee user: Gena Makhomed date: Mon Sep 24 20:26:46 2018 +0300 description: Cache: status must be less then 599 in *_cache_valid directives. Previously, configurations with typo, for example fastcgi_cache_valid 200301 302 5m; successfully pass configuration test. Adding check for status codes > 599, and such configurations are now properly rejected. diffstat: src/http/ngx_http_file_cache.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff --git a/src/http/ngx_http_file_cache.c b/src/http/ngx_http_file_cache.c --- a/src/http/ngx_http_file_cache.c +++ b/src/http/ngx_http_file_cache.c @@ -2669,7 +2669,7 @@ ngx_http_file_cache_valid_set_slot(ngx_c } else { status = ngx_atoi(value[i].data, value[i].len); - if (status < 100) { + if (status < 100 || status > 599) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid status \"%V\"", &value[i]); return NGX_CONF_ERROR; From mdounin at mdounin.ru Mon Sep 24 23:33:56 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 25 Sep 2018 02:33:56 +0300 Subject: [PATCH] Cache: status must be less then 599 in *_cache_valid directives. In-Reply-To: References: <20180921125839.GJ56558@mdounin.ru> <99493675-b3e3-1ead-134a-52e90132a32a@csdoc.com> <20180924162520.GZ56558@mdounin.ru> <2e5610b5-d75a-e50e-603f-20f4007dbbb5@csdoc.com> <20180924180000.GB56558@mdounin.ru> Message-ID: <20180924233356.GD56558@mdounin.ru> Hello! On Mon, Sep 24, 2018 at 10:10:08PM +0300, Gena Makhomed wrote: > On 24.09.2018 21:00, Maxim Dounin wrote: > > >> # HG changeset patch > >> # User Gena Makhomed > >> # Date 1537810006 -10800 > >> # Mon Sep 24 20:26:46 2018 +0300 > >> # Node ID fc6c7e03edaad907d6a85afab009cb5c1fa43c56 > >> # Parent 17092295247709a533acca09f990c13337a24948 > >> Cache: status must be less then 599 in *_cache_valid directives. > >> > >> Previously, configurations with typo, for example > >> > >> fastcgi_cache_valid 200301 302 5m; > >> > >> successfully pass configuration test. Adding check for status > >> codes > 599, and such configurations are now properly rejected. > > > Have you seen such configurations in the real life, or it is > > something made-up while looking at the code? > > While looking at the code and your patch > http://hg.nginx.org/nginx/rev/7c614ef3c6ea Note that the commit in question refers to the real-life usage, "proxy_cache_valid 2xx 30s;" is an excerpt from a real config. > Did you see in the real life configurations > need for *_cache_valid status codes > 599 ? I've seen configurations where status codes > 599 were actually used in production, see here for an example: http://mailman.nginx.org/pipermail/nginx-ru/2016-June/058317.html On the other hand, I don't really think that such configurations are worth considering, they are clearly violation HTTP specification. I've committed your patch, thanks. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Tue Sep 25 13:13:09 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 25 Sep 2018 13:13:09 +0000 Subject: [nginx] SSL: logging level of "no suitable key share". Message-ID: details: http://hg.nginx.org/nginx/rev/8f25a44d9add branches: changeset: 7360:8f25a44d9add user: Maxim Dounin date: Tue Sep 25 13:59:53 2018 +0300 description: SSL: logging level of "no suitable key share". The "no suitable key share" errors are reported by OpenSSL 1.1.1 when using TLSv1.3 if there are no shared groups (that is, elliptic curves). In particular, it is easy enough to trigger by using only a single curve in ssl_ecdh_curve: ssl_ecdh_curve secp384r1; and using a different curve in the client: openssl s_client -connect 127.0.0.1:443 -curves prime256v1 On the client side it is seen as "sslv3 alert handshake failure", "SSL alert number 40": 0:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:ssl/record/rec_layer_s3.c:1528:SSL alert number 40 It can be also triggered with default ssl_ecdh_curve by using a curve which is not in the default list (X25519, prime256v1, X448, secp521r1, secp384r1): openssl s_client -connect 127.0.0.1:8443 -curves brainpoolP512r1 Given that many clients hardcode prime256v1, these errors might become a common problem with TLSv1.3 if ssl_ecdh_curve is redefined. Previously this resulted in not using ECDH with such clients, but with TLSv1.3 it is no longer possible and will result in a handshake failure. The SSL_R_NO_SHARED_GROUP error is what BoringSSL returns in the same situation. Seen at: https://serverfault.com/questions/932102/nginx-ssl-handshake-error-no-suitable-key-share diffstat: src/event/ngx_event_openssl.c | 6 ++++++ 1 files changed, 6 insertions(+), 0 deletions(-) diffs (23 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2585,6 +2585,9 @@ ngx_ssl_connection_error(ngx_connection_ /* handshake failures */ if (n == SSL_R_BAD_CHANGE_CIPHER_SPEC /* 103 */ +#ifdef SSL_R_NO_SUITABLE_KEY_SHARE + || n == SSL_R_NO_SUITABLE_KEY_SHARE /* 101 */ +#endif || n == SSL_R_BLOCK_CIPHER_PAD_IS_WRONG /* 129 */ || n == SSL_R_DIGEST_CHECK_FAILED /* 149 */ || n == SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST /* 151 */ @@ -2607,6 +2610,9 @@ ngx_ssl_connection_error(ngx_connection_ || n == SSL_R_UNKNOWN_ALERT_TYPE /* 246 */ || n == SSL_R_UNKNOWN_PROTOCOL /* 252 */ || n == SSL_R_UNSUPPORTED_PROTOCOL /* 258 */ +#ifdef SSL_R_NO_SHARED_GROUP + || n == SSL_R_NO_SHARED_GROUP /* 266 */ +#endif || n == SSL_R_WRONG_VERSION_NUMBER /* 267 */ || n == SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC /* 281 */ #ifdef SSL_R_RENEGOTIATE_EXT_TOO_LONG From mdounin at mdounin.ru Tue Sep 25 13:13:11 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 25 Sep 2018 13:13:11 +0000 Subject: [nginx] SSL: logging level of "no suitable signature algorithm". Message-ID: details: http://hg.nginx.org/nginx/rev/c09c7d47acb9 branches: changeset: 7361:c09c7d47acb9 user: Maxim Dounin date: Tue Sep 25 14:00:04 2018 +0300 description: SSL: logging level of "no suitable signature algorithm". The "no suitable signature algorithm" errors are reported by OpenSSL 1.1.1 when using TLSv1.3 if there are no shared signature algorithms. In particular, this can happen if the client limits available signature algorithms to something we don't have a certificate for, or to an empty list. For example, the following command: openssl s_client -connect 127.0.0.1:8443 -sigalgs rsa_pkcs1_sha1 will always result in the "no suitable signature algorithm" error as the "rsa_pkcs1_sha1" algorithm refers solely to signatures which appear in certificates and not defined for use in TLS 1.3 handshake messages. The SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS error is what BoringSSL returns in the same situation. diffstat: src/event/ngx_event_openssl.c | 6 ++++++ 1 files changed, 6 insertions(+), 0 deletions(-) diffs (23 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2588,6 +2588,9 @@ ngx_ssl_connection_error(ngx_connection_ #ifdef SSL_R_NO_SUITABLE_KEY_SHARE || n == SSL_R_NO_SUITABLE_KEY_SHARE /* 101 */ #endif +#ifdef SSL_R_NO_SUITABLE_SIGNATURE_ALGORITHM + || n == SSL_R_NO_SUITABLE_SIGNATURE_ALGORITHM /* 118 */ +#endif || n == SSL_R_BLOCK_CIPHER_PAD_IS_WRONG /* 129 */ || n == SSL_R_DIGEST_CHECK_FAILED /* 149 */ || n == SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST /* 151 */ @@ -2609,6 +2612,9 @@ ngx_ssl_connection_error(ngx_connection_ || n == SSL_R_UNEXPECTED_RECORD /* 245 */ || n == SSL_R_UNKNOWN_ALERT_TYPE /* 246 */ || n == SSL_R_UNKNOWN_PROTOCOL /* 252 */ +#ifdef SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS + || n == SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS /* 253 */ +#endif || n == SSL_R_UNSUPPORTED_PROTOCOL /* 258 */ #ifdef SSL_R_NO_SHARED_GROUP || n == SSL_R_NO_SHARED_GROUP /* 266 */ From mdounin at mdounin.ru Tue Sep 25 15:18:09 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 25 Sep 2018 15:18:09 +0000 Subject: [nginx] nginx-1.15.4-RELEASE Message-ID: details: http://hg.nginx.org/nginx/rev/49d498356538 branches: changeset: 7362:49d498356538 user: Maxim Dounin date: Tue Sep 25 18:11:39 2018 +0300 description: nginx-1.15.4-RELEASE diffstat: docs/xml/nginx/changes.xml | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 90 insertions(+), 0 deletions(-) diffs (100 lines): diff --git a/docs/xml/nginx/changes.xml b/docs/xml/nginx/changes.xml --- a/docs/xml/nginx/changes.xml +++ b/docs/xml/nginx/changes.xml @@ -5,6 +5,96 @@ + + + + +?????? ????????? ssl_early_data ????? ???????????? ? OpenSSL. + + +now the "ssl_early_data" directive can be used with OpenSSL. + + + + + +? ?????? ngx_http_uwsgi_module.
+??????? Chris Caputo. +
+ +in the ngx_http_uwsgi_module.
+Thanks to Chris Caputo. +
+
+ + + +?????????? ? ????????? gRPC-???????? ????? ?? ???????????? +??? ????????????? ????????? keepalive. + + +connections with some gRPC backends might not be cached +when using the "keepalive" directive. + + + + + +??? ????????????? ????????? error_page ??? ??????????????? ??????, +??????????? ?? ?????? ?????? ????????? ???????, +? ????????? ?????? ? ????? 400, +????? ??????????? ?????? ???????. + + +a socket leak might occur +when using the "error_page" directive +to redirect early request processing errors, +notably errors with code 400. + + + + + +????????? return ??? ???????? ?????? ?? ???????? ??? ??????, +???? ?????? ??? ????????????? ? ??????? ????????? error_page. + + +the "return" directive did not change the response code when returning errors +if the request was redirected by the "error_page" directive. + + + + + +??????????? ????????? ?? ??????? ? ?????? ?????? ngx_http_autoindex_module +????????? ??????? bgcolor, ??? ????? ????????? ? ?? ????????????? ??????????? +??? ????????????? ???????????????? ???????? ?????? ? ?????????.
+??????? Nova DasSarma. +
+ +standard error pages and responses of the ngx_http_autoindex_module module +used the "bgcolor" attribute, and might be displayed incorrectly when using +custom color settings in browsers.
+Thanks to Nova DasSarma. +
+
+ + + +??????? ???????????? ?????? SSL "no suitable key share" ? +"no suitable signature algorithm" +??????? ? ?????? crit ?? info. + + +the logging level of the "no suitable key share" and +"no suitable signature algorithm" SSL errors +has been lowered from "crit" to "info". + + + +
+ + From mdounin at mdounin.ru Tue Sep 25 15:18:11 2018 From: mdounin at mdounin.ru (Maxim Dounin) Date: Tue, 25 Sep 2018 15:18:11 +0000 Subject: [nginx] release-1.15.4 tag Message-ID: details: http://hg.nginx.org/nginx/rev/dcd9303b0d73 branches: changeset: 7363:dcd9303b0d73 user: Maxim Dounin date: Tue Sep 25 18:11:39 2018 +0300 description: release-1.15.4 tag diffstat: .hgtags | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (8 lines): diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -429,3 +429,4 @@ 990b3e885636d763b97ed02d0d2cfc161a4e0c09 4189160cb946bb38d0bc0a452b5eb4cdd8979fb5 release-1.15.1 b234199c7ed8a156a6bb98f7ff58302c857c954f release-1.15.2 28b3e17ca7eba1e6a0891afde0e4bc5bcc99c861 release-1.15.3 +49d49835653857daa418e68d6cbfed4958c78fca release-1.15.4 From ru at nginx.com Thu Sep 27 10:43:19 2018 From: ru at nginx.com (Ruslan Ermilov) Date: Thu, 27 Sep 2018 10:43:19 +0000 Subject: [nginx] Version bump. Message-ID: details: http://hg.nginx.org/nginx/rev/5fa22beeaf11 branches: changeset: 7364:5fa22beeaf11 user: Ruslan Ermilov date: Thu Sep 27 13:05:39 2018 +0300 description: Version bump. diffstat: src/core/nginx.h | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (14 lines): diff -r dcd9303b0d73 -r 5fa22beeaf11 src/core/nginx.h --- a/src/core/nginx.h Tue Sep 25 18:11:39 2018 +0300 +++ b/src/core/nginx.h Thu Sep 27 13:05:39 2018 +0300 @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1015004 -#define NGINX_VERSION "1.15.4" +#define nginx_version 1015005 +#define NGINX_VERSION "1.15.5" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD From ru at nginx.com Thu Sep 27 10:43:20 2018 From: ru at nginx.com (Ruslan Ermilov) Date: Thu, 27 Sep 2018 10:43:20 +0000 Subject: [nginx] SSL: fixed unlocked access to sess_id->len. Message-ID: details: http://hg.nginx.org/nginx/rev/cd4fa2fab8d8 branches: changeset: 7365:cd4fa2fab8d8 user: Ruslan Ermilov date: Tue Sep 25 14:07:59 2018 +0300 description: SSL: fixed unlocked access to sess_id->len. diffstat: src/event/ngx_event_openssl.c | 7 +++++-- 1 files changed, 5 insertions(+), 2 deletions(-) diffs (28 lines): diff -r 5fa22beeaf11 -r cd4fa2fab8d8 src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c Thu Sep 27 13:05:39 2018 +0300 +++ b/src/event/ngx_event_openssl.c Tue Sep 25 14:07:59 2018 +0300 @@ -3146,6 +3146,7 @@ ngx_ssl_get_cached_session(ngx_ssl_conn_ const #endif u_char *p; + size_t slen; uint32_t hash; ngx_int_t rc; ngx_shm_zone_t *shm_zone; @@ -3201,12 +3202,14 @@ ngx_ssl_get_cached_session(ngx_ssl_conn_ if (rc == 0) { if (sess_id->expire > ngx_time()) { - ngx_memcpy(buf, sess_id->session, sess_id->len); + slen = sess_id->len; + + ngx_memcpy(buf, sess_id->session, slen); ngx_shmtx_unlock(&shpool->mutex); p = buf; - sess = d2i_SSL_SESSION(NULL, &p, sess_id->len); + sess = d2i_SSL_SESSION(NULL, &p, slen); return sess; } From xeioex at nginx.com Thu Sep 27 14:37:09 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 27 Sep 2018 14:37:09 +0000 Subject: [njs] Fixed code size mismatch error message. Message-ID: details: http://hg.nginx.org/njs/rev/5175716b5e80 branches: changeset: 613:5175716b5e80 user: Dmitry Volyntsev date: Thu Sep 27 17:36:38 2018 +0300 description: Fixed code size mismatch error message. diffstat: njs/njs_generator.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r 68a3580688ab -r 5175716b5e80 njs/njs_generator.c --- a/njs/njs_generator.c Wed Sep 19 19:36:00 2018 +0300 +++ b/njs/njs_generator.c Thu Sep 27 17:36:38 2018 +0300 @@ -2066,7 +2066,7 @@ njs_generate_scope(njs_vm_t *vm, njs_par parser->code_size, code_size); if (nxt_slow_path(parser->code_size < code_size)) { - njs_internal_error(vm, "code size mismatch actual %uz < expected %uz", + njs_internal_error(vm, "code size mismatch expected %uz < actual %uz", parser->code_size, code_size); return NXT_ERROR; } From xeioex at nginx.com Thu Sep 27 14:38:12 2018 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 27 Sep 2018 14:38:12 +0000 Subject: [njs] Fixed http response and parent getters. Message-ID: details: http://hg.nginx.org/njs/rev/c4035cc63370 branches: changeset: 614:c4035cc63370 user: Dmitry Volyntsev date: Thu Sep 27 17:37:55 2018 +0300 description: Fixed http response and parent getters. Getters are expected to set resulting value to the provided argument, not to vm->retval. diffstat: nginx/ngx_http_js_module.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (21 lines): diff -r 5175716b5e80 -r c4035cc63370 nginx/ngx_http_js_module.c --- a/nginx/ngx_http_js_module.c Thu Sep 27 17:36:38 2018 +0300 +++ b/nginx/ngx_http_js_module.c Thu Sep 27 17:37:55 2018 +0300 @@ -1844,7 +1844,7 @@ ngx_http_js_ext_get_response(njs_vm_t *v ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); - njs_vm_retval_set(vm, njs_value_arg(&ctx->args[1])); + njs_value_assign(value, njs_value_arg(&ctx->args[1])); return NJS_OK; } @@ -2189,7 +2189,7 @@ ngx_http_js_ext_get_parent(njs_vm_t *vm, return NJS_ERROR; } - njs_vm_retval_set(vm, njs_value_arg(&ctx->args[0])); + njs_value_assign(value, njs_value_arg(&ctx->args[0])); return NJS_OK; } From dewanggaba at xtremenitro.org Sat Sep 29 01:29:56 2018 From: dewanggaba at xtremenitro.org (Dewangga Alam) Date: Sat, 29 Sep 2018 08:29:56 +0700 Subject: Bring SPDY back in nginx-1.14.0 Message-ID: <636e17f1-bc63-74b7-f5c1-725b202ef1fd@xtremenitro.org> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Hello! This is my first post in nginx-devel mailing list. Currently, I am using nginx-1.8.1 in production. And I need SPDY to works on nginx-1.14.0 co-exists with http/2. Because I still need SPDY for compatibility, and for future deployment, I will migrate it all to HTTP/2. To make this possible, so I was create patch to make SPDY and HTTP/2 run together. I was read cloudflare patch regarding this issue (spdy & http/2) and make some adjustment (patch attached). But, got some errors when compiling. The error message was : objs/src/http/ngx_http_spdy.o: In function `ngx_http_spdy_send_output_queue': ./debian/build-nginx/src/http/ngx_http_spdy.c:664: multiple definition of `ngx_http_spdy_send_output_queue' objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_spd y.c:664: first defined here objs/src/http/ngx_http_spdy.o: In function `ngx_http_spdy_init': ./debian/build-nginx/src/http/ngx_http_spdy.c:386: multiple definition of `ngx_http_spdy_init' objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_spd y.c:386: first defined here objs/src/http/ngx_http_spdy.o: In function `ngx_http_spdy_request_headers_init': ./debian/build-nginx/src/http/ngx_http_spdy.c:2813: multiple definition of `ngx_http_spdy_request_headers_init' objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_spd y.c:2813: first defined here objs/src/http/ngx_http_spdy.o: In function `ngx_http_spdy_read_request_body': ./debian/build-nginx/src/http/ngx_http_spdy.c:3274: multiple definition of `ngx_http_spdy_read_request_body' objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_spd y.c:3274: first defined here objs/src/http/ngx_http_spdy.o: In function `ngx_http_spdy_close_stream': ./debian/build-nginx/src/http/ngx_http_spdy.c:3362: multiple definition of `ngx_http_spdy_close_stream' objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_spd y.c:3362: first defined here objs/src/http/ngx_http_spdy_module.o:(.data.rel.local+0xc0): multiple definition of `ngx_http_spdy_module' objs/src/http/ngx_http_spdy_module.o:(.data.rel.local+0xc0): first defined here https://paste.fedoraproject.org/paste/HGW3Q7Pj8oxnD473Xw3Cdw/raw Is there any hints to fix the error? Wr, Dewangga Alam -----BEGIN PGP SIGNATURE----- iQIzBAEBCAAdFiEEZpxiw/Jg6pEte5xQ5X/SIKAozXAFAluu1ZQACgkQ5X/SIKAo zXD0gA//VBB7+qdDZV7L9/qIgijQP0sZAwNzCn0CnCGA7hbWZVKsxT+qSp7zgDZG 8nvT8ckcD3PA4ryCrUiEe0EQP/SzBIjklICTqnUVtPNvw2+w2LAnCECILNJH9+e4 WRfLcTfvlVZyNED7KKSzzG+36heHclswBUn7paJdrEBosGSAGn/lZYC7xyVsHhly tc26EzIKKnvNYn5mnAVh/45Y9qqJ2weoTbQqljNEYHehyT8bQLaPcb73qkjWIoo8 0QrvN3pUwaqExTJ4NS/Zu8zMPFeEsmSUykoEeUYwCBRp2bDT+jX/heWCsaPzz033 4Op0HwHfsaHS7BhD5nswMZsOCl1qSEmiFpC3W2WgpisStV6SAGp1kZZHD8juKebw s8Z9gCvqeQxfG0vTjhtkX32+yK82ft0SX48QKPkZ6umVRT46VGMtmkI+KQMCTxQl g4JK3tFcz53uBKpR6T3IBU36R29SfLItSkFpMRjCFPopGsY4Dh1kwpgqBiSpQqzv oGIfsadiaKKOGEz8gBr9txR9Wz66jVtJlt51vGXS/zvrIsx+TLHki6ju94+O+dOy DarPsqvUFqAtZxfyBvg3K5WKH8nSFeC3S6o/qUrAabhLv5wplg0o/Fqrm9mMwyju NJchSz/ZsY+RMQ53jcgrlEftv0b9LlPW3TkBnniF+uIuNhv6JMw= =JBp1 -----END PGP SIGNATURE----- -------------- next part -------------- --- a/auto/modules 2018-09-28 11:10:26.000000000 +0000 +++ b/auto/modules 2018-09-28 11:45:45.000000000 +0000 @@ -134,6 +134,7 @@ # ngx_http_header_filter # ngx_http_chunked_filter # ngx_http_v2_filter + # ngx_http_spdy_filter # ngx_http_range_header_filter # ngx_http_gzip_filter # ngx_http_postpone_filter @@ -166,6 +167,7 @@ ngx_http_header_filter_module \ ngx_http_chunked_filter_module \ ngx_http_v2_filter_module \ + ngx_http_spdy_filter \ ngx_http_range_header_filter_module \ ngx_http_gzip_filter_module \ ngx_http_postpone_filter_module \ @@ -227,6 +229,20 @@ . auto/module fi + if [ $HTTP_SPDY = YES ]; then + have=NGX_HTTP_SPDY . auto/have + + ngx_module_name=ngx_http_spdy_module + ngx_module_incs=src/http + ngx_module_deps=src/http/ngx_http_spdy.h src/http/ngx_http_spdy_module.h + ngx_module_srcs="src/http/ngx_http_spdy.c \ + src/http/ngx_http_spdy_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_SPDY + + . auto/module + fi + if :; then ngx_module_name=ngx_http_range_header_filter_module ngx_module_incs= @@ -437,6 +453,20 @@ . auto/module fi + + if [ $HTTP_SPDY = YES ]; then + have=NGX_HTTP_SPDY . auto/have + + ngx_module_name=ngx_http_spdy_module + ngx_module_incs=src/http + ngx_module_deps=src/http/ngx_http_spdy.h src/http/ngx_http_spdy_module.h + ngx_module_srcs="src/http/ngx_http_spdy.c \ + src/http/ngx_http_spdy_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_SPDY + + . auto/module + fi if :; then ngx_module_name=ngx_http_static_module --- a/auto/options 2018-09-28 10:58:30.000000000 +0000 +++ b/auto/options 2018-09-28 11:50:44.000000000 +0000 @@ -59,6 +59,7 @@ HTTP_GZIP=YES HTTP_SSL=NO HTTP_V2=NO +HTTP_SPDY=NO HTTP_SSI=YES HTTP_POSTPONE=NO HTTP_REALIP=NO @@ -223,7 +224,8 @@ --with-http_ssl_module) HTTP_SSL=YES ;; --with-http_v2_module) HTTP_V2=YES ;; - --with-http_realip_module) HTTP_REALIP=YES ;; + --with-http_spdy_module) HTTP_SPDY=YES ;; + --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; --with-http_xslt_module) HTTP_XSLT=YES ;; --with-http_xslt_module=dynamic) HTTP_XSLT=DYNAMIC ;; @@ -434,6 +436,7 @@ --with-http_ssl_module enable ngx_http_ssl_module --with-http_v2_module enable ngx_http_v2_module + --with-http_spdy_module enable ngx_http_spdy_module --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module --with-http_xslt_module enable ngx_http_xslt_module --- a/src/core/ngx_connection.h 2018-09-28 10:57:47.000000000 +0000 +++ b/src/core/ngx_connection.h 2018-09-28 11:52:02.000000000 +0000 @@ -116,6 +116,7 @@ #define NGX_LOWLEVEL_BUFFERED 0x0f #define NGX_SSL_BUFFERED 0x01 #define NGX_HTTP_V2_BUFFERED 0x02 +#define NGX_SPDY_BUFFERED 0x04 struct ngx_connection_s { --- a/src/http/modules/ngx_http_ssl_module.c 2018-09-28 10:59:15.000000000 +0000 +++ b/src/http/modules/ngx_http_ssl_module.c 2018-09-28 12:16:36.000000000 +0000 @@ -352,10 +352,10 @@ #if (NGX_DEBUG) unsigned int i; #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) ngx_http_connection_t *hc; #endif -#if (NGX_HTTP_V2 || NGX_DEBUG) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_DEBUG) ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); @@ -369,6 +369,15 @@ } #endif +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + if (hc->addr_conf->http2 && hc->addr_conf->spdy) { + srv = (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE + NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + srvlen = sizeof(NGX_HTTP_V2_ALPN_ADVERTISE NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE) - 1; + + } else +#endif #if (NGX_HTTP_V2) hc = c->data; @@ -379,6 +388,13 @@ } else #endif +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + srv = (unsigned char *) NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + srvlen = sizeof(NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1; + + } else +#endif { srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE; srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1; @@ -406,19 +422,32 @@ ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, unsigned int *outlen, void *arg) { -#if (NGX_HTTP_V2 || NGX_DEBUG) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_DEBUG) ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "SSL NPN advertised"); #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) { ngx_http_connection_t *hc; hc = c->data; +#endif + +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + if (hc->addr_conf->http2 && hc->addr_conf->spdy) { + *out = (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE + NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE; + *outlen = sizeof(NGX_HTTP_V2_NPN_ADVERTISE NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE) - 1; + return SSL_TLSEXT_ERR_OK; + } else +#endif +#if (NGX_HTTP_V2) if (hc->addr_conf->http2) { *out = (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; @@ -426,6 +455,20 @@ return SSL_TLSEXT_ERR_OK; } +#endif +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + else +#endif +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + *out = (unsigned char *) NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + *outlen = sizeof(NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1; + + return SSL_TLSEXT_ERR_OK; + } +#endif + +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) } #endif --- a/src/http/ngx_http.c 2018-09-28 10:59:41.000000000 +0000 +++ b/src/http/ngx_http.c 2018-09-28 12:32:27.000000000 +0000 @@ -1199,6 +1199,9 @@ #if (NGX_HTTP_V2) ngx_uint_t http2; #endif +#if (NGX_HTTP_SPDY) + ngx_uint_t spdy; +#endif /* * we cannot compare whole sockaddr struct's as kernel @@ -1234,6 +1237,9 @@ #if (NGX_HTTP_V2) http2 = lsopt->http2 || addr[i].opt.http2; #endif +#if (NGX_HTTP_SPDY) + spdy = lsopt->spdy || addr[i].opt.spdy; +#endif if (lsopt->set) { @@ -1265,9 +1271,13 @@ #if (NGX_HTTP_SSL) addr[i].opt.ssl = ssl; #endif + #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif +#if (NGX_HTTP_SPDY) + addr[i].opt.spdy = spdy; +#endif return NGX_OK; } @@ -1310,6 +1320,18 @@ } #endif +#if (NGX_HTTP_SPDY && NGX_HTTP_SSL \ + && !defined TLSEXT_TYPE_application_layer_protocol_negotiation \ + && !defined TLSEXT_TYPE_next_proto_neg) + + if (lsopt->spdy && lsopt->ssl) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "nginx was built with OpenSSL that lacks ALPN " + "and NPN support, SPDY is not enabled for %s", + lsopt->addr); + } + +#endif addr = ngx_array_push(&port->addrs); if (addr == NULL) { @@ -1804,6 +1826,9 @@ #if (NGX_HTTP_V2) addrs[i].conf.http2 = addr[i].opt.http2; #endif +#if (NGX_HTTP_SPDY) + addrs[i].conf.spdy = addr[i].opt.spdy; +#endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; if (addr[i].hash.buckets == NULL @@ -1869,6 +1894,9 @@ #if (NGX_HTTP_V2) addrs6[i].conf.http2 = addr[i].opt.http2; #endif +#if (NGX_HTTP_SPDY) + addrs6[i].conf.spdy = addr[i].opt.spdy; +#endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; if (addr[i].hash.buckets == NULL --- a/src/http/ngx_http.h 2018-09-28 17:54:59.000000000 +0000 +++ b/src/http/ngx_http.h 2018-09-28 12:34:38.000000000 +0000 @@ -20,6 +20,7 @@ typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; typedef struct ngx_http_chunked_s ngx_http_chunked_t; typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; +typedef struct ngx_http_spdy_stream_s ngx_http_spdy_stream_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); @@ -38,6 +39,9 @@ #if (NGX_HTTP_V2) #include #endif +#if (NGX_HTTP_SPDY) +#include +#endif #if (NGX_HTTP_CACHE) #include #endif --- a/src/http/ngx_http_core_module.c 2018-09-28 17:55:29.000000000 +0000 +++ b/src/http/ngx_http_core_module.c 2018-09-28 12:40:12.000000000 +0000 @@ -1940,6 +1940,13 @@ return NGX_DECLINED; } +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + r->gzip_ok = 1; + return NGX_OK; + } +#endif + ae = r->headers_in.accept_encoding; if (ae == NULL) { return NGX_DECLINED; @@ -2297,6 +2304,9 @@ #if (NGX_HTTP_V2) sr->stream = r->stream; #endif +#if (NGX_HTTP_SPDY) + sr->spdy_stream = r->spdy_stream; +#endif sr->method = NGX_HTTP_GET; sr->http_version = r->http_version; @@ -3986,11 +3996,15 @@ } if (ngx_strcmp(value[n].data, "spdy") == 0) { - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "invalid parameter \"spdy\": " - "ngx_http_spdy_module was superseded " - "by ngx_http_v2_module"); - continue; +#if (NGX_HTTP_SPDY) + lsopt.spdy = 1; + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"spdy\" parameter requires " + "ngx_http_spdy_module"); + return NGX_CONF_ERROR; +#endif } if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { --- a/src/http/ngx_http_core_module.h 2018-09-28 17:55:43.000000000 +0000 +++ b/src/http/ngx_http_core_module.h 2018-09-28 12:42:45.000000000 +0000 @@ -74,6 +74,7 @@ unsigned wildcard:1; unsigned ssl:1; unsigned http2:1; + unsigned spdy:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -235,6 +236,7 @@ unsigned ssl:1; unsigned http2:1; + unsigned spdy:1; unsigned proxy_protocol:1; }; --- a/src/http/ngx_http_request.c 2018-09-28 17:56:12.000000000 +0000 +++ b/src/http/ngx_http_request.c 2018-09-28 12:52:11.000000000 +0000 @@ -322,7 +322,11 @@ rev = c->read; rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; - +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + rev->handler = ngx_http_spdy_init; + } +#endif #if (NGX_HTTP_V2) if (hc->addr_conf->http2) { rev->handler = ngx_http_v2_init; @@ -824,6 +828,33 @@ } } #endif +#if (NGX_HTTP_SPDY \ + && (defined TLSEXT_TYPE_application_layer_protocol_negotiation \ + || defined TLSEXT_TYPE_next_proto_neg)) + { + unsigned int len; + const unsigned char *data; + static const ngx_str_t spdy = ngx_string(NGX_SPDY_NPN_NEGOTIATED); + +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + +#ifdef TLSEXT_TYPE_next_proto_neg + if (len == 0) { + SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len); + } +#endif + +#else /* TLSEXT_TYPE_next_proto_neg */ + SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len); +#endif + + if (len == spdy.len && ngx_strncmp(data, spdy.data, spdy.len) == 0) { + ngx_http_spdy_init(c->read); + return; + } + } +#endif c->log->action = "waiting for request"; @@ -2584,6 +2615,12 @@ return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_close_request(r, 0); + return; + } +#endif clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); @@ -3457,6 +3494,13 @@ } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_spdy_close_stream(r->spdy_stream, rc); + return; + } +#endif + ngx_http_free_request(r, rc); ngx_http_close_connection(c); } --- a/src/http/ngx_http_request.h 2018-09-28 17:56:12.000000000 +0000 +++ b/src/http/ngx_http_request.h 2018-09-28 15:45:13.000000000 +0000 @@ -444,6 +444,7 @@ ngx_http_connection_t *http_connection; ngx_http_v2_stream_t *stream; + ngx_http_spdy_stream_t *spdy_stream; ngx_http_log_handler_pt log_handler; --- a/src/http/ngx_http_request_body.c 2018-09-28 17:56:26.000000000 +0000 +++ b/src/http/ngx_http_request_body.c 2018-09-28 16:15:06.000000000 +0000 @@ -46,6 +46,14 @@ return NGX_OK; } +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + rc = ngx_http_spdy_read_request_body(r, post_handler); + + goto done; + } +#endif + if (ngx_http_test_expect(r) != NGX_OK) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; @@ -525,6 +533,13 @@ } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + r->spdy_stream->skip_data = 1; + return NGX_OK; + } +#endif + if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } --- a/src/http/ngx_http_spdy.c 1970-01-01 00:00:00.000000000 +0000 +++ b/src/http/ngx_http_spdy.c 2018-09-28 16:22:57.000000000 +0000 @@ -0,0 +1,3701 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + +#include + + +#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED) + +#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \ + *(uint32_t *) m == (c3 << 24 | c2 << 16 | c1 << 8 | c0) \ + && m[4] == c4 + +#else + +#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \ + m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 && m[4] == c4 + +#endif + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_spdy_frame_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_spdy_frame_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#else + +#define ngx_spdy_frame_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_spdy_frame_parse_uint32(p) \ + ((p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#endif + +#define ngx_spdy_frame_parse_sid(p) \ + (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff) +#define ngx_spdy_frame_parse_delta(p) \ + (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff) + + +#define ngx_spdy_ctl_frame_check(h) \ + (((h) & 0xffff0000) == ngx_spdy_ctl_frame_head(0)) +#define ngx_spdy_data_frame_check(h) \ + (!((h) & (uint32_t) NGX_SPDY_CTL_BIT << 31)) + +#define ngx_spdy_ctl_frame_type(h) ((h) & 0x0000ffff) +#define ngx_spdy_frame_flags(p) ((p) >> 24) +#define ngx_spdy_frame_length(p) ((p) & 0x00ffffff) +#define ngx_spdy_frame_id(p) ((p) & 0x00ffffff) + + +#define NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE 4096 +#define NGX_SPDY_CTL_FRAME_BUFFER_SIZE 16 + +#define NGX_SPDY_PROTOCOL_ERROR 1 +#define NGX_SPDY_INVALID_STREAM 2 +#define NGX_SPDY_REFUSED_STREAM 3 +#define NGX_SPDY_UNSUPPORTED_VERSION 4 +#define NGX_SPDY_CANCEL 5 +#define NGX_SPDY_INTERNAL_ERROR 6 +#define NGX_SPDY_FLOW_CONTROL_ERROR 7 +#define NGX_SPDY_STREAM_IN_USE 8 +#define NGX_SPDY_STREAM_ALREADY_CLOSED 9 +/* deprecated 10 */ +#define NGX_SPDY_FRAME_TOO_LARGE 11 + +#define NGX_SPDY_SETTINGS_MAX_STREAMS 4 +#define NGX_SPDY_SETTINGS_INIT_WINDOW 7 + +#define NGX_SPDY_SETTINGS_FLAG_PERSIST 0x01 +#define NGX_SPDY_SETTINGS_FLAG_PERSISTED 0x02 + +#define NGX_SPDY_MAX_WINDOW NGX_MAX_INT32_VALUE +#define NGX_SPDY_CONNECTION_WINDOW 65536 +#define NGX_SPDY_INIT_STREAM_WINDOW 65536 +#define NGX_SPDY_STREAM_WINDOW NGX_SPDY_MAX_WINDOW + +typedef struct { + ngx_uint_t hash; + u_char len; + u_char header[7]; + ngx_int_t (*handler)(ngx_http_request_t *r); +} ngx_http_spdy_request_header_t; + + +static void ngx_http_spdy_read_handler(ngx_event_t *rev); +static void ngx_http_spdy_write_handler(ngx_event_t *wev); +static void ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc); + +static u_char *ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_rst_stream(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_ping(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_skip(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_settings(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_complete(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_save(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end, ngx_http_spdy_handler_pt handler); + +static u_char *ngx_http_spdy_state_inflate_error( + ngx_http_spdy_connection_t *sc, int rc); +static u_char *ngx_http_spdy_state_protocol_error( + ngx_http_spdy_connection_t *sc); +static u_char *ngx_http_spdy_state_internal_error( + ngx_http_spdy_connection_t *sc); + +static ngx_int_t ngx_http_spdy_send_window_update( + ngx_http_spdy_connection_t *sc, ngx_uint_t sid, ngx_uint_t delta); +static ngx_int_t ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc, + ngx_uint_t sid, ngx_uint_t status, ngx_uint_t priority); +static ngx_int_t ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc); +static ngx_int_t ngx_http_spdy_settings_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_http_spdy_out_frame_t *ngx_http_spdy_get_ctl_frame( + ngx_http_spdy_connection_t *sc, size_t size, ngx_uint_t priority); +static ngx_int_t ngx_http_spdy_ctl_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); + +static ngx_http_spdy_stream_t *ngx_http_spdy_create_stream( + ngx_http_spdy_connection_t *sc, ngx_uint_t id, ngx_uint_t priority); +static ngx_http_spdy_stream_t *ngx_http_spdy_get_stream_by_id( + ngx_http_spdy_connection_t *sc, ngx_uint_t sid); +#define ngx_http_spdy_streams_index_size(sscf) (sscf->streams_index_mask + 1) +#define ngx_http_spdy_stream_index(sscf, sid) \ + ((sid >> 1) & sscf->streams_index_mask) + +static ngx_int_t ngx_http_spdy_parse_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_handle_request_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_method(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_scheme(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_host(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_path(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_version(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_construct_request_line(ngx_http_request_t *r); +static void ngx_http_spdy_run_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_init_request_body(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_terminate_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream, ngx_uint_t status); + +static void ngx_http_spdy_close_stream_handler(ngx_event_t *ev); + +static void ngx_http_spdy_handle_connection_handler(ngx_event_t *rev); +static void ngx_http_spdy_keepalive_handler(ngx_event_t *rev); +static void ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc, + ngx_int_t rc); + +static ngx_int_t ngx_http_spdy_adjust_windows(ngx_http_spdy_connection_t *sc, + ssize_t delta); + +static void ngx_http_spdy_pool_cleanup(void *data); + +static void *ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size); +static void ngx_http_spdy_zfree(void *opaque, void *address); + + +static const u_char ngx_http_spdy_dict[] = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, /* - - - - o p t i */ + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, /* o n s - - - - h */ + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, /* e a d - - - - p */ + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, /* o s t - - - - p */ + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, /* u t - - - - d e */ + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, /* l e t e - - - - */ + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, /* t r a c e - - - */ + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, /* - a c c e p t - */ + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, /* - - - a c c e p */ + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, /* t - c h a r s e */ + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, /* t - - - - a c c */ + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, /* e p t - e n c o */ + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, /* d i n g - - - - */ + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, /* a c c e p t - l */ + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, /* a n g u a g e - */ + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, /* - - - a c c e p */ + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, /* t - r a n g e s */ + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, /* - - - - a g e - */ + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, /* - - - a l l o w */ + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, /* - - - - a u t h */ + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, /* o r i z a t i o */ + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, /* n - - - - c a c */ + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, /* h e - c o n t r */ + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, /* o l - - - - c o */ + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, /* n n e c t i o n */ + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, /* e n t - b a s e */ + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, /* e n t - e n c o */ + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, /* d i n g - - - - */ + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, /* c o n t e n t - */ + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, /* l a n g u a g e */ + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, /* e n t - l e n g */ + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, /* t h - - - - c o */ + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, /* n t e n t - l o */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, /* c a t i o n - - */ + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, /* - - c o n t e n */ + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, /* t - m d 5 - - - */ + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, /* - c o n t e n t */ + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, /* - r a n g e - - */ + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, /* - - c o n t e n */ + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, /* t - t y p e - - */ + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, /* - - d a t e - - */ + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, /* - - e t a g - - */ + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, /* - - e x p e c t */ + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, /* - - - - e x p i */ + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, /* r e s - - - - f */ + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, /* r o m - - - - h */ + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, /* o s t - - - - i */ + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, /* f - m a t c h - */ + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, /* - - - i f - m o */ + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, /* d i f i e d - s */ + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, /* i n c e - - - - */ + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, /* i f - n o n e - */ + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, /* m a t c h - - - */ + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, /* - i f - r a n g */ + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, /* e - - - - i f - */ + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, /* u n m o d i f i */ + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, /* e d - s i n c e */ + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, /* - - - - l a s t */ + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, /* - m o d i f i e */ + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, /* d - - - - l o c */ + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, /* a t i o n - - - */ + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, /* - m a x - f o r */ + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, /* w a r d s - - - */ + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, /* - p r a g m a - */ + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, /* - - - p r o x y */ + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, /* - a u t h e n t */ + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, /* i c a t e - - - */ + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, /* - p r o x y - a */ + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, /* u t h o r i z a */ + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, /* t i o n - - - - */ + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, /* r a n g e - - - */ + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, /* - r e f e r e r */ + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, /* - - - - r e t r */ + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, /* y - a f t e r - */ + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, /* - - - s e r v e */ + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, /* r - - - - t e - */ + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, /* - - - t r a i l */ + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, /* e r - - - - t r */ + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, /* a n s f e r - e */ + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, /* n c o d i n g - */ + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, /* - - - u p g r a */ + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, /* d e - - - - u s */ + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, /* e r - a g e n t */ + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, /* - - - - v a r y */ + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, /* - - - - v i a - */ + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, /* - - - w a r n i */ + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, /* n g - - - - w w */ + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, /* w - a u t h e n */ + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, /* t i c a t e - - */ + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, /* - - m e t h o d */ + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, /* - - - - g e t - */ + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, /* - - - s t a t u */ + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, /* s - - - - 2 0 0 */ + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, /* - O K - - - - v */ + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, /* e r s i o n - - */ + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, /* - - H T T P - 1 */ + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, /* - 1 - - - - u r */ + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, /* l - - - - p u b */ + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, /* l i c - - - - s */ + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, /* e t - c o o k i */ + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, /* e - - - - k e e */ + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, /* p - a l i v e - */ + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, /* - - - o r i g i */ + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, /* n 1 0 0 1 0 1 2 */ + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, /* 0 1 2 0 2 2 0 5 */ + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, /* 2 0 6 3 0 0 3 0 */ + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, /* 2 3 0 3 3 0 4 3 */ + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, /* 0 5 3 0 6 3 0 7 */ + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, /* 4 0 2 4 0 5 4 0 */ + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, /* 6 4 0 7 4 0 8 4 */ + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, /* 0 9 4 1 0 4 1 1 */ + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, /* 4 1 2 4 1 3 4 1 */ + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, /* 4 4 1 5 4 1 6 4 */ + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, /* 1 7 5 0 2 5 0 4 */ + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, /* 5 0 5 2 0 3 - N */ + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, /* o n - A u t h o */ + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, /* r i t a t i v e */ + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, /* - I n f o r m a */ + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, /* t i o n 2 0 4 - */ + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, /* N o - C o n t e */ + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, /* n t 3 0 1 - M o */ + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, /* v e d - P e r m */ + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, /* a n e n t l y 4 */ + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, /* 0 0 - B a d - R */ + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, /* e q u e s t 4 0 */ + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, /* 1 - U n a u t h */ + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, /* o r i z e d 4 0 */ + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, /* 3 - F o r b i d */ + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, /* d e n 4 0 4 - N */ + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, /* o t - F o u n d */ + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, /* 5 0 0 - I n t e */ + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, /* r n a l - S e r */ + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, /* v e r - E r r o */ + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, /* r 5 0 1 - N o t */ + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, /* - I m p l e m e */ + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, /* n t e d 5 0 3 - */ + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, /* S e r v i c e - */ + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, /* U n a v a i l a */ + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, /* b l e J a n - F */ + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, /* e b - M a r - A */ + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, /* p r - M a y - J */ + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, /* u n - J u l - A */ + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, /* u g - S e p t - */ + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, /* O c t - N o v - */ + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, /* D e c - 0 0 - 0 */ + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, /* 0 - 0 0 - M o n */ + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, /* - - T u e - - W */ + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, /* e d - - T h u - */ + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, /* - F r i - - S a */ + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, /* t - - S u n - - */ + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, /* G M T c h u n k */ + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, /* e d - t e x t - */ + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, /* h t m l - i m a */ + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, /* g e - p n g - i */ + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, /* m a g e - j p g */ + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, /* - i m a g e - g */ + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, /* i f - a p p l i */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, /* c a t i o n - x */ + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, /* m l - a p p l i */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, /* c a t i o n - x */ + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, /* h t m l - x m l */ + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, /* - t e x t - p l */ + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, /* a i n - t e x t */ + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, /* - j a v a s c r */ + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, /* i p t - p u b l */ + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, /* i c p r i v a t */ + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, /* e m a x - a g e */ + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, /* - g z i p - d e */ + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, /* f l a t e - s d */ + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, /* c h c h a r s e */ + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, /* t - u t f - 8 c */ + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, /* h a r s e t - i */ + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, /* s o - 8 8 5 9 - */ + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, /* 1 - u t f - - - */ + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e /* - e n q - 0 - */ +}; + + +static ngx_http_spdy_request_header_t ngx_http_spdy_request_headers[] = { + { 0, 6, "method", ngx_http_spdy_parse_method }, + { 0, 6, "scheme", ngx_http_spdy_parse_scheme }, + { 0, 4, "host", ngx_http_spdy_parse_host }, + { 0, 4, "path", ngx_http_spdy_parse_path }, + { 0, 7, "version", ngx_http_spdy_parse_version }, +}; + +#define NGX_SPDY_REQUEST_HEADERS \ + (sizeof(ngx_http_spdy_request_headers) \ + / sizeof(ngx_http_spdy_request_header_t)) + + +void +ngx_http_spdy_init(ngx_event_t *rev) +{ + int rc; + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_main_conf_t *smcf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init spdy request"); + + c->log->action = "processing SPDY"; + + smcf = ngx_http_get_module_main_conf(hc->conf_ctx, ngx_http_spdy_module); + + if (smcf->recv_buffer == NULL) { + smcf->recv_buffer = ngx_palloc(ngx_cycle->pool, smcf->recv_buffer_size); + if (smcf->recv_buffer == NULL) { + ngx_http_close_connection(c); + return; + } + } + + sc = ngx_pcalloc(c->pool, sizeof(ngx_http_spdy_connection_t)); + if (sc == NULL) { + ngx_http_close_connection(c); + return; + } + + sc->connection = c; + sc->http_connection = hc; + + sc->send_window = NGX_SPDY_CONNECTION_WINDOW; + sc->recv_window = NGX_SPDY_CONNECTION_WINDOW; + + sc->init_window = NGX_SPDY_INIT_STREAM_WINDOW; + + sc->handler = hc->proxy_protocol ? ngx_http_spdy_proxy_protocol + : ngx_http_spdy_state_head; + + sc->zstream_in.zalloc = ngx_http_spdy_zalloc; + sc->zstream_in.zfree = ngx_http_spdy_zfree; + sc->zstream_in.opaque = sc; + + rc = inflateInit(&sc->zstream_in); + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "inflateInit() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + sc->zstream_out.zalloc = ngx_http_spdy_zalloc; + sc->zstream_out.zfree = ngx_http_spdy_zfree; + sc->zstream_out.opaque = sc; + + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_spdy_module); + + rc = deflateInit2(&sc->zstream_out, (int) sscf->headers_comp, + Z_DEFLATED, 11, 4, Z_DEFAULT_STRATEGY); + + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "deflateInit2() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + rc = deflateSetDictionary(&sc->zstream_out, ngx_http_spdy_dict, + sizeof(ngx_http_spdy_dict)); + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "deflateSetDictionary() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log); + if (sc->pool == NULL) { + ngx_http_close_connection(c); + return; + } + + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_pool_cleanup_file_t)); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_spdy_pool_cleanup; + cln->data = sc; + + sc->streams_index = ngx_pcalloc(sc->pool, + ngx_http_spdy_streams_index_size(sscf) + * sizeof(ngx_http_spdy_stream_t *)); + if (sc->streams_index == NULL) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_spdy_send_settings(sc) == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_spdy_send_window_update(sc, 0, NGX_SPDY_MAX_WINDOW + - sc->recv_window) + == NGX_ERROR) + { + ngx_http_close_connection(c); + return; + } + + sc->recv_window = NGX_SPDY_MAX_WINDOW; + + ngx_queue_init(&sc->waiting); + ngx_queue_init(&sc->posted); + + c->data = sc; + + rev->handler = ngx_http_spdy_read_handler; + c->write->handler = ngx_http_spdy_write_handler; + + ngx_http_spdy_read_handler(rev); +} + + +static void +ngx_http_spdy_read_handler(ngx_event_t *rev) +{ + u_char *p, *end; + size_t available; + ssize_t n; + ngx_connection_t *c; + ngx_http_spdy_main_conf_t *smcf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + sc = c->data; + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy read handler"); + + sc->blocked = 1; + + smcf = ngx_http_get_module_main_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + available = smcf->recv_buffer_size - 2 * NGX_SPDY_STATE_BUFFER_SIZE; + + do { + p = smcf->recv_buffer; + + ngx_memcpy(p, sc->buffer, NGX_SPDY_STATE_BUFFER_SIZE); + end = p + sc->buffer_used; + + n = c->recv(c, end, available); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0 && (sc->incomplete || sc->processing)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); + } + + if (n == 0 || n == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + end += n; + + sc->buffer_used = 0; + sc->incomplete = 0; + + do { + p = sc->handler(sc, p, end); + + if (p == NULL) { + return; + } + + } while (p != end); + + } while (rev->ready); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (sc->last_out && ngx_http_spdy_send_output_queue(sc) == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + sc->blocked = 0; + + if (sc->processing) { + if (rev->timer_set) { + ngx_del_timer(rev); + } + return; + } + + ngx_http_spdy_handle_connection(sc); +} + + +static void +ngx_http_spdy_write_handler(ngx_event_t *wev) +{ + ngx_int_t rc; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_connection_t *sc; + + c = wev->data; + sc = c->data; + + if (wev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy write event timed out"); + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy write handler"); + + sc->blocked = 1; + + rc = ngx_http_spdy_send_output_queue(sc); + + if (rc == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + while (!ngx_queue_empty(&sc->posted)) { + q = ngx_queue_head(&sc->posted); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + stream->handled = 0; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "run spdy stream %ui", stream->id); + + wev = stream->request->connection->write; + wev->handler(wev); + } + + sc->blocked = 0; + + if (rc == NGX_AGAIN) { + return; + } + + ngx_http_spdy_handle_connection(sc); +} + + +ngx_int_t +ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc) +{ + int tcp_nodelay; + ngx_chain_t *cl; + ngx_event_t *wev; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + ngx_http_spdy_out_frame_t *out, *frame, *fn; + + c = sc->connection; + + if (c->error) { + return NGX_ERROR; + } + + wev = c->write; + + if (!wev->ready) { + return NGX_OK; + } + + cl = NULL; + out = NULL; + + for (frame = sc->last_out; frame; frame = fn) { + frame->last->next = cl; + cl = frame->first; + + fn = frame->next; + frame->next = out; + out = frame; + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy frame out: %p sid:%ui prio:%ui bl:%d len:%uz", + out, out->stream ? out->stream->id : 0, out->priority, + out->blocked, out->length); + } + + cl = c->send_chain(c, cl, 0); + + if (cl == NGX_CHAIN_ERROR) { + goto error; + } + + clcf = ngx_http_get_module_loc_conf(sc->http_connection->conf_ctx, + ngx_http_core_module); + + if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { + goto error; + } + + if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { + if (ngx_tcp_push(c->fd) == -1) { + ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed"); + goto error; + } + + c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; + tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0; + + } else { + tcp_nodelay = 1; + } + + if (tcp_nodelay + && clcf->tcp_nodelay + && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) + == -1) + { +#if (NGX_SOLARIS) + /* Solaris returns EINVAL if a socket has been shut down */ + c->log_error = NGX_ERROR_IGNORE_EINVAL; +#endif + + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + + c->log_error = NGX_ERROR_INFO; + goto error; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + if (cl) { + ngx_add_timer(wev, clcf->send_timeout); + + } else { + if (wev->timer_set) { + ngx_del_timer(wev); + } + } + + for ( /* void */ ; out; out = fn) { + fn = out->next; + + if (out->handler(sc, out) != NGX_OK) { + out->blocked = 1; + out->priority = NGX_SPDY_HIGHEST_PRIORITY; + break; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy frame sent: %p sid:%ui bl:%d len:%uz", + out, out->stream ? out->stream->id : 0, + out->blocked, out->length); + } + + frame = NULL; + + for ( /* void */ ; out; out = fn) { + fn = out->next; + out->next = frame; + frame = out; + } + + sc->last_out = frame; + + return NGX_OK; + +error: + + c->error = 1; + + if (!sc->blocked) { + ngx_post_event(wev, &ngx_posted_events); + } + + return NGX_ERROR; +} + + +static void +ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc) +{ + ngx_connection_t *c; + ngx_http_spdy_srv_conf_t *sscf; + + if (sc->last_out || sc->processing) { + return; + } + + c = sc->connection; + + if (c->error) { + ngx_http_close_connection(c); + return; + } + + if (c->buffered) { + return; + } + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + if (sc->incomplete) { + ngx_add_timer(c->read, sscf->recv_timeout); + return; + } + + if (ngx_terminate || ngx_exiting) { + ngx_http_close_connection(c); + return; + } + + ngx_destroy_pool(sc->pool); + + sc->pool = NULL; + sc->free_ctl_frames = NULL; + sc->free_fake_connections = NULL; + +#if (NGX_HTTP_SSL) + if (c->ssl) { + ngx_ssl_free_buffer(c); + } +#endif + + c->destroyed = 1; + c->idle = 1; + ngx_reusable_connection(c, 1); + + c->write->handler = ngx_http_empty_handler; + c->read->handler = ngx_http_spdy_keepalive_handler; + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + ngx_add_timer(c->read, sscf->keepalive_timeout); +} + + +static u_char * +ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_log_t *log; + + log = sc->connection->log; + log->action = "reading PROXY protocol"; + + pos = ngx_proxy_protocol_read(sc->connection, pos, end); + + log->action = "processing SPDY"; + + if (pos == NULL) { + return ngx_http_spdy_state_protocol_error(sc); + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + uint32_t head, flen; + ngx_uint_t type; + + if (end - pos < NGX_SPDY_FRAME_HEADER_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_head); + } + + head = ngx_spdy_frame_parse_uint32(pos); + + pos += sizeof(uint32_t); + + flen = ngx_spdy_frame_parse_uint32(pos); + + sc->flags = ngx_spdy_frame_flags(flen); + sc->length = ngx_spdy_frame_length(flen); + + pos += sizeof(uint32_t); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "process spdy frame head:%08XD f:%Xd l:%uz", + head, sc->flags, sc->length); + + if (ngx_spdy_ctl_frame_check(head)) { + type = ngx_spdy_ctl_frame_type(head); + + switch (type) { + + case NGX_SPDY_SYN_STREAM: + return ngx_http_spdy_state_syn_stream(sc, pos, end); + + case NGX_SPDY_SYN_REPLY: + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent unexpected SYN_REPLY frame"); + return ngx_http_spdy_state_protocol_error(sc); + + case NGX_SPDY_RST_STREAM: + return ngx_http_spdy_state_rst_stream(sc, pos, end); + + case NGX_SPDY_SETTINGS: + return ngx_http_spdy_state_settings(sc, pos, end); + + case NGX_SPDY_PING: + return ngx_http_spdy_state_ping(sc, pos, end); + + case NGX_SPDY_GOAWAY: + return ngx_http_spdy_state_skip(sc, pos, end); /* TODO */ + + case NGX_SPDY_HEADERS: + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent unexpected HEADERS frame"); + return ngx_http_spdy_state_protocol_error(sc); + + case NGX_SPDY_WINDOW_UPDATE: + return ngx_http_spdy_state_window_update(sc, pos, end); + + default: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy control frame with unknown type %ui", type); + return ngx_http_spdy_state_skip(sc, pos, end); + } + } + + if (ngx_spdy_data_frame_check(head)) { + sc->stream = ngx_http_spdy_get_stream_by_id(sc, head); + return ngx_http_spdy_state_data(sc, pos, end); + } + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent invalid frame"); + + return ngx_http_spdy_state_protocol_error(sc); +} + + +static u_char * +ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t sid, prio; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + if (end - pos < NGX_SPDY_SYN_STREAM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_syn_stream); + } + + if (sc->length <= NGX_SPDY_SYN_STREAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->length -= NGX_SPDY_SYN_STREAM_SIZE; + + sid = ngx_spdy_frame_parse_sid(pos); + prio = pos[8] >> 5; + + pos += NGX_SPDY_SYN_STREAM_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SYN_STREAM frame sid:%ui prio:%ui", sid, prio); + + if (sid % 2 == 0 || sid <= sc->last_sid) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame " + "with invalid Stream-ID %ui", sid); + + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream) { + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_PROTOCOL_ERROR) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + } + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->last_sid = sid; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + if (sc->processing >= sscf->concurrent_streams) { + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "concurrent streams exceeded %ui", sc->processing); + + if (ngx_http_spdy_send_rst_stream(sc, sid, NGX_SPDY_REFUSED_STREAM, + prio) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + stream = ngx_http_spdy_create_stream(sc, sid, prio); + if (stream == NULL) { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->in_closed = (sc->flags & NGX_SPDY_FLAG_FIN) ? 1 : 0; + + stream->request->request_length = NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SYN_STREAM_SIZE + + sc->length; + + sc->stream = stream; + + return ngx_http_spdy_state_headers(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + int z; + size_t size; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_http_request_t *r; + + size = end - pos; + + if (size == 0) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + if (size > sc->length) { + size = sc->length; + } + + r = sc->stream->request; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "process spdy header block %uz of %uz", size, sc->length); + + buf = r->header_in; + + sc->zstream_in.next_in = pos; + sc->zstream_in.avail_in = size; + sc->zstream_in.next_out = buf->last; + + /* one byte is reserved for null-termination of the last header value */ + sc->zstream_in.avail_out = buf->end - buf->last - 1; + + z = inflate(&sc->zstream_in, Z_NO_FLUSH); + + if (z == Z_NEED_DICT) { + z = inflateSetDictionary(&sc->zstream_in, ngx_http_spdy_dict, + sizeof(ngx_http_spdy_dict)); + + if (z != Z_OK) { + if (z == Z_DATA_ERROR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent SYN_STREAM frame with header " + "block encoded using wrong dictionary: %ul", + (u_long) sc->zstream_in.adler); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "inflateSetDictionary() failed: %d", z); + + return ngx_http_spdy_state_internal_error(sc); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflateSetDictionary(): %d", z); + + z = sc->zstream_in.avail_in ? inflate(&sc->zstream_in, Z_NO_FLUSH) + : Z_OK; + } + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + z); + + if (z != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, z); + } + + sc->length -= sc->zstream_in.next_in - pos; + pos = sc->zstream_in.next_in; + + buf->last = sc->zstream_in.next_out; + + if (r->headers_in.headers.part.elts == NULL) { + + if (buf->last - buf->pos < NGX_SPDY_NV_NUM_SIZE) { + + if (sc->length == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "premature end of spdy header block"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + sc->entries = ngx_spdy_frame_parse_uint32(buf->pos); + + buf->pos += NGX_SPDY_NV_NUM_SIZE; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy header block has %ui entries", + sc->entries); + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (ngx_array_init(&r->headers_in.cookies, r->pool, 2, + sizeof(ngx_table_elt_t *)) + != NGX_OK) + { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + } + + while (sc->entries) { + + rc = ngx_http_spdy_parse_header(r); + + switch (rc) { + + case NGX_DONE: + sc->entries--; + + case NGX_OK: + break; + + case NGX_AGAIN: + + if (sc->zstream_in.avail_in) { + + rc = ngx_http_spdy_alloc_large_header_buffer(r); + + if (rc == NGX_DECLINED) { + ngx_http_finalize_request(r, + NGX_HTTP_REQUEST_HEADER_TOO_LARGE); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (rc != NGX_OK) { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + /* null-terminate the last processed header name or value */ + *buf->pos = '\0'; + + buf = r->header_in; + + sc->zstream_in.next_out = buf->last; + + /* one byte is reserved for null-termination */ + sc->zstream_in.avail_out = buf->end - buf->last - 1; + + z = inflate(&sc->zstream_in, Z_NO_FLUSH); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + z); + + if (z != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, z); + } + + sc->length -= sc->zstream_in.next_in - pos; + pos = sc->zstream_in.next_in; + + buf->last = sc->zstream_in.next_out; + + continue; + } + + if (sc->length == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "premature end of spdy header block"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + + case NGX_HTTP_PARSE_INVALID_HEADER: + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + + default: /* NGX_ERROR */ + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + /* a header line has been parsed successfully */ + + rc = ngx_http_spdy_handle_request_header(r); + + if (rc != NGX_OK) { + if (rc == NGX_HTTP_PARSE_INVALID_HEADER) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (rc != NGX_ABORT) { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + } + + if (buf->pos != buf->last || sc->zstream_in.avail_in) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "incorrect number of spdy header block entries"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + if (sc->length) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + /* null-terminate the last header value */ + *buf->pos = '\0'; + + ngx_http_spdy_run_request(r); + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + int n; + size_t size; + u_char buffer[NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE]; + + if (sc->length == 0) { + return ngx_http_spdy_state_complete(sc, pos, end); + } + + size = end - pos; + + if (size == 0) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers_skip); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy header block skip %uz of %uz", size, sc->length); + + sc->zstream_in.next_in = pos; + sc->zstream_in.avail_in = (size < sc->length) ? size : sc->length; + + while (sc->zstream_in.avail_in) { + sc->zstream_in.next_out = buffer; + sc->zstream_in.avail_out = NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE; + + n = inflate(&sc->zstream_in, Z_NO_FLUSH); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + n); + + if (n != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, n); + } + } + + pos = sc->zstream_in.next_in; + + if (size < sc->length) { + sc->length -= size; + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers_skip); + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_http_spdy_stream_t *stream; + + stream = sc->stream; + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame for stream %ui " + "with invalid header block", stream->id); + + if (ngx_http_spdy_send_rst_stream(sc, stream->id, NGX_SPDY_PROTOCOL_ERROR, + stream->priority) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->out_closed = 1; + + ngx_http_spdy_close_stream(stream, NGX_HTTP_BAD_REQUEST); + + return ngx_http_spdy_state_headers_skip(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t delta; + ngx_uint_t sid; + ngx_event_t *wev; + ngx_queue_t *q; + ngx_http_spdy_stream_t *stream; + + if (end - pos < NGX_SPDY_WINDOW_UPDATE_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_window_update); + } + + if (sc->length != NGX_SPDY_WINDOW_UPDATE_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent WINDOW_UPDATE frame " + "with incorrect length %uz", sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sid = ngx_spdy_frame_parse_sid(pos); + + pos += NGX_SPDY_SID_SIZE; + + delta = ngx_spdy_frame_parse_delta(pos); + + pos += NGX_SPDY_DELTA_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy WINDOW_UPDATE sid:%ui delta:%uz", sid, delta); + + if (sid) { + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + if (stream->send_window > (ssize_t) (NGX_SPDY_MAX_WINDOW - delta)) { + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated flow control for stream %ui: " + "received WINDOW_UPDATE frame with delta %uz " + "not allowed for window %z", + sid, delta, stream->send_window); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + stream->send_window += delta; + + if (stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + } + } + + } else { + sc->send_window += delta; + + if (sc->send_window > NGX_SPDY_MAX_WINDOW) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated connection flow control: " + "received WINDOW_UPDATE frame with delta %uz " + "not allowed for window %uz", + delta, sc->send_window); + + return ngx_http_spdy_state_protocol_error(sc); + } + + while (!ngx_queue_empty(&sc->waiting)) { + q = ngx_queue_head(&sc->waiting); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + stream->handled = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + + if (sc->send_window == 0) { + break; + } + } + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_http_spdy_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy DATA frame"); + + if (sc->length > sc->recv_window) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated connection flow control: " + "received DATA frame length %uz, available window %uz", + sc->length, sc->recv_window); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->recv_window -= sc->length; + + if (sc->recv_window < NGX_SPDY_MAX_WINDOW / 4) { + + if (ngx_http_spdy_send_window_update(sc, 0, + NGX_SPDY_MAX_WINDOW + - sc->recv_window) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + sc->recv_window = NGX_SPDY_MAX_WINDOW; + } + + stream = sc->stream; + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + if (sc->length > stream->recv_window) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated flow control for stream %ui: " + "received DATA frame length %uz, available window %uz", + stream->id, sc->length, stream->recv_window); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + stream->recv_window -= sc->length; + + if (stream->recv_window < NGX_SPDY_STREAM_WINDOW / 4) { + + if (ngx_http_spdy_send_window_update(sc, stream->id, + NGX_SPDY_STREAM_WINDOW + - stream->recv_window) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->recv_window = NGX_SPDY_STREAM_WINDOW; + } + + if (stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent DATA frame for half-closed stream %ui", + stream->id); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_STREAM_ALREADY_CLOSED) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + return ngx_http_spdy_state_read_data(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t size; + ssize_t n; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_temp_file_t *tf; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + stream = sc->stream; + + if (stream == NULL) { + return ngx_http_spdy_state_skip(sc, pos, end); + } + + if (stream->skip_data) { + + if (sc->flags & NGX_SPDY_FLAG_FIN) { + stream->in_closed = 1; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "skipping spdy DATA frame, reason: %d", + stream->skip_data); + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + size = end - pos; + + if (size > sc->length) { + size = sc->length; + } + + r = stream->request; + + if (r->request_body == NULL + && ngx_http_spdy_init_request_body(r) != NGX_OK) + { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + return ngx_http_spdy_state_skip(sc, pos, end); + } + + rb = r->request_body; + tf = rb->temp_file; + buf = rb->buf; + + if (size) { + rb->rest += size; + + if (r->headers_in.content_length_n != -1 + && r->headers_in.content_length_n < rb->rest) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client intended to send body data " + "larger than declared"); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + + } else { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->client_max_body_size + && clcf->client_max_body_size < rb->rest) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send " + "too large chunked body: %O bytes", rb->rest); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + } + } + + sc->length -= size; + + if (tf) { + buf->start = pos; + buf->pos = pos; + + pos += size; + + buf->end = pos; + buf->last = pos; + + n = ngx_write_chain_to_temp_file(tf, rb->bufs); + + /* TODO: n == 0 or not complete and level event */ + + if (n == NGX_ERROR) { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + goto error; + } + + tf->offset += n; + + } else { + buf->last = ngx_cpymem(buf->last, pos, size); + pos += size; + } + + r->request_length += size; + } + + if (sc->length) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_read_data); + } + + if (sc->flags & NGX_SPDY_FLAG_FIN) { + + stream->in_closed = 1; + + if (r->headers_in.content_length_n < 0) { + r->headers_in.content_length_n = rb->rest; + + } else if (r->headers_in.content_length_n != rb->rest) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream: " + "only %O out of %O bytes of request body received", + rb->rest, r->headers_in.content_length_n); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + } + + if (tf) { + ngx_memzero(buf, sizeof(ngx_buf_t)); + + buf->in_file = 1; + buf->file_last = tf->file.offset; + buf->file = &tf->file; + + rb->buf = NULL; + } + + if (rb->post_handler) { + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); + +error: + + if (rb->post_handler) { + + if (stream->skip_data == NGX_SPDY_DATA_ERROR) { + rc = (r->headers_in.content_length_n == -1) + ? NGX_HTTP_REQUEST_ENTITY_TOO_LARGE + : NGX_HTTP_BAD_REQUEST; + + } else { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_http_finalize_request(r, rc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_rst_stream(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t sid, status; + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_spdy_stream_t *stream; + + if (end - pos < NGX_SPDY_RST_STREAM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_rst_stream); + } + + if (sc->length != NGX_SPDY_RST_STREAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent RST_STREAM frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sid = ngx_spdy_frame_parse_sid(pos); + + pos += NGX_SPDY_SID_SIZE; + + status = ngx_spdy_frame_parse_uint32(pos); + + pos += sizeof(uint32_t); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy RST_STREAM sid:%ui st:%ui", sid, status); + + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + stream->in_closed = 1; + stream->out_closed = 1; + + fc = stream->request->connection; + fc->error = 1; + + switch (status) { + + case NGX_SPDY_CANCEL: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client canceled stream %ui", sid); + break; + + case NGX_SPDY_INTERNAL_ERROR: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui due to internal error", + sid); + break; + + default: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui with status %ui", + sid, status); + break; + } + + ev = fc->read; + ev->handler(ev); + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_ping(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + if (end - pos < NGX_SPDY_PING_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_ping); + } + + if (sc->length != NGX_SPDY_PING_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent PING frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy PING frame"); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_PING_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return ngx_http_spdy_state_internal_error(sc); + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_PING); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_PING_SIZE); + + p = ngx_cpymem(p, pos, NGX_SPDY_PING_SIZE); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + pos += NGX_SPDY_PING_SIZE; + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_skip(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t size; + + size = end - pos; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame skip %uz of %uz", size, sc->length); + + if (size < sc->length) { + sc->length -= size; + return ngx_http_spdy_state_save(sc, end, end, + ngx_http_spdy_state_skip); + } + + return ngx_http_spdy_state_complete(sc, pos + sc->length, end); +} + + +static u_char * +ngx_http_spdy_state_settings(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t fid, val; + + if (sc->entries == 0) { + + if (end - pos < NGX_SPDY_SETTINGS_NUM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_settings); + } + + sc->entries = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_NUM_SIZE; + sc->length -= NGX_SPDY_SETTINGS_NUM_SIZE; + + if (sc->length < sc->entries * NGX_SPDY_SETTINGS_PAIR_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SETTINGS frame with incorrect " + "length %uz or number of entries %ui", + sc->length, sc->entries); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SETTINGS frame has %ui entries", sc->entries); + } + + while (sc->entries) { + if (end - pos < NGX_SPDY_SETTINGS_PAIR_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_settings); + } + + sc->entries--; + sc->length -= NGX_SPDY_SETTINGS_PAIR_SIZE; + + fid = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_FID_SIZE; + + val = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_VAL_SIZE; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SETTINGS entry fl:%ui id:%ui val:%ui", + ngx_spdy_frame_flags(fid), ngx_spdy_frame_id(fid), val); + + if (ngx_spdy_frame_flags(fid) == NGX_SPDY_SETTINGS_FLAG_PERSISTED) { + continue; + } + + switch (ngx_spdy_frame_id(fid)) { + + case NGX_SPDY_SETTINGS_INIT_WINDOW: + + if (val > NGX_SPDY_MAX_WINDOW) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SETTINGS frame with " + "incorrect INIT_WINDOW value: %ui", val); + + return ngx_http_spdy_state_protocol_error(sc); + } + + if (ngx_http_spdy_adjust_windows(sc, val - sc->init_window) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + sc->init_window = val; + + continue; + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_complete(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame complete pos:%p end:%p", pos, end); + + if (pos > end) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "receive buffer overrun"); + + ngx_debug_point(); + return ngx_http_spdy_state_internal_error(sc); + } + + sc->handler = ngx_http_spdy_state_head; + sc->stream = NULL; + + return pos; +} + + +static u_char * +ngx_http_spdy_state_save(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end, ngx_http_spdy_handler_pt handler) +{ + size_t size; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame state save pos:%p end:%p handler:%p", + pos, end, handler); + + size = end - pos; + + if (size > NGX_SPDY_STATE_BUFFER_SIZE) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "state buffer overflow: %uz bytes required", size); + + ngx_debug_point(); + return ngx_http_spdy_state_internal_error(sc); + } + + ngx_memcpy(sc->buffer, pos, NGX_SPDY_STATE_BUFFER_SIZE); + + sc->buffer_used = size; + sc->handler = handler; + sc->incomplete = 1; + + return end; +} + + +static u_char * +ngx_http_spdy_state_inflate_error(ngx_http_spdy_connection_t *sc, int rc) +{ + if (rc == Z_DATA_ERROR || rc == Z_STREAM_END) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame with " + "corrupted header block, inflate() failed: %d", rc); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_error(NGX_LOG_ERR, sc->connection->log, 0, + "inflate() failed: %d", rc); + + return ngx_http_spdy_state_internal_error(sc); +} + + +static u_char * +ngx_http_spdy_state_protocol_error(ngx_http_spdy_connection_t *sc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy state protocol error"); + + if (sc->stream) { + sc->stream->out_closed = 1; + ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_BAD_REQUEST); + } + + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + + return NULL; +} + + +static u_char * +ngx_http_spdy_state_internal_error(ngx_http_spdy_connection_t *sc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy state internal error"); + + if (sc->stream) { + sc->stream->out_closed = 1; + ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR); + + return NULL; +} + + +static ngx_int_t +ngx_http_spdy_send_window_update(ngx_http_spdy_connection_t *sc, ngx_uint_t sid, + ngx_uint_t delta) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send WINDOW_UPDATE sid:%ui delta:%ui", sid, delta); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_WINDOW_UPDATE_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_WINDOW_UPDATE); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_WINDOW_UPDATE_SIZE); + + p = ngx_spdy_frame_write_sid(p, sid); + p = ngx_spdy_frame_aligned_write_uint32(p, delta); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t sid, + ngx_uint_t status, ngx_uint_t priority) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + if (sc->connection->error) { + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send RST_STREAM sid:%ui st:%ui", sid, status); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_RST_STREAM_SIZE, + priority); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_RST_STREAM); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_RST_STREAM_SIZE); + + p = ngx_spdy_frame_write_sid(p, sid); + p = ngx_spdy_frame_aligned_write_uint32(p, status); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +#if 0 +static ngx_int_t +ngx_http_spdy_send_goaway(ngx_http_spdy_connection_t *sc) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send GOAWAY sid:%ui", sc->last_sid); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_GOAWAY_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_GOAWAY); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_GOAWAY_SIZE); + + p = ngx_spdy_frame_write_sid(p, sc->last_sid); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} +#endif + + +static ngx_int_t +ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc) +{ + u_char *p; + ngx_buf_t *buf; + ngx_chain_t *cl; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send SETTINGS frame"); + + frame = ngx_palloc(sc->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + cl = ngx_alloc_chain_link(sc->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + buf = ngx_create_temp_buf(sc->pool, NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE); + if (buf == NULL) { + return NGX_ERROR; + } + + buf->last_buf = 1; + + cl->buf = buf; + cl->next = NULL; + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_settings_frame_handler; + frame->stream = NULL; +#if (NGX_DEBUG) + frame->length = NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE; +#endif + frame->priority = NGX_SPDY_HIGHEST_PRIORITY; + frame->blocked = 0; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_SETTINGS); + p = ngx_spdy_frame_write_flags_and_len(p, NGX_SPDY_FLAG_CLEAR_SETTINGS, + NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE); + + p = ngx_spdy_frame_aligned_write_uint32(p, 2); + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + p = ngx_spdy_frame_write_flags_and_id(p, 0, NGX_SPDY_SETTINGS_MAX_STREAMS); + p = ngx_spdy_frame_aligned_write_uint32(p, sscf->concurrent_streams); + + p = ngx_spdy_frame_write_flags_and_id(p, 0, NGX_SPDY_SETTINGS_INIT_WINDOW); + p = ngx_spdy_frame_aligned_write_uint32(p, NGX_SPDY_STREAM_WINDOW); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_http_spdy_settings_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + ngx_free_chain(sc->pool, frame->first); + + return NGX_OK; +} + + +static ngx_http_spdy_out_frame_t * +ngx_http_spdy_get_ctl_frame(ngx_http_spdy_connection_t *sc, size_t length, + ngx_uint_t priority) +{ + ngx_chain_t *cl; + ngx_http_spdy_out_frame_t *frame; + + frame = sc->free_ctl_frames; + + if (frame) { + sc->free_ctl_frames = frame->next; + + cl = frame->first; + cl->buf->pos = cl->buf->start; + + } else { + frame = ngx_palloc(sc->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NULL; + } + + cl = ngx_alloc_chain_link(sc->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = ngx_create_temp_buf(sc->pool, + NGX_SPDY_CTL_FRAME_BUFFER_SIZE); + if (cl->buf == NULL) { + return NULL; + } + + cl->buf->last_buf = 1; + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_ctl_frame_handler; + frame->stream = NULL; + } + +#if (NGX_DEBUG) + if (length > NGX_SPDY_CTL_FRAME_BUFFER_SIZE - NGX_SPDY_FRAME_HEADER_SIZE) { + ngx_log_error(NGX_LOG_ALERT, sc->pool->log, 0, + "requested control frame is too large: %uz", length); + return NULL; + } + + frame->length = length; +#endif + + frame->priority = priority; + frame->blocked = 0; + + return frame; +} + + +static ngx_int_t +ngx_http_spdy_ctl_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + frame->next = sc->free_ctl_frames; + sc->free_ctl_frames = frame; + + return NGX_OK; +} + + +static ngx_http_spdy_stream_t * +ngx_http_spdy_create_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t id, + ngx_uint_t priority) +{ + ngx_log_t *log; + ngx_uint_t index; + ngx_event_t *rev, *wev; + ngx_connection_t *fc; + ngx_http_log_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_srv_conf_t *sscf; + + fc = sc->free_fake_connections; + + if (fc) { + sc->free_fake_connections = fc->data; + + rev = fc->read; + wev = fc->write; + log = fc->log; + ctx = log->data; + + } else { + fc = ngx_palloc(sc->pool, sizeof(ngx_connection_t)); + if (fc == NULL) { + return NULL; + } + + rev = ngx_palloc(sc->pool, sizeof(ngx_event_t)); + if (rev == NULL) { + return NULL; + } + + wev = ngx_palloc(sc->pool, sizeof(ngx_event_t)); + if (wev == NULL) { + return NULL; + } + + log = ngx_palloc(sc->pool, sizeof(ngx_log_t)); + if (log == NULL) { + return NULL; + } + + ctx = ngx_palloc(sc->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + return NULL; + } + + ctx->connection = fc; + ctx->request = NULL; + } + + ngx_memcpy(log, sc->connection->log, sizeof(ngx_log_t)); + + log->data = ctx; + + ngx_memzero(rev, sizeof(ngx_event_t)); + + rev->data = fc; + rev->ready = 1; + rev->handler = ngx_http_spdy_close_stream_handler; + rev->log = log; + + ngx_memcpy(wev, rev, sizeof(ngx_event_t)); + + wev->write = 1; + + ngx_memcpy(fc, sc->connection, sizeof(ngx_connection_t)); + + fc->data = sc->http_connection; + fc->read = rev; + fc->write = wev; + fc->sent = 0; + fc->log = log; + fc->buffered = 0; + fc->sndlowat = 1; + fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + r = ngx_http_create_request(fc); + if (r == NULL) { + return NULL; + } + + r->valid_location = 1; + + fc->data = r; + sc->connection->requests++; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + stream = ngx_pcalloc(r->pool, sizeof(ngx_http_spdy_stream_t)); + if (stream == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->spdy_stream = stream; + + stream->id = id; + stream->request = r; + stream->connection = sc; + + stream->send_window = sc->init_window; + stream->recv_window = NGX_SPDY_STREAM_WINDOW; + + stream->priority = priority; + + sscf = ngx_http_get_module_srv_conf(r, ngx_http_spdy_module); + + index = ngx_http_spdy_stream_index(sscf, id); + + stream->index = sc->streams_index[index]; + sc->streams_index[index] = stream; + + sc->processing++; + + return stream; +} + + +static ngx_http_spdy_stream_t * +ngx_http_spdy_get_stream_by_id(ngx_http_spdy_connection_t *sc, + ngx_uint_t sid) +{ + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + stream = sc->streams_index[ngx_http_spdy_stream_index(sscf, sid)]; + + while (stream) { + if (stream->id == sid) { + return stream; + } + + stream = stream->index; + } + + return NULL; +} + + +static ngx_int_t +ngx_http_spdy_parse_header(ngx_http_request_t *r) +{ + u_char *p, *end, ch; + ngx_uint_t hash; + ngx_http_core_srv_conf_t *cscf; + + enum { + sw_name_len = 0, + sw_name, + sw_value_len, + sw_value + } state; + + state = r->state; + + p = r->header_in->pos; + end = r->header_in->last; + + switch (state) { + + case sw_name_len: + + if (end - p < NGX_SPDY_NV_NLEN_SIZE) { + return NGX_AGAIN; + } + + r->lowcase_index = ngx_spdy_frame_parse_uint32(p); + + if (r->lowcase_index == 0) { + return NGX_ERROR; + } + + /* null-terminate the previous header value */ + *p = '\0'; + + p += NGX_SPDY_NV_NLEN_SIZE; + + r->invalid_header = 0; + + state = sw_name; + + /* fall through */ + + case sw_name: + + if ((ngx_uint_t) (end - p) < r->lowcase_index) { + break; + } + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_name_start = p; + r->header_name_end = p + r->lowcase_index; + + if (p[0] == ':') { + p++; + } + + hash = 0; + + for ( /* void */ ; p != r->header_name_end; p++) { + + ch = *p; + + hash = ngx_hash(hash, ch); + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + switch (ch) { + case '\0': + case LF: + case CR: + case ':': + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%*s\"", + r->lowcase_index, r->header_name_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ch >= 'A' && ch <= 'Z') { + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + r->header_hash = hash; + + state = sw_value_len; + + /* fall through */ + + case sw_value_len: + + if (end - p < NGX_SPDY_NV_VLEN_SIZE) { + break; + } + + r->lowcase_index = ngx_spdy_frame_parse_uint32(p); + + /* null-terminate header name */ + *p = '\0'; + + p += NGX_SPDY_NV_VLEN_SIZE; + + state = sw_value; + + /* fall through */ + + case sw_value: + + if ((ngx_uint_t) (end - p) < r->lowcase_index) { + break; + } + + r->header_start = p; + + while (r->lowcase_index--) { + ch = *p; + + if (ch == '\0') { + + if (p == r->header_start) { + return NGX_ERROR; + } + + r->header_end = p; + r->header_in->pos = p + 1; + + r->state = sw_value; + + return NGX_OK; + } + + if (ch == CR || ch == LF) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%*s\" with " + "invalid value: \"%*s\\%c...\"", + r->header_name_end - r->header_name_start, + r->header_name_start, + p - r->header_start, + r->header_start, + ch == CR ? 'r' : 'n'); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p++; + } + + r->header_end = p; + r->header_in->pos = p; + + r->state = 0; + + return NGX_DONE; + } + + r->header_in->pos = p; + r->state = state; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r) +{ + u_char *old, *new, *p; + size_t rest; + ngx_buf_t *buf; + ngx_http_spdy_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy alloc large header buffer"); + + stream = r->spdy_stream; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (stream->header_buffers + == (ngx_uint_t) cscf->large_client_header_buffers.num) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too large request"); + + return NGX_DECLINED; + } + + rest = r->header_in->last - r->header_in->pos; + + /* + * One more byte is needed for null-termination + * and another one for further progress. + */ + if (rest > cscf->large_client_header_buffers.size - 2) { + p = r->header_in->pos; + + if (rest > NGX_MAX_ERROR_STR - 300) { + rest = NGX_MAX_ERROR_STR - 300; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too long header name or value: \"%*s...\"", + rest, p); + + return NGX_DECLINED; + } + + buf = ngx_create_temp_buf(r->pool, cscf->large_client_header_buffers.size); + if (buf == NULL) { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy large header alloc: %p %uz", + buf->pos, buf->end - buf->last); + + old = r->header_in->pos; + new = buf->pos; + + if (rest) { + buf->last = ngx_cpymem(new, old, rest); + } + + r->header_in = buf; + + stream->header_buffers++; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_handle_request_header(ngx_http_request_t *r) +{ + ngx_uint_t i; + ngx_table_elt_t *h; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_request_header_t *sh; + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%*s\"", + r->header_end - r->header_name_start, + r->header_name_start); + return NGX_OK; + } + + } + + if (r->header_name_start[0] == ':') { + r->header_name_start++; + + for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) { + sh = &ngx_http_spdy_request_headers[i]; + + if (sh->hash != r->header_hash + || sh->len != r->header_name_end - r->header_name_start + || ngx_strncmp(sh->header, r->header_name_start, sh->len) != 0) + { + continue; + } + + return sh->handler(r); + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \":%*s\"", + r->header_end - r->header_name_start, + r->header_name_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = r->header_hash; + + h->key.len = r->header_name_end - r->header_name_start; + h->key.data = r->header_name_start; + + h->value.len = r->header_end - r->header_start; + h->value.data = r->header_start; + + h->lowcase_key = h->key.data; + + return NGX_OK; +} + + +void +ngx_http_spdy_request_headers_init(void) +{ + ngx_uint_t i; + ngx_http_spdy_request_header_t *h; + + for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) { + h = &ngx_http_spdy_request_headers[i]; + h->hash = ngx_hash_key(h->header, h->len); + } +} + + +static ngx_int_t +ngx_http_spdy_parse_method(ngx_http_request_t *r) +{ + size_t k, len; + ngx_uint_t n; + const u_char *p, *m; + + /* + * This array takes less than 256 sequential bytes, + * and if typical CPU cache line size is 64 bytes, + * it is prefetched for 4 load operations. + */ + static const struct { + u_char len; + const u_char method[11]; + uint32_t value; + } tests[] = { + { 3, "GET", NGX_HTTP_GET }, + { 4, "POST", NGX_HTTP_POST }, + { 4, "HEAD", NGX_HTTP_HEAD }, + { 7, "OPTIONS", NGX_HTTP_OPTIONS }, + { 8, "PROPFIND", NGX_HTTP_PROPFIND }, + { 3, "PUT", NGX_HTTP_PUT }, + { 5, "MKCOL", NGX_HTTP_MKCOL }, + { 6, "DELETE", NGX_HTTP_DELETE }, + { 4, "COPY", NGX_HTTP_COPY }, + { 4, "MOVE", NGX_HTTP_MOVE }, + { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, + { 4, "LOCK", NGX_HTTP_LOCK }, + { 6, "UNLOCK", NGX_HTTP_UNLOCK }, + { 5, "PATCH", NGX_HTTP_PATCH }, + { 5, "TRACE", NGX_HTTP_TRACE } + }, *test; + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :method header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + len = r->header_end - r->header_start; + + r->method_name.len = len; + r->method_name.data = r->header_start; + + test = tests; + n = sizeof(tests) / sizeof(tests[0]); + + do { + if (len == test->len) { + p = r->method_name.data; + m = test->method; + k = len; + + do { + if (*p++ != *m++) { + goto next; + } + } while (--k); + + r->method = test->value; + return NGX_OK; + } + + next: + test++; + + } while (--n); + + p = r->method_name.data; + + do { + if ((*p < 'A' || *p > 'Z') && *p != '_') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", + &r->method_name); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p++; + + } while (--len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_scheme(ngx_http_request_t *r) +{ + if (r->schema_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :schema header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + r->schema_start = r->header_start; + r->schema_end = r->header_end; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_host(ngx_http_request_t *r) +{ + ngx_table_elt_t *h; + + if (r->headers_in.host) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :host header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + r->headers_in.host = h; + + h->hash = r->header_hash; + + h->key.len = r->header_name_end - r->header_name_start; + h->key.data = r->header_name_start; + + h->value.len = r->header_end - r->header_start; + h->value.data = r->header_start; + + h->lowcase_key = h->key.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_path(ngx_http_request_t *r) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + r->uri_start = r->header_start; + r->uri_end = r->header_end; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid URI: \"%*s\"", + r->uri_end - r->uri_start, r->uri_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_request_uri() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_version(ngx_http_request_t *r) +{ + u_char *p, ch; + + if (r->http_protocol.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :version header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p = r->header_start; + + if (r->header_end - p < 8 || !(ngx_str5cmp(p, 'H', 'T', 'T', 'P', '/'))) { + goto invalid; + } + + ch = *(p + 5); + + if (ch < '1' || ch > '9') { + goto invalid; + } + + r->http_major = ch - '0'; + + for (p += 6; p != r->header_end - 2; p++) { + + ch = *p; + + if (ch == '.') { + break; + } + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_major = r->http_major * 10 + ch - '0'; + } + + if (*p != '.') { + goto invalid; + } + + ch = *(p + 1); + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_minor = ch - '0'; + + for (p += 2; p != r->header_end; p++) { + + ch = *p; + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_minor = r->http_minor * 10 + ch - '0'; + } + + r->http_protocol.len = r->header_end - r->header_start; + r->http_protocol.data = r->header_start; + r->http_version = r->http_major * 1000 + r->http_minor; + + return NGX_OK; + +invalid: + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid http version: \"%*s\"", + r->header_end - r->header_start, r->header_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; +} + + +static ngx_int_t +ngx_http_spdy_construct_request_line(ngx_http_request_t *r) +{ + u_char *p; + + if (r->method_name.len == 0 + || r->unparsed_uri.len == 0 + || r->http_protocol.len == 0) + { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + r->request_line.len = r->method_name.len + 1 + + r->unparsed_uri.len + 1 + + r->http_protocol.len; + + p = ngx_pnalloc(r->pool, r->request_line.len + 1); + if (p == NULL) { + ngx_http_spdy_close_stream(r->spdy_stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + + *p++ = ' '; + + p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); + + *p++ = ' '; + + ngx_memcpy(p, r->http_protocol.data, r->http_protocol.len + 1); + + /* some modules expect the space character after method name */ + r->method_name.data = r->request_line.data; + + return NGX_OK; +} + + +static void +ngx_http_spdy_run_request(ngx_http_request_t *r) +{ + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + if (ngx_http_spdy_construct_request_line(r) != NGX_OK) { + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy http request line: \"%V\"", &r->request_line); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + part = &r->headers_in.headers.part; + h = part->elts; + + for (i = 0 ;; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + hh = ngx_hash_find(&cmcf->headers_in_hash, h[i].hash, + h[i].lowcase_key, h[i].key.len); + + if (hh && hh->handler(r, &h[i], hh->offset) != NGX_OK) { + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy http header: \"%V: %V\"", &h[i].key, &h[i].value); + } + + r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; + + if (ngx_http_process_request_header(r) != NGX_OK) { + return; + } + + if (r->headers_in.content_length_n > 0 && r->spdy_stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + + r->spdy_stream->skip_data = NGX_SPDY_DATA_ERROR; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; + } + + ngx_http_process_request(r); +} + + +static ngx_int_t +ngx_http_spdy_init_request_body(ngx_http_request_t *r) +{ + ngx_buf_t *buf; + ngx_temp_file_t *tf; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); + if (rb == NULL) { + return NGX_ERROR; + } + + r->request_body = rb; + + if (r->spdy_stream->in_closed) { + return NGX_OK; + } + + rb->rest = r->headers_in.content_length_n; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->request_body_in_file_only + || rb->rest > (off_t) clcf->client_body_buffer_size + || rb->rest < 0) + { + tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); + if (tf == NULL) { + return NGX_ERROR; + } + + tf->file.fd = NGX_INVALID_FILE; + tf->file.log = r->connection->log; + tf->path = clcf->client_body_temp_path; + tf->pool = r->pool; + tf->warn = "a client request body is buffered to a temporary file"; + tf->log_level = r->request_body_file_log_level; + tf->persistent = r->request_body_in_persistent_file; + tf->clean = r->request_body_in_clean_file; + + if (r->request_body_file_group_access) { + tf->access = 0660; + } + + rb->temp_file = tf; + + if (r->spdy_stream->in_closed + && ngx_create_temp_file(&tf->file, tf->path, tf->pool, + tf->persistent, tf->clean, tf->access) + != NGX_OK) + { + return NGX_ERROR; + } + + buf = ngx_calloc_buf(r->pool); + if (buf == NULL) { + return NGX_ERROR; + } + + } else { + + if (rb->rest == 0) { + return NGX_OK; + } + + buf = ngx_create_temp_buf(r->pool, (size_t) rb->rest); + if (buf == NULL) { + return NGX_ERROR; + } + } + + rb->buf = buf; + + rb->bufs = ngx_alloc_chain_link(r->pool); + if (rb->bufs == NULL) { + return NGX_ERROR; + } + + rb->bufs->buf = buf; + rb->bufs->next = NULL; + + rb->rest = 0; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_spdy_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler) +{ + ngx_http_spdy_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy read request body"); + + stream = r->spdy_stream; + + switch (stream->skip_data) { + + case NGX_SPDY_DATA_DISCARD: + post_handler(r); + return NGX_OK; + + case NGX_SPDY_DATA_ERROR: + if (r->headers_in.content_length_n == -1) { + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } else { + return NGX_HTTP_BAD_REQUEST; + } + + case NGX_SPDY_DATA_INTERNAL_ERROR: + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (!r->request_body && ngx_http_spdy_init_request_body(r) != NGX_OK) { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (stream->in_closed) { + post_handler(r); + return NGX_OK; + } + + r->request_body->post_handler = post_handler; + + r->read_event_handler = ngx_http_test_reading; + r->write_event_handler = ngx_http_request_empty_handler; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_spdy_terminate_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream, ngx_uint_t status) +{ + ngx_event_t *rev; + ngx_connection_t *fc; + + if (ngx_http_spdy_send_rst_stream(sc, stream->id, status, + NGX_SPDY_HIGHEST_PRIORITY) + == NGX_ERROR) + { + return NGX_ERROR; + } + + stream->out_closed = 1; + + fc = stream->request->connection; + fc->error = 1; + + rev = fc->read; + rev->handler(rev); + + return NGX_OK; +} + + +static void +ngx_http_spdy_close_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *fc; + ngx_http_request_t *r; + + fc = ev->data; + r = fc->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy close stream handler"); + + ngx_http_spdy_close_stream(r->spdy_stream, 0); +} + + +void +ngx_http_spdy_close_stream(ngx_http_spdy_stream_t *stream, ngx_int_t rc) +{ + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_spdy_stream_t **index, *s; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_connection_t *sc; + + sc = stream->connection; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy close stream %ui, queued %ui, processing %ui", + stream->id, stream->queued, sc->processing); + + fc = stream->request->connection; + + if (stream->queued) { + fc->write->handler = ngx_http_spdy_close_stream_handler; + return; + } + + if (!stream->out_closed) { + if (ngx_http_spdy_send_rst_stream(sc, stream->id, + NGX_SPDY_INTERNAL_ERROR, + stream->priority) + != NGX_OK) + { + sc->connection->error = 1; + } + } + + if (sc->stream == stream) { + sc->stream = NULL; + } + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + index = sc->streams_index + ngx_http_spdy_stream_index(sscf, stream->id); + + for ( ;; ) { + s = *index; + + if (s == NULL) { + break; + } + + if (s == stream) { + *index = s->index; + break; + } + + index = &s->index; + } + + ngx_http_free_request(stream->request, rc); + + ev = fc->read; + + if (ev->active || ev->disabled) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "fake read event was activated"); + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + ev = fc->write; + + if (ev->active || ev->disabled) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "fake write event was activated"); + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + fc->data = sc->free_fake_connections; + sc->free_fake_connections = fc; + + sc->processing--; + + if (sc->processing || sc->blocked) { + return; + } + + ev = sc->connection->read; + + ev->handler = ngx_http_spdy_handle_connection_handler; + ngx_post_event(ev, &ngx_posted_events); +} + + +static void +ngx_http_spdy_handle_connection_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + + rev->handler = ngx_http_spdy_read_handler; + + if (rev->ready) { + ngx_http_spdy_read_handler(rev); + return; + } + + c = rev->data; + + ngx_http_spdy_handle_connection(c->data); +} + + +static void +ngx_http_spdy_keepalive_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy keepalive handler"); + + if (rev->timedout || c->close) { + ngx_http_close_connection(c); + return; + } + +#if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + if (rev->pending_eof) { + c->log->handler = NULL; + ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno, + "kevent() reported that client %V closed " + "keepalive connection", &c->addr_text); +#if (NGX_HTTP_SSL) + if (c->ssl) { + c->ssl->no_send_shutdown = 1; + } +#endif + ngx_http_close_connection(c); + return; + } + } + +#endif + + c->destroyed = 0; + c->idle = 0; + ngx_reusable_connection(c, 0); + + sc = c->data; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log); + if (sc->pool == NULL) { + ngx_http_close_connection(c); + return; + } + + sc->streams_index = ngx_pcalloc(sc->pool, + ngx_http_spdy_streams_index_size(sscf) + * sizeof(ngx_http_spdy_stream_t *)); + if (sc->streams_index == NULL) { + ngx_http_close_connection(c); + return; + } + + c->write->handler = ngx_http_spdy_write_handler; + + rev->handler = ngx_http_spdy_read_handler; + ngx_http_spdy_read_handler(rev); +} + + +static void +ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc, + ngx_int_t rc) +{ + ngx_uint_t i, size; + ngx_event_t *ev; + ngx_connection_t *c, *fc; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + c = sc->connection; + + if (!sc->processing) { + ngx_http_close_connection(c); + return; + } + + c->error = 1; + c->read->handler = ngx_http_empty_handler; + c->write->handler = ngx_http_empty_handler; + + sc->last_out = NULL; + + sc->blocked = 1; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + size = ngx_http_spdy_streams_index_size(sscf); + + for (i = 0; i < size; i++) { + stream = sc->streams_index[i]; + + while (stream) { + stream->handled = 0; + + r = stream->request; + fc = r->connection; + + fc->error = 1; + + if (stream->queued) { + stream->queued = 0; + + ev = fc->write; + ev->delayed = 0; + + } else { + ev = fc->read; + } + + stream = stream->index; + + ev->eof = 1; + ev->handler(ev); + } + } + + sc->blocked = 0; + + if (sc->processing) { + return; + } + + ngx_http_close_connection(c); +} + + +static ngx_int_t +ngx_http_spdy_adjust_windows(ngx_http_spdy_connection_t *sc, ssize_t delta) +{ + ngx_uint_t i, size; + ngx_event_t *wev; + ngx_http_spdy_stream_t *stream, *sn; + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + size = ngx_http_spdy_streams_index_size(sscf); + + for (i = 0; i < size; i++) { + + for (stream = sc->streams_index[i]; stream; stream = sn) { + sn = stream->index; + + if (delta > 0 + && stream->send_window + > (ssize_t) (NGX_SPDY_MAX_WINDOW - delta)) + { + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return NGX_ERROR; + } + + continue; + } + + stream->send_window += delta; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui adjust window:%z", + stream->id, stream->send_window); + + if (stream->send_window > 0 && stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + } + } + } + } + + return NGX_OK; +} + + +static void +ngx_http_spdy_pool_cleanup(void *data) +{ + ngx_http_spdy_connection_t *sc = data; + + if (sc->pool) { + ngx_destroy_pool(sc->pool); + } +} + + +static void * +ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size) +{ + ngx_http_spdy_connection_t *sc = opaque; + + return ngx_palloc(sc->connection->pool, items * size); +} + + +static void +ngx_http_spdy_zfree(void *opaque, void *address) +{ +#if 0 + ngx_http_spdy_connection_t *sc = opaque; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy zfree: %p", address); +#endif +} --- a/src/http/ngx_http_spdy.h 1970-01-01 00:00:00.000000000 +0000 +++ b/src/http/ngx_http_spdy.h 2018-09-28 16:28:07.000000000 +0000 @@ -0,0 +1,261 @@ +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_SPDY_H_INCLUDED_ +#define _NGX_HTTP_SPDY_H_INCLUDED_ + + +#include +#include +#include + +#include + + +#define NGX_SPDY_VERSION 3 + +#define NGX_SPDY_NPN_ADVERTISE "\x08spdy/3.1" +#define NGX_SPDY_NPN_NEGOTIATED "spdy/3.1" + +#define NGX_SPDY_STATE_BUFFER_SIZE 16 + +#define NGX_SPDY_CTL_BIT 1 + +#define NGX_SPDY_SYN_STREAM 1 +#define NGX_SPDY_SYN_REPLY 2 +#define NGX_SPDY_RST_STREAM 3 +#define NGX_SPDY_SETTINGS 4 +#define NGX_SPDY_PING 6 +#define NGX_SPDY_GOAWAY 7 +#define NGX_SPDY_HEADERS 8 +#define NGX_SPDY_WINDOW_UPDATE 9 + +#define NGX_SPDY_FRAME_HEADER_SIZE 8 + +#define NGX_SPDY_SID_SIZE 4 +#define NGX_SPDY_DELTA_SIZE 4 + +#define NGX_SPDY_SYN_STREAM_SIZE 10 +#define NGX_SPDY_SYN_REPLY_SIZE 4 +#define NGX_SPDY_RST_STREAM_SIZE 8 +#define NGX_SPDY_PING_SIZE 4 +#define NGX_SPDY_GOAWAY_SIZE 8 +#define NGX_SPDY_WINDOW_UPDATE_SIZE 8 +#define NGX_SPDY_NV_NUM_SIZE 4 +#define NGX_SPDY_NV_NLEN_SIZE 4 +#define NGX_SPDY_NV_VLEN_SIZE 4 +#define NGX_SPDY_SETTINGS_NUM_SIZE 4 +#define NGX_SPDY_SETTINGS_FID_SIZE 4 +#define NGX_SPDY_SETTINGS_VAL_SIZE 4 + +#define NGX_SPDY_SETTINGS_PAIR_SIZE \ + (NGX_SPDY_SETTINGS_FID_SIZE + NGX_SPDY_SETTINGS_VAL_SIZE) + +#define NGX_SPDY_HIGHEST_PRIORITY 0 +#define NGX_SPDY_LOWEST_PRIORITY 7 + +#define NGX_SPDY_FLAG_FIN 0x01 +#define NGX_SPDY_FLAG_UNIDIRECTIONAL 0x02 +#define NGX_SPDY_FLAG_CLEAR_SETTINGS 0x01 + +#define NGX_SPDY_MAX_FRAME_SIZE ((1 << 24) - 1) + +#define NGX_SPDY_DATA_DISCARD 1 +#define NGX_SPDY_DATA_ERROR 2 +#define NGX_SPDY_DATA_INTERNAL_ERROR 3 + + +typedef struct ngx_http_spdy_connection_s ngx_http_spdy_connection_t; +typedef struct ngx_http_spdy_out_frame_s ngx_http_spdy_out_frame_t; + + +typedef u_char *(*ngx_http_spdy_handler_pt) (ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); + +struct ngx_http_spdy_connection_s { + ngx_connection_t *connection; + ngx_http_connection_t *http_connection; + + ngx_uint_t processing; + + size_t send_window; + size_t recv_window; + size_t init_window; + + ngx_queue_t waiting; + + u_char buffer[NGX_SPDY_STATE_BUFFER_SIZE]; + size_t buffer_used; + ngx_http_spdy_handler_pt handler; + + z_stream zstream_in; + z_stream zstream_out; + + ngx_pool_t *pool; + + ngx_http_spdy_out_frame_t *free_ctl_frames; + ngx_connection_t *free_fake_connections; + + ngx_http_spdy_stream_t **streams_index; + + ngx_http_spdy_out_frame_t *last_out; + + ngx_queue_t posted; + + ngx_http_spdy_stream_t *stream; + + ngx_uint_t entries; + size_t length; + u_char flags; + + ngx_uint_t last_sid; + + unsigned blocked:1; + unsigned incomplete:1; +}; + + +struct ngx_http_spdy_stream_s { + ngx_uint_t id; + ngx_http_request_t *request; + ngx_http_spdy_connection_t *connection; + ngx_http_spdy_stream_t *index; + + ngx_uint_t header_buffers; + ngx_uint_t queued; + + /* + * A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the + * send_window to become negative, hence it's signed. + */ + ssize_t send_window; + size_t recv_window; + + ngx_http_spdy_out_frame_t *free_frames; + ngx_chain_t *free_data_headers; + ngx_chain_t *free_bufs; + + ngx_queue_t queue; + + unsigned priority:3; + unsigned handled:1; + unsigned blocked:1; + unsigned exhausted:1; + unsigned in_closed:1; + unsigned out_closed:1; + unsigned skip_data:2; +}; + + +struct ngx_http_spdy_out_frame_s { + ngx_http_spdy_out_frame_t *next; + ngx_chain_t *first; + ngx_chain_t *last; + ngx_int_t (*handler)(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame); + + ngx_http_spdy_stream_t *stream; + size_t length; + + ngx_uint_t priority; + unsigned blocked:1; + unsigned fin:1; +}; + + +static ngx_inline void +ngx_http_spdy_queue_frame(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_spdy_out_frame_t **out; + + for (out = &sc->last_out; *out; out = &(*out)->next) + { + /* + * NB: higher values represent lower priorities. + */ + if (frame->priority >= (*out)->priority) { + break; + } + } + + frame->next = *out; + *out = frame; +} + + +static ngx_inline void +ngx_http_spdy_queue_blocked_frame(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_spdy_out_frame_t **out; + + for (out = &sc->last_out; *out; out = &(*out)->next) + { + if ((*out)->blocked) { + break; + } + } + + frame->next = *out; + *out = frame; +} + + +void ngx_http_spdy_init(ngx_event_t *rev); +void ngx_http_spdy_request_headers_init(void); + +ngx_int_t ngx_http_spdy_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler); + +void ngx_http_spdy_close_stream(ngx_http_spdy_stream_t *stream, ngx_int_t rc); + +ngx_int_t ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc); + + +#define ngx_spdy_frame_aligned_write_uint16(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_spdy_frame_aligned_write_uint32(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_spdy_frame_write_uint16 ngx_spdy_frame_aligned_write_uint16 +#define ngx_spdy_frame_write_uint32 ngx_spdy_frame_aligned_write_uint32 + +#else + +#define ngx_spdy_frame_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_spdy_frame_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + + +#define ngx_spdy_ctl_frame_head(t) \ + ((uint32_t) NGX_SPDY_CTL_BIT << 31 | NGX_SPDY_VERSION << 16 | (t)) + +#define ngx_spdy_frame_write_head(p, t) \ + ngx_spdy_frame_aligned_write_uint32(p, ngx_spdy_ctl_frame_head(t)) + +#define ngx_spdy_frame_write_flags_and_len(p, f, l) \ + ngx_spdy_frame_aligned_write_uint32(p, (f) << 24 | (l)) +#define ngx_spdy_frame_write_flags_and_id(p, f, i) \ + ngx_spdy_frame_aligned_write_uint32(p, (f) << 24 | (i)) + +#define ngx_spdy_frame_write_sid ngx_spdy_frame_aligned_write_uint32 +#define ngx_spdy_frame_write_window ngx_spdy_frame_aligned_write_uint32 + +#endif /* _NGX_HTTP_SPDY_H_INCLUDED_ */ --- a/src/http/ngx_http_spdy_filter_module.c 1970-01-01 00:00:00.000000000 +0000 +++ b/src/http/ngx_http_spdy_filter_module.c 2018-09-28 16:28:50.000000000 +0000 @@ -0,0 +1,1222 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include +#include + +#include + + +#define ngx_http_spdy_nv_nsize(h) (NGX_SPDY_NV_NLEN_SIZE + sizeof(h) - 1) +#define ngx_http_spdy_nv_vsize(h) (NGX_SPDY_NV_VLEN_SIZE + sizeof(h) - 1) + +#define ngx_http_spdy_nv_write_num ngx_spdy_frame_write_uint32 +#define ngx_http_spdy_nv_write_nlen ngx_spdy_frame_write_uint32 +#define ngx_http_spdy_nv_write_vlen ngx_spdy_frame_write_uint32 + +#define ngx_http_spdy_nv_write_name(p, h) \ + ngx_cpymem(ngx_http_spdy_nv_write_nlen(p, sizeof(h) - 1), h, sizeof(h) - 1) + +#define ngx_http_spdy_nv_write_val(p, h) \ + ngx_cpymem(ngx_http_spdy_nv_write_vlen(p, sizeof(h) - 1), h, sizeof(h) - 1) + + +static ngx_chain_t *ngx_http_spdy_send_chain(ngx_connection_t *fc, + ngx_chain_t *in, off_t limit); + +static ngx_inline ngx_int_t ngx_http_spdy_filter_send( + ngx_connection_t *fc, ngx_http_spdy_stream_t *stream); +static ngx_inline ngx_int_t ngx_http_spdy_flow_control( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_stream_t *stream); +static void ngx_http_spdy_waiting_queue(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream); + +static ngx_chain_t *ngx_http_spdy_filter_get_shadow( + ngx_http_spdy_stream_t *stream, ngx_buf_t *buf, off_t offset, off_t size); +static ngx_http_spdy_out_frame_t *ngx_http_spdy_filter_get_data_frame( + ngx_http_spdy_stream_t *stream, size_t len, ngx_chain_t *first, + ngx_chain_t *last); + +static ngx_int_t ngx_http_spdy_syn_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_int_t ngx_http_spdy_data_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_inline void ngx_http_spdy_handle_frame( + ngx_http_spdy_stream_t *stream, ngx_http_spdy_out_frame_t *frame); +static ngx_inline void ngx_http_spdy_handle_stream( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_stream_t *stream); + +static void ngx_http_spdy_filter_cleanup(void *data); + +static ngx_int_t ngx_http_spdy_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_spdy_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_spdy_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_spdy_filter_module = { + NGX_MODULE_V1, + &ngx_http_spdy_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; + + +static ngx_int_t +ngx_http_spdy_header_filter(ngx_http_request_t *r) +{ + int rc; + size_t len; + u_char *p, *buf, *last; + ngx_buf_t *b; + ngx_str_t host; + ngx_uint_t i, j, count, port; + ngx_chain_t *cl; + ngx_list_part_t *part, *pt; + ngx_table_elt_t *header, *h; + ngx_connection_t *c; + ngx_http_cleanup_t *cln; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_out_frame_t *frame; + ngx_http_spdy_connection_t *sc; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + u_char addr[NGX_SOCKADDR_STRLEN]; + + if (!r->spdy_stream) { + return ngx_http_next_header_filter(r); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy header filter"); + + if (r->header_sent) { + return NGX_OK; + } + + r->header_sent = 1; + + if (r != r->main) { + return NGX_OK; + } + + c = r->connection; + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + switch (r->headers_out.status) { + + case NGX_HTTP_OK: + case NGX_HTTP_PARTIAL_CONTENT: + break; + + case NGX_HTTP_NOT_MODIFIED: + r->header_only = 1; + break; + + case NGX_HTTP_NO_CONTENT: + r->header_only = 1; + + ngx_str_null(&r->headers_out.content_type); + + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + + /* fall through */ + + default: + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + } + + len = NGX_SPDY_NV_NUM_SIZE + + ngx_http_spdy_nv_nsize(":version") + + ngx_http_spdy_nv_vsize("HTTP/1.1") + + ngx_http_spdy_nv_nsize(":status") + + (r->headers_out.status_line.len + ? NGX_SPDY_NV_VLEN_SIZE + r->headers_out.status_line.len + : ngx_http_spdy_nv_vsize("418")); + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.server == NULL) { + len += ngx_http_spdy_nv_nsize("server"); + len += clcf->server_tokens ? ngx_http_spdy_nv_vsize(NGINX_VER) + : ngx_http_spdy_nv_vsize("nginx"); + } + + if (r->headers_out.date == NULL) { + len += ngx_http_spdy_nv_nsize("date") + + ngx_http_spdy_nv_vsize("Wed, 31 Dec 1986 10:00:00 GMT"); + } + + if (r->headers_out.content_type.len) { + len += ngx_http_spdy_nv_nsize("content-type") + + NGX_SPDY_NV_VLEN_SIZE + r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + len += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + len += ngx_http_spdy_nv_nsize("content-length") + + NGX_SPDY_NV_VLEN_SIZE + NGX_OFF_T_LEN; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += ngx_http_spdy_nv_nsize("last-modified") + + ngx_http_spdy_nv_vsize("Wed, 31 Dec 1986 10:00:00 GMT"); + } + + if (r->headers_out.location + && r->headers_out.location->value.len + && r->headers_out.location->value.data[0] == '/') + { + r->headers_out.location->hash = 0; + + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + switch (c->local_sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) c->local_sockaddr; + port = ntohs(sin6->sin6_port); + break; +#endif +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + port = 0; + break; +#endif + default: /* AF_INET */ + sin = (struct sockaddr_in *) c->local_sockaddr; + port = ntohs(sin->sin_port); + break; + } + + len += ngx_http_spdy_nv_nsize("location") + + ngx_http_spdy_nv_vsize("https://") + + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + +#if (NGX_HTTP_SSL) + if (c->ssl) + port = (port == 443) ? 0 : port; + else +#endif + port = (port == 80) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + len += sizeof(":65535") - 1; + } + + } else { + ngx_str_null(&host); + port = 0; + } + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += NGX_SPDY_NV_NLEN_SIZE + header[i].key.len + + NGX_SPDY_NV_VLEN_SIZE + header[i].value.len; + } + + buf = ngx_alloc(len, r->pool->log); + if (buf == NULL) { + return NGX_ERROR; + } + + last = buf + NGX_SPDY_NV_NUM_SIZE; + + last = ngx_http_spdy_nv_write_name(last, ":version"); + last = ngx_http_spdy_nv_write_val(last, "HTTP/1.1"); + + last = ngx_http_spdy_nv_write_name(last, ":status"); + + if (r->headers_out.status_line.len) { + last = ngx_http_spdy_nv_write_vlen(last, + r->headers_out.status_line.len); + last = ngx_cpymem(last, r->headers_out.status_line.data, + r->headers_out.status_line.len); + } else { + last = ngx_http_spdy_nv_write_vlen(last, 3); + last = ngx_sprintf(last, "%03ui", r->headers_out.status); + } + + count = 2; + + if (r->headers_out.server == NULL) { + last = ngx_http_spdy_nv_write_name(last, "server"); + last = clcf->server_tokens + ? ngx_http_spdy_nv_write_val(last, NGINX_VER) + : ngx_http_spdy_nv_write_val(last, "nginx"); + + count++; + } + + if (r->headers_out.date == NULL) { + last = ngx_http_spdy_nv_write_name(last, "date"); + + last = ngx_http_spdy_nv_write_vlen(last, ngx_cached_http_time.len); + + last = ngx_cpymem(last, ngx_cached_http_time.data, + ngx_cached_http_time.len); + + count++; + } + + if (r->headers_out.content_type.len) { + + last = ngx_http_spdy_nv_write_name(last, "content-type"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + last = ngx_cpymem(last, "; charset=", sizeof("; charset=") - 1); + + last = ngx_cpymem(last, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* update r->headers_out.content_type for possible logging */ + + r->headers_out.content_type.len = last - p; + r->headers_out.content_type.data = p; + } + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + r->headers_out.content_type.len); + + count++; + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + last = ngx_http_spdy_nv_write_name(last, "content-length"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_sprintf(p, "%O", r->headers_out.content_length_n); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + last = ngx_http_spdy_nv_write_name(last, "last-modified"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_http_time(p, r->headers_out.last_modified_time); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + if (host.data) { + + last = ngx_http_spdy_nv_write_name(last, "location"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, "http", sizeof("http") - 1); + +#if (NGX_HTTP_SSL) + if (c->ssl) { + *last++ ='s'; + } +#endif + + *last++ = ':'; *last++ = '/'; *last++ = '/'; + + last = ngx_cpymem(last, host.data, host.len); + + if (port) { + last = ngx_sprintf(last, ":%ui", port); + } + + last = ngx_cpymem(last, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = last - p; + r->headers_out.location->value.data = p; + ngx_str_set(&r->headers_out.location->key, "location"); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + r->headers_out.location->value.len); + + count++; + } + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0 || header[i].hash == 2) { + continue; + } + + last = ngx_http_spdy_nv_write_nlen(last, header[i].key.len); + + ngx_strlow(last, header[i].key.data, header[i].key.len); + last += header[i].key.len; + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, header[i].value.data, header[i].value.len); + + pt = part; + h = header; + + for (j = i + 1; /* void */; j++) { + + if (j >= pt->nelts) { + if (pt->next == NULL) { + break; + } + + pt = pt->next; + h = pt->elts; + j = 0; + } + + if (h[j].hash == 0 || h[j].hash == 2 + || h[j].key.len != header[i].key.len + || ngx_strncasecmp(header[i].key.data, h[j].key.data, + header[i].key.len)) + { + continue; + } + + if (h[j].value.len) { + if (last != p) { + *last++ = '\0'; + } + + last = ngx_cpymem(last, h[j].value.data, h[j].value.len); + } + + h[j].hash = 2; + } + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + (void) ngx_http_spdy_nv_write_num(buf, count); + + stream = r->spdy_stream; + sc = stream->connection; + + len = last - buf; + + b = ngx_create_temp_buf(r->pool, NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SYN_REPLY_SIZE + + deflateBound(&sc->zstream_out, len)); + if (b == NULL) { + ngx_free(buf); + return NGX_ERROR; + } + + b->last += NGX_SPDY_FRAME_HEADER_SIZE + NGX_SPDY_SYN_REPLY_SIZE; + + sc->zstream_out.next_in = buf; + sc->zstream_out.avail_in = len; + sc->zstream_out.next_out = b->last; + sc->zstream_out.avail_out = b->end - b->last; + + rc = deflate(&sc->zstream_out, Z_SYNC_FLUSH); + + ngx_free(buf); + + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "deflate() failed: %d", rc); + return NGX_ERROR; + } + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_out.next_in, sc->zstream_out.next_out, + sc->zstream_out.avail_in, sc->zstream_out.avail_out, + rc); + + b->last = sc->zstream_out.next_out; + + p = b->pos; + p = ngx_spdy_frame_write_head(p, NGX_SPDY_SYN_REPLY); + + len = b->last - b->pos; + + r->header_size = len; + + len -= NGX_SPDY_FRAME_HEADER_SIZE; + + if (r->header_only) { + b->last_buf = 1; + p = ngx_spdy_frame_write_flags_and_len(p, NGX_SPDY_FLAG_FIN, len); + + } else { + p = ngx_spdy_frame_write_flags_and_len(p, 0, len); + } + + (void) ngx_spdy_frame_write_sid(p, stream->id); + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + frame = ngx_palloc(r->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_syn_frame_handler; + frame->stream = stream; + frame->length = len; + frame->priority = stream->priority; + frame->blocked = 1; + frame->fin = r->header_only; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, + "spdy:%ui create SYN_REPLY frame %p: len:%uz", + stream->id, frame, frame->length); + + ngx_http_spdy_queue_blocked_frame(sc, frame); + + cln = ngx_http_cleanup_add(r, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_spdy_filter_cleanup; + cln->data = stream; + + stream->queued = 1; + + c->send_chain = ngx_http_spdy_send_chain; + c->need_last_buf = 1; + + return ngx_http_spdy_filter_send(c, stream); +} + + +static ngx_chain_t * +ngx_http_spdy_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) +{ + off_t size, offset; + size_t rest, frame_size; + ngx_chain_t *cl, *out, **ln; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_loc_conf_t *slcf; + ngx_http_spdy_out_frame_t *frame; + ngx_http_spdy_connection_t *sc; + + r = fc->data; + stream = r->spdy_stream; + +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + while (in) { + size = ngx_buf_size(in->buf); + + if (size || in->buf->last_buf) { + break; + } + + in = in->next; + } + + if (in == NULL) { + + if (stream->queued) { + fc->write->delayed = 1; + } else { + fc->buffered &= ~NGX_SPDY_BUFFERED; + } + + return NULL; + } + + sc = stream->connection; + + if (size && ngx_http_spdy_flow_control(sc, stream) == NGX_DECLINED) { + fc->write->delayed = 1; + return in; + } + + if (limit == 0 || limit > (off_t) sc->send_window) { + limit = sc->send_window; + } + + if (limit > stream->send_window) { + limit = (stream->send_window > 0) ? stream->send_window : 0; + } + + if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = in->buf; + in->buf = cl->buf->shadow; + + offset = ngx_buf_in_memory(in->buf) + ? (cl->buf->pos - in->buf->pos) + : (cl->buf->file_pos - in->buf->file_pos); + + cl->next = stream->free_bufs; + stream->free_bufs = cl; + + } else { + offset = 0; + } + +#if (NGX_SUPPRESS_WARN) + cl = NULL; +#endif + + slcf = ngx_http_get_module_loc_conf(r, ngx_http_spdy_module); + + frame_size = (limit <= (off_t) slcf->chunk_size) ? (size_t) limit + : slcf->chunk_size; + + for ( ;; ) { + ln = &out; + rest = frame_size; + + while ((off_t) rest >= size) { + + if (offset) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, + offset, size); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + offset = 0; + + } else { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = in->buf; + } + + *ln = cl; + ln = &cl->next; + + rest -= (size_t) size; + in = in->next; + + if (in == NULL) { + frame_size -= rest; + rest = 0; + break; + } + + size = ngx_buf_size(in->buf); + } + + if (rest) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, + offset, rest); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf->flush = 0; + cl->buf->last_buf = 0; + + *ln = cl; + + offset += rest; + size -= rest; + } + + frame = ngx_http_spdy_filter_get_data_frame(stream, frame_size, + out, cl); + if (frame == NULL) { + return NGX_CHAIN_ERROR; + } + + ngx_http_spdy_queue_frame(sc, frame); + + sc->send_window -= frame_size; + + stream->send_window -= frame_size; + stream->queued++; + + if (in == NULL) { + break; + } + + limit -= frame_size; + + if (limit == 0) { + break; + } + + if (limit < (off_t) slcf->chunk_size) { + frame_size = (size_t) limit; + } + } + + if (offset) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, offset, size); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + in->buf = cl->buf; + ngx_free_chain(r->pool, cl); + } + + if (ngx_http_spdy_filter_send(fc, stream) == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + if (in && ngx_http_spdy_flow_control(sc, stream) == NGX_DECLINED) { + fc->write->delayed = 1; + } + + return in; +} + + +static ngx_chain_t * +ngx_http_spdy_filter_get_shadow(ngx_http_spdy_stream_t *stream, ngx_buf_t *buf, + off_t offset, off_t size) +{ + ngx_buf_t *chunk; + ngx_chain_t *cl; + + cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_bufs); + if (cl == NULL) { + return NULL; + } + + chunk = cl->buf; + + ngx_memcpy(chunk, buf, sizeof(ngx_buf_t)); + + chunk->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow; + chunk->shadow = buf; + + if (ngx_buf_in_memory(chunk)) { + chunk->pos += offset; + chunk->last = chunk->pos + size; + } + + if (chunk->in_file) { + chunk->file_pos += offset; + chunk->file_last = chunk->file_pos + size; + } + + return cl; +} + + +static ngx_http_spdy_out_frame_t * +ngx_http_spdy_filter_get_data_frame(ngx_http_spdy_stream_t *stream, + size_t len, ngx_chain_t *first, ngx_chain_t *last) +{ + u_char *p; + ngx_buf_t *buf; + ngx_uint_t flags; + ngx_chain_t *cl; + ngx_http_spdy_out_frame_t *frame; + + + frame = stream->free_frames; + + if (frame) { + stream->free_frames = frame->next; + + } else { + frame = ngx_palloc(stream->request->pool, + sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NULL; + } + } + + flags = last->buf->last_buf ? NGX_SPDY_FLAG_FIN : 0; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, + "spdy:%ui create DATA frame %p: len:%uz flags:%ui", + stream->id, frame, len, flags); + + cl = ngx_chain_get_free_buf(stream->request->pool, + &stream->free_data_headers); + if (cl == NULL) { + return NULL; + } + + buf = cl->buf; + + if (buf->start) { + p = buf->start; + buf->pos = p; + + p += NGX_SPDY_SID_SIZE; + + (void) ngx_spdy_frame_write_flags_and_len(p, flags, len); + + } else { + p = ngx_palloc(stream->request->pool, NGX_SPDY_FRAME_HEADER_SIZE); + if (p == NULL) { + return NULL; + } + + buf->pos = p; + buf->start = p; + + p = ngx_spdy_frame_write_sid(p, stream->id); + p = ngx_spdy_frame_write_flags_and_len(p, flags, len); + + buf->last = p; + buf->end = p; + + buf->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_get_data_frame; + buf->memory = 1; + } + + cl->next = first; + first = cl; + + last->buf->flush = 1; + + frame->first = first; + frame->last = last; + frame->handler = ngx_http_spdy_data_frame_handler; + frame->stream = stream; + frame->length = len; + frame->priority = stream->priority; + frame->blocked = 0; + frame->fin = last->buf->last_buf; + + return frame; +} + + +static ngx_inline ngx_int_t +ngx_http_spdy_filter_send(ngx_connection_t *fc, ngx_http_spdy_stream_t *stream) +{ + stream->blocked = 1; + + if (ngx_http_spdy_send_output_queue(stream->connection) == NGX_ERROR) { + fc->error = 1; + return NGX_ERROR; + } + + stream->blocked = 0; + + if (stream->queued) { + fc->buffered |= NGX_SPDY_BUFFERED; + fc->write->delayed = 1; + return NGX_AGAIN; + } + + fc->buffered &= ~NGX_SPDY_BUFFERED; + + return NGX_OK; +} + + +static ngx_inline ngx_int_t +ngx_http_spdy_flow_control(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + if (stream->send_window <= 0) { + stream->exhausted = 1; + return NGX_DECLINED; + } + + if (sc->send_window == 0) { + ngx_http_spdy_waiting_queue(sc, stream); + return NGX_DECLINED; + } + + return NGX_OK; +} + + +static void +ngx_http_spdy_waiting_queue(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + ngx_queue_t *q; + ngx_http_spdy_stream_t *s; + + if (stream->handled) { + return; + } + + stream->handled = 1; + + for (q = ngx_queue_last(&sc->waiting); + q != ngx_queue_sentinel(&sc->waiting); + q = ngx_queue_prev(q)) + { + s = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + /* + * NB: higher values represent lower priorities. + */ + if (stream->priority >= s->priority) { + break; + } + } + + ngx_queue_insert_after(q, &stream->queue); +} + + +static ngx_int_t +ngx_http_spdy_syn_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + ngx_http_spdy_stream_t *stream; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + stream = frame->stream; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui SYN_REPLY frame %p was sent", stream->id, frame); + + ngx_free_chain(stream->request->pool, frame->first); + + ngx_http_spdy_handle_frame(stream, frame); + + ngx_http_spdy_handle_stream(sc, stream); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_data_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + ngx_chain_t *cl, *ln; + ngx_http_spdy_stream_t *stream; + + stream = frame->stream; + + cl = frame->first; + + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_data_frame) { + + if (cl->buf->pos != cl->buf->last) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent partially", + stream->id, frame); + + return NGX_AGAIN; + } + + ln = cl->next; + + cl->next = stream->free_data_headers; + stream->free_data_headers = cl; + + if (cl == frame->last) { + goto done; + } + + cl = ln; + } + + for ( ;; ) { + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + buf = cl->buf->shadow; + + if (ngx_buf_in_memory(buf)) { + buf->pos = cl->buf->pos; + } + + if (buf->in_file) { + buf->file_pos = cl->buf->file_pos; + } + } + + if (ngx_buf_size(cl->buf) != 0) { + + if (cl != frame->first) { + frame->first = cl; + ngx_http_spdy_handle_stream(sc, stream); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent partially", + stream->id, frame); + + return NGX_AGAIN; + } + + ln = cl->next; + + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + cl->next = stream->free_bufs; + stream->free_bufs = cl; + + } else { + ngx_free_chain(stream->request->pool, cl); + } + + if (cl == frame->last) { + goto done; + } + + cl = ln; + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent", stream->id, frame); + + stream->request->header_size += NGX_SPDY_FRAME_HEADER_SIZE; + + ngx_http_spdy_handle_frame(stream, frame); + + ngx_http_spdy_handle_stream(sc, stream); + + return NGX_OK; +} + + +static ngx_inline void +ngx_http_spdy_handle_frame(ngx_http_spdy_stream_t *stream, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_request_t *r; + + r = stream->request; + + r->connection->sent += NGX_SPDY_FRAME_HEADER_SIZE + frame->length; + + if (frame->fin) { + stream->out_closed = 1; + } + + frame->next = stream->free_frames; + stream->free_frames = frame; + + stream->queued--; +} + + +static ngx_inline void +ngx_http_spdy_handle_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + ngx_event_t *wev; + + if (stream->handled || stream->blocked || stream->exhausted) { + return; + } + + wev = stream->request->connection->write; + + /* + * This timer can only be set if the stream was delayed because of rate + * limit. In that case the event should be triggered by the timer. + */ + + if (!wev->timer_set) { + wev->delayed = 0; + + stream->handled = 1; + ngx_queue_insert_tail(&sc->posted, &stream->queue); + } +} + + +static void +ngx_http_spdy_filter_cleanup(void *data) +{ + ngx_http_spdy_stream_t *stream = data; + + size_t delta; + ngx_http_spdy_out_frame_t *frame, **fn; + ngx_http_spdy_connection_t *sc; + + if (stream->handled) { + stream->handled = 0; + ngx_queue_remove(&stream->queue); + } + + if (stream->queued == 0) { + return; + } + + delta = 0; + sc = stream->connection; + fn = &sc->last_out; + + for ( ;; ) { + frame = *fn; + + if (frame == NULL) { + break; + } + + if (frame->stream == stream && !frame->blocked) { + *fn = frame->next; + + delta += frame->length; + + if (--stream->queued == 0) { + break; + } + + continue; + } + + fn = &frame->next; + } + + if (sc->send_window == 0 && delta && !ngx_queue_empty(&sc->waiting)) { + ngx_queue_add(&sc->posted, &sc->waiting); + ngx_queue_init(&sc->waiting); + } + + sc->send_window += delta; +} + + +static ngx_int_t +ngx_http_spdy_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_spdy_header_filter; + + return NGX_OK; +} --- a/src/http/ngx_http_spdy_module.c 1970-01-01 00:00:00.000000000 +0000 +++ b/src/http/ngx_http_spdy_module.c 2018-09-28 16:36:12.000000000 +0000 @@ -0,0 +1,408 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + + +static ngx_int_t ngx_http_spdy_add_variables(ngx_conf_t *cf); + +static ngx_int_t ngx_http_spdy_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_spdy_request_priority_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_http_spdy_module_init(ngx_cycle_t *cycle); + +static void *ngx_http_spdy_create_main_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_init_main_conf(ngx_conf_t *cf, void *conf); +static void *ngx_http_spdy_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static void *ngx_http_spdy_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_merge_loc_conf(ngx_conf_t *cf, void *parent, + void *child); + +static char *ngx_http_spdy_recv_buffer_size(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_spdy_pool_size(ngx_conf_t *cf, void *post, void *data); +static char *ngx_http_spdy_streams_index_mask(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_spdy_chunk_size(ngx_conf_t *cf, void *post, void *data); + + +static ngx_conf_num_bounds_t ngx_http_spdy_headers_comp_bounds = { + ngx_conf_check_num_bounds, 0, 9 +}; + +static ngx_conf_post_t ngx_http_spdy_recv_buffer_size_post = + { ngx_http_spdy_recv_buffer_size }; +static ngx_conf_post_t ngx_http_spdy_pool_size_post = + { ngx_http_spdy_pool_size }; +static ngx_conf_post_t ngx_http_spdy_streams_index_mask_post = + { ngx_http_spdy_streams_index_mask }; +static ngx_conf_post_t ngx_http_spdy_chunk_size_post = + { ngx_http_spdy_chunk_size }; + + +static ngx_command_t ngx_http_spdy_commands[] = { + + { ngx_string("spdy_recv_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_spdy_main_conf_t, recv_buffer_size), + &ngx_http_spdy_recv_buffer_size_post }, + + { ngx_string("spdy_pool_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, pool_size), + &ngx_http_spdy_pool_size_post }, + + { ngx_string("spdy_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, concurrent_streams), + NULL }, + + { ngx_string("spdy_streams_index_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, streams_index_mask), + &ngx_http_spdy_streams_index_mask_post }, + + { ngx_string("spdy_recv_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, recv_timeout), + NULL }, + + { ngx_string("spdy_keepalive_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, keepalive_timeout), + NULL }, + + { ngx_string("spdy_headers_comp"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, headers_comp), + &ngx_http_spdy_headers_comp_bounds }, + + { ngx_string("spdy_chunk_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_spdy_loc_conf_t, chunk_size), + &ngx_http_spdy_chunk_size_post }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_spdy_module_ctx = { + ngx_http_spdy_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + ngx_http_spdy_create_main_conf, /* create main configuration */ + ngx_http_spdy_init_main_conf, /* init main configuration */ + + ngx_http_spdy_create_srv_conf, /* create server configuration */ + ngx_http_spdy_merge_srv_conf, /* merge server configuration */ + + ngx_http_spdy_create_loc_conf, /* create location configuration */ + ngx_http_spdy_merge_loc_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_spdy_module = { + NGX_MODULE_V1, + &ngx_http_spdy_module_ctx, /* module context */ + ngx_http_spdy_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + ngx_http_spdy_module_init, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_variable_t ngx_http_spdy_vars[] = { + + { ngx_string("spdy"), NULL, + ngx_http_spdy_variable, 0, 0, 0 }, + + { ngx_string("spdy_request_priority"), NULL, + ngx_http_spdy_request_priority_variable, 0, 0, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static ngx_int_t +ngx_http_spdy_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_spdy_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->spdy_stream) { + v->len = sizeof("3.1") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "3.1"; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_request_priority_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->spdy_stream) { + v->len = 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + v->data = ngx_pnalloc(r->pool, 1); + if (v->data == NULL) { + return NGX_ERROR; + } + + v->data[0] = '0' + (u_char) r->spdy_stream->priority; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_module_init(ngx_cycle_t *cycle) +{ + ngx_http_spdy_request_headers_init(); + + return NGX_OK; +} + + +static void * +ngx_http_spdy_create_main_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_main_conf_t *smcf; + + smcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_main_conf_t)); + if (smcf == NULL) { + return NULL; + } + + smcf->recv_buffer_size = NGX_CONF_UNSET_SIZE; + + return smcf; +} + + +static char * +ngx_http_spdy_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_http_spdy_main_conf_t *smcf = conf; + + ngx_conf_init_size_value(smcf->recv_buffer_size, 256 * 1024); + + return NGX_CONF_OK; +} + + +static void * +ngx_http_spdy_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_srv_conf_t)); + if (sscf == NULL) { + return NULL; + } + + sscf->pool_size = NGX_CONF_UNSET_SIZE; + + sscf->concurrent_streams = NGX_CONF_UNSET_UINT; + sscf->streams_index_mask = NGX_CONF_UNSET_UINT; + + sscf->recv_timeout = NGX_CONF_UNSET_MSEC; + sscf->keepalive_timeout = NGX_CONF_UNSET_MSEC; + + sscf->headers_comp = NGX_CONF_UNSET; + + return sscf; +} + + +static char * +ngx_http_spdy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_spdy_srv_conf_t *prev = parent; + ngx_http_spdy_srv_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->pool_size, prev->pool_size, 4096); + + ngx_conf_merge_uint_value(conf->concurrent_streams, + prev->concurrent_streams, 100); + + ngx_conf_merge_uint_value(conf->streams_index_mask, + prev->streams_index_mask, 32 - 1); + + ngx_conf_merge_msec_value(conf->recv_timeout, + prev->recv_timeout, 30000); + ngx_conf_merge_msec_value(conf->keepalive_timeout, + prev->keepalive_timeout, 180000); + + ngx_conf_merge_value(conf->headers_comp, prev->headers_comp, 0); + + return NGX_CONF_OK; +} + + +static void * +ngx_http_spdy_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_loc_conf_t *slcf; + + slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_loc_conf_t)); + if (slcf == NULL) { + return NULL; + } + + slcf->chunk_size = NGX_CONF_UNSET_SIZE; + + return slcf; +} + + +static char * +ngx_http_spdy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_spdy_loc_conf_t *prev = parent; + ngx_http_spdy_loc_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->chunk_size, prev->chunk_size, 8 * 1024); + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_recv_buffer_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp <= 2 * NGX_SPDY_STATE_BUFFER_SIZE) { + return "value is too small"; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_pool_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp < NGX_MIN_POOL_SIZE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the pool size must be no less than %uz", + NGX_MIN_POOL_SIZE); + return NGX_CONF_ERROR; + } + + if (*sp % NGX_POOL_ALIGNMENT) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the pool size must be a multiple of %uz", + NGX_POOL_ALIGNMENT); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_streams_index_mask(ngx_conf_t *cf, void *post, void *data) +{ + ngx_uint_t *np = data; + + ngx_uint_t mask; + + mask = *np - 1; + + if (*np == 0 || (*np & mask)) { + return "must be a power of two"; + } + + *np = mask; + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_chunk_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the spdy chunk size cannot be zero"); + return NGX_CONF_ERROR; + } + + if (*sp > NGX_SPDY_MAX_FRAME_SIZE) { + *sp = NGX_SPDY_MAX_FRAME_SIZE; + } + + return NGX_CONF_OK; +} --- a/src/http/ngx_http_spdy_module.h 1970-01-01 00:00:00.000000000 +0000 +++ b/src/http/ngx_http_spdy_module.h 2018-09-28 16:36:24.000000000 +0000 @@ -0,0 +1,41 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ +#define _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + size_t recv_buffer_size; + u_char *recv_buffer; +} ngx_http_spdy_main_conf_t; + + +typedef struct { + size_t pool_size; + ngx_uint_t concurrent_streams; + ngx_uint_t streams_index_mask; + ngx_msec_t recv_timeout; + ngx_msec_t keepalive_timeout; + ngx_int_t headers_comp; +} ngx_http_spdy_srv_conf_t; + + +typedef struct { + size_t chunk_size; +} ngx_http_spdy_loc_conf_t; + + +extern ngx_module_t ngx_http_spdy_module; + + +#endif /* _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ */ --- a/src/http/ngx_http_upstream.c 2018-09-28 17:56:53.000000000 +0000 +++ b/src/http/ngx_http_upstream.c 2018-09-28 16:47:51.000000000 +0000 @@ -520,6 +520,12 @@ return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_upstream_init_request(r); + return; + } +#endif if (c->read->timer_set) { ngx_del_timer(c->read); @@ -1341,6 +1347,11 @@ return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + return; + } +#endif #if (NGX_HAVE_KQUEUE) -------------- next part -------------- A non-text attachment was scrubbed... Name: spdy_nginx-1.14+.patch.sig Type: application/octet-stream Size: 566 bytes Desc: not available URL: From dewanggaba at xtremenitro.org Sat Sep 29 04:12:02 2018 From: dewanggaba at xtremenitro.org (Dewangga Alam) Date: Sat, 29 Sep 2018 11:12:02 +0700 Subject: Bring SPDY back in nginx-1.14.0 In-Reply-To: <636e17f1-bc63-74b7-f5c1-725b202ef1fd@xtremenitro.org> References: <636e17f1-bc63-74b7-f5c1-725b202ef1fd@xtremenitro.org> Message-ID: <0d78ebbb-8ada-9e9e-1546-d2a506b4a37a@xtremenitro.org> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Sorry, please ignore my previous patch file. The previous error was reproduce-able using this attached patch. On 29/09/18 08.29, Dewangga Alam wrote: > Hello! > > This is my first post in nginx-devel mailing list. > > Currently, I am using nginx-1.8.1 in production. And I need SPDY to > works on nginx-1.14.0 co-exists with http/2. Because I still need > SPDY for compatibility, and for future deployment, I will migrate > it all to HTTP/2. > > To make this possible, so I was create patch to make SPDY and > HTTP/2 run together. > > I was read cloudflare patch regarding this issue (spdy & http/2) > and make some adjustment (patch attached). > > But, got some errors when compiling. The error message was : > > objs/src/http/ngx_http_spdy.o: In function > `ngx_http_spdy_send_output_queue': > ./debian/build-nginx/src/http/ngx_http_spdy.c:664: multiple > definition of `ngx_http_spdy_send_output_queue' > objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_s pd > > > y.c:664: > first defined here objs/src/http/ngx_http_spdy.o: In function > `ngx_http_spdy_init': > ./debian/build-nginx/src/http/ngx_http_spdy.c:386: multiple > definition of `ngx_http_spdy_init' > objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_s pd > > > y.c:386: > first defined here objs/src/http/ngx_http_spdy.o: In function > `ngx_http_spdy_request_headers_init': > ./debian/build-nginx/src/http/ngx_http_spdy.c:2813: multiple > definition of `ngx_http_spdy_request_headers_init' > objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_s pd > > > y.c:2813: > first defined here objs/src/http/ngx_http_spdy.o: In function > `ngx_http_spdy_read_request_body': > ./debian/build-nginx/src/http/ngx_http_spdy.c:3274: multiple > definition of `ngx_http_spdy_read_request_body' > objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_s pd > > > y.c:3274: > first defined here objs/src/http/ngx_http_spdy.o: In function > `ngx_http_spdy_close_stream': > ./debian/build-nginx/src/http/ngx_http_spdy.c:3362: multiple > definition of `ngx_http_spdy_close_stream' > objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_s pd > > > y.c:3362: > first defined here > objs/src/http/ngx_http_spdy_module.o:(.data.rel.local+0xc0): > multiple definition of `ngx_http_spdy_module' > objs/src/http/ngx_http_spdy_module.o:(.data.rel.local+0xc0): first > defined here > > https://paste.fedoraproject.org/paste/HGW3Q7Pj8oxnD473Xw3Cdw/raw > > Is there any hints to fix the error? > > Wr, Dewangga Alam > -----BEGIN PGP SIGNATURE----- iQIzBAEBCAAdFiEEZpxiw/Jg6pEte5xQ5X/SIKAozXAFAluu+5EACgkQ5X/SIKAo zXDfFhAAlkXodeHc2Cc5eMiamiJ3LXrV2VBjlNM3Hj5o9sQNvu6UKrR7TBoiGcf/ AQDCEUbdeW3GV8nS1tdM1Wh7LoXbjI7Cr2ObPBugKXg7CZKnmoE1KfWpqC3dEZ0+ xJscsxlaQij2/UM2ty5FGiKtbLwRvFKepQvX4uBHdPxfqGysaGTSK7u5Ct47/OcU OJVUo8yWfgzdh/0If6fupYDQnPRTwQImbsDEj6k+ykdAp267Ph/u2HADDen60LPv dM1LtCGjeLWslHAKaVsbEo+FKpllCunZTepvWtyZGYIYl5hCyAb75k7Guw5NCw6n CqXcQWPuTobdYfijr/Po078WShrO1CJD+/TvvR/MMuYG3bx43/rooqxToBtExPMx 9s2POBKdWV4QAnd08U9/j5tGQBFUfPL/sOPWz01vUHE/DlfwFf9sR/AXvhbiDoKc dq3oeNe079rFqi2XbsPiPKMDwNUnxqrEK7mOHOS58qjnYHA6nXpmhXyn4g3d/3Hu NTFI4+8V33NaQ8ij+ZfKdN5ZxJHE0wTN1WrU2p+ttL+qEtN18Iuos4qV7ZEokXUF SCPmOtwwZ/aFV3JOeSg1IADOgnN5JPLv+obhriOWYSCr/dAfLpzwNJsOKhr/ZoHG F/lGn/Py7Iplr5lFwqtuGcOmoofPlDSj5j07KSFl8crDOKAJ/yQ= =ESBh -----END PGP SIGNATURE----- -------------- next part -------------- Index: nginx-1.14.0/auto/modules =================================================================== --- nginx-1.14.0.orig/auto/modules +++ nginx-1.14.0/auto/modules @@ -134,6 +134,7 @@ if [ $HTTP = YES ]; then # ngx_http_header_filter # ngx_http_chunked_filter # ngx_http_v2_filter + # ngx_http_spdy_filter # ngx_http_range_header_filter # ngx_http_gzip_filter # ngx_http_postpone_filter @@ -166,6 +167,7 @@ if [ $HTTP = YES ]; then ngx_http_header_filter_module \ ngx_http_chunked_filter_module \ ngx_http_v2_filter_module \ + ngx_http_spdy_filter \ ngx_http_range_header_filter_module \ ngx_http_gzip_filter_module \ ngx_http_postpone_filter_module \ @@ -227,6 +229,20 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_SPDY = YES ]; then + have=NGX_HTTP_SPDY . auto/have + + ngx_module_name=ngx_http_spdy_module + ngx_module_incs=src/http + ngx_module_deps=src/http/ngx_http_spdy.h src/http/ngx_http_spdy_module.h + ngx_module_srcs="src/http/ngx_http_spdy.c \ + src/http/ngx_http_spdy_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_SPDY + + . auto/module + fi + if :; then ngx_module_name=ngx_http_range_header_filter_module ngx_module_incs= @@ -437,6 +453,20 @@ if [ $HTTP = YES ]; then . auto/module fi + + if [ $HTTP_SPDY = YES ]; then + have=NGX_HTTP_SPDY . auto/have + + ngx_module_name=ngx_http_spdy_module + ngx_module_incs=src/http + ngx_module_deps=src/http/ngx_http_spdy.h src/http/ngx_http_spdy_module.h + ngx_module_srcs="src/http/ngx_http_spdy.c \ + src/http/ngx_http_spdy_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_SPDY + + . auto/module + fi if :; then ngx_module_name=ngx_http_static_module Index: nginx-1.14.0/auto/options =================================================================== --- nginx-1.14.0.orig/auto/options +++ nginx-1.14.0/auto/options @@ -59,6 +59,7 @@ HTTP_CHARSET=YES HTTP_GZIP=YES HTTP_SSL=NO HTTP_V2=NO +HTTP_SPDY=NO HTTP_SSI=YES HTTP_POSTPONE=NO HTTP_REALIP=NO @@ -223,7 +224,8 @@ $0: warning: the \"--with-ipv6\" option --with-http_ssl_module) HTTP_SSL=YES ;; --with-http_v2_module) HTTP_V2=YES ;; - --with-http_realip_module) HTTP_REALIP=YES ;; + --with-http_spdy_module) HTTP_SPDY=YES ;; + --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; --with-http_xslt_module) HTTP_XSLT=YES ;; --with-http_xslt_module=dynamic) HTTP_XSLT=DYNAMIC ;; @@ -434,6 +436,7 @@ cat << END --with-http_ssl_module enable ngx_http_ssl_module --with-http_v2_module enable ngx_http_v2_module + --with-http_spdy_module enable ngx_http_spdy_module --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module --with-http_xslt_module enable ngx_http_xslt_module Index: nginx-1.14.0/src/core/ngx_connection.h =================================================================== --- nginx-1.14.0.orig/src/core/ngx_connection.h +++ nginx-1.14.0/src/core/ngx_connection.h @@ -116,6 +116,7 @@ typedef enum { #define NGX_LOWLEVEL_BUFFERED 0x0f #define NGX_SSL_BUFFERED 0x01 #define NGX_HTTP_V2_BUFFERED 0x02 +#define NGX_SPDY_BUFFERED 0x04 struct ngx_connection_s { Index: nginx-1.14.0/src/http/modules/ngx_http_ssl_module.c =================================================================== --- nginx-1.14.0.orig/src/http/modules/ngx_http_ssl_module.c +++ nginx-1.14.0/src/http/modules/ngx_http_ssl_module.c @@ -352,10 +352,10 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t #if (NGX_DEBUG) unsigned int i; #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) ngx_http_connection_t *hc; #endif -#if (NGX_HTTP_V2 || NGX_DEBUG) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_DEBUG) ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); @@ -369,6 +369,16 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t } #endif +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + hc = c->data; + if (hc->addr_conf->http2 && hc->addr_conf->spdy) { + srv = (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE + NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + srvlen = sizeof(NGX_HTTP_V2_ALPN_ADVERTISE NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE) - 1; + + } else +#endif #if (NGX_HTTP_V2) hc = c->data; @@ -379,6 +389,13 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t } else #endif +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + srv = (unsigned char *) NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + srvlen = sizeof(NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1; + + } else +#endif { srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE; srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1; @@ -406,19 +423,32 @@ static int ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, unsigned int *outlen, void *arg) { -#if (NGX_HTTP_V2 || NGX_DEBUG) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY || NGX_DEBUG) ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "SSL NPN advertised"); #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) { ngx_http_connection_t *hc; hc = c->data; +#endif +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + if (hc->addr_conf->http2 && hc->addr_conf->spdy) { + *out = (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE + NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE; + *outlen = sizeof(NGX_HTTP_V2_NPN_ADVERTISE NGX_SPDY_NPN_ADVERTISE + NGX_HTTP_NPN_ADVERTISE) - 1; + + return SSL_TLSEXT_ERR_OK; + } else +#endif +#if (NGX_HTTP_V2) if (hc->addr_conf->http2) { *out = (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; @@ -426,6 +456,20 @@ ngx_http_ssl_npn_advertised(ngx_ssl_conn return SSL_TLSEXT_ERR_OK; } +#endif +#if (NGX_HTTP_V2 && NGX_HTTP_SPDY) + else +#endif +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + *out = (unsigned char *) NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; + *outlen = sizeof(NGX_SPDY_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1; + + return SSL_TLSEXT_ERR_OK; + } +#endif + +#if (NGX_HTTP_V2 || NGX_HTTP_SPDY) } #endif Index: nginx-1.14.0/src/http/ngx_http.c =================================================================== --- nginx-1.14.0.orig/src/http/ngx_http.c +++ nginx-1.14.0/src/http/ngx_http.c @@ -1199,6 +1199,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n #if (NGX_HTTP_V2) ngx_uint_t http2; #endif +#if (NGX_HTTP_SPDY) + ngx_uint_t spdy; +#endif /* * we cannot compare whole sockaddr struct's as kernel @@ -1234,6 +1237,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n #if (NGX_HTTP_V2) http2 = lsopt->http2 || addr[i].opt.http2; #endif +#if (NGX_HTTP_SPDY) + spdy = lsopt->spdy || addr[i].opt.spdy; +#endif if (lsopt->set) { @@ -1265,9 +1271,13 @@ ngx_http_add_addresses(ngx_conf_t *cf, n #if (NGX_HTTP_SSL) addr[i].opt.ssl = ssl; #endif + #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif +#if (NGX_HTTP_SPDY) + addr[i].opt.spdy = spdy; +#endif return NGX_OK; } @@ -1310,6 +1320,18 @@ ngx_http_add_address(ngx_conf_t *cf, ngx } #endif +#if (NGX_HTTP_SPDY && NGX_HTTP_SSL \ + && !defined TLSEXT_TYPE_application_layer_protocol_negotiation \ + && !defined TLSEXT_TYPE_next_proto_neg) + + if (lsopt->spdy && lsopt->ssl) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "nginx was built with OpenSSL that lacks ALPN " + "and NPN support, SPDY is not enabled for %s", + lsopt->addr); + } + +#endif addr = ngx_array_push(&port->addrs); if (addr == NULL) { @@ -1804,6 +1826,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_h #if (NGX_HTTP_V2) addrs[i].conf.http2 = addr[i].opt.http2; #endif +#if (NGX_HTTP_SPDY) + addrs[i].conf.spdy = addr[i].opt.spdy; +#endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; if (addr[i].hash.buckets == NULL @@ -1869,6 +1894,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_ #if (NGX_HTTP_V2) addrs6[i].conf.http2 = addr[i].opt.http2; #endif +#if (NGX_HTTP_SPDY) + addrs6[i].conf.spdy = addr[i].opt.spdy; +#endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; if (addr[i].hash.buckets == NULL Index: nginx-1.14.0/src/http/ngx_http.h =================================================================== --- nginx-1.14.0.orig/src/http/ngx_http.h +++ nginx-1.14.0/src/http/ngx_http.h @@ -20,6 +20,7 @@ typedef struct ngx_http_file_cache_s ng typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; typedef struct ngx_http_chunked_s ngx_http_chunked_t; typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; +typedef struct ngx_http_spdy_stream_s ngx_http_spdy_stream_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); @@ -38,6 +39,9 @@ typedef u_char *(*ngx_http_log_handler_p #if (NGX_HTTP_V2) #include #endif +#if (NGX_HTTP_SPDY) +#include +#endif #if (NGX_HTTP_CACHE) #include #endif Index: nginx-1.14.0/src/http/ngx_http_core_module.c =================================================================== --- nginx-1.14.0.orig/src/http/ngx_http_core_module.c +++ nginx-1.14.0/src/http/ngx_http_core_module.c @@ -1940,6 +1940,13 @@ ngx_http_gzip_ok(ngx_http_request_t *r) return NGX_DECLINED; } +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + r->gzip_ok = 1; + return NGX_OK; + } +#endif + ae = r->headers_in.accept_encoding; if (ae == NULL) { return NGX_DECLINED; @@ -2297,6 +2304,9 @@ ngx_http_subrequest(ngx_http_request_t * #if (NGX_HTTP_V2) sr->stream = r->stream; #endif +#if (NGX_HTTP_SPDY) + sr->spdy_stream = r->spdy_stream; +#endif sr->method = NGX_HTTP_GET; sr->http_version = r->http_version; @@ -3986,11 +3996,15 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx } if (ngx_strcmp(value[n].data, "spdy") == 0) { - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "invalid parameter \"spdy\": " - "ngx_http_spdy_module was superseded " - "by ngx_http_v2_module"); - continue; +#if (NGX_HTTP_SPDY) + lsopt.spdy = 1; + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"spdy\" parameter requires " + "ngx_http_spdy_module"); + return NGX_CONF_ERROR; +#endif } if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { Index: nginx-1.14.0/src/http/ngx_http_core_module.h =================================================================== --- nginx-1.14.0.orig/src/http/ngx_http_core_module.h +++ nginx-1.14.0/src/http/ngx_http_core_module.h @@ -74,6 +74,7 @@ typedef struct { unsigned wildcard:1; unsigned ssl:1; unsigned http2:1; + unsigned spdy:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -235,6 +236,7 @@ struct ngx_http_addr_conf_s { unsigned ssl:1; unsigned http2:1; + unsigned spdy:1; unsigned proxy_protocol:1; }; Index: nginx-1.14.0/src/http/ngx_http_request.c =================================================================== --- nginx-1.14.0.orig/src/http/ngx_http_request.c +++ nginx-1.14.0/src/http/ngx_http_request.c @@ -322,7 +322,11 @@ ngx_http_init_connection(ngx_connection_ rev = c->read; rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; - +#if (NGX_HTTP_SPDY) + if (hc->addr_conf->spdy) { + rev->handler = ngx_http_spdy_init; + } +#endif #if (NGX_HTTP_V2) if (hc->addr_conf->http2) { rev->handler = ngx_http_v2_init; @@ -824,6 +828,33 @@ ngx_http_ssl_handshake_handler(ngx_conne } } #endif +#if (NGX_HTTP_SPDY \ + && (defined TLSEXT_TYPE_application_layer_protocol_negotiation \ + || defined TLSEXT_TYPE_next_proto_neg)) + { + unsigned int len; + const unsigned char *data; + static const ngx_str_t spdy = ngx_string(NGX_SPDY_NPN_NEGOTIATED); + +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + +#ifdef TLSEXT_TYPE_next_proto_neg + if (len == 0) { + SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len); + } +#endif + +#else /* TLSEXT_TYPE_next_proto_neg */ + SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len); +#endif + + if (len == spdy.len && ngx_strncmp(data, spdy.data, spdy.len) == 0) { + ngx_http_spdy_init(c->read); + return; + } + } +#endif c->log->action = "waiting for request"; @@ -2584,6 +2615,12 @@ ngx_http_finalize_connection(ngx_http_re return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_close_request(r, 0); + return; + } +#endif clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); @@ -3457,6 +3494,13 @@ ngx_http_close_request(ngx_http_request_ } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_spdy_close_stream(r->spdy_stream, rc); + return; + } +#endif + ngx_http_free_request(r, rc); ngx_http_close_connection(c); } Index: nginx-1.14.0/src/http/ngx_http_request.h =================================================================== --- nginx-1.14.0.orig/src/http/ngx_http_request.h +++ nginx-1.14.0/src/http/ngx_http_request.h @@ -444,6 +444,7 @@ struct ngx_http_request_s { ngx_http_connection_t *http_connection; ngx_http_v2_stream_t *stream; + ngx_http_spdy_stream_t *spdy_stream; ngx_http_log_handler_pt log_handler; Index: nginx-1.14.0/src/http/ngx_http_request_body.c =================================================================== --- nginx-1.14.0.orig/src/http/ngx_http_request_body.c +++ nginx-1.14.0/src/http/ngx_http_request_body.c @@ -46,6 +46,14 @@ ngx_http_read_client_request_body(ngx_ht return NGX_OK; } +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + rc = ngx_http_spdy_read_request_body(r, post_handler); + + goto done; + } +#endif + if (ngx_http_test_expect(r) != NGX_OK) { rc = NGX_HTTP_INTERNAL_SERVER_ERROR; goto done; @@ -525,6 +533,13 @@ ngx_http_discard_request_body(ngx_http_r } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + r->spdy_stream->skip_data = 1; + return NGX_OK; + } +#endif + if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } Index: nginx-1.14.0/src/http/ngx_http_spdy.c =================================================================== --- /dev/null +++ nginx-1.14.0/src/http/ngx_http_spdy.c @@ -0,0 +1,3701 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + +#include + + +#if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED) + +#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \ + *(uint32_t *) m == (c3 << 24 | c2 << 16 | c1 << 8 | c0) \ + && m[4] == c4 + +#else + +#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \ + m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 && m[4] == c4 + +#endif + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_spdy_frame_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_spdy_frame_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#else + +#define ngx_spdy_frame_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_spdy_frame_parse_uint32(p) \ + ((p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#endif + +#define ngx_spdy_frame_parse_sid(p) \ + (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff) +#define ngx_spdy_frame_parse_delta(p) \ + (ngx_spdy_frame_parse_uint32(p) & 0x7fffffff) + + +#define ngx_spdy_ctl_frame_check(h) \ + (((h) & 0xffff0000) == ngx_spdy_ctl_frame_head(0)) +#define ngx_spdy_data_frame_check(h) \ + (!((h) & (uint32_t) NGX_SPDY_CTL_BIT << 31)) + +#define ngx_spdy_ctl_frame_type(h) ((h) & 0x0000ffff) +#define ngx_spdy_frame_flags(p) ((p) >> 24) +#define ngx_spdy_frame_length(p) ((p) & 0x00ffffff) +#define ngx_spdy_frame_id(p) ((p) & 0x00ffffff) + + +#define NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE 4096 +#define NGX_SPDY_CTL_FRAME_BUFFER_SIZE 16 + +#define NGX_SPDY_PROTOCOL_ERROR 1 +#define NGX_SPDY_INVALID_STREAM 2 +#define NGX_SPDY_REFUSED_STREAM 3 +#define NGX_SPDY_UNSUPPORTED_VERSION 4 +#define NGX_SPDY_CANCEL 5 +#define NGX_SPDY_INTERNAL_ERROR 6 +#define NGX_SPDY_FLOW_CONTROL_ERROR 7 +#define NGX_SPDY_STREAM_IN_USE 8 +#define NGX_SPDY_STREAM_ALREADY_CLOSED 9 +/* deprecated 10 */ +#define NGX_SPDY_FRAME_TOO_LARGE 11 + +#define NGX_SPDY_SETTINGS_MAX_STREAMS 4 +#define NGX_SPDY_SETTINGS_INIT_WINDOW 7 + +#define NGX_SPDY_SETTINGS_FLAG_PERSIST 0x01 +#define NGX_SPDY_SETTINGS_FLAG_PERSISTED 0x02 + +#define NGX_SPDY_MAX_WINDOW NGX_MAX_INT32_VALUE +#define NGX_SPDY_CONNECTION_WINDOW 65536 +#define NGX_SPDY_INIT_STREAM_WINDOW 65536 +#define NGX_SPDY_STREAM_WINDOW NGX_SPDY_MAX_WINDOW + +typedef struct { + ngx_uint_t hash; + u_char len; + u_char header[7]; + ngx_int_t (*handler)(ngx_http_request_t *r); +} ngx_http_spdy_request_header_t; + + +static void ngx_http_spdy_read_handler(ngx_event_t *rev); +static void ngx_http_spdy_write_handler(ngx_event_t *wev); +static void ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc); + +static u_char *ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_rst_stream(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_ping(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_skip(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_settings(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_complete(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); +static u_char *ngx_http_spdy_state_save(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end, ngx_http_spdy_handler_pt handler); + +static u_char *ngx_http_spdy_state_inflate_error( + ngx_http_spdy_connection_t *sc, int rc); +static u_char *ngx_http_spdy_state_protocol_error( + ngx_http_spdy_connection_t *sc); +static u_char *ngx_http_spdy_state_internal_error( + ngx_http_spdy_connection_t *sc); + +static ngx_int_t ngx_http_spdy_send_window_update( + ngx_http_spdy_connection_t *sc, ngx_uint_t sid, ngx_uint_t delta); +static ngx_int_t ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc, + ngx_uint_t sid, ngx_uint_t status, ngx_uint_t priority); +static ngx_int_t ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc); +static ngx_int_t ngx_http_spdy_settings_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_http_spdy_out_frame_t *ngx_http_spdy_get_ctl_frame( + ngx_http_spdy_connection_t *sc, size_t size, ngx_uint_t priority); +static ngx_int_t ngx_http_spdy_ctl_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); + +static ngx_http_spdy_stream_t *ngx_http_spdy_create_stream( + ngx_http_spdy_connection_t *sc, ngx_uint_t id, ngx_uint_t priority); +static ngx_http_spdy_stream_t *ngx_http_spdy_get_stream_by_id( + ngx_http_spdy_connection_t *sc, ngx_uint_t sid); +#define ngx_http_spdy_streams_index_size(sscf) (sscf->streams_index_mask + 1) +#define ngx_http_spdy_stream_index(sscf, sid) \ + ((sid >> 1) & sscf->streams_index_mask) + +static ngx_int_t ngx_http_spdy_parse_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_handle_request_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_method(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_scheme(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_host(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_path(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_parse_version(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_construct_request_line(ngx_http_request_t *r); +static void ngx_http_spdy_run_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_spdy_init_request_body(ngx_http_request_t *r); + +static ngx_int_t ngx_http_spdy_terminate_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream, ngx_uint_t status); + +static void ngx_http_spdy_close_stream_handler(ngx_event_t *ev); + +static void ngx_http_spdy_handle_connection_handler(ngx_event_t *rev); +static void ngx_http_spdy_keepalive_handler(ngx_event_t *rev); +static void ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc, + ngx_int_t rc); + +static ngx_int_t ngx_http_spdy_adjust_windows(ngx_http_spdy_connection_t *sc, + ssize_t delta); + +static void ngx_http_spdy_pool_cleanup(void *data); + +static void *ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size); +static void ngx_http_spdy_zfree(void *opaque, void *address); + + +static const u_char ngx_http_spdy_dict[] = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, /* - - - - o p t i */ + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, /* o n s - - - - h */ + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, /* e a d - - - - p */ + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, /* o s t - - - - p */ + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, /* u t - - - - d e */ + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, /* l e t e - - - - */ + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, /* t r a c e - - - */ + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, /* - a c c e p t - */ + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, /* - - - a c c e p */ + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, /* t - c h a r s e */ + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, /* t - - - - a c c */ + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, /* e p t - e n c o */ + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, /* d i n g - - - - */ + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, /* a c c e p t - l */ + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, /* a n g u a g e - */ + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, /* - - - a c c e p */ + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, /* t - r a n g e s */ + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, /* - - - - a g e - */ + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, /* - - - a l l o w */ + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, /* - - - - a u t h */ + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, /* o r i z a t i o */ + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, /* n - - - - c a c */ + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, /* h e - c o n t r */ + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, /* o l - - - - c o */ + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, /* n n e c t i o n */ + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, /* e n t - b a s e */ + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, /* e n t - e n c o */ + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, /* d i n g - - - - */ + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, /* c o n t e n t - */ + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, /* l a n g u a g e */ + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, /* - - - - c o n t */ + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, /* e n t - l e n g */ + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, /* t h - - - - c o */ + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, /* n t e n t - l o */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, /* c a t i o n - - */ + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, /* - - c o n t e n */ + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, /* t - m d 5 - - - */ + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, /* - c o n t e n t */ + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, /* - r a n g e - - */ + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, /* - - c o n t e n */ + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, /* t - t y p e - - */ + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, /* - - d a t e - - */ + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, /* - - e t a g - - */ + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, /* - - e x p e c t */ + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, /* - - - - e x p i */ + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, /* r e s - - - - f */ + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, /* r o m - - - - h */ + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, /* o s t - - - - i */ + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, /* f - m a t c h - */ + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, /* - - - i f - m o */ + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, /* d i f i e d - s */ + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, /* i n c e - - - - */ + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, /* i f - n o n e - */ + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, /* m a t c h - - - */ + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, /* - i f - r a n g */ + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, /* e - - - - i f - */ + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, /* u n m o d i f i */ + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, /* e d - s i n c e */ + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, /* - - - - l a s t */ + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, /* - m o d i f i e */ + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, /* d - - - - l o c */ + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, /* a t i o n - - - */ + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, /* - m a x - f o r */ + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, /* w a r d s - - - */ + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, /* - p r a g m a - */ + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, /* - - - p r o x y */ + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, /* - a u t h e n t */ + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, /* i c a t e - - - */ + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, /* - p r o x y - a */ + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, /* u t h o r i z a */ + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, /* t i o n - - - - */ + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, /* r a n g e - - - */ + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, /* - r e f e r e r */ + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, /* - - - - r e t r */ + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, /* y - a f t e r - */ + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, /* - - - s e r v e */ + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, /* r - - - - t e - */ + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, /* - - - t r a i l */ + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, /* e r - - - - t r */ + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, /* a n s f e r - e */ + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, /* n c o d i n g - */ + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, /* - - - u p g r a */ + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, /* d e - - - - u s */ + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, /* e r - a g e n t */ + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, /* - - - - v a r y */ + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, /* - - - - v i a - */ + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, /* - - - w a r n i */ + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, /* n g - - - - w w */ + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, /* w - a u t h e n */ + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, /* t i c a t e - - */ + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, /* - - m e t h o d */ + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, /* - - - - g e t - */ + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, /* - - - s t a t u */ + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, /* s - - - - 2 0 0 */ + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, /* - O K - - - - v */ + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, /* e r s i o n - - */ + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, /* - - H T T P - 1 */ + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, /* - 1 - - - - u r */ + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, /* l - - - - p u b */ + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, /* l i c - - - - s */ + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, /* e t - c o o k i */ + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, /* e - - - - k e e */ + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, /* p - a l i v e - */ + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, /* - - - o r i g i */ + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, /* n 1 0 0 1 0 1 2 */ + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, /* 0 1 2 0 2 2 0 5 */ + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, /* 2 0 6 3 0 0 3 0 */ + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, /* 2 3 0 3 3 0 4 3 */ + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, /* 0 5 3 0 6 3 0 7 */ + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, /* 4 0 2 4 0 5 4 0 */ + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, /* 6 4 0 7 4 0 8 4 */ + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, /* 0 9 4 1 0 4 1 1 */ + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, /* 4 1 2 4 1 3 4 1 */ + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, /* 4 4 1 5 4 1 6 4 */ + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, /* 1 7 5 0 2 5 0 4 */ + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, /* 5 0 5 2 0 3 - N */ + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, /* o n - A u t h o */ + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, /* r i t a t i v e */ + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, /* - I n f o r m a */ + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, /* t i o n 2 0 4 - */ + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, /* N o - C o n t e */ + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, /* n t 3 0 1 - M o */ + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, /* v e d - P e r m */ + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, /* a n e n t l y 4 */ + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, /* 0 0 - B a d - R */ + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, /* e q u e s t 4 0 */ + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, /* 1 - U n a u t h */ + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, /* o r i z e d 4 0 */ + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, /* 3 - F o r b i d */ + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, /* d e n 4 0 4 - N */ + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, /* o t - F o u n d */ + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, /* 5 0 0 - I n t e */ + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, /* r n a l - S e r */ + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, /* v e r - E r r o */ + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, /* r 5 0 1 - N o t */ + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, /* - I m p l e m e */ + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, /* n t e d 5 0 3 - */ + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, /* S e r v i c e - */ + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, /* U n a v a i l a */ + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, /* b l e J a n - F */ + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, /* e b - M a r - A */ + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, /* p r - M a y - J */ + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, /* u n - J u l - A */ + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, /* u g - S e p t - */ + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, /* O c t - N o v - */ + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, /* D e c - 0 0 - 0 */ + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, /* 0 - 0 0 - M o n */ + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, /* - - T u e - - W */ + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, /* e d - - T h u - */ + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, /* - F r i - - S a */ + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, /* t - - S u n - - */ + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, /* G M T c h u n k */ + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, /* e d - t e x t - */ + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, /* h t m l - i m a */ + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, /* g e - p n g - i */ + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, /* m a g e - j p g */ + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, /* - i m a g e - g */ + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, /* i f - a p p l i */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, /* c a t i o n - x */ + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, /* m l - a p p l i */ + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, /* c a t i o n - x */ + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, /* h t m l - x m l */ + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, /* - t e x t - p l */ + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, /* a i n - t e x t */ + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, /* - j a v a s c r */ + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, /* i p t - p u b l */ + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, /* i c p r i v a t */ + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, /* e m a x - a g e */ + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, /* - g z i p - d e */ + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, /* f l a t e - s d */ + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, /* c h c h a r s e */ + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, /* t - u t f - 8 c */ + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, /* h a r s e t - i */ + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, /* s o - 8 8 5 9 - */ + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, /* 1 - u t f - - - */ + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e /* - e n q - 0 - */ +}; + + +static ngx_http_spdy_request_header_t ngx_http_spdy_request_headers[] = { + { 0, 6, "method", ngx_http_spdy_parse_method }, + { 0, 6, "scheme", ngx_http_spdy_parse_scheme }, + { 0, 4, "host", ngx_http_spdy_parse_host }, + { 0, 4, "path", ngx_http_spdy_parse_path }, + { 0, 7, "version", ngx_http_spdy_parse_version }, +}; + +#define NGX_SPDY_REQUEST_HEADERS \ + (sizeof(ngx_http_spdy_request_headers) \ + / sizeof(ngx_http_spdy_request_header_t)) + + +void +ngx_http_spdy_init(ngx_event_t *rev) +{ + int rc; + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_main_conf_t *smcf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init spdy request"); + + c->log->action = "processing SPDY"; + + smcf = ngx_http_get_module_main_conf(hc->conf_ctx, ngx_http_spdy_module); + + if (smcf->recv_buffer == NULL) { + smcf->recv_buffer = ngx_palloc(ngx_cycle->pool, smcf->recv_buffer_size); + if (smcf->recv_buffer == NULL) { + ngx_http_close_connection(c); + return; + } + } + + sc = ngx_pcalloc(c->pool, sizeof(ngx_http_spdy_connection_t)); + if (sc == NULL) { + ngx_http_close_connection(c); + return; + } + + sc->connection = c; + sc->http_connection = hc; + + sc->send_window = NGX_SPDY_CONNECTION_WINDOW; + sc->recv_window = NGX_SPDY_CONNECTION_WINDOW; + + sc->init_window = NGX_SPDY_INIT_STREAM_WINDOW; + + sc->handler = hc->proxy_protocol ? ngx_http_spdy_proxy_protocol + : ngx_http_spdy_state_head; + + sc->zstream_in.zalloc = ngx_http_spdy_zalloc; + sc->zstream_in.zfree = ngx_http_spdy_zfree; + sc->zstream_in.opaque = sc; + + rc = inflateInit(&sc->zstream_in); + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "inflateInit() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + sc->zstream_out.zalloc = ngx_http_spdy_zalloc; + sc->zstream_out.zfree = ngx_http_spdy_zfree; + sc->zstream_out.opaque = sc; + + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_spdy_module); + + rc = deflateInit2(&sc->zstream_out, (int) sscf->headers_comp, + Z_DEFLATED, 11, 4, Z_DEFAULT_STRATEGY); + + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "deflateInit2() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + rc = deflateSetDictionary(&sc->zstream_out, ngx_http_spdy_dict, + sizeof(ngx_http_spdy_dict)); + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "deflateSetDictionary() failed: %d", rc); + ngx_http_close_connection(c); + return; + } + + sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log); + if (sc->pool == NULL) { + ngx_http_close_connection(c); + return; + } + + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_pool_cleanup_file_t)); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_spdy_pool_cleanup; + cln->data = sc; + + sc->streams_index = ngx_pcalloc(sc->pool, + ngx_http_spdy_streams_index_size(sscf) + * sizeof(ngx_http_spdy_stream_t *)); + if (sc->streams_index == NULL) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_spdy_send_settings(sc) == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_spdy_send_window_update(sc, 0, NGX_SPDY_MAX_WINDOW + - sc->recv_window) + == NGX_ERROR) + { + ngx_http_close_connection(c); + return; + } + + sc->recv_window = NGX_SPDY_MAX_WINDOW; + + ngx_queue_init(&sc->waiting); + ngx_queue_init(&sc->posted); + + c->data = sc; + + rev->handler = ngx_http_spdy_read_handler; + c->write->handler = ngx_http_spdy_write_handler; + + ngx_http_spdy_read_handler(rev); +} + + +static void +ngx_http_spdy_read_handler(ngx_event_t *rev) +{ + u_char *p, *end; + size_t available; + ssize_t n; + ngx_connection_t *c; + ngx_http_spdy_main_conf_t *smcf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + sc = c->data; + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy read handler"); + + sc->blocked = 1; + + smcf = ngx_http_get_module_main_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + available = smcf->recv_buffer_size - 2 * NGX_SPDY_STATE_BUFFER_SIZE; + + do { + p = smcf->recv_buffer; + + ngx_memcpy(p, sc->buffer, NGX_SPDY_STATE_BUFFER_SIZE); + end = p + sc->buffer_used; + + n = c->recv(c, end, available); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0 && (sc->incomplete || sc->processing)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); + } + + if (n == 0 || n == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + end += n; + + sc->buffer_used = 0; + sc->incomplete = 0; + + do { + p = sc->handler(sc, p, end); + + if (p == NULL) { + return; + } + + } while (p != end); + + } while (rev->ready); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (sc->last_out && ngx_http_spdy_send_output_queue(sc) == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + sc->blocked = 0; + + if (sc->processing) { + if (rev->timer_set) { + ngx_del_timer(rev); + } + return; + } + + ngx_http_spdy_handle_connection(sc); +} + + +static void +ngx_http_spdy_write_handler(ngx_event_t *wev) +{ + ngx_int_t rc; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_connection_t *sc; + + c = wev->data; + sc = c->data; + + if (wev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy write event timed out"); + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy write handler"); + + sc->blocked = 1; + + rc = ngx_http_spdy_send_output_queue(sc); + + if (rc == NGX_ERROR) { + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + return; + } + + while (!ngx_queue_empty(&sc->posted)) { + q = ngx_queue_head(&sc->posted); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + stream->handled = 0; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "run spdy stream %ui", stream->id); + + wev = stream->request->connection->write; + wev->handler(wev); + } + + sc->blocked = 0; + + if (rc == NGX_AGAIN) { + return; + } + + ngx_http_spdy_handle_connection(sc); +} + + +ngx_int_t +ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc) +{ + int tcp_nodelay; + ngx_chain_t *cl; + ngx_event_t *wev; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + ngx_http_spdy_out_frame_t *out, *frame, *fn; + + c = sc->connection; + + if (c->error) { + return NGX_ERROR; + } + + wev = c->write; + + if (!wev->ready) { + return NGX_OK; + } + + cl = NULL; + out = NULL; + + for (frame = sc->last_out; frame; frame = fn) { + frame->last->next = cl; + cl = frame->first; + + fn = frame->next; + frame->next = out; + out = frame; + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy frame out: %p sid:%ui prio:%ui bl:%d len:%uz", + out, out->stream ? out->stream->id : 0, out->priority, + out->blocked, out->length); + } + + cl = c->send_chain(c, cl, 0); + + if (cl == NGX_CHAIN_ERROR) { + goto error; + } + + clcf = ngx_http_get_module_loc_conf(sc->http_connection->conf_ctx, + ngx_http_core_module); + + if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { + goto error; + } + + if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { + if (ngx_tcp_push(c->fd) == -1) { + ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed"); + goto error; + } + + c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; + tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0; + + } else { + tcp_nodelay = 1; + } + + if (tcp_nodelay + && clcf->tcp_nodelay + && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) + == -1) + { +#if (NGX_SOLARIS) + /* Solaris returns EINVAL if a socket has been shut down */ + c->log_error = NGX_ERROR_IGNORE_EINVAL; +#endif + + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + + c->log_error = NGX_ERROR_INFO; + goto error; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + if (cl) { + ngx_add_timer(wev, clcf->send_timeout); + + } else { + if (wev->timer_set) { + ngx_del_timer(wev); + } + } + + for ( /* void */ ; out; out = fn) { + fn = out->next; + + if (out->handler(sc, out) != NGX_OK) { + out->blocked = 1; + out->priority = NGX_SPDY_HIGHEST_PRIORITY; + break; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy frame sent: %p sid:%ui bl:%d len:%uz", + out, out->stream ? out->stream->id : 0, + out->blocked, out->length); + } + + frame = NULL; + + for ( /* void */ ; out; out = fn) { + fn = out->next; + out->next = frame; + frame = out; + } + + sc->last_out = frame; + + return NGX_OK; + +error: + + c->error = 1; + + if (!sc->blocked) { + ngx_post_event(wev, &ngx_posted_events); + } + + return NGX_ERROR; +} + + +static void +ngx_http_spdy_handle_connection(ngx_http_spdy_connection_t *sc) +{ + ngx_connection_t *c; + ngx_http_spdy_srv_conf_t *sscf; + + if (sc->last_out || sc->processing) { + return; + } + + c = sc->connection; + + if (c->error) { + ngx_http_close_connection(c); + return; + } + + if (c->buffered) { + return; + } + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + if (sc->incomplete) { + ngx_add_timer(c->read, sscf->recv_timeout); + return; + } + + if (ngx_terminate || ngx_exiting) { + ngx_http_close_connection(c); + return; + } + + ngx_destroy_pool(sc->pool); + + sc->pool = NULL; + sc->free_ctl_frames = NULL; + sc->free_fake_connections = NULL; + +#if (NGX_HTTP_SSL) + if (c->ssl) { + ngx_ssl_free_buffer(c); + } +#endif + + c->destroyed = 1; + c->idle = 1; + ngx_reusable_connection(c, 1); + + c->write->handler = ngx_http_empty_handler; + c->read->handler = ngx_http_spdy_keepalive_handler; + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + ngx_add_timer(c->read, sscf->keepalive_timeout); +} + + +static u_char * +ngx_http_spdy_proxy_protocol(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_log_t *log; + + log = sc->connection->log; + log->action = "reading PROXY protocol"; + + pos = ngx_proxy_protocol_read(sc->connection, pos, end); + + log->action = "processing SPDY"; + + if (pos == NULL) { + return ngx_http_spdy_state_protocol_error(sc); + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_head(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + uint32_t head, flen; + ngx_uint_t type; + + if (end - pos < NGX_SPDY_FRAME_HEADER_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_head); + } + + head = ngx_spdy_frame_parse_uint32(pos); + + pos += sizeof(uint32_t); + + flen = ngx_spdy_frame_parse_uint32(pos); + + sc->flags = ngx_spdy_frame_flags(flen); + sc->length = ngx_spdy_frame_length(flen); + + pos += sizeof(uint32_t); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "process spdy frame head:%08XD f:%Xd l:%uz", + head, sc->flags, sc->length); + + if (ngx_spdy_ctl_frame_check(head)) { + type = ngx_spdy_ctl_frame_type(head); + + switch (type) { + + case NGX_SPDY_SYN_STREAM: + return ngx_http_spdy_state_syn_stream(sc, pos, end); + + case NGX_SPDY_SYN_REPLY: + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent unexpected SYN_REPLY frame"); + return ngx_http_spdy_state_protocol_error(sc); + + case NGX_SPDY_RST_STREAM: + return ngx_http_spdy_state_rst_stream(sc, pos, end); + + case NGX_SPDY_SETTINGS: + return ngx_http_spdy_state_settings(sc, pos, end); + + case NGX_SPDY_PING: + return ngx_http_spdy_state_ping(sc, pos, end); + + case NGX_SPDY_GOAWAY: + return ngx_http_spdy_state_skip(sc, pos, end); /* TODO */ + + case NGX_SPDY_HEADERS: + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent unexpected HEADERS frame"); + return ngx_http_spdy_state_protocol_error(sc); + + case NGX_SPDY_WINDOW_UPDATE: + return ngx_http_spdy_state_window_update(sc, pos, end); + + default: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy control frame with unknown type %ui", type); + return ngx_http_spdy_state_skip(sc, pos, end); + } + } + + if (ngx_spdy_data_frame_check(head)) { + sc->stream = ngx_http_spdy_get_stream_by_id(sc, head); + return ngx_http_spdy_state_data(sc, pos, end); + } + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent invalid frame"); + + return ngx_http_spdy_state_protocol_error(sc); +} + + +static u_char * +ngx_http_spdy_state_syn_stream(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t sid, prio; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + if (end - pos < NGX_SPDY_SYN_STREAM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_syn_stream); + } + + if (sc->length <= NGX_SPDY_SYN_STREAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->length -= NGX_SPDY_SYN_STREAM_SIZE; + + sid = ngx_spdy_frame_parse_sid(pos); + prio = pos[8] >> 5; + + pos += NGX_SPDY_SYN_STREAM_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SYN_STREAM frame sid:%ui prio:%ui", sid, prio); + + if (sid % 2 == 0 || sid <= sc->last_sid) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame " + "with invalid Stream-ID %ui", sid); + + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream) { + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_PROTOCOL_ERROR) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + } + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->last_sid = sid; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + if (sc->processing >= sscf->concurrent_streams) { + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "concurrent streams exceeded %ui", sc->processing); + + if (ngx_http_spdy_send_rst_stream(sc, sid, NGX_SPDY_REFUSED_STREAM, + prio) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + stream = ngx_http_spdy_create_stream(sc, sid, prio); + if (stream == NULL) { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->in_closed = (sc->flags & NGX_SPDY_FLAG_FIN) ? 1 : 0; + + stream->request->request_length = NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SYN_STREAM_SIZE + + sc->length; + + sc->stream = stream; + + return ngx_http_spdy_state_headers(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + int z; + size_t size; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_http_request_t *r; + + size = end - pos; + + if (size == 0) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + if (size > sc->length) { + size = sc->length; + } + + r = sc->stream->request; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "process spdy header block %uz of %uz", size, sc->length); + + buf = r->header_in; + + sc->zstream_in.next_in = pos; + sc->zstream_in.avail_in = size; + sc->zstream_in.next_out = buf->last; + + /* one byte is reserved for null-termination of the last header value */ + sc->zstream_in.avail_out = buf->end - buf->last - 1; + + z = inflate(&sc->zstream_in, Z_NO_FLUSH); + + if (z == Z_NEED_DICT) { + z = inflateSetDictionary(&sc->zstream_in, ngx_http_spdy_dict, + sizeof(ngx_http_spdy_dict)); + + if (z != Z_OK) { + if (z == Z_DATA_ERROR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent SYN_STREAM frame with header " + "block encoded using wrong dictionary: %ul", + (u_long) sc->zstream_in.adler); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "inflateSetDictionary() failed: %d", z); + + return ngx_http_spdy_state_internal_error(sc); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflateSetDictionary(): %d", z); + + z = sc->zstream_in.avail_in ? inflate(&sc->zstream_in, Z_NO_FLUSH) + : Z_OK; + } + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + z); + + if (z != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, z); + } + + sc->length -= sc->zstream_in.next_in - pos; + pos = sc->zstream_in.next_in; + + buf->last = sc->zstream_in.next_out; + + if (r->headers_in.headers.part.elts == NULL) { + + if (buf->last - buf->pos < NGX_SPDY_NV_NUM_SIZE) { + + if (sc->length == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "premature end of spdy header block"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + sc->entries = ngx_spdy_frame_parse_uint32(buf->pos); + + buf->pos += NGX_SPDY_NV_NUM_SIZE; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy header block has %ui entries", + sc->entries); + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (ngx_array_init(&r->headers_in.cookies, r->pool, 2, + sizeof(ngx_table_elt_t *)) + != NGX_OK) + { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + } + + while (sc->entries) { + + rc = ngx_http_spdy_parse_header(r); + + switch (rc) { + + case NGX_DONE: + sc->entries--; + + case NGX_OK: + break; + + case NGX_AGAIN: + + if (sc->zstream_in.avail_in) { + + rc = ngx_http_spdy_alloc_large_header_buffer(r); + + if (rc == NGX_DECLINED) { + ngx_http_finalize_request(r, + NGX_HTTP_REQUEST_HEADER_TOO_LARGE); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (rc != NGX_OK) { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + /* null-terminate the last processed header name or value */ + *buf->pos = '\0'; + + buf = r->header_in; + + sc->zstream_in.next_out = buf->last; + + /* one byte is reserved for null-termination */ + sc->zstream_in.avail_out = buf->end - buf->last - 1; + + z = inflate(&sc->zstream_in, Z_NO_FLUSH); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + z); + + if (z != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, z); + } + + sc->length -= sc->zstream_in.next_in - pos; + pos = sc->zstream_in.next_in; + + buf->last = sc->zstream_in.next_out; + + continue; + } + + if (sc->length == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "premature end of spdy header block"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + + case NGX_HTTP_PARSE_INVALID_HEADER: + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + + default: /* NGX_ERROR */ + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + /* a header line has been parsed successfully */ + + rc = ngx_http_spdy_handle_request_header(r); + + if (rc != NGX_OK) { + if (rc == NGX_HTTP_PARSE_INVALID_HEADER) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + + if (rc != NGX_ABORT) { + ngx_http_spdy_close_stream(sc->stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + return ngx_http_spdy_state_headers_skip(sc, pos, end); + } + } + + if (buf->pos != buf->last || sc->zstream_in.avail_in) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "incorrect number of spdy header block entries"); + + return ngx_http_spdy_state_headers_error(sc, pos, end); + } + + if (sc->length) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers); + } + + /* null-terminate the last header value */ + *buf->pos = '\0'; + + ngx_http_spdy_run_request(r); + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers_skip(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + int n; + size_t size; + u_char buffer[NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE]; + + if (sc->length == 0) { + return ngx_http_spdy_state_complete(sc, pos, end); + } + + size = end - pos; + + if (size == 0) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers_skip); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy header block skip %uz of %uz", size, sc->length); + + sc->zstream_in.next_in = pos; + sc->zstream_in.avail_in = (size < sc->length) ? size : sc->length; + + while (sc->zstream_in.avail_in) { + sc->zstream_in.next_out = buffer; + sc->zstream_in.avail_out = NGX_SPDY_SKIP_HEADERS_BUFFER_SIZE; + + n = inflate(&sc->zstream_in, Z_NO_FLUSH); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy inflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_in.next_in, sc->zstream_in.next_out, + sc->zstream_in.avail_in, sc->zstream_in.avail_out, + n); + + if (n != Z_OK) { + return ngx_http_spdy_state_inflate_error(sc, n); + } + } + + pos = sc->zstream_in.next_in; + + if (size < sc->length) { + sc->length -= size; + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_headers_skip); + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_headers_error(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_http_spdy_stream_t *stream; + + stream = sc->stream; + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame for stream %ui " + "with invalid header block", stream->id); + + if (ngx_http_spdy_send_rst_stream(sc, stream->id, NGX_SPDY_PROTOCOL_ERROR, + stream->priority) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->out_closed = 1; + + ngx_http_spdy_close_stream(stream, NGX_HTTP_BAD_REQUEST); + + return ngx_http_spdy_state_headers_skip(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_window_update(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t delta; + ngx_uint_t sid; + ngx_event_t *wev; + ngx_queue_t *q; + ngx_http_spdy_stream_t *stream; + + if (end - pos < NGX_SPDY_WINDOW_UPDATE_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_window_update); + } + + if (sc->length != NGX_SPDY_WINDOW_UPDATE_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent WINDOW_UPDATE frame " + "with incorrect length %uz", sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sid = ngx_spdy_frame_parse_sid(pos); + + pos += NGX_SPDY_SID_SIZE; + + delta = ngx_spdy_frame_parse_delta(pos); + + pos += NGX_SPDY_DELTA_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy WINDOW_UPDATE sid:%ui delta:%uz", sid, delta); + + if (sid) { + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + if (stream->send_window > (ssize_t) (NGX_SPDY_MAX_WINDOW - delta)) { + + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated flow control for stream %ui: " + "received WINDOW_UPDATE frame with delta %uz " + "not allowed for window %z", + sid, delta, stream->send_window); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + stream->send_window += delta; + + if (stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + } + } + + } else { + sc->send_window += delta; + + if (sc->send_window > NGX_SPDY_MAX_WINDOW) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated connection flow control: " + "received WINDOW_UPDATE frame with delta %uz " + "not allowed for window %uz", + delta, sc->send_window); + + return ngx_http_spdy_state_protocol_error(sc); + } + + while (!ngx_queue_empty(&sc->waiting)) { + q = ngx_queue_head(&sc->waiting); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + stream->handled = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + + if (sc->send_window == 0) { + break; + } + } + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_data(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_http_spdy_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy DATA frame"); + + if (sc->length > sc->recv_window) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated connection flow control: " + "received DATA frame length %uz, available window %uz", + sc->length, sc->recv_window); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sc->recv_window -= sc->length; + + if (sc->recv_window < NGX_SPDY_MAX_WINDOW / 4) { + + if (ngx_http_spdy_send_window_update(sc, 0, + NGX_SPDY_MAX_WINDOW + - sc->recv_window) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + sc->recv_window = NGX_SPDY_MAX_WINDOW; + } + + stream = sc->stream; + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + if (sc->length > stream->recv_window) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client violated flow control for stream %ui: " + "received DATA frame length %uz, available window %uz", + stream->id, sc->length, stream->recv_window); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + stream->recv_window -= sc->length; + + if (stream->recv_window < NGX_SPDY_STREAM_WINDOW / 4) { + + if (ngx_http_spdy_send_window_update(sc, stream->id, + NGX_SPDY_STREAM_WINDOW + - stream->recv_window) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + stream->recv_window = NGX_SPDY_STREAM_WINDOW; + } + + if (stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent DATA frame for half-closed stream %ui", + stream->id); + + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_STREAM_ALREADY_CLOSED) + == NGX_ERROR) + { + return ngx_http_spdy_state_internal_error(sc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + return ngx_http_spdy_state_read_data(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_read_data(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t size; + ssize_t n; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_temp_file_t *tf; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + stream = sc->stream; + + if (stream == NULL) { + return ngx_http_spdy_state_skip(sc, pos, end); + } + + if (stream->skip_data) { + + if (sc->flags & NGX_SPDY_FLAG_FIN) { + stream->in_closed = 1; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "skipping spdy DATA frame, reason: %d", + stream->skip_data); + + return ngx_http_spdy_state_skip(sc, pos, end); + } + + size = end - pos; + + if (size > sc->length) { + size = sc->length; + } + + r = stream->request; + + if (r->request_body == NULL + && ngx_http_spdy_init_request_body(r) != NGX_OK) + { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + return ngx_http_spdy_state_skip(sc, pos, end); + } + + rb = r->request_body; + tf = rb->temp_file; + buf = rb->buf; + + if (size) { + rb->rest += size; + + if (r->headers_in.content_length_n != -1 + && r->headers_in.content_length_n < rb->rest) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client intended to send body data " + "larger than declared"); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + + } else { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->client_max_body_size + && clcf->client_max_body_size < rb->rest) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send " + "too large chunked body: %O bytes", rb->rest); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + } + } + + sc->length -= size; + + if (tf) { + buf->start = pos; + buf->pos = pos; + + pos += size; + + buf->end = pos; + buf->last = pos; + + n = ngx_write_chain_to_temp_file(tf, rb->bufs); + + /* TODO: n == 0 or not complete and level event */ + + if (n == NGX_ERROR) { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + goto error; + } + + tf->offset += n; + + } else { + buf->last = ngx_cpymem(buf->last, pos, size); + pos += size; + } + + r->request_length += size; + } + + if (sc->length) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_read_data); + } + + if (sc->flags & NGX_SPDY_FLAG_FIN) { + + stream->in_closed = 1; + + if (r->headers_in.content_length_n < 0) { + r->headers_in.content_length_n = rb->rest; + + } else if (r->headers_in.content_length_n != rb->rest) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream: " + "only %O out of %O bytes of request body received", + rb->rest, r->headers_in.content_length_n); + + stream->skip_data = NGX_SPDY_DATA_ERROR; + goto error; + } + + if (tf) { + ngx_memzero(buf, sizeof(ngx_buf_t)); + + buf->in_file = 1; + buf->file_last = tf->file.offset; + buf->file = &tf->file; + + rb->buf = NULL; + } + + if (rb->post_handler) { + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); + +error: + + if (rb->post_handler) { + + if (stream->skip_data == NGX_SPDY_DATA_ERROR) { + rc = (r->headers_in.content_length_n == -1) + ? NGX_HTTP_REQUEST_ENTITY_TOO_LARGE + : NGX_HTTP_BAD_REQUEST; + + } else { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_http_finalize_request(r, rc); + } + + return ngx_http_spdy_state_skip(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_rst_stream(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t sid, status; + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_spdy_stream_t *stream; + + if (end - pos < NGX_SPDY_RST_STREAM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_rst_stream); + } + + if (sc->length != NGX_SPDY_RST_STREAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent RST_STREAM frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + sid = ngx_spdy_frame_parse_sid(pos); + + pos += NGX_SPDY_SID_SIZE; + + status = ngx_spdy_frame_parse_uint32(pos); + + pos += sizeof(uint32_t); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy RST_STREAM sid:%ui st:%ui", sid, status); + + stream = ngx_http_spdy_get_stream_by_id(sc, sid); + + if (stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "unknown spdy stream"); + + return ngx_http_spdy_state_complete(sc, pos, end); + } + + stream->in_closed = 1; + stream->out_closed = 1; + + fc = stream->request->connection; + fc->error = 1; + + switch (status) { + + case NGX_SPDY_CANCEL: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client canceled stream %ui", sid); + break; + + case NGX_SPDY_INTERNAL_ERROR: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui due to internal error", + sid); + break; + + default: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui with status %ui", + sid, status); + break; + } + + ev = fc->read; + ev->handler(ev); + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_ping(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + if (end - pos < NGX_SPDY_PING_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_ping); + } + + if (sc->length != NGX_SPDY_PING_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent PING frame with incorrect length %uz", + sc->length); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy PING frame"); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_PING_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return ngx_http_spdy_state_internal_error(sc); + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_PING); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_PING_SIZE); + + p = ngx_cpymem(p, pos, NGX_SPDY_PING_SIZE); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + pos += NGX_SPDY_PING_SIZE; + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_skip(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + size_t size; + + size = end - pos; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame skip %uz of %uz", size, sc->length); + + if (size < sc->length) { + sc->length -= size; + return ngx_http_spdy_state_save(sc, end, end, + ngx_http_spdy_state_skip); + } + + return ngx_http_spdy_state_complete(sc, pos + sc->length, end); +} + + +static u_char * +ngx_http_spdy_state_settings(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_uint_t fid, val; + + if (sc->entries == 0) { + + if (end - pos < NGX_SPDY_SETTINGS_NUM_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_settings); + } + + sc->entries = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_NUM_SIZE; + sc->length -= NGX_SPDY_SETTINGS_NUM_SIZE; + + if (sc->length < sc->entries * NGX_SPDY_SETTINGS_PAIR_SIZE) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SETTINGS frame with incorrect " + "length %uz or number of entries %ui", + sc->length, sc->entries); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SETTINGS frame has %ui entries", sc->entries); + } + + while (sc->entries) { + if (end - pos < NGX_SPDY_SETTINGS_PAIR_SIZE) { + return ngx_http_spdy_state_save(sc, pos, end, + ngx_http_spdy_state_settings); + } + + sc->entries--; + sc->length -= NGX_SPDY_SETTINGS_PAIR_SIZE; + + fid = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_FID_SIZE; + + val = ngx_spdy_frame_parse_uint32(pos); + + pos += NGX_SPDY_SETTINGS_VAL_SIZE; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy SETTINGS entry fl:%ui id:%ui val:%ui", + ngx_spdy_frame_flags(fid), ngx_spdy_frame_id(fid), val); + + if (ngx_spdy_frame_flags(fid) == NGX_SPDY_SETTINGS_FLAG_PERSISTED) { + continue; + } + + switch (ngx_spdy_frame_id(fid)) { + + case NGX_SPDY_SETTINGS_INIT_WINDOW: + + if (val > NGX_SPDY_MAX_WINDOW) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SETTINGS frame with " + "incorrect INIT_WINDOW value: %ui", val); + + return ngx_http_spdy_state_protocol_error(sc); + } + + if (ngx_http_spdy_adjust_windows(sc, val - sc->init_window) + != NGX_OK) + { + return ngx_http_spdy_state_internal_error(sc); + } + + sc->init_window = val; + + continue; + } + } + + return ngx_http_spdy_state_complete(sc, pos, end); +} + + +static u_char * +ngx_http_spdy_state_complete(ngx_http_spdy_connection_t *sc, u_char *pos, + u_char *end) +{ + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame complete pos:%p end:%p", pos, end); + + if (pos > end) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "receive buffer overrun"); + + ngx_debug_point(); + return ngx_http_spdy_state_internal_error(sc); + } + + sc->handler = ngx_http_spdy_state_head; + sc->stream = NULL; + + return pos; +} + + +static u_char * +ngx_http_spdy_state_save(ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end, ngx_http_spdy_handler_pt handler) +{ + size_t size; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy frame state save pos:%p end:%p handler:%p", + pos, end, handler); + + size = end - pos; + + if (size > NGX_SPDY_STATE_BUFFER_SIZE) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "state buffer overflow: %uz bytes required", size); + + ngx_debug_point(); + return ngx_http_spdy_state_internal_error(sc); + } + + ngx_memcpy(sc->buffer, pos, NGX_SPDY_STATE_BUFFER_SIZE); + + sc->buffer_used = size; + sc->handler = handler; + sc->incomplete = 1; + + return end; +} + + +static u_char * +ngx_http_spdy_state_inflate_error(ngx_http_spdy_connection_t *sc, int rc) +{ + if (rc == Z_DATA_ERROR || rc == Z_STREAM_END) { + ngx_log_error(NGX_LOG_INFO, sc->connection->log, 0, + "client sent SYN_STREAM frame with " + "corrupted header block, inflate() failed: %d", rc); + + return ngx_http_spdy_state_protocol_error(sc); + } + + ngx_log_error(NGX_LOG_ERR, sc->connection->log, 0, + "inflate() failed: %d", rc); + + return ngx_http_spdy_state_internal_error(sc); +} + + +static u_char * +ngx_http_spdy_state_protocol_error(ngx_http_spdy_connection_t *sc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy state protocol error"); + + if (sc->stream) { + sc->stream->out_closed = 1; + ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_BAD_REQUEST); + } + + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_CLIENT_CLOSED_REQUEST); + + return NULL; +} + + +static u_char * +ngx_http_spdy_state_internal_error(ngx_http_spdy_connection_t *sc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy state internal error"); + + if (sc->stream) { + sc->stream->out_closed = 1; + ngx_http_spdy_close_stream(sc->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + ngx_http_spdy_finalize_connection(sc, NGX_HTTP_INTERNAL_SERVER_ERROR); + + return NULL; +} + + +static ngx_int_t +ngx_http_spdy_send_window_update(ngx_http_spdy_connection_t *sc, ngx_uint_t sid, + ngx_uint_t delta) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send WINDOW_UPDATE sid:%ui delta:%ui", sid, delta); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_WINDOW_UPDATE_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_WINDOW_UPDATE); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_WINDOW_UPDATE_SIZE); + + p = ngx_spdy_frame_write_sid(p, sid); + p = ngx_spdy_frame_aligned_write_uint32(p, delta); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_send_rst_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t sid, + ngx_uint_t status, ngx_uint_t priority) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + if (sc->connection->error) { + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send RST_STREAM sid:%ui st:%ui", sid, status); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_RST_STREAM_SIZE, + priority); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_RST_STREAM); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_RST_STREAM_SIZE); + + p = ngx_spdy_frame_write_sid(p, sid); + p = ngx_spdy_frame_aligned_write_uint32(p, status); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +#if 0 +static ngx_int_t +ngx_http_spdy_send_goaway(ngx_http_spdy_connection_t *sc) +{ + u_char *p; + ngx_buf_t *buf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send GOAWAY sid:%ui", sc->last_sid); + + frame = ngx_http_spdy_get_ctl_frame(sc, NGX_SPDY_GOAWAY_SIZE, + NGX_SPDY_HIGHEST_PRIORITY); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_GOAWAY); + p = ngx_spdy_frame_write_flags_and_len(p, 0, NGX_SPDY_GOAWAY_SIZE); + + p = ngx_spdy_frame_write_sid(p, sc->last_sid); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} +#endif + + +static ngx_int_t +ngx_http_spdy_send_settings(ngx_http_spdy_connection_t *sc) +{ + u_char *p; + ngx_buf_t *buf; + ngx_chain_t *cl; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_out_frame_t *frame; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy send SETTINGS frame"); + + frame = ngx_palloc(sc->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + cl = ngx_alloc_chain_link(sc->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + buf = ngx_create_temp_buf(sc->pool, NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE); + if (buf == NULL) { + return NGX_ERROR; + } + + buf->last_buf = 1; + + cl->buf = buf; + cl->next = NULL; + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_settings_frame_handler; + frame->stream = NULL; +#if (NGX_DEBUG) + frame->length = NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE; +#endif + frame->priority = NGX_SPDY_HIGHEST_PRIORITY; + frame->blocked = 0; + + p = buf->pos; + + p = ngx_spdy_frame_write_head(p, NGX_SPDY_SETTINGS); + p = ngx_spdy_frame_write_flags_and_len(p, NGX_SPDY_FLAG_CLEAR_SETTINGS, + NGX_SPDY_SETTINGS_NUM_SIZE + + 2 * NGX_SPDY_SETTINGS_PAIR_SIZE); + + p = ngx_spdy_frame_aligned_write_uint32(p, 2); + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + p = ngx_spdy_frame_write_flags_and_id(p, 0, NGX_SPDY_SETTINGS_MAX_STREAMS); + p = ngx_spdy_frame_aligned_write_uint32(p, sscf->concurrent_streams); + + p = ngx_spdy_frame_write_flags_and_id(p, 0, NGX_SPDY_SETTINGS_INIT_WINDOW); + p = ngx_spdy_frame_aligned_write_uint32(p, NGX_SPDY_STREAM_WINDOW); + + buf->last = p; + + ngx_http_spdy_queue_frame(sc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_http_spdy_settings_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + ngx_free_chain(sc->pool, frame->first); + + return NGX_OK; +} + + +static ngx_http_spdy_out_frame_t * +ngx_http_spdy_get_ctl_frame(ngx_http_spdy_connection_t *sc, size_t length, + ngx_uint_t priority) +{ + ngx_chain_t *cl; + ngx_http_spdy_out_frame_t *frame; + + frame = sc->free_ctl_frames; + + if (frame) { + sc->free_ctl_frames = frame->next; + + cl = frame->first; + cl->buf->pos = cl->buf->start; + + } else { + frame = ngx_palloc(sc->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NULL; + } + + cl = ngx_alloc_chain_link(sc->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = ngx_create_temp_buf(sc->pool, + NGX_SPDY_CTL_FRAME_BUFFER_SIZE); + if (cl->buf == NULL) { + return NULL; + } + + cl->buf->last_buf = 1; + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_ctl_frame_handler; + frame->stream = NULL; + } + +#if (NGX_DEBUG) + if (length > NGX_SPDY_CTL_FRAME_BUFFER_SIZE - NGX_SPDY_FRAME_HEADER_SIZE) { + ngx_log_error(NGX_LOG_ALERT, sc->pool->log, 0, + "requested control frame is too large: %uz", length); + return NULL; + } + + frame->length = length; +#endif + + frame->priority = priority; + frame->blocked = 0; + + return frame; +} + + +static ngx_int_t +ngx_http_spdy_ctl_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + frame->next = sc->free_ctl_frames; + sc->free_ctl_frames = frame; + + return NGX_OK; +} + + +static ngx_http_spdy_stream_t * +ngx_http_spdy_create_stream(ngx_http_spdy_connection_t *sc, ngx_uint_t id, + ngx_uint_t priority) +{ + ngx_log_t *log; + ngx_uint_t index; + ngx_event_t *rev, *wev; + ngx_connection_t *fc; + ngx_http_log_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_srv_conf_t *sscf; + + fc = sc->free_fake_connections; + + if (fc) { + sc->free_fake_connections = fc->data; + + rev = fc->read; + wev = fc->write; + log = fc->log; + ctx = log->data; + + } else { + fc = ngx_palloc(sc->pool, sizeof(ngx_connection_t)); + if (fc == NULL) { + return NULL; + } + + rev = ngx_palloc(sc->pool, sizeof(ngx_event_t)); + if (rev == NULL) { + return NULL; + } + + wev = ngx_palloc(sc->pool, sizeof(ngx_event_t)); + if (wev == NULL) { + return NULL; + } + + log = ngx_palloc(sc->pool, sizeof(ngx_log_t)); + if (log == NULL) { + return NULL; + } + + ctx = ngx_palloc(sc->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + return NULL; + } + + ctx->connection = fc; + ctx->request = NULL; + } + + ngx_memcpy(log, sc->connection->log, sizeof(ngx_log_t)); + + log->data = ctx; + + ngx_memzero(rev, sizeof(ngx_event_t)); + + rev->data = fc; + rev->ready = 1; + rev->handler = ngx_http_spdy_close_stream_handler; + rev->log = log; + + ngx_memcpy(wev, rev, sizeof(ngx_event_t)); + + wev->write = 1; + + ngx_memcpy(fc, sc->connection, sizeof(ngx_connection_t)); + + fc->data = sc->http_connection; + fc->read = rev; + fc->write = wev; + fc->sent = 0; + fc->log = log; + fc->buffered = 0; + fc->sndlowat = 1; + fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + r = ngx_http_create_request(fc); + if (r == NULL) { + return NULL; + } + + r->valid_location = 1; + + fc->data = r; + sc->connection->requests++; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + stream = ngx_pcalloc(r->pool, sizeof(ngx_http_spdy_stream_t)); + if (stream == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->spdy_stream = stream; + + stream->id = id; + stream->request = r; + stream->connection = sc; + + stream->send_window = sc->init_window; + stream->recv_window = NGX_SPDY_STREAM_WINDOW; + + stream->priority = priority; + + sscf = ngx_http_get_module_srv_conf(r, ngx_http_spdy_module); + + index = ngx_http_spdy_stream_index(sscf, id); + + stream->index = sc->streams_index[index]; + sc->streams_index[index] = stream; + + sc->processing++; + + return stream; +} + + +static ngx_http_spdy_stream_t * +ngx_http_spdy_get_stream_by_id(ngx_http_spdy_connection_t *sc, + ngx_uint_t sid) +{ + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + stream = sc->streams_index[ngx_http_spdy_stream_index(sscf, sid)]; + + while (stream) { + if (stream->id == sid) { + return stream; + } + + stream = stream->index; + } + + return NULL; +} + + +static ngx_int_t +ngx_http_spdy_parse_header(ngx_http_request_t *r) +{ + u_char *p, *end, ch; + ngx_uint_t hash; + ngx_http_core_srv_conf_t *cscf; + + enum { + sw_name_len = 0, + sw_name, + sw_value_len, + sw_value + } state; + + state = r->state; + + p = r->header_in->pos; + end = r->header_in->last; + + switch (state) { + + case sw_name_len: + + if (end - p < NGX_SPDY_NV_NLEN_SIZE) { + return NGX_AGAIN; + } + + r->lowcase_index = ngx_spdy_frame_parse_uint32(p); + + if (r->lowcase_index == 0) { + return NGX_ERROR; + } + + /* null-terminate the previous header value */ + *p = '\0'; + + p += NGX_SPDY_NV_NLEN_SIZE; + + r->invalid_header = 0; + + state = sw_name; + + /* fall through */ + + case sw_name: + + if ((ngx_uint_t) (end - p) < r->lowcase_index) { + break; + } + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_name_start = p; + r->header_name_end = p + r->lowcase_index; + + if (p[0] == ':') { + p++; + } + + hash = 0; + + for ( /* void */ ; p != r->header_name_end; p++) { + + ch = *p; + + hash = ngx_hash(hash, ch); + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + switch (ch) { + case '\0': + case LF: + case CR: + case ':': + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%*s\"", + r->lowcase_index, r->header_name_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ch >= 'A' && ch <= 'Z') { + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + r->header_hash = hash; + + state = sw_value_len; + + /* fall through */ + + case sw_value_len: + + if (end - p < NGX_SPDY_NV_VLEN_SIZE) { + break; + } + + r->lowcase_index = ngx_spdy_frame_parse_uint32(p); + + /* null-terminate header name */ + *p = '\0'; + + p += NGX_SPDY_NV_VLEN_SIZE; + + state = sw_value; + + /* fall through */ + + case sw_value: + + if ((ngx_uint_t) (end - p) < r->lowcase_index) { + break; + } + + r->header_start = p; + + while (r->lowcase_index--) { + ch = *p; + + if (ch == '\0') { + + if (p == r->header_start) { + return NGX_ERROR; + } + + r->header_end = p; + r->header_in->pos = p + 1; + + r->state = sw_value; + + return NGX_OK; + } + + if (ch == CR || ch == LF) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%*s\" with " + "invalid value: \"%*s\\%c...\"", + r->header_name_end - r->header_name_start, + r->header_name_start, + p - r->header_start, + r->header_start, + ch == CR ? 'r' : 'n'); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p++; + } + + r->header_end = p; + r->header_in->pos = p; + + r->state = 0; + + return NGX_DONE; + } + + r->header_in->pos = p; + r->state = state; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_spdy_alloc_large_header_buffer(ngx_http_request_t *r) +{ + u_char *old, *new, *p; + size_t rest; + ngx_buf_t *buf; + ngx_http_spdy_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy alloc large header buffer"); + + stream = r->spdy_stream; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (stream->header_buffers + == (ngx_uint_t) cscf->large_client_header_buffers.num) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too large request"); + + return NGX_DECLINED; + } + + rest = r->header_in->last - r->header_in->pos; + + /* + * One more byte is needed for null-termination + * and another one for further progress. + */ + if (rest > cscf->large_client_header_buffers.size - 2) { + p = r->header_in->pos; + + if (rest > NGX_MAX_ERROR_STR - 300) { + rest = NGX_MAX_ERROR_STR - 300; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too long header name or value: \"%*s...\"", + rest, p); + + return NGX_DECLINED; + } + + buf = ngx_create_temp_buf(r->pool, cscf->large_client_header_buffers.size); + if (buf == NULL) { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy large header alloc: %p %uz", + buf->pos, buf->end - buf->last); + + old = r->header_in->pos; + new = buf->pos; + + if (rest) { + buf->last = ngx_cpymem(new, old, rest); + } + + r->header_in = buf; + + stream->header_buffers++; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_handle_request_header(ngx_http_request_t *r) +{ + ngx_uint_t i; + ngx_table_elt_t *h; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_request_header_t *sh; + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%*s\"", + r->header_end - r->header_name_start, + r->header_name_start); + return NGX_OK; + } + + } + + if (r->header_name_start[0] == ':') { + r->header_name_start++; + + for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) { + sh = &ngx_http_spdy_request_headers[i]; + + if (sh->hash != r->header_hash + || sh->len != r->header_name_end - r->header_name_start + || ngx_strncmp(sh->header, r->header_name_start, sh->len) != 0) + { + continue; + } + + return sh->handler(r); + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \":%*s\"", + r->header_end - r->header_name_start, + r->header_name_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = r->header_hash; + + h->key.len = r->header_name_end - r->header_name_start; + h->key.data = r->header_name_start; + + h->value.len = r->header_end - r->header_start; + h->value.data = r->header_start; + + h->lowcase_key = h->key.data; + + return NGX_OK; +} + + +void +ngx_http_spdy_request_headers_init(void) +{ + ngx_uint_t i; + ngx_http_spdy_request_header_t *h; + + for (i = 0; i < NGX_SPDY_REQUEST_HEADERS; i++) { + h = &ngx_http_spdy_request_headers[i]; + h->hash = ngx_hash_key(h->header, h->len); + } +} + + +static ngx_int_t +ngx_http_spdy_parse_method(ngx_http_request_t *r) +{ + size_t k, len; + ngx_uint_t n; + const u_char *p, *m; + + /* + * This array takes less than 256 sequential bytes, + * and if typical CPU cache line size is 64 bytes, + * it is prefetched for 4 load operations. + */ + static const struct { + u_char len; + const u_char method[11]; + uint32_t value; + } tests[] = { + { 3, "GET", NGX_HTTP_GET }, + { 4, "POST", NGX_HTTP_POST }, + { 4, "HEAD", NGX_HTTP_HEAD }, + { 7, "OPTIONS", NGX_HTTP_OPTIONS }, + { 8, "PROPFIND", NGX_HTTP_PROPFIND }, + { 3, "PUT", NGX_HTTP_PUT }, + { 5, "MKCOL", NGX_HTTP_MKCOL }, + { 6, "DELETE", NGX_HTTP_DELETE }, + { 4, "COPY", NGX_HTTP_COPY }, + { 4, "MOVE", NGX_HTTP_MOVE }, + { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, + { 4, "LOCK", NGX_HTTP_LOCK }, + { 6, "UNLOCK", NGX_HTTP_UNLOCK }, + { 5, "PATCH", NGX_HTTP_PATCH }, + { 5, "TRACE", NGX_HTTP_TRACE } + }, *test; + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :method header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + len = r->header_end - r->header_start; + + r->method_name.len = len; + r->method_name.data = r->header_start; + + test = tests; + n = sizeof(tests) / sizeof(tests[0]); + + do { + if (len == test->len) { + p = r->method_name.data; + m = test->method; + k = len; + + do { + if (*p++ != *m++) { + goto next; + } + } while (--k); + + r->method = test->value; + return NGX_OK; + } + + next: + test++; + + } while (--n); + + p = r->method_name.data; + + do { + if ((*p < 'A' || *p > 'Z') && *p != '_') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", + &r->method_name); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p++; + + } while (--len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_scheme(ngx_http_request_t *r) +{ + if (r->schema_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :schema header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + r->schema_start = r->header_start; + r->schema_end = r->header_end; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_host(ngx_http_request_t *r) +{ + ngx_table_elt_t *h; + + if (r->headers_in.host) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :host header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + r->headers_in.host = h; + + h->hash = r->header_hash; + + h->key.len = r->header_name_end - r->header_name_start; + h->key.data = r->header_name_start; + + h->value.len = r->header_end - r->header_start; + h->value.data = r->header_start; + + h->lowcase_key = h->key.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_path(ngx_http_request_t *r) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + r->uri_start = r->header_start; + r->uri_end = r->header_end; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid URI: \"%*s\"", + r->uri_end - r->uri_start, r->uri_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_request_uri() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_parse_version(ngx_http_request_t *r) +{ + u_char *p, ch; + + if (r->http_protocol.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :version header"); + + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + p = r->header_start; + + if (r->header_end - p < 8 || !(ngx_str5cmp(p, 'H', 'T', 'T', 'P', '/'))) { + goto invalid; + } + + ch = *(p + 5); + + if (ch < '1' || ch > '9') { + goto invalid; + } + + r->http_major = ch - '0'; + + for (p += 6; p != r->header_end - 2; p++) { + + ch = *p; + + if (ch == '.') { + break; + } + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_major = r->http_major * 10 + ch - '0'; + } + + if (*p != '.') { + goto invalid; + } + + ch = *(p + 1); + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_minor = ch - '0'; + + for (p += 2; p != r->header_end; p++) { + + ch = *p; + + if (ch < '0' || ch > '9') { + goto invalid; + } + + r->http_minor = r->http_minor * 10 + ch - '0'; + } + + r->http_protocol.len = r->header_end - r->header_start; + r->http_protocol.data = r->header_start; + r->http_version = r->http_major * 1000 + r->http_minor; + + return NGX_OK; + +invalid: + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid http version: \"%*s\"", + r->header_end - r->header_start, r->header_start); + + return NGX_HTTP_PARSE_INVALID_HEADER; +} + + +static ngx_int_t +ngx_http_spdy_construct_request_line(ngx_http_request_t *r) +{ + u_char *p; + + if (r->method_name.len == 0 + || r->unparsed_uri.len == 0 + || r->http_protocol.len == 0) + { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + r->request_line.len = r->method_name.len + 1 + + r->unparsed_uri.len + 1 + + r->http_protocol.len; + + p = ngx_pnalloc(r->pool, r->request_line.len + 1); + if (p == NULL) { + ngx_http_spdy_close_stream(r->spdy_stream, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + + *p++ = ' '; + + p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); + + *p++ = ' '; + + ngx_memcpy(p, r->http_protocol.data, r->http_protocol.len + 1); + + /* some modules expect the space character after method name */ + r->method_name.data = r->request_line.data; + + return NGX_OK; +} + + +static void +ngx_http_spdy_run_request(ngx_http_request_t *r) +{ + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + if (ngx_http_spdy_construct_request_line(r) != NGX_OK) { + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy http request line: \"%V\"", &r->request_line); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + part = &r->headers_in.headers.part; + h = part->elts; + + for (i = 0 ;; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + hh = ngx_hash_find(&cmcf->headers_in_hash, h[i].hash, + h[i].lowcase_key, h[i].key.len); + + if (hh && hh->handler(r, &h[i], hh->offset) != NGX_OK) { + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy http header: \"%V: %V\"", &h[i].key, &h[i].value); + } + + r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; + + if (ngx_http_process_request_header(r) != NGX_OK) { + return; + } + + if (r->headers_in.content_length_n > 0 && r->spdy_stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + + r->spdy_stream->skip_data = NGX_SPDY_DATA_ERROR; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; + } + + ngx_http_process_request(r); +} + + +static ngx_int_t +ngx_http_spdy_init_request_body(ngx_http_request_t *r) +{ + ngx_buf_t *buf; + ngx_temp_file_t *tf; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); + if (rb == NULL) { + return NGX_ERROR; + } + + r->request_body = rb; + + if (r->spdy_stream->in_closed) { + return NGX_OK; + } + + rb->rest = r->headers_in.content_length_n; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->request_body_in_file_only + || rb->rest > (off_t) clcf->client_body_buffer_size + || rb->rest < 0) + { + tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); + if (tf == NULL) { + return NGX_ERROR; + } + + tf->file.fd = NGX_INVALID_FILE; + tf->file.log = r->connection->log; + tf->path = clcf->client_body_temp_path; + tf->pool = r->pool; + tf->warn = "a client request body is buffered to a temporary file"; + tf->log_level = r->request_body_file_log_level; + tf->persistent = r->request_body_in_persistent_file; + tf->clean = r->request_body_in_clean_file; + + if (r->request_body_file_group_access) { + tf->access = 0660; + } + + rb->temp_file = tf; + + if (r->spdy_stream->in_closed + && ngx_create_temp_file(&tf->file, tf->path, tf->pool, + tf->persistent, tf->clean, tf->access) + != NGX_OK) + { + return NGX_ERROR; + } + + buf = ngx_calloc_buf(r->pool); + if (buf == NULL) { + return NGX_ERROR; + } + + } else { + + if (rb->rest == 0) { + return NGX_OK; + } + + buf = ngx_create_temp_buf(r->pool, (size_t) rb->rest); + if (buf == NULL) { + return NGX_ERROR; + } + } + + rb->buf = buf; + + rb->bufs = ngx_alloc_chain_link(r->pool); + if (rb->bufs == NULL) { + return NGX_ERROR; + } + + rb->bufs->buf = buf; + rb->bufs->next = NULL; + + rb->rest = 0; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_spdy_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler) +{ + ngx_http_spdy_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy read request body"); + + stream = r->spdy_stream; + + switch (stream->skip_data) { + + case NGX_SPDY_DATA_DISCARD: + post_handler(r); + return NGX_OK; + + case NGX_SPDY_DATA_ERROR: + if (r->headers_in.content_length_n == -1) { + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } else { + return NGX_HTTP_BAD_REQUEST; + } + + case NGX_SPDY_DATA_INTERNAL_ERROR: + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (!r->request_body && ngx_http_spdy_init_request_body(r) != NGX_OK) { + stream->skip_data = NGX_SPDY_DATA_INTERNAL_ERROR; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (stream->in_closed) { + post_handler(r); + return NGX_OK; + } + + r->request_body->post_handler = post_handler; + + r->read_event_handler = ngx_http_test_reading; + r->write_event_handler = ngx_http_request_empty_handler; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_spdy_terminate_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream, ngx_uint_t status) +{ + ngx_event_t *rev; + ngx_connection_t *fc; + + if (ngx_http_spdy_send_rst_stream(sc, stream->id, status, + NGX_SPDY_HIGHEST_PRIORITY) + == NGX_ERROR) + { + return NGX_ERROR; + } + + stream->out_closed = 1; + + fc = stream->request->connection; + fc->error = 1; + + rev = fc->read; + rev->handler(rev); + + return NGX_OK; +} + + +static void +ngx_http_spdy_close_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *fc; + ngx_http_request_t *r; + + fc = ev->data; + r = fc->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy close stream handler"); + + ngx_http_spdy_close_stream(r->spdy_stream, 0); +} + + +void +ngx_http_spdy_close_stream(ngx_http_spdy_stream_t *stream, ngx_int_t rc) +{ + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_spdy_stream_t **index, *s; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_connection_t *sc; + + sc = stream->connection; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy close stream %ui, queued %ui, processing %ui", + stream->id, stream->queued, sc->processing); + + fc = stream->request->connection; + + if (stream->queued) { + fc->write->handler = ngx_http_spdy_close_stream_handler; + return; + } + + if (!stream->out_closed) { + if (ngx_http_spdy_send_rst_stream(sc, stream->id, + NGX_SPDY_INTERNAL_ERROR, + stream->priority) + != NGX_OK) + { + sc->connection->error = 1; + } + } + + if (sc->stream == stream) { + sc->stream = NULL; + } + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + index = sc->streams_index + ngx_http_spdy_stream_index(sscf, stream->id); + + for ( ;; ) { + s = *index; + + if (s == NULL) { + break; + } + + if (s == stream) { + *index = s->index; + break; + } + + index = &s->index; + } + + ngx_http_free_request(stream->request, rc); + + ev = fc->read; + + if (ev->active || ev->disabled) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "fake read event was activated"); + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + ev = fc->write; + + if (ev->active || ev->disabled) { + ngx_log_error(NGX_LOG_ALERT, sc->connection->log, 0, + "fake write event was activated"); + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + fc->data = sc->free_fake_connections; + sc->free_fake_connections = fc; + + sc->processing--; + + if (sc->processing || sc->blocked) { + return; + } + + ev = sc->connection->read; + + ev->handler = ngx_http_spdy_handle_connection_handler; + ngx_post_event(ev, &ngx_posted_events); +} + + +static void +ngx_http_spdy_handle_connection_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + + rev->handler = ngx_http_spdy_read_handler; + + if (rev->ready) { + ngx_http_spdy_read_handler(rev); + return; + } + + c = rev->data; + + ngx_http_spdy_handle_connection(c->data); +} + + +static void +ngx_http_spdy_keepalive_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_http_spdy_srv_conf_t *sscf; + ngx_http_spdy_connection_t *sc; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "spdy keepalive handler"); + + if (rev->timedout || c->close) { + ngx_http_close_connection(c); + return; + } + +#if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + if (rev->pending_eof) { + c->log->handler = NULL; + ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno, + "kevent() reported that client %V closed " + "keepalive connection", &c->addr_text); +#if (NGX_HTTP_SSL) + if (c->ssl) { + c->ssl->no_send_shutdown = 1; + } +#endif + ngx_http_close_connection(c); + return; + } + } + +#endif + + c->destroyed = 0; + c->idle = 0; + ngx_reusable_connection(c, 0); + + sc = c->data; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + sc->pool = ngx_create_pool(sscf->pool_size, sc->connection->log); + if (sc->pool == NULL) { + ngx_http_close_connection(c); + return; + } + + sc->streams_index = ngx_pcalloc(sc->pool, + ngx_http_spdy_streams_index_size(sscf) + * sizeof(ngx_http_spdy_stream_t *)); + if (sc->streams_index == NULL) { + ngx_http_close_connection(c); + return; + } + + c->write->handler = ngx_http_spdy_write_handler; + + rev->handler = ngx_http_spdy_read_handler; + ngx_http_spdy_read_handler(rev); +} + + +static void +ngx_http_spdy_finalize_connection(ngx_http_spdy_connection_t *sc, + ngx_int_t rc) +{ + ngx_uint_t i, size; + ngx_event_t *ev; + ngx_connection_t *c, *fc; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_srv_conf_t *sscf; + + c = sc->connection; + + if (!sc->processing) { + ngx_http_close_connection(c); + return; + } + + c->error = 1; + c->read->handler = ngx_http_empty_handler; + c->write->handler = ngx_http_empty_handler; + + sc->last_out = NULL; + + sc->blocked = 1; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + size = ngx_http_spdy_streams_index_size(sscf); + + for (i = 0; i < size; i++) { + stream = sc->streams_index[i]; + + while (stream) { + stream->handled = 0; + + r = stream->request; + fc = r->connection; + + fc->error = 1; + + if (stream->queued) { + stream->queued = 0; + + ev = fc->write; + ev->delayed = 0; + + } else { + ev = fc->read; + } + + stream = stream->index; + + ev->eof = 1; + ev->handler(ev); + } + } + + sc->blocked = 0; + + if (sc->processing) { + return; + } + + ngx_http_close_connection(c); +} + + +static ngx_int_t +ngx_http_spdy_adjust_windows(ngx_http_spdy_connection_t *sc, ssize_t delta) +{ + ngx_uint_t i, size; + ngx_event_t *wev; + ngx_http_spdy_stream_t *stream, *sn; + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_http_get_module_srv_conf(sc->http_connection->conf_ctx, + ngx_http_spdy_module); + + size = ngx_http_spdy_streams_index_size(sscf); + + for (i = 0; i < size; i++) { + + for (stream = sc->streams_index[i]; stream; stream = sn) { + sn = stream->index; + + if (delta > 0 + && stream->send_window + > (ssize_t) (NGX_SPDY_MAX_WINDOW - delta)) + { + if (ngx_http_spdy_terminate_stream(sc, stream, + NGX_SPDY_FLOW_CONTROL_ERROR) + == NGX_ERROR) + { + return NGX_ERROR; + } + + continue; + } + + stream->send_window += delta; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui adjust window:%z", + stream->id, stream->send_window); + + if (stream->send_window > 0 && stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + } + } + } + } + + return NGX_OK; +} + + +static void +ngx_http_spdy_pool_cleanup(void *data) +{ + ngx_http_spdy_connection_t *sc = data; + + if (sc->pool) { + ngx_destroy_pool(sc->pool); + } +} + + +static void * +ngx_http_spdy_zalloc(void *opaque, u_int items, u_int size) +{ + ngx_http_spdy_connection_t *sc = opaque; + + return ngx_palloc(sc->connection->pool, items * size); +} + + +static void +ngx_http_spdy_zfree(void *opaque, void *address) +{ +#if 0 + ngx_http_spdy_connection_t *sc = opaque; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy zfree: %p", address); +#endif +} Index: nginx-1.14.0/src/http/ngx_http_spdy.h =================================================================== --- /dev/null +++ nginx-1.14.0/src/http/ngx_http_spdy.h @@ -0,0 +1,261 @@ +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_SPDY_H_INCLUDED_ +#define _NGX_HTTP_SPDY_H_INCLUDED_ + + +#include +#include +#include + +#include + + +#define NGX_SPDY_VERSION 3 + +#define NGX_SPDY_NPN_ADVERTISE "\x08spdy/3.1" +#define NGX_SPDY_NPN_NEGOTIATED "spdy/3.1" + +#define NGX_SPDY_STATE_BUFFER_SIZE 16 + +#define NGX_SPDY_CTL_BIT 1 + +#define NGX_SPDY_SYN_STREAM 1 +#define NGX_SPDY_SYN_REPLY 2 +#define NGX_SPDY_RST_STREAM 3 +#define NGX_SPDY_SETTINGS 4 +#define NGX_SPDY_PING 6 +#define NGX_SPDY_GOAWAY 7 +#define NGX_SPDY_HEADERS 8 +#define NGX_SPDY_WINDOW_UPDATE 9 + +#define NGX_SPDY_FRAME_HEADER_SIZE 8 + +#define NGX_SPDY_SID_SIZE 4 +#define NGX_SPDY_DELTA_SIZE 4 + +#define NGX_SPDY_SYN_STREAM_SIZE 10 +#define NGX_SPDY_SYN_REPLY_SIZE 4 +#define NGX_SPDY_RST_STREAM_SIZE 8 +#define NGX_SPDY_PING_SIZE 4 +#define NGX_SPDY_GOAWAY_SIZE 8 +#define NGX_SPDY_WINDOW_UPDATE_SIZE 8 +#define NGX_SPDY_NV_NUM_SIZE 4 +#define NGX_SPDY_NV_NLEN_SIZE 4 +#define NGX_SPDY_NV_VLEN_SIZE 4 +#define NGX_SPDY_SETTINGS_NUM_SIZE 4 +#define NGX_SPDY_SETTINGS_FID_SIZE 4 +#define NGX_SPDY_SETTINGS_VAL_SIZE 4 + +#define NGX_SPDY_SETTINGS_PAIR_SIZE \ + (NGX_SPDY_SETTINGS_FID_SIZE + NGX_SPDY_SETTINGS_VAL_SIZE) + +#define NGX_SPDY_HIGHEST_PRIORITY 0 +#define NGX_SPDY_LOWEST_PRIORITY 7 + +#define NGX_SPDY_FLAG_FIN 0x01 +#define NGX_SPDY_FLAG_UNIDIRECTIONAL 0x02 +#define NGX_SPDY_FLAG_CLEAR_SETTINGS 0x01 + +#define NGX_SPDY_MAX_FRAME_SIZE ((1 << 24) - 1) + +#define NGX_SPDY_DATA_DISCARD 1 +#define NGX_SPDY_DATA_ERROR 2 +#define NGX_SPDY_DATA_INTERNAL_ERROR 3 + + +typedef struct ngx_http_spdy_connection_s ngx_http_spdy_connection_t; +typedef struct ngx_http_spdy_out_frame_s ngx_http_spdy_out_frame_t; + + +typedef u_char *(*ngx_http_spdy_handler_pt) (ngx_http_spdy_connection_t *sc, + u_char *pos, u_char *end); + +struct ngx_http_spdy_connection_s { + ngx_connection_t *connection; + ngx_http_connection_t *http_connection; + + ngx_uint_t processing; + + size_t send_window; + size_t recv_window; + size_t init_window; + + ngx_queue_t waiting; + + u_char buffer[NGX_SPDY_STATE_BUFFER_SIZE]; + size_t buffer_used; + ngx_http_spdy_handler_pt handler; + + z_stream zstream_in; + z_stream zstream_out; + + ngx_pool_t *pool; + + ngx_http_spdy_out_frame_t *free_ctl_frames; + ngx_connection_t *free_fake_connections; + + ngx_http_spdy_stream_t **streams_index; + + ngx_http_spdy_out_frame_t *last_out; + + ngx_queue_t posted; + + ngx_http_spdy_stream_t *stream; + + ngx_uint_t entries; + size_t length; + u_char flags; + + ngx_uint_t last_sid; + + unsigned blocked:1; + unsigned incomplete:1; +}; + + +struct ngx_http_spdy_stream_s { + ngx_uint_t id; + ngx_http_request_t *request; + ngx_http_spdy_connection_t *connection; + ngx_http_spdy_stream_t *index; + + ngx_uint_t header_buffers; + ngx_uint_t queued; + + /* + * A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the + * send_window to become negative, hence it's signed. + */ + ssize_t send_window; + size_t recv_window; + + ngx_http_spdy_out_frame_t *free_frames; + ngx_chain_t *free_data_headers; + ngx_chain_t *free_bufs; + + ngx_queue_t queue; + + unsigned priority:3; + unsigned handled:1; + unsigned blocked:1; + unsigned exhausted:1; + unsigned in_closed:1; + unsigned out_closed:1; + unsigned skip_data:2; +}; + + +struct ngx_http_spdy_out_frame_s { + ngx_http_spdy_out_frame_t *next; + ngx_chain_t *first; + ngx_chain_t *last; + ngx_int_t (*handler)(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame); + + ngx_http_spdy_stream_t *stream; + size_t length; + + ngx_uint_t priority; + unsigned blocked:1; + unsigned fin:1; +}; + + +static ngx_inline void +ngx_http_spdy_queue_frame(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_spdy_out_frame_t **out; + + for (out = &sc->last_out; *out; out = &(*out)->next) + { + /* + * NB: higher values represent lower priorities. + */ + if (frame->priority >= (*out)->priority) { + break; + } + } + + frame->next = *out; + *out = frame; +} + + +static ngx_inline void +ngx_http_spdy_queue_blocked_frame(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_spdy_out_frame_t **out; + + for (out = &sc->last_out; *out; out = &(*out)->next) + { + if ((*out)->blocked) { + break; + } + } + + frame->next = *out; + *out = frame; +} + + +void ngx_http_spdy_init(ngx_event_t *rev); +void ngx_http_spdy_request_headers_init(void); + +ngx_int_t ngx_http_spdy_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler); + +void ngx_http_spdy_close_stream(ngx_http_spdy_stream_t *stream, ngx_int_t rc); + +ngx_int_t ngx_http_spdy_send_output_queue(ngx_http_spdy_connection_t *sc); + + +#define ngx_spdy_frame_aligned_write_uint16(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_spdy_frame_aligned_write_uint32(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_spdy_frame_write_uint16 ngx_spdy_frame_aligned_write_uint16 +#define ngx_spdy_frame_write_uint32 ngx_spdy_frame_aligned_write_uint32 + +#else + +#define ngx_spdy_frame_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_spdy_frame_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + + +#define ngx_spdy_ctl_frame_head(t) \ + ((uint32_t) NGX_SPDY_CTL_BIT << 31 | NGX_SPDY_VERSION << 16 | (t)) + +#define ngx_spdy_frame_write_head(p, t) \ + ngx_spdy_frame_aligned_write_uint32(p, ngx_spdy_ctl_frame_head(t)) + +#define ngx_spdy_frame_write_flags_and_len(p, f, l) \ + ngx_spdy_frame_aligned_write_uint32(p, (f) << 24 | (l)) +#define ngx_spdy_frame_write_flags_and_id(p, f, i) \ + ngx_spdy_frame_aligned_write_uint32(p, (f) << 24 | (i)) + +#define ngx_spdy_frame_write_sid ngx_spdy_frame_aligned_write_uint32 +#define ngx_spdy_frame_write_window ngx_spdy_frame_aligned_write_uint32 + +#endif /* _NGX_HTTP_SPDY_H_INCLUDED_ */ Index: nginx-1.14.0/src/http/ngx_http_spdy_filter_module.c =================================================================== --- /dev/null +++ nginx-1.14.0/src/http/ngx_http_spdy_filter_module.c @@ -0,0 +1,1222 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include +#include + +#include + + +#define ngx_http_spdy_nv_nsize(h) (NGX_SPDY_NV_NLEN_SIZE + sizeof(h) - 1) +#define ngx_http_spdy_nv_vsize(h) (NGX_SPDY_NV_VLEN_SIZE + sizeof(h) - 1) + +#define ngx_http_spdy_nv_write_num ngx_spdy_frame_write_uint32 +#define ngx_http_spdy_nv_write_nlen ngx_spdy_frame_write_uint32 +#define ngx_http_spdy_nv_write_vlen ngx_spdy_frame_write_uint32 + +#define ngx_http_spdy_nv_write_name(p, h) \ + ngx_cpymem(ngx_http_spdy_nv_write_nlen(p, sizeof(h) - 1), h, sizeof(h) - 1) + +#define ngx_http_spdy_nv_write_val(p, h) \ + ngx_cpymem(ngx_http_spdy_nv_write_vlen(p, sizeof(h) - 1), h, sizeof(h) - 1) + + +static ngx_chain_t *ngx_http_spdy_send_chain(ngx_connection_t *fc, + ngx_chain_t *in, off_t limit); + +static ngx_inline ngx_int_t ngx_http_spdy_filter_send( + ngx_connection_t *fc, ngx_http_spdy_stream_t *stream); +static ngx_inline ngx_int_t ngx_http_spdy_flow_control( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_stream_t *stream); +static void ngx_http_spdy_waiting_queue(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream); + +static ngx_chain_t *ngx_http_spdy_filter_get_shadow( + ngx_http_spdy_stream_t *stream, ngx_buf_t *buf, off_t offset, off_t size); +static ngx_http_spdy_out_frame_t *ngx_http_spdy_filter_get_data_frame( + ngx_http_spdy_stream_t *stream, size_t len, ngx_chain_t *first, + ngx_chain_t *last); + +static ngx_int_t ngx_http_spdy_syn_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_int_t ngx_http_spdy_data_frame_handler( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_out_frame_t *frame); +static ngx_inline void ngx_http_spdy_handle_frame( + ngx_http_spdy_stream_t *stream, ngx_http_spdy_out_frame_t *frame); +static ngx_inline void ngx_http_spdy_handle_stream( + ngx_http_spdy_connection_t *sc, ngx_http_spdy_stream_t *stream); + +static void ngx_http_spdy_filter_cleanup(void *data); + +static ngx_int_t ngx_http_spdy_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_spdy_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_spdy_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_spdy_filter_module = { + NGX_MODULE_V1, + &ngx_http_spdy_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; + + +static ngx_int_t +ngx_http_spdy_header_filter(ngx_http_request_t *r) +{ + int rc; + size_t len; + u_char *p, *buf, *last; + ngx_buf_t *b; + ngx_str_t host; + ngx_uint_t i, j, count, port; + ngx_chain_t *cl; + ngx_list_part_t *part, *pt; + ngx_table_elt_t *header, *h; + ngx_connection_t *c; + ngx_http_cleanup_t *cln; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_out_frame_t *frame; + ngx_http_spdy_connection_t *sc; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + u_char addr[NGX_SOCKADDR_STRLEN]; + + if (!r->spdy_stream) { + return ngx_http_next_header_filter(r); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "spdy header filter"); + + if (r->header_sent) { + return NGX_OK; + } + + r->header_sent = 1; + + if (r != r->main) { + return NGX_OK; + } + + c = r->connection; + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + switch (r->headers_out.status) { + + case NGX_HTTP_OK: + case NGX_HTTP_PARTIAL_CONTENT: + break; + + case NGX_HTTP_NOT_MODIFIED: + r->header_only = 1; + break; + + case NGX_HTTP_NO_CONTENT: + r->header_only = 1; + + ngx_str_null(&r->headers_out.content_type); + + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + + /* fall through */ + + default: + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + } + + len = NGX_SPDY_NV_NUM_SIZE + + ngx_http_spdy_nv_nsize(":version") + + ngx_http_spdy_nv_vsize("HTTP/1.1") + + ngx_http_spdy_nv_nsize(":status") + + (r->headers_out.status_line.len + ? NGX_SPDY_NV_VLEN_SIZE + r->headers_out.status_line.len + : ngx_http_spdy_nv_vsize("418")); + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.server == NULL) { + len += ngx_http_spdy_nv_nsize("server"); + len += clcf->server_tokens ? ngx_http_spdy_nv_vsize(NGINX_VER) + : ngx_http_spdy_nv_vsize("nginx"); + } + + if (r->headers_out.date == NULL) { + len += ngx_http_spdy_nv_nsize("date") + + ngx_http_spdy_nv_vsize("Wed, 31 Dec 1986 10:00:00 GMT"); + } + + if (r->headers_out.content_type.len) { + len += ngx_http_spdy_nv_nsize("content-type") + + NGX_SPDY_NV_VLEN_SIZE + r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + len += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + len += ngx_http_spdy_nv_nsize("content-length") + + NGX_SPDY_NV_VLEN_SIZE + NGX_OFF_T_LEN; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += ngx_http_spdy_nv_nsize("last-modified") + + ngx_http_spdy_nv_vsize("Wed, 31 Dec 1986 10:00:00 GMT"); + } + + if (r->headers_out.location + && r->headers_out.location->value.len + && r->headers_out.location->value.data[0] == '/') + { + r->headers_out.location->hash = 0; + + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + switch (c->local_sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) c->local_sockaddr; + port = ntohs(sin6->sin6_port); + break; +#endif +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + port = 0; + break; +#endif + default: /* AF_INET */ + sin = (struct sockaddr_in *) c->local_sockaddr; + port = ntohs(sin->sin_port); + break; + } + + len += ngx_http_spdy_nv_nsize("location") + + ngx_http_spdy_nv_vsize("https://") + + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + +#if (NGX_HTTP_SSL) + if (c->ssl) + port = (port == 443) ? 0 : port; + else +#endif + port = (port == 80) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + len += sizeof(":65535") - 1; + } + + } else { + ngx_str_null(&host); + port = 0; + } + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += NGX_SPDY_NV_NLEN_SIZE + header[i].key.len + + NGX_SPDY_NV_VLEN_SIZE + header[i].value.len; + } + + buf = ngx_alloc(len, r->pool->log); + if (buf == NULL) { + return NGX_ERROR; + } + + last = buf + NGX_SPDY_NV_NUM_SIZE; + + last = ngx_http_spdy_nv_write_name(last, ":version"); + last = ngx_http_spdy_nv_write_val(last, "HTTP/1.1"); + + last = ngx_http_spdy_nv_write_name(last, ":status"); + + if (r->headers_out.status_line.len) { + last = ngx_http_spdy_nv_write_vlen(last, + r->headers_out.status_line.len); + last = ngx_cpymem(last, r->headers_out.status_line.data, + r->headers_out.status_line.len); + } else { + last = ngx_http_spdy_nv_write_vlen(last, 3); + last = ngx_sprintf(last, "%03ui", r->headers_out.status); + } + + count = 2; + + if (r->headers_out.server == NULL) { + last = ngx_http_spdy_nv_write_name(last, "server"); + last = clcf->server_tokens + ? ngx_http_spdy_nv_write_val(last, NGINX_VER) + : ngx_http_spdy_nv_write_val(last, "nginx"); + + count++; + } + + if (r->headers_out.date == NULL) { + last = ngx_http_spdy_nv_write_name(last, "date"); + + last = ngx_http_spdy_nv_write_vlen(last, ngx_cached_http_time.len); + + last = ngx_cpymem(last, ngx_cached_http_time.data, + ngx_cached_http_time.len); + + count++; + } + + if (r->headers_out.content_type.len) { + + last = ngx_http_spdy_nv_write_name(last, "content-type"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + last = ngx_cpymem(last, "; charset=", sizeof("; charset=") - 1); + + last = ngx_cpymem(last, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* update r->headers_out.content_type for possible logging */ + + r->headers_out.content_type.len = last - p; + r->headers_out.content_type.data = p; + } + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + r->headers_out.content_type.len); + + count++; + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + last = ngx_http_spdy_nv_write_name(last, "content-length"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_sprintf(p, "%O", r->headers_out.content_length_n); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + last = ngx_http_spdy_nv_write_name(last, "last-modified"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_http_time(p, r->headers_out.last_modified_time); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + if (host.data) { + + last = ngx_http_spdy_nv_write_name(last, "location"); + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, "http", sizeof("http") - 1); + +#if (NGX_HTTP_SSL) + if (c->ssl) { + *last++ ='s'; + } +#endif + + *last++ = ':'; *last++ = '/'; *last++ = '/'; + + last = ngx_cpymem(last, host.data, host.len); + + if (port) { + last = ngx_sprintf(last, ":%ui", port); + } + + last = ngx_cpymem(last, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = last - p; + r->headers_out.location->value.data = p; + ngx_str_set(&r->headers_out.location->key, "location"); + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + r->headers_out.location->value.len); + + count++; + } + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0 || header[i].hash == 2) { + continue; + } + + last = ngx_http_spdy_nv_write_nlen(last, header[i].key.len); + + ngx_strlow(last, header[i].key.data, header[i].key.len); + last += header[i].key.len; + + p = last + NGX_SPDY_NV_VLEN_SIZE; + + last = ngx_cpymem(p, header[i].value.data, header[i].value.len); + + pt = part; + h = header; + + for (j = i + 1; /* void */; j++) { + + if (j >= pt->nelts) { + if (pt->next == NULL) { + break; + } + + pt = pt->next; + h = pt->elts; + j = 0; + } + + if (h[j].hash == 0 || h[j].hash == 2 + || h[j].key.len != header[i].key.len + || ngx_strncasecmp(header[i].key.data, h[j].key.data, + header[i].key.len)) + { + continue; + } + + if (h[j].value.len) { + if (last != p) { + *last++ = '\0'; + } + + last = ngx_cpymem(last, h[j].value.data, h[j].value.len); + } + + h[j].hash = 2; + } + + (void) ngx_http_spdy_nv_write_vlen(p - NGX_SPDY_NV_VLEN_SIZE, + last - p); + + count++; + } + + (void) ngx_http_spdy_nv_write_num(buf, count); + + stream = r->spdy_stream; + sc = stream->connection; + + len = last - buf; + + b = ngx_create_temp_buf(r->pool, NGX_SPDY_FRAME_HEADER_SIZE + + NGX_SPDY_SYN_REPLY_SIZE + + deflateBound(&sc->zstream_out, len)); + if (b == NULL) { + ngx_free(buf); + return NGX_ERROR; + } + + b->last += NGX_SPDY_FRAME_HEADER_SIZE + NGX_SPDY_SYN_REPLY_SIZE; + + sc->zstream_out.next_in = buf; + sc->zstream_out.avail_in = len; + sc->zstream_out.next_out = b->last; + sc->zstream_out.avail_out = b->end - b->last; + + rc = deflate(&sc->zstream_out, Z_SYNC_FLUSH); + + ngx_free(buf); + + if (rc != Z_OK) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "deflate() failed: %d", rc); + return NGX_ERROR; + } + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "spdy deflate out: ni:%p no:%p ai:%ud ao:%ud rc:%d", + sc->zstream_out.next_in, sc->zstream_out.next_out, + sc->zstream_out.avail_in, sc->zstream_out.avail_out, + rc); + + b->last = sc->zstream_out.next_out; + + p = b->pos; + p = ngx_spdy_frame_write_head(p, NGX_SPDY_SYN_REPLY); + + len = b->last - b->pos; + + r->header_size = len; + + len -= NGX_SPDY_FRAME_HEADER_SIZE; + + if (r->header_only) { + b->last_buf = 1; + p = ngx_spdy_frame_write_flags_and_len(p, NGX_SPDY_FLAG_FIN, len); + + } else { + p = ngx_spdy_frame_write_flags_and_len(p, 0, len); + } + + (void) ngx_spdy_frame_write_sid(p, stream->id); + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + frame = ngx_palloc(r->pool, sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_spdy_syn_frame_handler; + frame->stream = stream; + frame->length = len; + frame->priority = stream->priority; + frame->blocked = 1; + frame->fin = r->header_only; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, + "spdy:%ui create SYN_REPLY frame %p: len:%uz", + stream->id, frame, frame->length); + + ngx_http_spdy_queue_blocked_frame(sc, frame); + + cln = ngx_http_cleanup_add(r, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_spdy_filter_cleanup; + cln->data = stream; + + stream->queued = 1; + + c->send_chain = ngx_http_spdy_send_chain; + c->need_last_buf = 1; + + return ngx_http_spdy_filter_send(c, stream); +} + + +static ngx_chain_t * +ngx_http_spdy_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) +{ + off_t size, offset; + size_t rest, frame_size; + ngx_chain_t *cl, *out, **ln; + ngx_http_request_t *r; + ngx_http_spdy_stream_t *stream; + ngx_http_spdy_loc_conf_t *slcf; + ngx_http_spdy_out_frame_t *frame; + ngx_http_spdy_connection_t *sc; + + r = fc->data; + stream = r->spdy_stream; + +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + while (in) { + size = ngx_buf_size(in->buf); + + if (size || in->buf->last_buf) { + break; + } + + in = in->next; + } + + if (in == NULL) { + + if (stream->queued) { + fc->write->delayed = 1; + } else { + fc->buffered &= ~NGX_SPDY_BUFFERED; + } + + return NULL; + } + + sc = stream->connection; + + if (size && ngx_http_spdy_flow_control(sc, stream) == NGX_DECLINED) { + fc->write->delayed = 1; + return in; + } + + if (limit == 0 || limit > (off_t) sc->send_window) { + limit = sc->send_window; + } + + if (limit > stream->send_window) { + limit = (stream->send_window > 0) ? stream->send_window : 0; + } + + if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = in->buf; + in->buf = cl->buf->shadow; + + offset = ngx_buf_in_memory(in->buf) + ? (cl->buf->pos - in->buf->pos) + : (cl->buf->file_pos - in->buf->file_pos); + + cl->next = stream->free_bufs; + stream->free_bufs = cl; + + } else { + offset = 0; + } + +#if (NGX_SUPPRESS_WARN) + cl = NULL; +#endif + + slcf = ngx_http_get_module_loc_conf(r, ngx_http_spdy_module); + + frame_size = (limit <= (off_t) slcf->chunk_size) ? (size_t) limit + : slcf->chunk_size; + + for ( ;; ) { + ln = &out; + rest = frame_size; + + while ((off_t) rest >= size) { + + if (offset) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, + offset, size); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + offset = 0; + + } else { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = in->buf; + } + + *ln = cl; + ln = &cl->next; + + rest -= (size_t) size; + in = in->next; + + if (in == NULL) { + frame_size -= rest; + rest = 0; + break; + } + + size = ngx_buf_size(in->buf); + } + + if (rest) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, + offset, rest); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf->flush = 0; + cl->buf->last_buf = 0; + + *ln = cl; + + offset += rest; + size -= rest; + } + + frame = ngx_http_spdy_filter_get_data_frame(stream, frame_size, + out, cl); + if (frame == NULL) { + return NGX_CHAIN_ERROR; + } + + ngx_http_spdy_queue_frame(sc, frame); + + sc->send_window -= frame_size; + + stream->send_window -= frame_size; + stream->queued++; + + if (in == NULL) { + break; + } + + limit -= frame_size; + + if (limit == 0) { + break; + } + + if (limit < (off_t) slcf->chunk_size) { + frame_size = (size_t) limit; + } + } + + if (offset) { + cl = ngx_http_spdy_filter_get_shadow(stream, in->buf, offset, size); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + in->buf = cl->buf; + ngx_free_chain(r->pool, cl); + } + + if (ngx_http_spdy_filter_send(fc, stream) == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + if (in && ngx_http_spdy_flow_control(sc, stream) == NGX_DECLINED) { + fc->write->delayed = 1; + } + + return in; +} + + +static ngx_chain_t * +ngx_http_spdy_filter_get_shadow(ngx_http_spdy_stream_t *stream, ngx_buf_t *buf, + off_t offset, off_t size) +{ + ngx_buf_t *chunk; + ngx_chain_t *cl; + + cl = ngx_chain_get_free_buf(stream->request->pool, &stream->free_bufs); + if (cl == NULL) { + return NULL; + } + + chunk = cl->buf; + + ngx_memcpy(chunk, buf, sizeof(ngx_buf_t)); + + chunk->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow; + chunk->shadow = buf; + + if (ngx_buf_in_memory(chunk)) { + chunk->pos += offset; + chunk->last = chunk->pos + size; + } + + if (chunk->in_file) { + chunk->file_pos += offset; + chunk->file_last = chunk->file_pos + size; + } + + return cl; +} + + +static ngx_http_spdy_out_frame_t * +ngx_http_spdy_filter_get_data_frame(ngx_http_spdy_stream_t *stream, + size_t len, ngx_chain_t *first, ngx_chain_t *last) +{ + u_char *p; + ngx_buf_t *buf; + ngx_uint_t flags; + ngx_chain_t *cl; + ngx_http_spdy_out_frame_t *frame; + + + frame = stream->free_frames; + + if (frame) { + stream->free_frames = frame->next; + + } else { + frame = ngx_palloc(stream->request->pool, + sizeof(ngx_http_spdy_out_frame_t)); + if (frame == NULL) { + return NULL; + } + } + + flags = last->buf->last_buf ? NGX_SPDY_FLAG_FIN : 0; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, stream->request->connection->log, 0, + "spdy:%ui create DATA frame %p: len:%uz flags:%ui", + stream->id, frame, len, flags); + + cl = ngx_chain_get_free_buf(stream->request->pool, + &stream->free_data_headers); + if (cl == NULL) { + return NULL; + } + + buf = cl->buf; + + if (buf->start) { + p = buf->start; + buf->pos = p; + + p += NGX_SPDY_SID_SIZE; + + (void) ngx_spdy_frame_write_flags_and_len(p, flags, len); + + } else { + p = ngx_palloc(stream->request->pool, NGX_SPDY_FRAME_HEADER_SIZE); + if (p == NULL) { + return NULL; + } + + buf->pos = p; + buf->start = p; + + p = ngx_spdy_frame_write_sid(p, stream->id); + p = ngx_spdy_frame_write_flags_and_len(p, flags, len); + + buf->last = p; + buf->end = p; + + buf->tag = (ngx_buf_tag_t) &ngx_http_spdy_filter_get_data_frame; + buf->memory = 1; + } + + cl->next = first; + first = cl; + + last->buf->flush = 1; + + frame->first = first; + frame->last = last; + frame->handler = ngx_http_spdy_data_frame_handler; + frame->stream = stream; + frame->length = len; + frame->priority = stream->priority; + frame->blocked = 0; + frame->fin = last->buf->last_buf; + + return frame; +} + + +static ngx_inline ngx_int_t +ngx_http_spdy_filter_send(ngx_connection_t *fc, ngx_http_spdy_stream_t *stream) +{ + stream->blocked = 1; + + if (ngx_http_spdy_send_output_queue(stream->connection) == NGX_ERROR) { + fc->error = 1; + return NGX_ERROR; + } + + stream->blocked = 0; + + if (stream->queued) { + fc->buffered |= NGX_SPDY_BUFFERED; + fc->write->delayed = 1; + return NGX_AGAIN; + } + + fc->buffered &= ~NGX_SPDY_BUFFERED; + + return NGX_OK; +} + + +static ngx_inline ngx_int_t +ngx_http_spdy_flow_control(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + if (stream->send_window <= 0) { + stream->exhausted = 1; + return NGX_DECLINED; + } + + if (sc->send_window == 0) { + ngx_http_spdy_waiting_queue(sc, stream); + return NGX_DECLINED; + } + + return NGX_OK; +} + + +static void +ngx_http_spdy_waiting_queue(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + ngx_queue_t *q; + ngx_http_spdy_stream_t *s; + + if (stream->handled) { + return; + } + + stream->handled = 1; + + for (q = ngx_queue_last(&sc->waiting); + q != ngx_queue_sentinel(&sc->waiting); + q = ngx_queue_prev(q)) + { + s = ngx_queue_data(q, ngx_http_spdy_stream_t, queue); + + /* + * NB: higher values represent lower priorities. + */ + if (stream->priority >= s->priority) { + break; + } + } + + ngx_queue_insert_after(q, &stream->queue); +} + + +static ngx_int_t +ngx_http_spdy_syn_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + ngx_http_spdy_stream_t *stream; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + stream = frame->stream; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui SYN_REPLY frame %p was sent", stream->id, frame); + + ngx_free_chain(stream->request->pool, frame->first); + + ngx_http_spdy_handle_frame(stream, frame); + + ngx_http_spdy_handle_stream(sc, stream); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_data_frame_handler(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_buf_t *buf; + ngx_chain_t *cl, *ln; + ngx_http_spdy_stream_t *stream; + + stream = frame->stream; + + cl = frame->first; + + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_data_frame) { + + if (cl->buf->pos != cl->buf->last) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent partially", + stream->id, frame); + + return NGX_AGAIN; + } + + ln = cl->next; + + cl->next = stream->free_data_headers; + stream->free_data_headers = cl; + + if (cl == frame->last) { + goto done; + } + + cl = ln; + } + + for ( ;; ) { + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + buf = cl->buf->shadow; + + if (ngx_buf_in_memory(buf)) { + buf->pos = cl->buf->pos; + } + + if (buf->in_file) { + buf->file_pos = cl->buf->file_pos; + } + } + + if (ngx_buf_size(cl->buf) != 0) { + + if (cl != frame->first) { + frame->first = cl; + ngx_http_spdy_handle_stream(sc, stream); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent partially", + stream->id, frame); + + return NGX_AGAIN; + } + + ln = cl->next; + + if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_spdy_filter_get_shadow) { + cl->next = stream->free_bufs; + stream->free_bufs = cl; + + } else { + ngx_free_chain(stream->request->pool, cl); + } + + if (cl == frame->last) { + goto done; + } + + cl = ln; + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0, + "spdy:%ui DATA frame %p was sent", stream->id, frame); + + stream->request->header_size += NGX_SPDY_FRAME_HEADER_SIZE; + + ngx_http_spdy_handle_frame(stream, frame); + + ngx_http_spdy_handle_stream(sc, stream); + + return NGX_OK; +} + + +static ngx_inline void +ngx_http_spdy_handle_frame(ngx_http_spdy_stream_t *stream, + ngx_http_spdy_out_frame_t *frame) +{ + ngx_http_request_t *r; + + r = stream->request; + + r->connection->sent += NGX_SPDY_FRAME_HEADER_SIZE + frame->length; + + if (frame->fin) { + stream->out_closed = 1; + } + + frame->next = stream->free_frames; + stream->free_frames = frame; + + stream->queued--; +} + + +static ngx_inline void +ngx_http_spdy_handle_stream(ngx_http_spdy_connection_t *sc, + ngx_http_spdy_stream_t *stream) +{ + ngx_event_t *wev; + + if (stream->handled || stream->blocked || stream->exhausted) { + return; + } + + wev = stream->request->connection->write; + + /* + * This timer can only be set if the stream was delayed because of rate + * limit. In that case the event should be triggered by the timer. + */ + + if (!wev->timer_set) { + wev->delayed = 0; + + stream->handled = 1; + ngx_queue_insert_tail(&sc->posted, &stream->queue); + } +} + + +static void +ngx_http_spdy_filter_cleanup(void *data) +{ + ngx_http_spdy_stream_t *stream = data; + + size_t delta; + ngx_http_spdy_out_frame_t *frame, **fn; + ngx_http_spdy_connection_t *sc; + + if (stream->handled) { + stream->handled = 0; + ngx_queue_remove(&stream->queue); + } + + if (stream->queued == 0) { + return; + } + + delta = 0; + sc = stream->connection; + fn = &sc->last_out; + + for ( ;; ) { + frame = *fn; + + if (frame == NULL) { + break; + } + + if (frame->stream == stream && !frame->blocked) { + *fn = frame->next; + + delta += frame->length; + + if (--stream->queued == 0) { + break; + } + + continue; + } + + fn = &frame->next; + } + + if (sc->send_window == 0 && delta && !ngx_queue_empty(&sc->waiting)) { + ngx_queue_add(&sc->posted, &sc->waiting); + ngx_queue_init(&sc->waiting); + } + + sc->send_window += delta; +} + + +static ngx_int_t +ngx_http_spdy_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_spdy_header_filter; + + return NGX_OK; +} Index: nginx-1.14.0/src/http/ngx_http_spdy_module.c =================================================================== --- /dev/null +++ nginx-1.14.0/src/http/ngx_http_spdy_module.c @@ -0,0 +1,408 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + + +static ngx_int_t ngx_http_spdy_add_variables(ngx_conf_t *cf); + +static ngx_int_t ngx_http_spdy_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_spdy_request_priority_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_http_spdy_module_init(ngx_cycle_t *cycle); + +static void *ngx_http_spdy_create_main_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_init_main_conf(ngx_conf_t *cf, void *conf); +static void *ngx_http_spdy_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static void *ngx_http_spdy_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_spdy_merge_loc_conf(ngx_conf_t *cf, void *parent, + void *child); + +static char *ngx_http_spdy_recv_buffer_size(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_spdy_pool_size(ngx_conf_t *cf, void *post, void *data); +static char *ngx_http_spdy_streams_index_mask(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_spdy_chunk_size(ngx_conf_t *cf, void *post, void *data); + + +static ngx_conf_num_bounds_t ngx_http_spdy_headers_comp_bounds = { + ngx_conf_check_num_bounds, 0, 9 +}; + +static ngx_conf_post_t ngx_http_spdy_recv_buffer_size_post = + { ngx_http_spdy_recv_buffer_size }; +static ngx_conf_post_t ngx_http_spdy_pool_size_post = + { ngx_http_spdy_pool_size }; +static ngx_conf_post_t ngx_http_spdy_streams_index_mask_post = + { ngx_http_spdy_streams_index_mask }; +static ngx_conf_post_t ngx_http_spdy_chunk_size_post = + { ngx_http_spdy_chunk_size }; + + +static ngx_command_t ngx_http_spdy_commands[] = { + + { ngx_string("spdy_recv_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_spdy_main_conf_t, recv_buffer_size), + &ngx_http_spdy_recv_buffer_size_post }, + + { ngx_string("spdy_pool_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, pool_size), + &ngx_http_spdy_pool_size_post }, + + { ngx_string("spdy_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, concurrent_streams), + NULL }, + + { ngx_string("spdy_streams_index_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, streams_index_mask), + &ngx_http_spdy_streams_index_mask_post }, + + { ngx_string("spdy_recv_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, recv_timeout), + NULL }, + + { ngx_string("spdy_keepalive_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, keepalive_timeout), + NULL }, + + { ngx_string("spdy_headers_comp"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_spdy_srv_conf_t, headers_comp), + &ngx_http_spdy_headers_comp_bounds }, + + { ngx_string("spdy_chunk_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_spdy_loc_conf_t, chunk_size), + &ngx_http_spdy_chunk_size_post }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_spdy_module_ctx = { + ngx_http_spdy_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + ngx_http_spdy_create_main_conf, /* create main configuration */ + ngx_http_spdy_init_main_conf, /* init main configuration */ + + ngx_http_spdy_create_srv_conf, /* create server configuration */ + ngx_http_spdy_merge_srv_conf, /* merge server configuration */ + + ngx_http_spdy_create_loc_conf, /* create location configuration */ + ngx_http_spdy_merge_loc_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_spdy_module = { + NGX_MODULE_V1, + &ngx_http_spdy_module_ctx, /* module context */ + ngx_http_spdy_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + ngx_http_spdy_module_init, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_variable_t ngx_http_spdy_vars[] = { + + { ngx_string("spdy"), NULL, + ngx_http_spdy_variable, 0, 0, 0 }, + + { ngx_string("spdy_request_priority"), NULL, + ngx_http_spdy_request_priority_variable, 0, 0, 0 }, + + { ngx_null_string, NULL, NULL, 0, 0, 0 } +}; + + +static ngx_int_t +ngx_http_spdy_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_spdy_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->spdy_stream) { + v->len = sizeof("3.1") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "3.1"; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_request_priority_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->spdy_stream) { + v->len = 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + v->data = ngx_pnalloc(r->pool, 1); + if (v->data == NULL) { + return NGX_ERROR; + } + + v->data[0] = '0' + (u_char) r->spdy_stream->priority; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_spdy_module_init(ngx_cycle_t *cycle) +{ + ngx_http_spdy_request_headers_init(); + + return NGX_OK; +} + + +static void * +ngx_http_spdy_create_main_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_main_conf_t *smcf; + + smcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_main_conf_t)); + if (smcf == NULL) { + return NULL; + } + + smcf->recv_buffer_size = NGX_CONF_UNSET_SIZE; + + return smcf; +} + + +static char * +ngx_http_spdy_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_http_spdy_main_conf_t *smcf = conf; + + ngx_conf_init_size_value(smcf->recv_buffer_size, 256 * 1024); + + return NGX_CONF_OK; +} + + +static void * +ngx_http_spdy_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_srv_conf_t *sscf; + + sscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_srv_conf_t)); + if (sscf == NULL) { + return NULL; + } + + sscf->pool_size = NGX_CONF_UNSET_SIZE; + + sscf->concurrent_streams = NGX_CONF_UNSET_UINT; + sscf->streams_index_mask = NGX_CONF_UNSET_UINT; + + sscf->recv_timeout = NGX_CONF_UNSET_MSEC; + sscf->keepalive_timeout = NGX_CONF_UNSET_MSEC; + + sscf->headers_comp = NGX_CONF_UNSET; + + return sscf; +} + + +static char * +ngx_http_spdy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_spdy_srv_conf_t *prev = parent; + ngx_http_spdy_srv_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->pool_size, prev->pool_size, 4096); + + ngx_conf_merge_uint_value(conf->concurrent_streams, + prev->concurrent_streams, 100); + + ngx_conf_merge_uint_value(conf->streams_index_mask, + prev->streams_index_mask, 32 - 1); + + ngx_conf_merge_msec_value(conf->recv_timeout, + prev->recv_timeout, 30000); + ngx_conf_merge_msec_value(conf->keepalive_timeout, + prev->keepalive_timeout, 180000); + + ngx_conf_merge_value(conf->headers_comp, prev->headers_comp, 0); + + return NGX_CONF_OK; +} + + +static void * +ngx_http_spdy_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_spdy_loc_conf_t *slcf; + + slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_spdy_loc_conf_t)); + if (slcf == NULL) { + return NULL; + } + + slcf->chunk_size = NGX_CONF_UNSET_SIZE; + + return slcf; +} + + +static char * +ngx_http_spdy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_spdy_loc_conf_t *prev = parent; + ngx_http_spdy_loc_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->chunk_size, prev->chunk_size, 8 * 1024); + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_recv_buffer_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp <= 2 * NGX_SPDY_STATE_BUFFER_SIZE) { + return "value is too small"; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_pool_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp < NGX_MIN_POOL_SIZE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the pool size must be no less than %uz", + NGX_MIN_POOL_SIZE); + return NGX_CONF_ERROR; + } + + if (*sp % NGX_POOL_ALIGNMENT) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the pool size must be a multiple of %uz", + NGX_POOL_ALIGNMENT); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_streams_index_mask(ngx_conf_t *cf, void *post, void *data) +{ + ngx_uint_t *np = data; + + ngx_uint_t mask; + + mask = *np - 1; + + if (*np == 0 || (*np & mask)) { + return "must be a power of two"; + } + + *np = mask; + + return NGX_CONF_OK; +} + + +static char * +ngx_http_spdy_chunk_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the spdy chunk size cannot be zero"); + return NGX_CONF_ERROR; + } + + if (*sp > NGX_SPDY_MAX_FRAME_SIZE) { + *sp = NGX_SPDY_MAX_FRAME_SIZE; + } + + return NGX_CONF_OK; +} Index: nginx-1.14.0/src/http/ngx_http_spdy_module.h =================================================================== --- /dev/null +++ nginx-1.14.0/src/http/ngx_http_spdy_module.h @@ -0,0 +1,41 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#ifndef _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ +#define _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + size_t recv_buffer_size; + u_char *recv_buffer; +} ngx_http_spdy_main_conf_t; + + +typedef struct { + size_t pool_size; + ngx_uint_t concurrent_streams; + ngx_uint_t streams_index_mask; + ngx_msec_t recv_timeout; + ngx_msec_t keepalive_timeout; + ngx_int_t headers_comp; +} ngx_http_spdy_srv_conf_t; + + +typedef struct { + size_t chunk_size; +} ngx_http_spdy_loc_conf_t; + + +extern ngx_module_t ngx_http_spdy_module; + + +#endif /* _NGX_HTTP_SPDY_MODULE_H_INCLUDED_ */ Index: nginx-1.14.0/src/http/ngx_http_upstream.c =================================================================== --- nginx-1.14.0.orig/src/http/ngx_http_upstream.c +++ nginx-1.14.0/src/http/ngx_http_upstream.c @@ -520,6 +520,12 @@ ngx_http_upstream_init(ngx_http_request_ return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + ngx_http_upstream_init_request(r); + return; + } +#endif if (c->read->timer_set) { ngx_del_timer(c->read); @@ -1341,6 +1347,11 @@ ngx_http_upstream_check_broken_connectio return; } #endif +#if (NGX_HTTP_SPDY) + if (r->spdy_stream) { + return; + } +#endif #if (NGX_HAVE_KQUEUE) -------------- next part -------------- A non-text attachment was scrubbed... Name: spdy.patch.sig Type: application/octet-stream Size: 566 bytes Desc: not available URL: From dewanggaba at xtremenitro.org Sat Sep 29 04:57:22 2018 From: dewanggaba at xtremenitro.org (Dewangga Alam) Date: Sat, 29 Sep 2018 11:57:22 +0700 Subject: Bring SPDY back in nginx-1.14.0 In-Reply-To: <0d78ebbb-8ada-9e9e-1546-d2a506b4a37a@xtremenitro.org> References: <636e17f1-bc63-74b7-f5c1-725b202ef1fd@xtremenitro.org> <0d78ebbb-8ada-9e9e-1546-d2a506b4a37a@xtremenitro.org> Message-ID: <4a0492ab-5b83-2f24-ec9c-c75d5806ccc4@xtremenitro.org> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Found a solution but need to confirm. Is it save to change : DEB_LDFLAGS_MAINT_APPEND=-Wl,--as-needed -pie to DEB_LDFLAGS_MAINT_APPEND=-Wl,--allow-multiple-definition -pie Need advice. On 29/09/18 11.12, Dewangga Alam wrote: > Sorry, please ignore my previous patch file. The previous error was > reproduce-able using this attached patch. > > On 29/09/18 08.29, Dewangga Alam wrote: >> Hello! > >> This is my first post in nginx-devel mailing list. > >> Currently, I am using nginx-1.8.1 in production. And I need SPDY >> to works on nginx-1.14.0 co-exists with http/2. Because I still >> need SPDY for compatibility, and for future deployment, I will >> migrate it all to HTTP/2. > >> To make this possible, so I was create patch to make SPDY and >> HTTP/2 run together. > >> I was read cloudflare patch regarding this issue (spdy & http/2) >> and make some adjustment (patch attached). > >> But, got some errors when compiling. The error message was : > >> objs/src/http/ngx_http_spdy.o: In function >> `ngx_http_spdy_send_output_queue': >> ./debian/build-nginx/src/http/ngx_http_spdy.c:664: multiple >> definition of `ngx_http_spdy_send_output_queue' >> objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_ s > >> pd > > > > y.c:664: >> first defined here objs/src/http/ngx_http_spdy.o: In function >> `ngx_http_spdy_init': >> ./debian/build-nginx/src/http/ngx_http_spdy.c:386: multiple >> definition of `ngx_http_spdy_init' >> objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_ s > >> pd > > > > y.c:386: >> first defined here objs/src/http/ngx_http_spdy.o: In function >> `ngx_http_spdy_request_headers_init': >> ./debian/build-nginx/src/http/ngx_http_spdy.c:2813: multiple >> definition of `ngx_http_spdy_request_headers_init' >> objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_ s > >> pd > > > > y.c:2813: >> first defined here objs/src/http/ngx_http_spdy.o: In function >> `ngx_http_spdy_read_request_body': >> ./debian/build-nginx/src/http/ngx_http_spdy.c:3274: multiple >> definition of `ngx_http_spdy_read_request_body' >> objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_ s > >> pd > > > > y.c:3274: >> first defined here objs/src/http/ngx_http_spdy.o: In function >> `ngx_http_spdy_close_stream': >> ./debian/build-nginx/src/http/ngx_http_spdy.c:3362: multiple >> definition of `ngx_http_spdy_close_stream' >> objs/src/http/ngx_http_spdy.o:./debian/build-nginx/src/http/ngx_http_ s > >> pd > > > > y.c:3362: >> first defined here >> objs/src/http/ngx_http_spdy_module.o:(.data.rel.local+0xc0): >> multiple definition of `ngx_http_spdy_module' >> objs/src/http/ngx_http_spdy_module.o:(.data.rel.local+0xc0): >> first defined here > >> https://paste.fedoraproject.org/paste/HGW3Q7Pj8oxnD473Xw3Cdw/raw > >> Is there any hints to fix the error? > >> Wr, Dewangga Alam > > -----BEGIN PGP SIGNATURE----- iQIzBAEBCAAdFiEEZpxiw/Jg6pEte5xQ5X/SIKAozXAFAluvBjIACgkQ5X/SIKAo zXAKyw//ZDn7FhL6K1G9f+p9AXGdM1LwOOG1bT96VOcPoT6Lep4dkK4DuCwSNuJV SjwRX85sEz2JtFCBkTezTxWy2m1UkxCfuyVkChADrzLQYSZxNu3kzqIln1KNwmW0 ztaZincR9zisxp+B6n5E8QMpjQlwGdqT8wlueDyTj901F8idklJlH2TBv3QYbIaq jlzzmd9Bfqhexj3yl8mlLyn3yskADG4Y8S/EhgnW+rw2+oxa5xzKJdIAm2tPL5lu Y7j4vFu+1S5+S8SHBiDKKIemnajSwlKOkL6JUZtGQUna+r8alfhDikdN8NDCKIi8 iUAF5FBnKhMxVdGZEO7yTQcmUfAPnEiXyTUN+2h0Buj0SmBKlsnRWCRZW2JcfY6k cZrJRYLdeK6FCjJLmCzgFmUyzImfVf7Ty/Hmqpq5213GZQEJKj6n8/mvrVOYKSpP gheDFl03g2hhrM14p5S2u/EPtkRmUyxEYBWwjdo6xJFFIXmM96/V6f5rpIgHMA3p x5LIkO554vNRLG9jMcok0ciT8uu7c3re0o71Q0frDkyPDqMmNeK4aGzbvkg4YmQo mx9BDUV7aOA1CW0dgPrCDs+74IP1gk41E5TxhCcbu2FfShI8knz6uh+67Wc2bA1y absI1FeYEuQZc+VhvVf7F22yfEWLTmTv77BIBPTi6LSDmY03j28= =fzbI -----END PGP SIGNATURE-----