]> git.kaiwu.me - nginx.git/commitdiff
HTTP tunnel module
authorRoman Arutyunyan <arut@nginx.com>
Thu, 16 Apr 2026 16:48:02 +0000 (20:48 +0400)
committerRoman Arutyunyan <arutyunyan.roman@gmail.com>
Fri, 8 May 2026 05:42:58 +0000 (09:42 +0400)
The module handles CONNECT requests and establishes a tunnel to a
backend.

Example config:

http {

    map $request_port $allow_port {
        80             1;
        443            1;
    }

    map $host $allow_host {
        hostnames;

        example.com    1;
        *.example.org  1;
    }

    server {
        listen 8000;

        resolver dns.example.com;

        if ($allow_port != 1) {
            return 403;
        }

        if ($allow_host != 1) {
            return 403;
        }

        tunnel_pass;
    }
}

Request:

    $ curl -x 127.0.0.1:8000 https://example.com

auto/modules
auto/options
src/http/modules/ngx_http_tunnel_module.c [new file with mode: 0644]
src/http/ngx_http_upstream.c
src/http/ngx_http_upstream.h

index 0f40ed12eca9d27cff52a447fb0c6109be07f93b..572eebe6ed939b24e9191ba05d8e0e638f9ed2a3 100644 (file)
@@ -792,6 +792,17 @@ if [ $HTTP = YES ]; then
         . auto/module
     fi
 
+    if [ $HTTP_TUNNEL = YES ]; then
+        ngx_module_name=ngx_http_tunnel_module
+        ngx_module_incs=
+        ngx_module_deps=
+        ngx_module_srcs=src/http/modules/ngx_http_tunnel_module.c
+        ngx_module_libs=
+        ngx_module_link=$HTTP_TUNNEL
+
+        . auto/module
+    fi
+
     if [ $HTTP_PERL != NO ]; then
         ngx_module_name=ngx_http_perl_module
         ngx_module_incs=src/http/modules/perl
index 2a53dd297734262015b2022e7ac902229042978f..6699c69141c09344308755cb377c423ebbc06290 100644 (file)
@@ -89,6 +89,7 @@ HTTP_FASTCGI=YES
 HTTP_UWSGI=YES
 HTTP_SCGI=YES
 HTTP_GRPC=YES
+HTTP_TUNNEL=YES
 HTTP_PERL=NO
 HTTP_MEMCACHED=YES
 HTTP_LIMIT_CONN=YES
@@ -282,6 +283,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated"
         --without-http_uwsgi_module)     HTTP_UWSGI=NO              ;;
         --without-http_scgi_module)      HTTP_SCGI=NO               ;;
         --without-http_grpc_module)      HTTP_GRPC=NO               ;;
+        --without-http_tunnel_module)    HTTP_TUNNEL=NO             ;;
         --without-http_memcached_module) HTTP_MEMCACHED=NO          ;;
         --without-http_limit_conn_module) HTTP_LIMIT_CONN=NO        ;;
         --without-http_limit_req_module) HTTP_LIMIT_REQ=NO         ;;
@@ -513,6 +515,7 @@ cat << END
   --without-http_uwsgi_module        disable ngx_http_uwsgi_module
   --without-http_scgi_module         disable ngx_http_scgi_module
   --without-http_grpc_module         disable ngx_http_grpc_module
+  --without-http_tunnel_module       disable ngx_http_tunnel_module
   --without-http_memcached_module    disable ngx_http_memcached_module
   --without-http_limit_conn_module   disable ngx_http_limit_conn_module
   --without-http_limit_req_module    disable ngx_http_limit_req_module
diff --git a/src/http/modules/ngx_http_tunnel_module.c b/src/http/modules/ngx_http_tunnel_module.c
new file mode 100644 (file)
index 0000000..a249fe2
--- /dev/null
@@ -0,0 +1,515 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_http_upstream_conf_t       upstream;
+
+    ngx_array_t                   *tunnel_lengths;
+    ngx_array_t                   *tunnel_values;
+} ngx_http_tunnel_loc_conf_t;
+
+
+static ngx_int_t ngx_http_tunnel_eval(ngx_http_request_t *r,
+    ngx_http_tunnel_loc_conf_t *tlcf);
+static ngx_int_t ngx_http_tunnel_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_tunnel_reinit_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_tunnel_process_header(ngx_http_request_t *r);
+static void ngx_http_tunnel_abort_request(ngx_http_request_t *r);
+static void ngx_http_tunnel_finalize_request(ngx_http_request_t *r,
+    ngx_int_t rc);
+
+static void *ngx_http_tunnel_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_tunnel_merge_loc_conf(ngx_conf_t *cf,
+    void *parent, void *child);
+
+static char *ngx_http_tunnel_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+static char *ngx_http_tunnel_lowat_check(ngx_conf_t *cf, void *post, void *data);
+
+
+static ngx_conf_post_t  ngx_http_tunnel_lowat_post =
+    { ngx_http_tunnel_lowat_check };
+
+
+static ngx_conf_bitmask_t  ngx_http_tunnel_next_upstream_masks[] = {
+    { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
+    { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
+    { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_http_tunnel_commands[] = {
+
+    { ngx_string("tunnel_pass"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_NOARGS
+      |NGX_CONF_TAKE1,
+      ngx_http_tunnel_pass,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("tunnel_bind"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_http_upstream_bind_set_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_tunnel_loc_conf_t, upstream.local),
+      NULL },
+
+    { ngx_string("tunnel_socket_keepalive"),
+      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_tunnel_loc_conf_t, upstream.socket_keepalive),
+      NULL },
+
+    { ngx_string("tunnel_connect_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_tunnel_loc_conf_t, upstream.connect_timeout),
+      NULL },
+
+    { ngx_string("tunnel_send_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_tunnel_loc_conf_t, upstream.send_timeout),
+      NULL },
+
+    { ngx_string("tunnel_send_lowat"),
+      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_tunnel_loc_conf_t, upstream.send_lowat),
+      &ngx_http_tunnel_lowat_post },
+
+    { ngx_string("tunnel_buffer_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_tunnel_loc_conf_t, upstream.buffer_size),
+      NULL },
+
+    { ngx_string("tunnel_read_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_tunnel_loc_conf_t, upstream.read_timeout),
+      NULL },
+
+    { ngx_string("tunnel_next_upstream"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_tunnel_loc_conf_t, upstream.next_upstream),
+      &ngx_http_tunnel_next_upstream_masks },
+
+    { ngx_string("tunnel_next_upstream_tries"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_tunnel_loc_conf_t, upstream.next_upstream_tries),
+      NULL },
+
+    { ngx_string("tunnel_next_upstream_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_tunnel_loc_conf_t, upstream.next_upstream_timeout),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_tunnel_module_ctx = {
+    NULL,                                  /* preconfiguration */
+    NULL,                                  /* postconfiguration */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_tunnel_create_loc_conf,       /* create location configuration */
+    ngx_http_tunnel_merge_loc_conf         /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_tunnel_module = {
+    NGX_MODULE_V1,
+    &ngx_http_tunnel_module_ctx,           /* module context */
+    ngx_http_tunnel_commands,              /* 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_int_t
+ngx_http_tunnel_handler(ngx_http_request_t *r)
+{
+    ngx_int_t                    rc;
+    ngx_http_upstream_t         *u;
+    ngx_http_tunnel_loc_conf_t  *tlcf;
+
+    if (r->method != NGX_HTTP_CONNECT) {
+        return NGX_HTTP_NOT_ALLOWED;
+    }
+
+    if (ngx_http_upstream_create(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    tlcf = ngx_http_get_module_loc_conf(r, ngx_http_tunnel_module);
+
+    if (tlcf->tunnel_lengths) {
+        if (ngx_http_tunnel_eval(r, tlcf) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    u = r->upstream;
+
+    u->conf = &tlcf->upstream;
+
+    u->create_request = ngx_http_tunnel_create_request;
+    u->reinit_request = ngx_http_tunnel_reinit_request;
+    u->process_header = ngx_http_tunnel_process_header;
+    u->abort_request = ngx_http_tunnel_abort_request;
+    u->finalize_request = ngx_http_tunnel_finalize_request;
+
+    rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        return rc;
+    }
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_http_tunnel_eval(ngx_http_request_t *r, ngx_http_tunnel_loc_conf_t *tlcf)
+{
+    ngx_url_t             url;
+    ngx_http_upstream_t  *u;
+
+    ngx_memzero(&url, sizeof(ngx_url_t));
+
+    if (ngx_http_script_run(r, &url.url, tlcf->tunnel_lengths->elts, 0,
+                            tlcf->tunnel_values->elts)
+        == NULL)
+    {
+        return NGX_ERROR;
+    }
+
+    url.no_resolve = 1;
+
+    if (ngx_parse_url(r->pool, &url) != NGX_OK) {
+        if (url.err) {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "%s in upstream \"%V\"", url.err, &url.url);
+        }
+
+        return NGX_ERROR;
+    }
+
+    u = r->upstream;
+
+    u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
+    if (u->resolved == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (url.addrs) {
+        u->resolved->sockaddr = url.addrs[0].sockaddr;
+        u->resolved->socklen = url.addrs[0].socklen;
+        u->resolved->name = url.addrs[0].name;
+        u->resolved->naddrs = 1;
+    }
+
+    u->resolved->host = url.host;
+    u->resolved->port = url.port;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_tunnel_create_request(ngx_http_request_t *r)
+{
+    /* u->request_bufs = NULL */
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_tunnel_reinit_request(ngx_http_request_t *r)
+{
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_tunnel_process_header(ngx_http_request_t *r)
+{
+    ngx_http_upstream_t  *u;
+
+    u = r->upstream;
+
+    u->headers_in.status_n = NGX_HTTP_OK;
+    ngx_str_set(&u->headers_in.status_line, "200 OK");
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http tunnel status %ui \"%V\"",
+                   u->headers_in.status_n, &u->headers_in.status_line);
+
+    r->keepalive = 0;
+    u->keepalive = 0;
+    u->upgrade = 1;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_tunnel_abort_request(ngx_http_request_t *r)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "abort http tunnel request");
+
+    return;
+}
+
+
+static void
+ngx_http_tunnel_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "finalize http tunnel request");
+
+    return;
+}
+
+
+static void *
+ngx_http_tunnel_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_tunnel_loc_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_tunnel_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->upstream.next_upstream = 0;
+     */
+
+    conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT;
+
+    conf->upstream.local = NGX_CONF_UNSET_PTR;
+    conf->upstream.socket_keepalive = NGX_CONF_UNSET;
+
+    conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+    conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC;
+
+    conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE;
+    conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
+
+    conf->upstream.ignore_input = 1;
+
+    ngx_str_set(&conf->upstream.module, "tunnel");
+
+    return conf;
+}
+
+
+static char *
+ngx_http_tunnel_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_tunnel_loc_conf_t *prev = parent;
+    ngx_http_tunnel_loc_conf_t *conf = child;
+
+    ngx_http_core_loc_conf_t  *clcf;
+
+    ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries,
+                              prev->upstream.next_upstream_tries, 0);
+
+    ngx_conf_merge_ptr_value(conf->upstream.local,
+                              prev->upstream.local, NULL);
+
+    ngx_conf_merge_value(conf->upstream.socket_keepalive,
+                              prev->upstream.socket_keepalive, 0);
+
+    ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+                              prev->upstream.connect_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.send_timeout,
+                              prev->upstream.send_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+                              prev->upstream.read_timeout, 60000);
+
+    ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout,
+                              prev->upstream.next_upstream_timeout, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.send_lowat,
+                              prev->upstream.send_lowat, 0);
+
+    ngx_conf_merge_size_value(conf->upstream.buffer_size,
+                              prev->upstream.buffer_size,
+                              (size_t) ngx_pagesize);
+
+    ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
+                              prev->upstream.next_upstream,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_HTTP_UPSTREAM_FT_ERROR
+                               |NGX_HTTP_UPSTREAM_FT_TIMEOUT));
+
+    if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
+        conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
+                                       |NGX_HTTP_UPSTREAM_FT_OFF;
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    if (clcf->noname
+        && conf->upstream.upstream == NULL && conf->tunnel_lengths == NULL)
+    {
+        conf->upstream.upstream = prev->upstream.upstream;
+
+        conf->tunnel_lengths = prev->tunnel_lengths;
+        conf->tunnel_values = prev->tunnel_values;
+    }
+
+    if (clcf->lmt_excpt && clcf->handler == NULL
+        && (conf->upstream.upstream || conf->tunnel_lengths))
+    {
+        clcf->handler = ngx_http_tunnel_handler;
+    }
+
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_tunnel_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_tunnel_loc_conf_t *tlcf = conf;
+
+    ngx_url_t                   u;
+    ngx_str_t                  *value, url;
+    ngx_uint_t                  n;
+    ngx_http_core_srv_conf_t   *cscf;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_script_compile_t   sc;
+
+    if (tlcf->upstream.upstream || tlcf->tunnel_lengths) {
+        return "is duplicate";
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    clcf->handler = ngx_http_tunnel_handler;
+
+    cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module);
+
+    cscf->allow_connect = 1;
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 1) {
+        ngx_str_set(&url, "$host:$request_port");
+
+    } else {
+        url = value[1];
+    }
+
+    n = ngx_http_script_variables_count(&url);
+
+    if (n) {
+
+        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+        sc.cf = cf;
+        sc.source = &url;
+        sc.lengths = &tlcf->tunnel_lengths;
+        sc.values = &tlcf->tunnel_values;
+        sc.variables = n;
+        sc.complete_lengths = 1;
+        sc.complete_values = 1;
+
+        if (ngx_http_script_compile(&sc) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        return NGX_CONF_OK;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+
+    u.url = url;
+    u.no_resolve = 1;
+
+    tlcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
+    if (tlcf->upstream.upstream == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_tunnel_lowat_check(ngx_conf_t *cf, void *post, void *data)
+{
+#if (NGX_FREEBSD)
+    ssize_t *np = data;
+
+    if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"tunnel_send_lowat\" must be less than %d "
+                           "(sysctl net.inet.tcp.sendspace)",
+                           ngx_freebsd_net_inet_tcp_sendspace);
+
+        return NGX_CONF_ERROR;
+    }
+
+#elif !(NGX_HAVE_SO_SNDLOWAT)
+    ssize_t *np = data;
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"tunnel_send_lowat\" is not supported, ignored");
+
+    *np = 0;
+
+#endif
+
+    return NGX_CONF_OK;
+}
index b6456136936be88569704d7910a07575224cd995..bb8964addbfe74f3511fb4f16e72e7e34575197a 100644 (file)
@@ -2247,6 +2247,11 @@ ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u,
             return;
         }
 
+        if (u->conf->ignore_input) {
+            ngx_http_upstream_process_header(r, u);
+            return;
+        }
+
         ngx_add_timer(c->read, u->conf->read_timeout);
 
         if (c->read->ready) {
@@ -2502,6 +2507,11 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u)
 #endif
     }
 
+    if (u->conf->ignore_input) {
+        rc = u->process_header(r);
+        goto done;
+    }
+
     for ( ;; ) {
 
         n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last);
@@ -2580,6 +2590,8 @@ again:
         break;
     }
 
+done:
+
     if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {
         ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);
         return;
index 0d48db78807651d0086c275789cf7096ad797e9c..4560bbe9a2cfcfd9ff3dd712072237c28a341573 100644 (file)
@@ -240,6 +240,7 @@ typedef struct {
     unsigned                         intercept_404:1;
     unsigned                         change_buffering:1;
     unsigned                         preserve_output:1;
+    unsigned                         ignore_input:1;
 
 #if (NGX_HTTP_SSL || NGX_COMPAT)
     ngx_ssl_t                       *ssl;
@@ -257,7 +258,7 @@ typedef struct {
 
     ngx_str_t                        module;
 
-    NGX_COMPAT_BEGIN(6)
+    NGX_COMPAT_BEGIN(5)
     NGX_COMPAT_END
 } ngx_http_upstream_conf_t;