[PATCH] Make ngx_http_upstream provide a way to expose errors after sending out the response header

Maxim Dounin mdounin at mdounin.ru
Mon Sep 10 10:35:40 UTC 2012


Hello!

On Sun, Sep 09, 2012 at 10:03:24PM -0700, agentzh wrote:

> Hello!
> 
> According to the current implementation of ngx_http_upstream, there is
> almost no way for 3rd-party output body filters and "post_subrequest"
> handlers (in the subrequest context) to know if there's any errors
> while ngx_http_upstream is processing the upstream response body after
> the response header is sent out (in both the buffered and non-buffered
> modes).
> 
> For example, if
> 
> 1. a read-timeout happens in the middle of the process of reading the
> upstream response body,
> 2. or the upstream connection is closed prematurely in the same situation,
> 
> then ngx_http_upstream will just happily finalize the current request
> with the status code 0 (i.e., NGX_OK). This issue already affects at
> least our ngx_srcache and ngx_lua modules (originally reported by
> Bryan Alger).
> 
> Here attaches a patch that makes ngx_http_upstream set
> r->headers_out.status to a new error status code to notify the outside
> world if there is a problem. Comments will be highly appreciated as
> always :)

I don't like the idea of changing r->headers_out.status here.  
It's really incorrect and confusing: r->headers_out.status is a 
status code of a response, and it's way too late to change it when 
we've found an error during sending the response body.

> 
> Thanks!
> -agentzh
> 
> --- nginx-1.2.3/src/http/ngx_http_upstream.c	2012-08-06 10:34:08.000000000 -0700
> +++ nginx-1.2.3-patched/src/http/ngx_http_upstream.c	2012-09-09
> 21:58:04.727761891 -0700
> @@ -2383,7 +2383,7 @@

Just a side note: please use -p when submitting patches.

> 
>      if (c->read->timedout) {
>          ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out");
> -        ngx_http_upstream_finalize_request(r, u, 0);
> +        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT);
>          return;
>      }
> 
> @@ -2430,13 +2430,17 @@
>              if (u->busy_bufs == NULL) {
> 
>                  if (u->length == 0
> -                    || upstream->read->eof
> -                    || upstream->read->error)
> +                    || (upstream->read->eof &&
> u->headers_in.content_length_n == -1))

Note: you can't be sure that eof with u->headers_in.content_length_n == 
-1 is ok.  E.g. with chunked encoding used it's certainly not 
unless u->length == 0 as well.

While content_length_n check is already used in cache and store 
cases in ngx_http_upstream_process_request(), we might want to get 
rid of it instead of reintroducing it in various other places.

>                  {
>                      ngx_http_upstream_finalize_request(r, u, 0);
>                      return;
>                  }
> 
> +                if (upstream->read->eof || upstream->read->error) {
> +                    ngx_http_upstream_finalize_request(r, u,
> NGX_HTTP_BAD_GATEWAY);
> +                    return;
> +                }
> +

Just a side note: resulting code looks not very readable for me.

>                  b->pos = b->start;
>                  b->last = b->start;
>              }
> @@ -2710,7 +2714,16 @@
>  #if 0
>              ngx_http_busy_unlock(u->conf->busy_lock, &u->busy_lock);
>  #endif
> -            ngx_http_upstream_finalize_request(r, u, 0);
> +
> +            if (p->upstream_done
> +                || (p->upstream_eof && u->headers_in.content_length_n == -1))
> +            {
> +                ngx_http_upstream_finalize_request(r, u, 0);
> +
> +            } else {
> +                ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);
> +            }
> +

Same here (well, two above comments about readability and 
content_length_n apply).

>              return;
>          }
>      }
> @@ -3073,6 +3086,13 @@
>          && rc != NGX_HTTP_REQUEST_TIME_OUT
>          && (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE))
>      {
> +        if (rc == NGX_ERROR) {
> +            r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR;
> +
> +        } else {
> +            r->headers_out.status = rc;
> +        }
> +
>          rc = 0;
>      }

See above.

Additionally, I think major problem here isn't 
subrequest-in-memory special case, but sending buf with "last" 
set, which in some situations may result in truncated response 
being considered full by a downstream.

Maxim Dounin



More information about the nginx-devel mailing list