| # vim:set ft= ts=4 sw=4 et fdm=marker: |
| use lib 'lib'; |
| use Test::Nginx::Socket::Lua; |
| |
| #worker_connections(1014); |
| #master_on(); |
| #workers(2); |
| #log_level('warn'); |
| |
| repeat_each(2); |
| #repeat_each(1); |
| |
| plan tests => repeat_each() * (blocks() * 2 + 18); |
| |
| #no_diff(); |
| #no_long_string(); |
| run_tests(); |
| |
| __DATA__ |
| |
| === TEST 1: basic print |
| --- config |
| location /lua { |
| # NOTE: the newline escape sequence must be double-escaped, as nginx config |
| # parser will unescape first! |
| content_by_lua ' |
| local ok, err = ngx.print("Hello, Lua!\\n") |
| if not ok then |
| ngx.log(ngx.ERR, "print failed: ", err) |
| end |
| '; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| Hello, Lua! |
| --- no_error_log |
| [error] |
| |
| |
| |
| === TEST 2: basic say |
| --- config |
| location /say { |
| # NOTE: the newline escape sequence must be double-escaped, as nginx config |
| # parser will unescape first! |
| content_by_lua ' |
| local ok, err = ngx.say("Hello, Lua!") |
| if not ok then |
| ngx.log(ngx.ERR, "say failed: ", err) |
| return |
| end |
| local ok, err = ngx.say("Yay! ", 123) |
| if not ok then |
| ngx.log(ngx.ERR, "say failed: ", err) |
| return |
| end |
| '; |
| } |
| --- request |
| GET /say |
| --- response_body |
| Hello, Lua! |
| Yay! 123 |
| --- no_error_log |
| [error] |
| |
| |
| |
| === TEST 3: no ngx.echo |
| --- config |
| location /lua { |
| content_by_lua 'ngx.echo("Hello, Lua!\\n")'; |
| } |
| --- request |
| GET /lua |
| --- response_body_like: 500 Internal Server Error |
| --- error_code: 500 |
| |
| |
| |
| === TEST 4: variable |
| --- config |
| location /lua { |
| # NOTE: the newline escape sequence must be double-escaped, as nginx config |
| # parser will unescape first! |
| content_by_lua 'v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\\n")'; |
| } |
| --- request |
| GET /lua?a=1&b=2 |
| --- response_body |
| request_uri: /lua?a=1&b=2 |
| |
| |
| |
| === TEST 5: variable (file) |
| --- config |
| location /lua { |
| content_by_lua_file html/test.lua; |
| } |
| --- user_files |
| >>> test.lua |
| v = ngx.var["request_uri"] |
| ngx.print("request_uri: ", v, "\n") |
| --- request |
| GET /lua?a=1&b=2 |
| --- response_body |
| request_uri: /lua?a=1&b=2 |
| |
| |
| |
| === TEST 6: calc expression |
| --- config |
| location /lua { |
| content_by_lua_file html/calc.lua; |
| } |
| --- user_files |
| >>> calc.lua |
| local function uri_unescape(uri) |
| local function convert(hex) |
| return string.char(tonumber("0x"..hex)) |
| end |
| local s = string.gsub(uri, "%%([0-9a-fA-F][0-9a-fA-F])", convert) |
| return s |
| end |
| |
| local function eval_exp(str) |
| return loadstring("return "..str)() |
| end |
| |
| local exp_str = ngx.var["arg_exp"] |
| -- print("exp: '", exp_str, "'\n") |
| local status, res |
| status, res = pcall(uri_unescape, exp_str) |
| if not status then |
| ngx.print("error: ", res, "\n") |
| return |
| end |
| status, res = pcall(eval_exp, res) |
| if status then |
| ngx.print("result: ", res, "\n") |
| else |
| ngx.print("error: ", res, "\n") |
| end |
| --- request |
| GET /lua?exp=1%2B2*math.sin(3)%2Fmath.exp(4)-math.sqrt(2) |
| --- response_body |
| result: -0.4090441561579 |
| |
| |
| |
| === TEST 7: read $arg_xxx |
| --- config |
| location = /lua { |
| content_by_lua 'who = ngx.var.arg_who |
| ngx.print("Hello, ", who, "!")'; |
| } |
| --- request |
| GET /lua?who=agentzh |
| --- response_body chomp |
| Hello, agentzh! |
| |
| |
| |
| === TEST 8: capture location |
| --- config |
| location /other { |
| echo "hello, world"; |
| } |
| |
| location /lua { |
| content_by_lua 'res = ngx.location.capture("/other"); ngx.print("status=", res.status, " "); ngx.print("body=", res.body)'; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| status=200 body=hello, world |
| |
| |
| |
| ei= TEST 9: capture non-existed location |
| --- config |
| location /lua { |
| content_by_lua 'res = ngx.location.capture("/other"); ngx.print("status=", res.status)'; |
| } |
| --- request |
| GET /lua |
| --- response_body: status=404 |
| |
| |
| |
| === TEST 9: invalid capture location (not as expected...) |
| --- config |
| location /lua { |
| content_by_lua 'res = ngx.location.capture("*(#*"); ngx.say("res=", res.status)'; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| res=404 |
| |
| |
| |
| === TEST 10: nil is "nil" |
| --- config |
| location /lua { |
| content_by_lua 'ngx.say(nil)'; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| nil |
| |
| |
| |
| === TEST 11: write boolean |
| --- config |
| location /lua { |
| content_by_lua 'ngx.say(true, " ", false)'; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| true false |
| |
| |
| |
| === TEST 12: bad argument type to ngx.location.capture |
| --- config |
| location /lua { |
| content_by_lua 'ngx.location.capture(nil)'; |
| } |
| --- request |
| GET /lua |
| --- response_body_like: 500 Internal Server Error |
| --- error_code: 500 |
| |
| |
| |
| === TEST 13: capture location (default 0); |
| --- config |
| location /recur { |
| content_by_lua ' |
| local num = tonumber(ngx.var.arg_num) or 0; |
| ngx.print("num is: ", num, "\\n"); |
| |
| if (num > 0) then |
| res = ngx.location.capture("/recur?num="..tostring(num - 1)); |
| ngx.print("status=", res.status, " "); |
| ngx.print("body=", res.body, "\\n"); |
| else |
| ngx.print("end\\n"); |
| end |
| '; |
| } |
| --- request |
| GET /recur |
| --- response_body |
| num is: 0 |
| end |
| |
| |
| |
| === TEST 14: capture location |
| --- config |
| location /recur { |
| content_by_lua ' |
| local num = tonumber(ngx.var.arg_num) or 0; |
| ngx.print("num is: ", num, "\\n"); |
| |
| if (num > 0) then |
| res = ngx.location.capture("/recur?num="..tostring(num - 1)); |
| ngx.print("status=", res.status, " "); |
| ngx.print("body=", res.body); |
| else |
| ngx.print("end\\n"); |
| end |
| '; |
| } |
| --- request |
| GET /recur?num=3 |
| --- response_body |
| num is: 3 |
| status=200 body=num is: 2 |
| status=200 body=num is: 1 |
| status=200 body=num is: 0 |
| end |
| |
| |
| |
| === TEST 15: setting nginx variables from within Lua |
| --- config |
| location /set { |
| set $a ""; |
| content_by_lua 'ngx.var.a = 32; ngx.say(ngx.var.a)'; |
| add_header Foo $a; |
| } |
| --- request |
| GET /set |
| --- response_headers |
| Foo: 32 |
| --- response_body |
| 32 |
| |
| |
| |
| === TEST 16: nginx quote sql string 1 |
| --- config |
| location /set { |
| set $a 'hello\n\r\'"\\'; |
| content_by_lua 'ngx.say(ngx.quote_sql_str(ngx.var.a))'; |
| } |
| --- request |
| GET /set |
| --- response_body |
| 'hello\n\r\'\"\\' |
| |
| |
| |
| === TEST 17: nginx quote sql string 2 |
| --- config |
| location /set { |
| set $a "hello\n\r'\"\\"; |
| content_by_lua 'ngx.say(ngx.quote_sql_str(ngx.var.a))'; |
| } |
| --- request |
| GET /set |
| --- response_body |
| 'hello\n\r\'\"\\' |
| |
| |
| |
| === TEST 18: use dollar |
| --- config |
| location /set { |
| content_by_lua ' |
| local s = "hello 112"; |
| ngx.say(string.find(s, "%d+$"))'; |
| } |
| --- request |
| GET /set |
| --- response_body |
| 79 |
| |
| |
| |
| === TEST 19: subrequests do not share variables of main requests by default |
| --- config |
| location /sub { |
| echo $a; |
| } |
| location /parent { |
| set $a 12; |
| content_by_lua 'res = ngx.location.capture("/sub"); ngx.print(res.body)'; |
| } |
| --- request |
| GET /parent |
| --- response_body eval: "\n" |
| |
| |
| |
| === TEST 20: subrequests can share variables of main requests |
| --- config |
| location /sub { |
| echo $a; |
| } |
| location /parent { |
| set $a 12; |
| content_by_lua ' |
| res = ngx.location.capture( |
| "/sub", |
| { share_all_vars = true } |
| ); |
| ngx.print(res.body) |
| '; |
| } |
| --- request |
| GET /parent |
| --- response_body |
| 12 |
| |
| |
| |
| === TEST 21: main requests use subrequests' variables |
| --- config |
| location /sub { |
| set $a 12; |
| } |
| location /parent { |
| content_by_lua ' |
| res = ngx.location.capture("/sub", { share_all_vars = true }); |
| ngx.say(ngx.var.a) |
| '; |
| } |
| --- request |
| GET /parent |
| --- response_body |
| 12 |
| |
| |
| |
| === TEST 22: main requests do NOT use subrequests' variables |
| --- config |
| location /sub { |
| set $a 12; |
| } |
| location /parent { |
| content_by_lua ' |
| res = ngx.location.capture("/sub", { share_all_vars = false }); |
| ngx.say(ngx.var.a) |
| '; |
| } |
| --- request |
| GET /parent |
| --- response_body_like eval: "\n" |
| |
| |
| |
| === TEST 23: capture location headers |
| --- config |
| location /other { |
| default_type 'foo/bar'; |
| echo "hello, world"; |
| } |
| |
| location /lua { |
| content_by_lua ' |
| res = ngx.location.capture("/other"); |
| ngx.say("type: ", res.header["Content-Type"]); |
| '; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| type: foo/bar |
| |
| |
| |
| === TEST 24: capture location multi-value headers |
| --- config |
| location /other { |
| #echo "hello, world"; |
| content_by_lua ' |
| ngx.header["Set-Cookie"] = {"a", "hello, world", "foo"} |
| local ok, err = ngx.eof() |
| if not ok then |
| ngx.log(ngx.ERR, "eof failed: ", err) |
| return |
| end |
| '; |
| } |
| |
| location /lua { |
| content_by_lua ' |
| res = ngx.location.capture("/other"); |
| ngx.say("type: ", type(res.header["Set-Cookie"])); |
| ngx.say("len: ", #res.header["Set-Cookie"]); |
| ngx.say("value: ", table.concat(res.header["Set-Cookie"], "|")) |
| '; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| type: table |
| len: 3 |
| value: a|hello, world|foo |
| --- no_error_log |
| [error] |
| |
| |
| |
| === TEST 25: capture location headers |
| --- config |
| location /other { |
| default_type 'foo/bar'; |
| content_by_lua ' |
| ngx.header.Bar = "Bah"; |
| '; |
| } |
| |
| location /lua { |
| content_by_lua ' |
| res = ngx.location.capture("/other"); |
| ngx.say("type: ", res.header["Content-Type"]); |
| ngx.say("Bar: ", res.header["Bar"]); |
| '; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| type: foo/bar |
| Bar: Bah |
| |
| |
| |
| === TEST 26: capture location headers |
| --- config |
| location /other { |
| default_type 'foo/bar'; |
| content_by_lua ' |
| ngx.header.Bar = "Bah"; |
| ngx.header.Bar = nil; |
| '; |
| } |
| |
| location /lua { |
| content_by_lua ' |
| res = ngx.location.capture("/other"); |
| ngx.say("type: ", res.header["Content-Type"]); |
| ngx.say("Bar: ", res.header["Bar"] or "nil"); |
| '; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| type: foo/bar |
| Bar: nil |
| |
| |
| |
| === TEST 27: HTTP 1.0 response |
| --- config |
| location /lua { |
| content_by_lua ' |
| data = "hello, world" |
| -- ngx.header["Content-Length"] = #data |
| -- ngx.header.content_length = #data |
| ngx.print(data) |
| '; |
| } |
| location /main { |
| proxy_pass http://127.0.0.1:$server_port/lua; |
| } |
| --- request |
| GET /main |
| --- response_headers |
| Content-Length: 12 |
| --- response_body chop |
| hello, world |
| |
| |
| |
| === TEST 28: multiple eof |
| --- config |
| location /lua { |
| content_by_lua ' |
| ngx.say("Hi") |
| |
| local ok, err = ngx.eof() |
| if not ok then |
| ngx.log(ngx.WARN, "eof failed: ", err) |
| return |
| end |
| |
| ok, err = ngx.eof() |
| if not ok then |
| ngx.log(ngx.WARN, "eof failed: ", err) |
| return |
| end |
| |
| '; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| Hi |
| --- no_error_log |
| [error] |
| --- error_log |
| eof failed: seen eof |
| |
| |
| |
| === TEST 29: nginx vars in script path |
| --- config |
| location ~ ^/lua/(.+)$ { |
| content_by_lua_file html/$1.lua; |
| } |
| --- user_files |
| >>> calc.lua |
| local a,b = ngx.var.arg_a, ngx.var.arg_b |
| ngx.say(a+b) |
| --- request |
| GET /lua/calc?a=19&b=81 |
| --- response_body |
| 100 |
| |
| |
| |
| === TEST 30: nginx vars in script path |
| --- config |
| location ~ ^/lua/(.+)$ { |
| content_by_lua_file html/$1.lua; |
| } |
| location /main { |
| echo_location /lua/sum a=3&b=2; |
| echo_location /lua/diff a=3&b=2; |
| } |
| --- user_files |
| >>> sum.lua |
| local a,b = ngx.var.arg_a, ngx.var.arg_b |
| ngx.say(a+b) |
| >>> diff.lua |
| local a,b = ngx.var.arg_a, ngx.var.arg_b |
| ngx.say(a-b) |
| --- request |
| GET /main |
| --- response_body |
| 5 |
| 1 |
| |
| |
| |
| === TEST 31: basic print (HEAD + HTTP 1.1) |
| --- config |
| location /lua { |
| # NOTE: the newline escape sequence must be double-escaped, as nginx config |
| # parser will unescape first! |
| content_by_lua 'ngx.print("Hello, Lua!\\n")'; |
| } |
| --- request |
| HEAD /lua |
| --- response_body |
| |
| |
| |
| === TEST 32: basic print (HEAD + HTTP 1.0) |
| --- config |
| location /lua { |
| # NOTE: the newline escape sequence must be double-escaped, as nginx config |
| # parser will unescape first! |
| content_by_lua ' |
| ngx.print("Hello, Lua!\\n") |
| '; |
| } |
| --- request |
| HEAD /lua HTTP/1.0 |
| --- response_headers |
| !Content-Length |
| --- response_body |
| |
| |
| |
| === TEST 33: headers_sent & HEAD |
| --- config |
| location /lua { |
| content_by_lua ' |
| ngx.say(ngx.headers_sent) |
| local ok, err = ngx.flush() |
| if not ok then |
| ngx.log(ngx.WARN, "failed to flush: ", err) |
| return |
| end |
| ngx.say(ngx.headers_sent) |
| '; |
| } |
| --- request |
| HEAD /lua |
| --- response_body |
| --- no_error_log |
| [error] |
| --- error_log |
| failed to flush: header only |
| |
| |
| |
| === TEST 34: HEAD & ngx.say |
| --- config |
| location /lua { |
| content_by_lua ' |
| ngx.send_headers() |
| local ok, err = ngx.say(ngx.headers_sent) |
| if not ok then |
| ngx.log(ngx.WARN, "failed to say: ", err) |
| return |
| end |
| '; |
| } |
| --- request |
| HEAD /lua |
| --- response_body |
| --- no_error_log |
| [error] |
| --- error_log |
| failed to say: header only |
| |
| |
| |
| === TEST 35: ngx.eof before ngx.say |
| --- config |
| location /lua { |
| content_by_lua ' |
| local ok, err = ngx.eof() |
| if not ok then |
| ngx.log(ngx.ERR, "eof failed: ", err) |
| return |
| end |
| |
| ok, err = ngx.say(ngx.headers_sent) |
| if not ok then |
| ngx.log(ngx.WARN, "failed to say: ", err) |
| return |
| end |
| '; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| --- no_error_log |
| [error] |
| --- error_log |
| failed to say: seen eof |
| |
| |
| |
| === TEST 36: headers_sent + GET |
| --- config |
| location /lua { |
| content_by_lua ' |
| -- print("headers sent: ", ngx.headers_sent) |
| ngx.say(ngx.headers_sent) |
| ngx.say(ngx.headers_sent) |
| -- ngx.flush() |
| ngx.say(ngx.headers_sent) |
| '; |
| } |
| --- request |
| GET /lua |
| --- response_body |
| false |
| true |
| true |
| |
| |
| |
| === TEST 37: HTTP 1.0 response with Content-Length |
| --- config |
| location /lua { |
| content_by_lua ' |
| data = "hello,\\nworld\\n" |
| ngx.header["Content-Length"] = #data |
| ngx.say("hello,") |
| ngx.flush() |
| -- ngx.location.capture("/sleep") |
| ngx.say("world") |
| '; |
| } |
| location /sleep { |
| echo_sleep 2; |
| } |
| location /main { |
| proxy_pass http://127.0.0.1:$server_port/lua; |
| } |
| --- request |
| GET /main |
| --- response_headers |
| Content-Length: 13 |
| --- response_body |
| hello, |
| world |
| --- timeout: 5 |
| |
| |
| |
| === TEST 38: ngx.print table arguments (github issue #54) |
| --- config |
| location /t { |
| content_by_lua 'ngx.print({10, {0, 5}, 15}, 32)'; |
| } |
| --- request |
| GET /t |
| --- response_body chop |
| 10051532 |
| |
| |
| |
| === TEST 39: ngx.say table arguments (github issue #54) |
| --- config |
| location /t { |
| content_by_lua 'ngx.say({10, {0, "5"}, 15}, 32)'; |
| } |
| --- request |
| GET /t |
| --- response_body |
| 10051532 |
| |
| |
| |
| === TEST 40: Lua file does not exist |
| --- config |
| location /lua { |
| content_by_lua_file html/test2.lua; |
| } |
| --- user_files |
| >>> test.lua |
| v = ngx.var["request_uri"] |
| ngx.print("request_uri: ", v, "\n") |
| --- request |
| GET /lua?a=1&b=2 |
| --- response_body_like: 500 Internal Server Error |
| --- error_code: 500 |
| --- error_log eval |
| qr/failed to load external Lua file: cannot open .*? No such file or directory/ |
| |
| |
| |
| === TEST 41: .lua file with shebang |
| --- config |
| location /lua { |
| content_by_lua_file html/test.lua; |
| } |
| --- user_files |
| >>> test.lua |
| #!/bin/lua |
| |
| ngx.say("line ", debug.getinfo(1).currentline) |
| --- request |
| GET /lua?a=1&b=2 |
| --- response_body |
| line 3 |
| --- no_error_log |
| [error] |
| |
| |
| |
| === TEST 42: syntax error in inlined Lua code |
| --- config |
| location /lua { |
| content_by_lua 'for end'; |
| } |
| --- request |
| GET /lua |
| --- response_body_like: 500 Internal Server Error |
| --- error_code: 500 |
| --- error_log eval |
| qr/failed to load inlined Lua code: / |
| |