Skip to content

Connection hang up when calling ngx.location.capture after read request body in http2 #1242

@qleein

Description

@qleein

Nginx with lastest lua-nginx-module

root@ubuntu17:/usr/local/nginx# ./sbin/nginx -V
nginx version: nginx/1.13.8
built by gcc 7.2.0 (Ubuntu 7.2.0-8ubuntu3) 
built with OpenSSL 1.0.2g  1 Mar 2016
TLS SNI support enabled
configure arguments: --with-debug --with-cc-opt='-g -O0' --with-ld-opt=-Wl,-rpath=/usr/local/lib/ --with-zlib=/home/kenan/code/zlib-1.2.11 --with-pcre=/home/kenan/code/pcre-8.41 --add-module=/home/kenan/code/lua-nginx-module --with-http_ssl_module --with-http_slice_module --with-http_v2_module --with-stream --with-stream_ssl_module --with-http_stub_status_module
root@ubuntu17:/usr/local/lua-nginx-module# git log
commit df1b0198ff0262c755029738058f5917a11a398a (HEAD, openresty/master)
Author: spacewander <spacewanderlzx@gmail.com>
Date:   Mon Jan 22 14:50:40 2018 +0800

The example nginx config file like this

user  root;
master_process off;
daemon off;

events {
use epoll;
}

http {
    server {
        listen 80 http2;
    
        location = /callback {
            internal;
            content_by_lua_block {
                ngx.print("fallback called")
            }
        }

        location / {
            rewrite_by_lua_block {
                ngx.req.read_body()
                --ngx.sleep(0.01)
                ngx.status = 200
                local res = ngx.location.capture('/callback')
                ngx.header["Content-Type"] = "text/html"
                ngx.print(res.body)
                return ngx.exit(ngx.status)
            }
            root html;
        }
    }
}

Send a http2 request by nghttp tool

$ echo hello > bodydata
$ nghttp http://127.0.0.1 -d bodydata -v
[  0.000] Connected
[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
          (niv=2)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
          (dep_stream_id=0, weight=201, exclusive=0)
[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
          (dep_stream_id=0, weight=101, exclusive=0)
[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
          (dep_stream_id=0, weight=1, exclusive=0)
[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
          (dep_stream_id=7, weight=1, exclusive=0)
[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
          (dep_stream_id=3, weight=1, exclusive=0)
[  0.000] send HEADERS frame <length=39, flags=0x24, stream_id=13>
          ; END_HEADERS | PRIORITY
          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)
          ; Open new stream
          :method: POST
          :path: /
          :scheme: http
          :authority: 127.0.0.1
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: nghttp2/1.25.0
          content-length: 6
[  0.000] send DATA frame <length=6, flags=0x01, stream_id=13>
          ; END_STREAM
[  0.000] recv SETTINGS frame <length=18, flags=0x00, stream_id=0>
          (niv=3)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):128]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65536]
          [SETTINGS_MAX_FRAME_SIZE(0x05):16777215]
[  0.000] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=2147418112)
[  0.000] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.000] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=13>
          (window_size_increment=2147418111)
[  0.000] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
^C
$ 

then the nghttp sending request to nginx, then block forever, no response was sent by nginx .
if I call ngx.sleep before ngx.location.capture, it works well.

Blow is my analysis.

In Nginx, if one created a subrequest, the new subrequest should been posted to queue(posted_requests in ngx_http_request_t structure), then been executed by calling ngx_http_run_posted_requests.

While here when calling ngx.location.capture, a subrequest was created and posted to the queue, but the function ngx_http_run_posted_requests would not been called, and the conneciton hang up without any response.

In nginx, ngx_http_read_client_request_body function is used to read the request body nonblockly, defined as this

ngx_int_t
ngx_http_read_client_request_body(ngx_http_request_t *r,
    ngx_http_client_body_handler_pt post_handler)

the post_handler is called after read request body done. In http2 mode, if you created subrequest in the post_handler, the subrequest would become zombie and never be executed.

In lua-nginx-module, ngx_http_read_client_request_body called in four positions, Testing the four position with following testsuits in Test-Nginx , all failed with this problems.

# vim:set ft= ts=4 sw=4 et fdm=marker:
use Test::Nginx::Socket::Lua;

#worker_connections(1014);
#master_process_enabled(1);
log_level('warn');

repeat_each(2);

plan tests => repeat_each() * (blocks() * 4);

#no_diff();
no_long_string();
#master_on();
#workers(2);
run_tests();

__DATA__

=== TEST 1: read buffered body and then subrequest
--- config
    location /foo {
        echo -n foo;
    }
    location = /test {
        content_by_lua '
            ngx.req.read_body()
            local res = ngx.location.capture("/foo");
            ngx.say(ngx.var.request_body)
            ngx.say("sub: ", res.body)
        ';
    }
--- http2
--- request
POST /test
hello, world
--- response_body
hello, world
sub: foo
--- no_error_log
[error]
[alert]

=== TEST 2: read body with ngx.location.capture in rewrite phase
--- config
    location /proxy {
        proxy_pass http://127.0.0.1:$server_port/hi;
    }
    location /hi {
        echo_request_body;
    }
    location /echo_body {
        lua_need_request_body on;
        rewrite_by_lua '
            ngx.say(ngx.var.request_body or "nil")
            local res = ngx.location.capture(
                "/proxy",
                { method = ngx.HTTP_POST,
                  body = ngx.var.request_body })

            ngx.print(res.body)
        ';
    }
--- http2
--- request eval
"POST /echo_body
hello\x00\x01\x02
world\x03\x04\xff"
--- response_body eval
"hello\x00\x01\x02
world\x03\x04\xff
"

=== TEST 2: read body with ngx.location.capture in access
--- config
    location /proxy {
        proxy_pass http://127.0.0.1:$server_port/hi;
    }
    location /hi {
        echo_request_body;
    }
    location /echo_body {
        lua_need_request_body on;
        access_by_lua '
            ngx.say(ngx.var.request_body or "nil")
            local res = ngx.location.capture(
                "/proxy",
                { method = ngx.HTTP_POST,
                  body = ngx.var.request_body })

            ngx.print(res.body)
        ';
    }
--- http2
--- request eval
"POST /echo_body
hello\x00\x01\x02
world\x03\x04\xff"
--- response_body eval
"hello\x00\x01\x02
world\x03\x04\xff
"

=== TEST 4: read body with ngx.location.capture in content
--- config
    location /proxy {
        proxy_pass http://127.0.0.1:$server_port/hi;
    }
    location /hi {
        echo_request_body;
    }
    location /echo_body {
        lua_need_request_body on;
        content_by_lua '
            ngx.say(ngx.var.request_body or "nil")
            local res = ngx.location.capture(
                "/proxy",
                { method = ngx.HTTP_POST,
                  body = ngx.var.request_body })

            ngx.print(res.body)
        ';
    }
--- http2
--- request eval
"POST /echo_body
hello\x00\x01\x02
world\x03\x04\xff"
--- response_body eval
"hello\x00\x01\x02
world\x03\x04\xff
"

The problem is here, but I'm not sure it should be fixed in lua-nginx-module ,or nginx project?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions