# vim:set ft= ts=4 sw=4 et fdm=marker:

use Test::Nginx::Socket::Lua::Stream;

repeat_each(2);

plan tests => repeat_each() * 227;

our $HtmlDir = html_dir;

$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8';
$ENV{TEST_NGINX_HTML_DIR} ||= html_dir();

#log_level 'warn';
log_level 'debug';

no_long_string();
#no_diff();
run_tests();

__DATA__

=== TEST 1: sanity
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;

    location /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }

--- stream_response
connected: 1
request sent: 57
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Content-Length: 4
received: Connection: close
received: 
received: foo
failed to receive a line: closed []
close: 1 nil
--- no_error_log
[error]



=== TEST 2: no trailing newline
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        sock:close()
        ngx.say("closed")
    }

--- config
    server_tokens off;

    location /foo {
        content_by_lua_block { ngx.print("foo") }
        more_clear_headers Date;
    }
--- stream_response
connected: 1
request sent: 57
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Content-Length: 3
received: Connection: close
received: 
failed to receive a line: closed [foo]
closed
--- no_error_log
[error]



=== TEST 3: no resolver defined
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("agentzh.org", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)
    }
--- stream_response
failed to connect: no resolver defined to resolve "agentzh.org"
connected: nil
failed to send request: closed
--- error_log
attempt to send data on a closed socket:



=== TEST 4: with resolver
--- timeout: 10
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    resolver_timeout 3s;
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = 80
        local ok, err = sock:connect("agentzh.org", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET / HTTP/1.0\r\nHost: agentzh.org\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        local line, err = sock:receive()
        if line then
            ngx.say("first line received: ", line)

        else
            ngx.say("failed to receive the first line: ", err)
        end

        line, err = sock:receive()
        if line then
            ngx.say("second line received: ", line)

        else
            ngx.say("failed to receive the second line: ", err)
        end
    }

--- stream_response_like
connected: 1
request sent: 56
first line received: HTTP\/1\.1 200 OK
second line received: (?:Date|Server): .*?
--- no_error_log
[error]
--- timeout: 10



=== TEST 5: connection refused (tcp)
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", 16787)
        ngx.say("connect: ", ok, " ", err)

        local bytes
        bytes, err = sock:send("hello")
        ngx.say("send: ", bytes, " ", err)

        local line
        line, err = sock:receive()
        ngx.say("receive: ", line, " ", err)

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }
--- stream_response
connect: nil connection refused
send: nil closed
receive: nil closed
close: nil closed
--- error_log eval
qr/connect\(\) failed \(\d+: Connection refused\)/



=== TEST 6: connection timeout (tcp)
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    #lua_socket_connect_timeout 100ms;
    #lua_socket_send_timeout 100ms;
    #lua_socket_read_timeout 100ms;
    resolver_timeout 3s;
    content_by_lua_block {
        local sock = ngx.socket.tcp()

        sock:settimeout(100)  -- ms

        local ok, err = sock:connect("127.0.0.2", 12345)
        ngx.say("connect: ", ok, " ", err)

        local bytes
        bytes, err = sock:send("hello")
        ngx.say("send: ", bytes, " ", err)

        local line
        line, err = sock:receive()
        ngx.say("receive: ", line, " ", err)

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }
--- stream_response
connect: nil timeout
send: nil closed
receive: nil closed
close: nil closed
--- error_log
lua tcp socket connect timed out, upstream: 127.0.0.2:12345(127.0.0.2)
--- timeout: 10



=== TEST 7: not closed manually
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)
    }
--- stream_response
connected: 1
--- no_error_log
[error]



=== TEST 8: resolver error (host not found)
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    resolver_timeout 3s;
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = 80
        local ok, err = sock:connect("blah-blah-not-found.agentzh.org", port)
        print("connected: ", ok, " ", err, " ", not ok)
        if not ok then
            ngx.say("failed to connect: ", err)
        end

        ngx.say("connected: ", ok)

        local req = "GET / HTTP/1.0\r\nHost: agentzh.org\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)
    }
--- stream_response_like
^failed to connect: blah-blah-not-found\.agentzh\.org could not be resolved(?: \(3: Host not found\))?
connected: nil
failed to send request: closed$
--- error_log
attempt to send data on a closed socket
--- timeout: 10



=== TEST 9: resolver error (timeout)
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    resolver_timeout 1ms;
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = 80
        local ok, err = sock:connect("blah-blah-not-found.agentzh.org", port)
        print("connected: ", ok, " ", err, " ", not ok)
        if not ok then
            ngx.say("failed to connect: ", err)
        end

        ngx.say("connected: ", ok)

        local req = "GET / HTTP/1.0\r\nHost: agentzh.org\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)
    }
--- stream_response_like
^failed to connect: blah-blah-not-found\.agentzh\.org could not be resolved(?: \(\d+: (?:Operation timed out|Host not found)\))?
connected: nil
failed to send request: closed$
--- error_log
attempt to send data on a closed socket



=== TEST 10: explicit *l pattern for receive
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err = sock:receive("*l")
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err)
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    location = /foo {
        server_tokens off;
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }
--- stream_response
connected: 1
request sent: 57
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Content-Length: 4
received: Connection: close
received: 
received: foo
failed to receive a line: closed
close: 1 nil
--- no_error_log
[error]



=== TEST 11: *a pattern for receive
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        local data, err = sock:receive("*a")
        if data then
            ngx.say("receive: ", data)
            ngx.say("err: ", err)

        else
            ngx.say("failed to receive: ", err)
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;

    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }
--- stream_response eval
"connected: 1
request sent: 57
receive: HTTP/1.1 200 OK\r
Server: nginx\r
Content-Type: text/plain\r
Content-Length: 4\r
Connection: close\r
\r
foo

err: nil
close: 1 nil
"
--- no_error_log
[error]



=== TEST 12: mixing *a and *l patterns for receive
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        local line, err = sock:receive("*l")
        if line then
            ngx.say("receive: ", line)
            ngx.say("err: ", err)

        else
            ngx.say("failed to receive: ", err)
        end

        local data
        data, err = sock:receive("*a")
        if data then
            ngx.say("receive: ", data)
            ngx.say("err: ", err)

        else
            ngx.say("failed to receive: ", err)
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }
--- stream_response eval
"connected: 1
request sent: 57
receive: HTTP/1.1 200 OK
err: nil
receive: Server: nginx\r
Content-Type: text/plain\r
Content-Length: 4\r
Connection: close\r
\r
foo

err: nil
close: 1 nil
"
--- no_error_log
[error]



=== TEST 13: receive by chunks
--- timeout: 5
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local data, err, partial = sock:receive(10)
            if data then
                local len = string.len(data)
                if len == 10 then
                    ngx.print("[", data, "]")
                else
                    ngx.say("ERROR: returned invalid length of data: ", len)
                end

            else
                ngx.say("failed to receive a line: ", err, " [", partial, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;

    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }
--- stream_response eval
"connected: 1
request sent: 57
[HTTP/1.1 2][00 OK\r
Ser][ver: nginx][\r
Content-][Type: text][/plain\r
Co][ntent-Leng][th: 4\r
Con][nection: c][lose\r
\r
fo]failed to receive a line: closed [o
]
close: 1 nil
"
--- no_error_log
[error]



=== TEST 14: receive by chunks (very small buffer)
--- timeout: 5
--- stream_server_config
    lua_socket_buffer_size 1;

    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local data, err, partial = sock:receive(10)
            if data then
                local len = string.len(data)
                if len == 10 then
                    ngx.print("[", data, "]")
                else
                    ngx.say("ERROR: returned invalid length of data: ", len)
                end

            else
                ngx.say("failed to receive a line: ", err, " [", partial, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }
--- stream_response eval
"connected: 1
request sent: 57
[HTTP/1.1 2][00 OK\r
Ser][ver: nginx][\r
Content-][Type: text][/plain\r
Co][ntent-Leng][th: 4\r
Con][nection: c][lose\r
\r
fo]failed to receive a line: closed [o
]
close: 1 nil
"
--- no_error_log
[error]



=== TEST 15: line reading (very small buffer)
--- stream_server_config
    lua_socket_buffer_size 1;

    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }
--- stream_response
connected: 1
request sent: 57
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Content-Length: 4
received: Connection: close
received: 
received: foo
failed to receive a line: closed []
close: 1 nil
--- no_error_log
[error]



=== TEST 16: ngx.socket.connect (working)
--- stream_server_config
    content_by_lua_block {
        local sock, err = ngx.socket.connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not sock then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected.")

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;

    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }
--- stream_response
connected.
request sent: 57
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Content-Length: 4
received: Connection: close
received: 
received: foo
failed to receive a line: closed []
close: 1 nil
--- no_error_log
[error]



=== TEST 17: ngx.socket.connect() shortcut (connection refused)
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local sock, err = sock:connect("127.0.0.1", 16787)
        if not sock then
            ngx.say("failed to connect: ", err)
            return
        end

        local bytes
        bytes, err = sock:send("hello")
        ngx.say("send: ", bytes, " ", err)

        local line
        line, err = sock:receive()
        ngx.say("receive: ", line, " ", err)

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }
--- stap2
M(http-lua-info) {
    printf("tcp resume: %p\n", $coctx)
    print_ubacktrace()
}

--- stream_response
failed to connect: connection refused
--- error_log eval
qr/connect\(\) failed \(\d+: Connection refused\)/



=== TEST 18: receive by chunks (stringified size)
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local data, err, partial = sock:receive("10")
            if data then
                local len = string.len(data)
                if len == 10 then
                    ngx.print("[", data, "]")
                else
                    ngx.say("ERROR: returned invalid length of data: ", len)
                end

            else
                ngx.say("failed to receive a line: ", err, " [", partial, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;

    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }
--- stream_response eval
"connected: 1
request sent: 57
[HTTP/1.1 2][00 OK\r
Ser][ver: nginx][\r
Content-][Type: text][/plain\r
Co][ntent-Leng][th: 4\r
Con][nection: c][lose\r
\r
fo]failed to receive a line: closed [o
]
close: 1 nil
"
--- no_error_log
[error]



=== TEST 19: cannot survive across request boundary (send)
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local test = require "test"
        test.go($TEST_NGINX_MEMCACHED_PORT)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function go(port)
    if not sock then
        sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)
    end

    local req = "flush_all\r\n"

    local bytes, err = sock:send(req)
    if not bytes then
        ngx.say("failed to send request: ", err)
        return
    end
    ngx.say("request sent: ", bytes)

    local line, err, part = sock:receive()
    if line then
        ngx.say("received: ", line)

    else
        ngx.say("failed to receive a line: ", err, " [", part, "]")
    end
end
--- stream_response_like eval
"^(?:connected: 1
request sent: 11
received: OK|failed to send request: closed)\$"



=== TEST 20: cannot survive across request boundary (receive)
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local test = require "test"
        test.go($TEST_NGINX_MEMCACHED_PORT)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function go(port)
    if not sock then
        sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

    else
        local line, err, part = sock:receive()
        if line then
            ngx.say("received: ", line)

        else
            ngx.say("failed to receive a line: ", err, " [", part, "]")
        end
        return
    end

    local req = "flush_all\r\n"

    local bytes, err = sock:send(req)
    if not bytes then
        ngx.say("failed to send request: ", err)
        return
    end
    ngx.say("request sent: ", bytes)

    local line, err, part = sock:receive()
    if line then
        ngx.say("received: ", line)

    else
        ngx.say("failed to receive a line: ", err, " [", part, "]")
    end
end

--- stap2
M(http-lua-info) {
    printf("tcp resume\n")
    print_ubacktrace()
}
--- stream_response_like eval
qr/^(?:connected: 1
request sent: 11
received: OK|failed to receive a line: closed \[nil\])$/



=== TEST 21: cannot survive across request boundary (close)
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local test = require "test"
        test.go($TEST_NGINX_MEMCACHED_PORT)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function go(port)
    if not sock then
        sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

    else
        local ok, err = sock:close()
        if ok then
            ngx.say("successfully closed")

        else
            ngx.say("failed to close: ", err)
        end
        return
    end

    local req = "flush_all\r\n"

    local bytes, err = sock:send(req)
    if not bytes then
        ngx.say("failed to send request: ", err)
        return
    end
    ngx.say("request sent: ", bytes)

    local line, err, part = sock:receive()
    if line then
        ngx.say("received: ", line)

    else
        ngx.say("failed to receive a line: ", err, " [", part, "]")
    end
end
--- stream_response_like eval
qr/^(?:connected: 1
request sent: 11
received: OK|failed to close: closed)$/



=== TEST 22: cannot survive across request boundary (connect)
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local test = require "test"
        test.go($TEST_NGINX_MEMCACHED_PORT)
        test.go($TEST_NGINX_MEMCACHED_PORT)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function go(port)
    if not sock then
        sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

    else
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect again: ", err)
            return
        end

        ngx.say("connected again: ", ok)
    end

    local req = "flush_all\r\n"

    local bytes, err = sock:send(req)
    if not bytes then
        ngx.say("failed to send request: ", err)
        return
    end
    ngx.say("request sent: ", bytes)

    local line, err, part = sock:receive()
    if line then
        ngx.say("received: ", line)

    else
        ngx.say("failed to receive a line: ", err, " [", part, "]")
    end
end
--- stream_response_like eval
qr/^(?:connected(?: again)?: 1
request sent: 11
received: OK
){2}$/
--- error_log
lua reuse socket upstream ctx
--- no_error_log
[error]
--- SKIP



=== TEST 23: connect again immediately
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_MEMCACHED_PORT

        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected again: ", ok)

        local req = "flush_all\r\n"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end
        ngx.say("request sent: ", bytes)

        local line, err, part = sock:receive()
        if line then
            ngx.say("received: ", line)

        else
            ngx.say("failed to receive a line: ", err, " [", part, "]")
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }
--- stream_response
connected: 1
connected again: 1
request sent: 11
received: OK
close: 1 nil
--- no_error_log
[error]
--- error_log eval
["lua reuse socket upstream", "lua tcp socket reconnect without shutting down"]



=== TEST 24: two sockets mix together
--- stream_server_config
    content_by_lua_block {
        local sock1 = ngx.socket.tcp()
        local sock2 = ngx.socket.tcp()

        local port1 = $TEST_NGINX_MEMCACHED_PORT
        local port2 = $TEST_NGINX_SERVER_PORT

        local ok, err = sock1:connect("127.0.0.1", port1)
        if not ok then
            ngx.say("1: failed to connect: ", err)
            return
        end

        ngx.say("1: connected: ", ok)

        ok, err = sock2:connect("127.0.0.1", port2)
        if not ok then
            ngx.say("2: failed to connect: ", err)
            return
        end

        ngx.say("2: connected: ", ok)

        local req1 = "flush_all\r\n"
        local bytes, err = sock1:send(req1)
        if not bytes then
            ngx.say("1: failed to send request: ", err)
            return
        end
        ngx.say("1: request sent: ", bytes)

        local req2 = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        local bytes, err = sock2:send(req2)
        if not bytes then
            ngx.say("2: failed to send request: ", err)
            return
        end
        ngx.say("2: request sent: ", bytes)

        local line, err, part = sock1:receive()
        if line then
            ngx.say("1: received: ", line)

        else
            ngx.say("1: failed to receive a line: ", err, " [", part, "]")
        end

        line, err, part = sock2:receive()
        if line then
            ngx.say("2: received: ", line)

        else
            ngx.say("2: failed to receive a line: ", err, " [", part, "]")
        end

        ok, err = sock1:close()
        ngx.say("1: close: ", ok, " ", err)

        ok, err = sock2:close()
        ngx.say("2: close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /foo {
        echo foo;
        more_clear_headers Date;
    }
--- stream_response
1: connected: 1
2: connected: 1
1: request sent: 11
2: request sent: 57
1: received: OK
2: received: HTTP/1.1 200 OK
1: close: 1 nil
2: close: 1 nil
--- no_error_log
[error]



=== TEST 25: send tables of string fragments
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = {"GET", " ", "/foo", " HTTP/", 1, ".", 0, "\r\n",
                     "Host: localhost\r\n", "Connection: close\r\n",
                     "\r\n"}
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;

    location /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }
--- stream_response
connected: 1
request sent: 57
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Content-Length: 4
received: Connection: close
received: 
received: foo
failed to receive a line: closed []
close: 1 nil
--- no_error_log
[error]



=== TEST 26: send tables of string fragments (bad type "nil")
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = {"GET", " ", "/foo", " HTTP/", nil, 1, ".", 0, "\r\n",
                     "Host: localhost\r\n", "Connection: close\r\n",
                     "\r\n"}
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /foo {
        echo foo;
        more_clear_headers Date;
    }
--- stream_response
connected: 1
--- error_log
bad argument #1 to 'send' (bad data type nil found)



=== TEST 27: send tables of string fragments (bad type "boolean")
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = {"GET", " ", "/foo", " HTTP/", true, 1, ".", 0, "\r\n",
                     "Host: localhost\r\n", "Connection: close\r\n",
                     "\r\n"}
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /foo {
        echo foo;
        more_clear_headers Date;
    }
--- stream_response
connected: 1
--- error_log
bad argument #1 to 'send' (bad data type boolean found)



=== TEST 28: send tables of string fragments (bad type ngx.null)
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = {"GET", " ", "/foo", " HTTP/", ngx.null, 1, ".", 0, "\r\n",
                     "Host: localhost\r\n", "Connection: close\r\n",
                     "\r\n"}
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /foo {
        echo foo;
        more_clear_headers Date;
    }
--- stream_response
connected: 1
--- error_log
bad argument #1 to 'send' (bad data type userdata found)



=== TEST 29: CR in a line
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /foo {
        echo "foo\r\rbar\rbaz";
        more_clear_headers Date;
    }
--- stream_response
connected: 1
request sent: 57
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Content-Length: 13
received: Connection: close
received: 
received: foobarbaz
failed to receive a line: closed []
close: nil closed
--- no_error_log
[error]
--- SKIP



=== TEST 30: receive(0)
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        local data, err, part = sock:receive(0)
        if not data then
            ngx.say("failed to receive(0): ", err)
            return
        end

        ngx.say("receive(0): [", data, "]")

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /foo {
        echo foo;
        more_clear_headers Date;
    }
--- stream_response
connected: 1
request sent: 57
receive(0): []
close: 1 nil
--- no_error_log
[error]



=== TEST 31: send("")
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        local bytes, err = sock:send("")
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("send(\"\"): ", bytes)

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /foo {
        echo foo;
        more_clear_headers Date;
    }
--- stream_response
connected: 1
request sent: 57
send(""): 0
close: 1 nil
--- no_error_log
[error]
[alert]



=== TEST 32: connection refused (tcp) - lua_socket_log_errors off
--- stream_server_config
    lua_socket_log_errors off;
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", 16787)
        ngx.say("connect: ", ok, " ", err)

        local bytes
        bytes, err = sock:send("hello")
        ngx.say("send: ", bytes, " ", err)

        local line
        line, err = sock:receive()
        ngx.say("receive: ", line, " ", err)

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }
--- stream_response
connect: nil connection refused
send: nil closed
receive: nil closed
close: nil closed
--- no_error_log eval
[qr/connect\(\) failed \(\d+: Connection refused\)/]



=== TEST 33: reread after a read time out happen (receive -> receive)
--- stream_server_config
    lua_socket_read_timeout 100ms;
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local line
        line, err = sock:receive()
        if line then
            ngx.say("received: ", line)
        else
            ngx.say("failed to receive: ", err)

            line, err = sock:receive()
            if not line then
                ngx.say("failed to receive: ", err)
            end
        end
    }
--- stream_response
connected: 1
failed to receive: timeout
failed to receive: timeout
--- error_log
lua tcp socket read timeout: 100
lua tcp socket connect timeout: 60000
lua tcp socket read timed out



=== TEST 34: successful reread after a read time out happen (receive -> receive)
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        local bytes, err = sock:send("GET /back HTTP/1.1\r\nHost: localhost\r\n\r\n")
        if not bytes then
            ngx.say("failed to send: ", err)
            return
        end

        local reader = sock:receiveuntil("\r\n\r\n")
        local header, err = reader()
        if not header then
            ngx.say("failed to read the response header: ", err)
            return
        end

        sock:settimeout(100)

        local data, err, partial = sock:receive(100)
        if data then
            ngx.say("received: ", data)
        else
            ngx.say("failed to receive: ", err, ", partial: ", partial)

            sock:settimeout(123)
            ngx.sleep(0.1)
            local line, err = sock:receive()
            if not line then
                ngx.say("failed to receive: ", err)
                return
            end
            ngx.say("received: ", line)

            local line, err = sock:receive()
            if not line then
                ngx.say("failed to receive: ", err)
                return
            end
            ngx.say("received: ", line)
        end
    }

--- config
    location = /back {
        content_by_lua_block {
            ngx.print("hi")
            ngx.flush(true)
            ngx.sleep(0.2)
            ngx.print("world")
        }
    }
--- stream_response eval
"failed to receive: timeout, partial: 2\r
hi\r

received: 5
received: world
"
--- error_log
lua tcp socket read timed out
--- no_error_log
[alert]



=== TEST 35: successful reread after a read time out happen (receive -> receiveuntil)
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        local bytes, err = sock:send("GET /back HTTP/1.1\r\nHost: localhost\r\n\r\n")
        if not bytes then
            ngx.say("failed to send: ", err)
            return
        end

        local reader = sock:receiveuntil("\r\n\r\n")
        local header, err = reader()
        if not header then
            ngx.say("failed to read the response header: ", err)
            return
        end

        sock:settimeout(100)

        local data, err, partial = sock:receive(100)
        if data then
            ngx.say("received: ", data)
        else
            ngx.say("failed to receive: ", err, ", partial: ", partial)

            ngx.sleep(0.1)

            sock:settimeout(123)
            local reader = sock:receiveuntil("\r\n")

            local line, err = reader()
            if not line then
                ngx.say("failed to receive: ", err)
                return
            end
            ngx.say("received: ", line)

            local line, err = reader()
            if not line then
                ngx.say("failed to receive: ", err)
                return
            end
            ngx.say("received: ", line)
        end
    }

--- config
    server_tokens off;
    location = /back {
        content_by_lua_block {
            ngx.print("hi")
            ngx.flush(true)
            ngx.sleep(0.2)
            ngx.print("world")
        }
    }
--- stream_response eval
"failed to receive: timeout, partial: 2\r
hi\r

received: 5
received: world
"
--- error_log
lua tcp socket read timed out
--- no_error_log
[alert]



=== TEST 36: successful reread after a read time out happen (receiveuntil -> receiveuntil)
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        local bytes, err = sock:send("GET /back HTTP/1.1\r\nHost: localhost\r\n\r\n")
        if not bytes then
            ngx.say("failed to send: ", err)
            return
        end

        local reader = sock:receiveuntil("\r\n\r\n")
        local header, err = reader()
        if not header then
            ngx.say("failed to read the response header: ", err)
            return
        end

        sock:settimeout(100)

        local reader = sock:receiveuntil("no-such-terminator")
        local data, err, partial = reader()
        if data then
            ngx.say("received: ", data)
        else
            ngx.say("failed to receive: ", err, ", partial: ", partial)

            ngx.sleep(0.1)

            sock:settimeout(123)

            local reader = sock:receiveuntil("\r\n")

            local line, err = reader()
            if not line then
                ngx.say("failed to receive: ", err)
                return
            end
            ngx.say("received: ", line)

            local line, err = reader()
            if not line then
                ngx.say("failed to receive: ", err)
                return
            end
            ngx.say("received: ", line)
        end
    }

--- config
    server_tokens off;
    location = /back {
        content_by_lua_block {
            ngx.print("hi")
            ngx.flush(true)
            ngx.sleep(0.2)
            ngx.print("world")
        }
    }
--- stream_response eval
"failed to receive: timeout, partial: 2\r
hi\r

received: 5
received: world
"
--- error_log
lua tcp socket read timed out
--- no_error_log
[alert]



=== TEST 37: successful reread after a read time out happen (receiveuntil -> receive)
--- stream_server_config
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        local bytes, err = sock:send("GET /back HTTP/1.1\r\nHost: localhost\r\n\r\n")
        if not bytes then
            ngx.say("failed to send: ", err)
            return
        end

        local reader = sock:receiveuntil("\r\n\r\n")
        local header, err = reader()
        if not header then
            ngx.say("failed to read the response header: ", err)
            return
        end

        sock:settimeout(100)

        local reader = sock:receiveuntil("no-such-terminator")
        local data, err, partial = reader()
        if data then
            ngx.say("received: ", data)
        else
            ngx.say("failed to receive: ", err, ", partial: ", partial)

            ngx.sleep(0.1)

            sock:settimeout(123)

            local line, err = sock:receive()
            if not line then
                ngx.say("failed to receive: ", err)
                return
            end
            ngx.say("received: ", line)

            local line, err = sock:receive()
            if not line then
                ngx.say("failed to receive: ", err)
                return
            end
            ngx.say("received: ", line)
        end
    }

--- config
    server_tokens off;
    location = /back {
        content_by_lua_block {
            ngx.print("hi")
            ngx.flush(true)
            ngx.sleep(0.2)
            ngx.print("world")
        }
    }
--- stream_response eval
"failed to receive: timeout, partial: 2\r
hi\r

received: 5
received: world
"
--- error_log
lua tcp socket read timed out
--- no_error_log
[alert]



=== TEST 38: receive(0)
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local data, err = sock:receive(0)
        if not data then
            ngx.say("failed to receive: ", err)
            return
        end

        ngx.say("received: ", data)

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /back {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }

--- stream_response
connected: 1
received: 
close: 1 nil
--- no_error_log
[error]



=== TEST 39: empty options table
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port, {})
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- stream_response
connected: 1
close: 1 nil
--- no_error_log
[error]



=== TEST 40: u->coctx left over bug
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        local ready = false
        local fatal = false

        function f()
            local line, err, part = sock:receive()
            if not line then
                ngx.say("failed to receive the 1st line: ", err, " [", part, "]")
                fatal = true
                return
            end
            ready = true
            ngx.sleep(1)
        end

        local st = ngx.thread.spawn(f)
        while true do
            if fatal then
                return
            end

            if not ready then
                ngx.sleep(0.01)
            else
                break
            end
        end

        while true do
            local line, err, part = sock:receive()
            if line then
                -- ngx.say("received: ", line)

            else
                -- ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
        ngx.exit(0)
    }

--- config
    server_tokens off;
    location = /foo {
        content_by_lua_block { ngx.sleep(0.1) ngx.say("foo") }
        more_clear_headers Date;
    }

--- stream_response
connected: 1
request sent: 57
close: 1 nil
--- no_error_log
[error]
--- error_log
lua clean up the timer for pending ngx.sleep



=== TEST 41: bad request tries to connect
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local test = require "test"
        test.new_sock()
        local sock = test.get_sock()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
        else
            ngx.say("connected")
        end

        local function f()
            local sock = test.get_sock()
            local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
            if not ok then
                ngx.log(ngx.ERR, "failed to connect: ", err)
            end
        end

        ngx.timer.at(0, f)
        ngx.sleep(0.001)
    }

--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function new_sock()
    sock = ngx.socket.tcp()
end

function get_sock()
    return sock
end
--- stream_response
connected

--- error_log eval
qr/runtime error: content_by_lua\(nginx\.conf:\d+\):14: bad request/

--- no_error_log
[alert]



=== TEST 42: bad request tries to receive
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local test = require "test"
        local sock = test.new_sock()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
        else
            ngx.say("connected")
        end
        local function f()
            local test = require "test"
            local sock = test.get_sock()
            sock:receive()
        end
        ngx.timer.at(0, f)
        ngx.sleep(0.001)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function new_sock()
    sock = ngx.socket.tcp()
    return sock
end

function get_sock()
    return sock
end
--- stream_response
connected

--- error_log eval
qr/runtime error: content_by_lua\(nginx\.conf:\d+\):13: bad request/

--- no_error_log
[alert]



=== TEST 43: bad request tries to send
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local test = require "test"
        local sock = test.new_sock()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
        else
            ngx.say("connected")
        end
        local function f()
            local sock = test.get_sock()
            sock:send("a")
        end
        ngx.timer.at(0, f)
        ngx.sleep(0.001)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function new_sock()
    sock = ngx.socket.tcp()
    return sock
end

function get_sock()
    return sock
end
--- stream_response
connected

--- error_log eval
qr/runtime error: content_by_lua\(nginx\.conf:\d+\):12: bad request/

--- no_error_log
[alert]



=== TEST 44: bad request tries to close
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local test = require "test"
        local sock = test.new_sock()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
        else
            ngx.say("connected")
        end
        local function f()
            local sock = test.get_sock()
            sock:close()
        end
        ngx.timer.at(0, f)
        ngx.sleep(0.001)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function new_sock()
    sock = ngx.socket.tcp()
    return sock
end

function get_sock()
    return sock
end
--- stream_response
connected

--- error_log eval
qr/runtime error: content_by_lua\(nginx\.conf:\d+\):12: bad request/

--- no_error_log
[alert]



=== TEST 45: bad request tries to set keepalive
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local test = require "test"
        local sock = test.new_sock()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
        else
            ngx.say("connected")
        end
        local function f()
            local sock = test.get_sock()
            sock:setkeepalive()
        end
        ngx.timer.at(0, f)
        ngx.sleep(0.001)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function new_sock()
    sock = ngx.socket.tcp()
    return sock
end

function get_sock()
    return sock
end
--- stream_response
connected

--- error_log eval
qr/runtime error: content_by_lua\(nginx\.conf:\d+\):12: bad request/

--- no_error_log
[alert]



=== TEST 46: bad request tries to receiveuntil
--- stream_config eval
    "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
--- stream_server_config
    content_by_lua_block {
        local test = require "test"
        local sock = test.new_sock()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
        else
            ngx.say("connected")
        end
        local function f()
            local sock = test.get_sock()
            local it, err = sock:receiveuntil("abc")
            if it then
                it()
            end
        end
        ngx.timer.at(0, f)
        ngx.sleep(0.001)
    }
--- user_files
>>> test.lua
module("test", package.seeall)

local sock

function new_sock()
    sock = ngx.socket.tcp()
    return sock
end

function get_sock()
    return sock
end
--- stream_response
connected

--- error_log eval
qr/runtime error: content_by_lua\(nginx\.conf:\d+\):14: bad request/

--- no_error_log
[alert]



=== TEST 47: cosocket resolving aborted by coroutine yielding failures (require)
--- stream_config
    lua_package_path "$prefix/html/?.lua;;";
    resolver $TEST_NGINX_RESOLVER ipv6=off;

--- stream_server_config
    content_by_lua_block {
        package.loaded.myfoo = nil
        require "myfoo"
    }
--- user_files
>>> myfoo.lua
local sock = ngx.socket.tcp()
local ok, err = sock:connect("agentzh.org")
if not ok then
    ngx.log(ngx.ERR, "failed to connect: ", err)
    return
end

--- stream_response
--- wait: 0.3
--- error_log
resolve name done
runtime error: attempt to yield across C-call boundary
--- no_error_log
[alert]



=== TEST 48: cosocket resolving aborted by coroutine yielding failures (xpcall err)
--- stream_config
    lua_package_path "$prefix/html/?.lua;;";
    resolver $TEST_NGINX_RESOLVER ipv6=off;

--- stream_server_config
    content_by_lua_block {
        local function f()
            return error(1)
        end
        local function err()
            local sock = ngx.socket.tcp()
            local ok, err = sock:connect("agentzh.org")
            if not ok then
                ngx.log(ngx.ERR, "failed to connect: ", err)
                return
            end
        end
        xpcall(f, err)
        ngx.say("ok")
    }
--- stream_response
ok
--- wait: 0.3
--- error_log
resolve name done
--- no_error_log
[error]
[alert]
could not cancel



=== TEST 49: tcp_nodelay on
--- stream_server_config
    tcp_nodelay on;
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;

    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }

--- stream_response
connected: 1
request sent: 57
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Content-Length: 4
received: Connection: close
received: 
received: foo
failed to receive a line: closed []
close: 1 nil
--- error_log
stream lua socket tcp_nodelay
--- no_error_log
[error]



=== TEST 50: tcp_nodelay off
--- stream_server_config
    tcp_nodelay off;

    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT
        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;
    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }

--- stream_response
connected: 1
request sent: 57
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Content-Length: 4
received: Connection: close
received: 
received: foo
failed to receive a line: closed []
close: 1 nil
--- no_error_log
stream lua socket tcp_nodelay
[error]



=== TEST 51: IPv6
--- http_config
    server_tokens off;

    server {
        listen [::1]:$TEST_NGINX_SERVER_PORT;

        location = /foo {
            content_by_lua_block { ngx.say("foo") }
            more_clear_headers Date;
        }
    }
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("[::1]", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- stream_response
connected: 1
request sent: 57
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Content-Length: 4
received: Connection: close
received: 
received: foo
failed to receive a line: closed []
close: 1 nil
--- no_error_log
[error]
--- skip_eval: 3: system("ping6 -c 1 ::1 >/dev/null 2>&1") ne 0



=== TEST 52: kill a thread with a connecting socket
--- stream_server_config
    lua_socket_connect_timeout 1s;
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    resolver_timeout 3s;

    content_by_lua_block {
        local sock

        local thr = ngx.thread.spawn(function ()
            sock = ngx.socket.tcp()
            local ok, err = sock:connect("127.0.0.2", 12345)
            if not ok then
                ngx.say("failed to connect: ", err)
                return
            end

            ngx.say("connected: ", ok)
        end)

        ngx.sleep(0.002)
        ngx.thread.kill(thr)
        ngx.sleep(0.001)

        local ok, err = sock:setkeepalive()
        if not ok then
            ngx.say("failed to setkeepalive: ", err)
        else
            ngx.say("setkeepalive: ", ok)
        end
    }

--- stream_response
failed to setkeepalive: closed
--- error_log
stream lua tcp socket connect timeout: 100
--- timeout: 10



=== TEST 53: reuse cleanup
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT

        for i = 1, 2 do
            local ok, err = sock:connect("127.0.0.1", port)
            if not ok then
                ngx.say("failed to connect: ", err)
                return
            end

            ngx.say("connected: ", ok)

            local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"

            local bytes, err = sock:send(req)
            if not bytes then
                ngx.say("failed to send request: ", err)
                return
            end

            ngx.say("request sent: ", bytes)

            while true do
                local line, err, part = sock:receive()
                if not line then
                    ngx.say("failed to receive a line: ", err, " [", part, "]")
                    break
                end
            end

            ok, err = sock:close()
            ngx.say("close: ", ok, " ", err)
        end
    }

--- config
    server_tokens off;
    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }

--- stream_response
connected: 1
request sent: 57
failed to receive a line: closed []
close: 1 nil
connected: 1
request sent: 57
failed to receive a line: closed []
close: 1 nil
--- error_log
lua stream cleanup reuse



=== TEST 54: reuse cleanup in ngx.timer (fake_request)
--- stream_server_config
    content_by_lua_block {
        local total_send_bytes = 0
        local port = $TEST_NGINX_SERVER_PORT

        local function network()
            local sock = ngx.socket.tcp()

            local ok, err = sock:connect("127.0.0.1", port)
            if not ok then
                ngx.log(ngx.ERR, "failed to connect: ", err)
                return
            end

            local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"

            local bytes, err = sock:send(req)
            if not bytes then
                ngx.log(ngx.ERR, "failed to send request: ", err)
                return
            end

            total_send_bytes = total_send_bytes + bytes

            while true do
                local line, err, part = sock:receive()
                if not line then
                    break
                end
            end

            ok, err = sock:close()
        end

        local done = false

        local function double_network()
            network()
            network()
            done = true
        end

        local ok, err = ngx.timer.at(0, double_network)
        if not ok then
            ngx.say("failed to create timer: ", err)
        end

        i = 1
        while not done do
            local time = 0.005 * i
            if time > 0.1 then
                time = 0.1
            end
            ngx.sleep(time)
            i = i + 1
        end

        collectgarbage("collect")

        ngx.say("total_send_bytes: ", total_send_bytes)
    }

--- config
    server_tokens off;
    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }

--- stream_response
total_send_bytes: 114
--- error_log
lua stream cleanup reuse



=== TEST 55: free cleanup in ngx.timer (without sock:close)
--- stream_server_config
    content_by_lua_block {
        local total_send_bytes = 0

        local function network()
            local sock = ngx.socket.tcp()

            local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
            if not ok then
                ngx.log(ngx.ERR, "failed to connect: ", err)
                return
            end

            local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"

            local bytes, err = sock:send(req)
            if not bytes then
                ngx.log(ngx.ERR, "failed to send request: ", err)
                return
            end

            total_send_bytes = total_send_bytes + bytes

            while true do
                local line, err, part = sock:receive()
                if not line then
                    break
                end
            end
        end

        local done = false

        local function double_network()
            network()
            network()
            done = true
        end

        local ok, err = ngx.timer.at(0, double_network)
        if not ok then
            ngx.say("failed to create timer: ", err)
        end

        i = 1
        while not done do
            local time = 0.005 * i
            if time > 0.1 then
                time = 0.1
            end
            ngx.sleep(time)
            i = i + 1
        end

        collectgarbage("collect")

        ngx.say("total_send_bytes: ", total_send_bytes)
    }

--- config
    server_tokens off;

    location = /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }

--- stream_response
total_send_bytes: 114
--- no_error_log
[error]



=== TEST 56: setkeepalive on socket already shutdown
--- stream_server_config
    lua_socket_connect_timeout 1s;
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    resolver_timeout 3s;

    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("openresty.org", 443)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        local ok, err = sock:shutdown('send')
        if not ok then
            ngx.log(ngx.ERR, 'failed to shutdown socket: ', err)
            return
        end

        local ok, err = sock:setkeepalive()
        if not ok then
            ngx.log(ngx.ERR, "failed to setkeepalive: ", err)
        end
    }

--- stream_response
connected: 1
--- error_log
stream lua shutdown socket write direction
failed to setkeepalive: closed



=== TEST 57: options_table is nil
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT

        local ok, err = sock:connect("127.0.0.1", port, nil)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }
--- stream_response
connected: 1
close: 1 nil
--- no_error_log
[error]



=== TEST 58: resolver send query failing immediately in connect()
this case did not clear coctx->cleanup properly and would lead to memory invalid accesses.

this test case requires the following iptables rule to work properly:

sudo iptables -I OUTPUT 1 -p udp --dport 10086 -j REJECT

--- stream_server_config
    resolver 127.0.0.1:10086 ipv6=off;
    resolver_timeout 10ms;

    content_by_lua_block {
        local sock = ngx.socket.tcp()

        for i = 1, 3 do -- retry
            local ok, err = sock:connect("www.google.com", 80)
            if not ok then
                ngx.say("failed to connect: ", err)
            end
        end

        ngx.say("hello!")
    }
--- stream_response_body_like
failed to connect: www.google.com could not be resolved(?: \(\d+: Operation timed out\))?
failed to connect: www.google.com could not be resolved(?: \(\d+: Operation timed out\))?
failed to connect: www.google.com could not be resolved(?: \(\d+: Operation timed out\))?
hello!
--- error_log eval
qr{\[alert\] .*? send\(\) failed \(\d+: Operation not permitted\) while resolving}



=== TEST 59: the upper bound of port range should be 2^16 - 1
--- stream_server_config
    content_by_lua_block {
        local sock, err = ngx.socket.connect("127.0.0.1", 65536)
        if not sock then
            ngx.say("failed to connect: ", err)
        end
    }

--- stream_response
failed to connect: bad port number: 65536
--- no_error_log
[error]



=== TEST 60: send boolean and nil
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local port = $TEST_NGINX_SERVER_PORT

        local ok, err = sock:connect("127.0.0.1", port)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        local function send(data)
            local bytes, err = sock:send(data)
            if not bytes then
                ngx.say("failed to send request: ", err)
                return
            end
        end

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\nTest: "
        send(req)
        send(true)
        send(false)
        send(nil)
        send("\r\n\r\n")

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)
            else
                break
            end
        end

        ok, err = sock:close()
    }
--- config
    location /foo {
        server_tokens off;
        more_clear_headers Date;
        echo $http_test;
    }
--- stream_response
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Connection: close
received: 
received: truefalsenil
--- no_error_log
[error]



=== TEST 61: TCP socket GC'ed in preread phase without Lua content phase
--- stream_server_config
    lua_socket_connect_timeout 1s;
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    resolver_timeout 3s;

    preread_by_lua_block {
        do
            local sock = ngx.socket.tcp()
            local ok, err = sock:connect("openresty.org", 443)
            if not ok then
                ngx.say("failed to connect: ", err)
                return
            end

            ngx.say("connected: ", ok)
        end

        ngx.timer.at(0, function()
            collectgarbage()
            ngx.log(ngx.WARN, "GC cycle done")
        end)
    }

    return 1;

--- stream_response chomp
connected: 1
1
--- no_error_log
[error]
--- error_log
cleanup lua tcp socket request
GC cycle done



=== TEST 62: receiveany method in cosocket
--- config
    location = /foo {
        server_tokens off;

        content_by_lua_block {
            local resp = {
                '1',
                '22',
                'hello world',
            }

            local length = 0
            for _, v in ipairs(resp) do
                length = length + #v
            end

            -- flush http header
            ngx.header['Content-Length'] = length
            ngx.flush(true)
            ngx.sleep(0.01)

            -- send http body
            for _, v in ipairs(resp) do
                ngx.print(v)
                ngx.flush(true)
                ngx.sleep(0.01)
            end
        }
    }
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        sock:settimeout(500)

        assert(sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT))
        local req = {
            'GET /foo HTTP/1.0\r\n',
            'Host: localhost\r\n',
            'Connection: close\r\n\r\n',
        }
        local ok, err = sock:send(req)
        if not ok then
            ngx.say("send request failed: ", err)
            return
        end

        -- skip http header
        while true do
            local data, err, _ = sock:receive('*l')
            if err then
                ngx.say('unexpected error occurs when receiving http head: ', err)
                return
            end

            if #data == 0 then -- read last line of head
                break
            end
        end

        -- receive http body
        while true do
            local data, err = sock:receiveany(1024)
            if err then
                if err ~= 'closed' then
                    ngx.say('unexpected err: ', err)
                end
                break
            end
            ngx.say(data)
        end

        sock:close()
    }
--- stream_response
1
22
hello world
--- no_error_log
[error]
--- error_log
lua tcp socket read any



=== TEST 63: receiveany send data after read side closed
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        sock:settimeout(500)
        assert(sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1))

        while true do
            local data, err = sock:receiveany(1024)
            if err then
                if err ~= 'closed' then
                    ngx.say('unexpected err: ', err)
                    break
                end

                local data = "send data after read side closed"
                local bytes, err = sock:send(data)
                if not bytes then
                    ngx.say(err)
                end

                break
            end
            ngx.say(data)
        end

        sock:close()
    }
--- tcp_listen: $TEST_NGINX_RAND_PORT_1
--- tcp_shutdown: 1
--- tcp_query eval: "send data after read side closed"
--- tcp_query_len: 32
--- stream_response
--- no_error_log
[error]



=== TEST 64: receiveany with limited, max <= 0
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        sock:settimeout(500)
        assert(sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT))

        local function receiveany_say_err(...)
            local ok, err = pcall(sock.receiveany, sock, ...)
            if not ok then
                ngx.say(err)
            end
        end


        receiveany_say_err(0)
        receiveany_say_err(-1)
        receiveany_say_err()
        receiveany_say_err(nil)
    }
--- stream_response
bad argument #2 to '?' (bad max argument)
bad argument #2 to '?' (bad max argument)
expecting 2 arguments (including the object), but got 1
bad argument #2 to '?' (bad max argument)
--- no_error_log
[error]



=== TEST 65: receiveany with limited, max is larger than data
--- config
    location = /foo {
        server_tokens off;

        content_by_lua_block {
            local resp = 'hello world'
            local length = #resp

            ngx.header['Content-Length'] = length
            ngx.flush(true)
            ngx.sleep(0.01)

            ngx.print(resp)
        }
    }
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        sock:settimeout(500)

        assert(sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT))
        local req = {
            'GET /foo HTTP/1.0\r\n',
            'Host: localhost\r\n',
            'Connection: close\r\n\r\n',
        }
        local ok, err = sock:send(req)
        if not ok then
            ngx.say("send request failed: ", err)
            return
        end

        while true do
            local data, err, _ = sock:receive('*l')
            if err then
                ngx.say('unexpected error occurs when receiving http head: ', err)
                return
            end

            if #data == 0 then -- read last line of head
                break
            end
        end

        local data, err = sock:receiveany(128)
        if err then
            if err ~= 'closed' then
                ngx.say('unexpected err: ', err)
            end
        else
            ngx.say(data)
        end

        sock:close()
    }
--- stream_response
hello world
--- no_error_log
[error]
--- error_log
lua tcp socket calling receiveany() method to read at most 128 bytes



=== TEST 66: receiveany with limited, max is smaller than data
--- config
    location = /foo {
        server_tokens off;

        content_by_lua_block {
            local resp = 'hello world'
            local length = #resp

            ngx.header['Content-Length'] = length
            ngx.flush(true)
            ngx.sleep(0.01)

            ngx.print(resp)
        }
    }
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        sock:settimeout(500)

        assert(sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT))
        local req = {
            'GET /foo HTTP/1.0\r\n',
            'Host: localhost\r\n',
            'Connection: close\r\n\r\n',
        }
        local ok, err = sock:send(req)
        if not ok then
            ngx.say("send request failed: ", err)
            return
        end

        while true do
            local data, err, _ = sock:receive('*l')
            if err then
                ngx.say('unexpected error occurs when receiving http head: ', err)
                return
            end

            if #data == 0 then -- read last line of head
                break
            end
        end

        while true do
            local data, err = sock:receiveany(7)
            if err then
                if err ~= 'closed' then
                    ngx.say('unexpected err: ', err)
                end
                break

            else
                ngx.say(data)
            end
        end

        sock:close()
    }
--- stream_response
hello w
orld
--- no_error_log
[error]
--- error_log
lua tcp socket calling receiveany() method to read at most 7 bytes



=== TEST 67: shutdown on a not connected socket correctly throws error
--- stream_server_config
    lua_socket_connect_timeout 1s;
    resolver $TEST_NGINX_RESOLVER ipv6=off;
    resolver_timeout 3s;

    content_by_lua_block {
        local sock = ngx.socket.tcp()

        local ok, err = sock:shutdown('send')
        ngx.log(ngx.ERR, 'shutdown on a not connected socket: ', err)

    }

--- error_log
shutdown on a not connected socket: closed



=== TEST 68: setkeepalive with TLSv1.3
--- skip_openssl: 3: < 1.1.1
--- stream_config
    server {
        listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
        ssl_certificate     ../../cert/test_ecdsa.crt;
        ssl_certificate_key ../../cert/test_ecdsa.key;
        ssl_protocols TLSv1.3;
        content_by_lua_block {
            local sock = assert(ngx.req.socket(true))
            local data, err
            while true do
                data, err = sock:receive()
                if not data then
                    break
                end
                assert(data == "hello")
            end
        }
    }
--- stream_server_config
    lua_ssl_protocols TLSv1.3;
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        sock:settimeout(2000)
        local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock")
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end
        ngx.say("connected: ", ok)
        local ok, err = sock:sslhandshake(false, nil, false)
        if not ok then
            ngx.say("failed to sslhandshake: ", err)
            return
        end
        local ok, err = sock:send("hello\n")
        if not ok then
            ngx.say("failed to send: ", err)
            return
        end
        -- sleep a while to make sure the NewSessionTicket message has arrived
        ngx.sleep(1)
        local ok, err = sock:setkeepalive()
        if not ok then
            ngx.say("failed to setkeepalive: ", err)
        else
            ngx.say("setkeepalive: ", ok)
        end
    }
--- stream_response
connected: 1
setkeepalive: 1
--- no_error_log
[error]



=== TEST 69: getfd()
--- stream_server_config
    content_by_lua_block {
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT)
        if not ok then
            ngx.say("failed to connect: ", err)
            return
        end

        ngx.say("connected: ", ok)
        ngx.say("fd: ", sock:getfd())

        local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
        -- req = "OK"

        local bytes, err = sock:send(req)
        if not bytes then
            ngx.say("failed to send request: ", err)
            return
        end

        ngx.say("request sent: ", bytes)

        while true do
            local line, err, part = sock:receive()
            if line then
                ngx.say("received: ", line)

            else
                ngx.say("failed to receive a line: ", err, " [", part, "]")
                break
            end
        end

        ok, err = sock:close()
        ngx.say("close: ", ok, " ", err)
    }

--- config
    server_tokens off;

    location /foo {
        content_by_lua_block { ngx.say("foo") }
        more_clear_headers Date;
    }

--- stream_response eval
qr{connected: 1
fd: \d+
request sent: 57
received: HTTP/1.1 200 OK
received: Server: nginx
received: Content-Type: text/plain
received: Content-Length: 4
received: Connection: close
received: 
received: foo
failed to receive a line: closed \[\]
close: 1 nil
}
--- no_error_log
[error]
