├── .ci └── setup_openresty.sh ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── lib └── resty │ └── socket.lua ├── lua-resty-socket-1.0.0-1.rockspec ├── spec └── socket_spec.lua └── t ├── 01-sanity.t ├── 02-contexts.t ├── 03-compat.t ├── 04-memoized-closures.t ├── 05-setting.t └── reindex /.ci/setup_openresty.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | mkdir -p $OPENRESTY_DIR 4 | 5 | if [ ! "$(ls -A $OPENRESTY_DIR)" ]; then 6 | OPENRESTY_BASE=openresty-$OPENRESTY 7 | 8 | curl https://openresty.org/download/$OPENRESTY_BASE.tar.gz | tar xz 9 | pushd $OPENRESTY_BASE 10 | ./configure \ 11 | --prefix=$OPENRESTY_DIR \ 12 | --with-ipv6 \ 13 | --without-http_coolkit_module \ 14 | --without-lua_resty_dns \ 15 | --without-lua_resty_lrucache \ 16 | --without-lua_resty_upstream_healthcheck \ 17 | --without-lua_resty_websocket \ 18 | --without-lua_resty_upload \ 19 | --without-lua_resty_string \ 20 | --without-lua_resty_mysql \ 21 | --without-lua_resty_redis \ 22 | --without-http_redis_module \ 23 | --without-http_redis2_module \ 24 | --without-lua_redis_parser 25 | make 26 | make install 27 | popd 28 | fi 29 | 30 | git clone git://github.com/travis-perl/helpers travis-perl-helpers 31 | pushd travis-perl-helpers 32 | source ./init 33 | popd 34 | cpan-install Test::Nginx::Socket 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | t/servroot 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | perl: 5.18 3 | sudo: false 4 | notifications: 5 | email: false 6 | addons: 7 | apt: 8 | packages: 9 | - libreadline-dev 10 | - libncurses5-dev 11 | - libpcre3-dev 12 | - libssl-dev 13 | - build-essential 14 | env: 15 | global: 16 | - OPENRESTY=1.9.15.1 17 | - OPENRESTY_DIR=$HOME/openresty 18 | before_install: 19 | - bash .ci/setup_openresty.sh 20 | - pip install --user hererocks 21 | - hererocks lua_install -r^ -l 5.1 22 | - export PATH=$PATH:$PWD/lua_install/bin:$OPENRESTY_DIR/nginx/sbin 23 | - eval `luarocks path` 24 | - luarocks install luasocket 25 | - luarocks install luasec 26 | - luarocks install busted 27 | - luarocks install luacheck 28 | install: 29 | - luarocks make 30 | script: make lint && make test 31 | cache: 32 | cpan: true 33 | apt: true 34 | pip: true 35 | directories: 36 | - $OPENRESTY_DIR 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2019 Thibault Charbonnier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test lint 2 | 3 | test: 4 | @busted -v -o gtest spec 5 | @t/reindex t/*.t 6 | @prove 7 | 8 | lint: 9 | @luacheck lib --std ngx_lua --no-redefined 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-resty-socket 2 | 3 | ![Module Version][badge-version-image] 4 | [![Build Status][badge-travis-image]][badge-travis-url] 5 | 6 | cosocket/LuaSocket automatic compatibility module for lua-resty modules wanting 7 | to be compatible with plain Lua or OpenResty's `init` context. 8 | 9 | The use case for this library is: you are developing a lua-resty module relying 10 | on cosockets, but you want it to also be usable in OpenResty's `init` context 11 | or even in plain Lua. This module aims at always providing your library with 12 | sockets that will be compatible in the current context, saving you time and 13 | effort, and extending LuaSocket's API to match that of cosockets, allowing you 14 | to always write your code as if you were in a cosocket-compatible OpenResty 15 | context. 16 | 17 | ### Table of Contents 18 | 19 | * [Features](#features) 20 | * [Motivation](#motivation) 21 | * [Libraries using it](#libraries-using-it) 22 | * [Important note](#important-note) 23 | * [Usage](#usage) 24 | * [Requirements](#requirements) 25 | * [Installation](#installation) 26 | * [License](#license) 27 | 28 | ### Features 29 | 30 | * Allows your lua-resty modules to automatically use cosockets/LuaSocket 31 | * Provides `sslhandshake` proxy when using LuaSocket, with a dependency on 32 | LuaSec 33 | * Does not get blocked to using LuaSocket in further contexts if loaded in the 34 | ngx_lua `init` (easy mistake to make) 35 | * Memoizes underlying socket methods for performance 36 | * Outputs a warning log for your users when spawning a socket using LuaSocket 37 | while in OpenResty 38 | 39 | [Back to TOC](#table-of-contents) 40 | 41 | ### Motivation 42 | 43 | The aim of this module is to provide an automatic fallback to LuaSocket when 44 | [ngx_lua]'s cosockets are not available. That is: 45 | - When not used in ngx_lua 46 | - In ngx_lua contexts where cosockets are not supported (`init`, `init_worker`, 47 | etc...) 48 | 49 | When falling back to LuaSocket, it provides you with shims for cosocket-only 50 | functions such as `getreusedtimes`, `setkeepalive` etc... 51 | 52 | It comes handy when one is developing a module/library that aims at being 53 | either compatible with both ngx_lua **and** plain Lua, **or** in ngx_lua 54 | contexts such as `init`. 55 | 56 | [Back to TOC](#table-of-contents) 57 | 58 | ### Libraries using it 59 | 60 | Here are some concrete examples uses of this module. You can see how we only 61 | write code as if we were constantly in an cosocket-compatible OpenResty 62 | context, which greatly simplifies our work and provides out of the box plain 63 | Lua compatibility. 64 | 65 | * [lua-cassandra](https://github.com/thibaultcha/lua-cassandra): see how the 66 | [cassandra](https://github.com/thibaultcha/lua-cassandra/blob/master/lib/cassandra/init.lua) 67 | module is compatible in both OpenResty and plain Lua with no efforts or 68 | special code paths distinguishing cosockets and LuaSocket. 69 | 70 | [Back to TOC](#table-of-contents) 71 | 72 | ### Important note 73 | 74 | The use of LuaSocket inside ngx_lua is **very strongly** discouraged due to its 75 | blocking nature. However, it is fine to use it in the `init` context where 76 | blocking is not considered harmful. 77 | 78 | In the future, only the `init` phase will allow falling back to LuaSocket. 79 | 80 | It currently only support TCP sockets. 81 | 82 | [Back to TOC](#table-of-contents) 83 | 84 | ## Usage 85 | 86 | All of the available functions follow the same prototype as the cosocket API, 87 | allowing this example to run in any ngx_lua context or outside ngx_lua 88 | altogether: 89 | ```lua 90 | local socket = require 'resty.socket' 91 | local sock = socket.tcp() 92 | 93 | getmetatable(sock) == socket.luasocket_mt ---> true/false depending on underlying socket 94 | 95 | sock:settimeout(1000) ---> 1000ms translated to 1s if LuaSocket 96 | 97 | sock:getreusedtimes(...) ---> 0 if LuaSocket 98 | 99 | sock:setkeepalive(...) ---> calls close() if LuaSocket 100 | 101 | sock:sslhandshake(...) ---> LuaSec dependency if LuaSocket 102 | ``` 103 | 104 | As such, one can write a module relying on TCP sockets such as: 105 | ```lua 106 | local socket = require 'resty.socket' 107 | 108 | local _M = {} 109 | 110 | function _M.new() 111 | local sock = socket.tcp() -- similar to ngx.socket.tcp() 112 | 113 | return setmetatable({ 114 | sock = sock 115 | }, {__index = _M}) 116 | end 117 | 118 | function _M:connect(host, port) 119 | local ok, err = self.sock:connect(host, port) 120 | if not ok then 121 | return nil, err 122 | end 123 | 124 | local times, err = self.sock:getreusedtimes() -- cosocket API 125 | if not times then 126 | return nil, err 127 | elseif times == 0 then 128 | -- handle connection 129 | end 130 | end 131 | 132 | return _M 133 | ``` 134 | 135 | The user of such a module could use it in contexts with cosocket support, or 136 | in the `init` phase of ngx_lua, with little effort from the developer. 137 | 138 | [Back to TOC](#table-of-contents) 139 | 140 | ### Requirements 141 | 142 | **As long as sockets are created in contexts with support for cosockets, this 143 | module will never require LuaSocket nor LuaSec.** 144 | 145 | - LuaSocket (only if sockets are created where cosockets don't exist) 146 | - LuaSec (only if the fallbacked socket attempts to perform an SSL handshake) 147 | 148 | [Back to TOC](#table-of-contents) 149 | 150 | ### Installation 151 | 152 | This module can either be copied in a lua-resty library, allowing one to 153 | modify the list of contexts allowing fallback. 154 | 155 | It can also be installed via LuaRocks: 156 | 157 | ```shell 158 | $ luarocks install lua-resty-socket 159 | ``` 160 | 161 | [Back to TOC](#table-of-contents) 162 | 163 | ### License 164 | 165 | Work licensed under the MIT License. 166 | 167 | [Back to TOC](#table-of-contents) 168 | 169 | [ngx_lua]: https://github.com/openresty/lua-nginx-module 170 | 171 | [badge-travis-url]: https://travis-ci.org/thibaultcha/lua-resty-socket 172 | [badge-travis-image]: https://travis-ci.org/thibaultcha/lua-resty-socket.svg?branch=master 173 | 174 | [badge-version-image]: https://img.shields.io/badge/version-1.0.0-blue.svg?style=flat 175 | -------------------------------------------------------------------------------- /lib/resty/socket.lua: -------------------------------------------------------------------------------- 1 | local type = type 2 | 3 | ---------------------------- 4 | -- LuaSocket proxy metatable 5 | ---------------------------- 6 | 7 | local proxy_mt 8 | 9 | do 10 | local tostring = tostring 11 | local concat = table.concat 12 | local pairs = pairs 13 | 14 | local function flatten(v, buf) 15 | if type(v) == 'string' then 16 | buf[#buf+1] = v 17 | elseif type(v) == 'table' then 18 | for i = 1, #v do 19 | flatten(v[i], buf) 20 | end 21 | end 22 | end 23 | 24 | proxy_mt = { 25 | send = function(self, data) 26 | if type(data) == 'table' then 27 | local buffer = {} 28 | flatten(data, buffer) 29 | data = concat(buffer) 30 | end 31 | 32 | return self.sock:send(data) 33 | end, 34 | getreusedtimes = function() return 0 end, 35 | settimeout = function(self, t) 36 | if t then 37 | t = t/1000 38 | end 39 | self.sock:settimeout(t) 40 | end, 41 | setkeepalive = function(self) 42 | self.sock:close() 43 | return true 44 | end, 45 | close = function(self) 46 | -- LuaSec dismisses the return value from sock:close(), so we override 47 | -- sock:close() here to ensure that we always return non-nil from it, 48 | -- even when wrapped by LuaSec 49 | self.sock:close() 50 | return 1 51 | end, 52 | sslhandshake = function(self, reused_session, _, verify, opts) 53 | opts = opts or {} 54 | local return_bool = reused_session == false 55 | 56 | local ssl = require 'ssl' 57 | local params = { 58 | mode = 'client', 59 | protocol = 'tlsv1', 60 | key = opts.key, 61 | certificate = opts.cert, 62 | cafile = opts.cafile, 63 | verify = verify and 'peer' or 'none', 64 | options = 'all' 65 | } 66 | 67 | local sock, err = ssl.wrap(self.sock, params) 68 | if not sock then 69 | return return_bool and false or nil, err 70 | end 71 | 72 | local ok, err = sock:dohandshake() 73 | if not ok then 74 | return return_bool and false or nil, err 75 | end 76 | 77 | -- purge memoized closures 78 | for k, v in pairs(self) do 79 | if type(v) == 'function' then 80 | self[k] = nil 81 | end 82 | end 83 | 84 | self.sock = sock 85 | 86 | return return_bool and true or self 87 | end 88 | } 89 | 90 | proxy_mt.__tostring = function(self) 91 | return tostring(self.sock) 92 | end 93 | 94 | proxy_mt.__index = function(self, key) 95 | local override = proxy_mt[key] 96 | if override then 97 | return override 98 | end 99 | 100 | local orig = self.sock[key] 101 | if type(orig) == 'function' then 102 | local f = function(_, ...) 103 | return orig(self.sock, ...) 104 | end 105 | self[key] = f 106 | return f 107 | elseif orig then 108 | return orig 109 | end 110 | end 111 | end 112 | 113 | --------- 114 | -- Module 115 | --------- 116 | 117 | local _M = { 118 | luasocket_mt = proxy_mt, 119 | _VERSION = '1.0.0' 120 | } 121 | 122 | ----------------------- 123 | -- ngx_lua/plain compat 124 | ----------------------- 125 | 126 | local COSOCKET_PHASES = { 127 | rewrite = true, 128 | access = true, 129 | content = true, 130 | timer = true, 131 | ssl_cert = true, 132 | ssl_session_fetch = true 133 | } 134 | 135 | local forced_luasocket_phases = {} 136 | local forbidden_luasocket_phases = {} 137 | 138 | do 139 | local setmetatable = setmetatable 140 | 141 | if ngx then 142 | local log, WARN, INFO = ngx.log, ngx.WARN, ngx.INFO 143 | local get_phase = ngx.get_phase 144 | local ngx_socket = ngx.socket 145 | 146 | function _M.tcp(...) 147 | local phase = get_phase() 148 | if not forced_luasocket_phases[phase] 149 | and COSOCKET_PHASES[phase] 150 | or forbidden_luasocket_phases[phase] then 151 | return ngx_socket.tcp(...) 152 | end 153 | 154 | -- LuaSocket 155 | if phase ~= 'init' then 156 | if forced_luasocket_phases[phase] then 157 | log(INFO, 'support for cosocket in this context, but LuaSocket forced') 158 | else 159 | log(WARN, 'no support for cosockets in this context, falling back to LuaSocket') 160 | end 161 | end 162 | 163 | local socket = require 'socket' 164 | 165 | return setmetatable({ 166 | sock = socket.tcp(...) 167 | }, proxy_mt) 168 | end 169 | else 170 | local socket = require 'socket' 171 | 172 | function _M.tcp(...) 173 | return setmetatable({ 174 | sock = socket.tcp(...) 175 | }, proxy_mt) 176 | end 177 | end 178 | end 179 | 180 | --------------------------------------- 181 | -- Disabling/forcing LuaSocket fallback 182 | --------------------------------------- 183 | 184 | do 185 | local function check_phase(phase) 186 | if type(phase) ~= 'string' then 187 | local info = debug.getinfo(2) 188 | local err = string.format("bad argument #1 to '%s' (%s expected, got %s)", 189 | info.name, 'string', type(phase)) 190 | error(err, 3) 191 | end 192 | end 193 | 194 | function _M.force_luasocket(phase, force) 195 | check_phase(phase) 196 | forced_luasocket_phases[phase] = force 197 | end 198 | 199 | function _M.disable_luasocket(phase, disable) 200 | check_phase(phase) 201 | forbidden_luasocket_phases[phase] = disable 202 | end 203 | end 204 | 205 | return _M 206 | -------------------------------------------------------------------------------- /lua-resty-socket-1.0.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-socket" 2 | version = "1.0.0-1" 3 | source = { 4 | url = "git://github.com/thibaultCha/lua-resty-socket", 5 | tag = "1.0.0" 6 | } 7 | description = { 8 | summary = "Graceful fallback to LuaSocket for ngx_lua", 9 | homepage = "http://thibaultcha.github.io/lua-resty-socket", 10 | license = "MIT" 11 | } 12 | build = { 13 | type = "builtin", 14 | modules = { 15 | ["resty.socket"] = "lib/resty/socket.lua" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spec/socket_spec.lua: -------------------------------------------------------------------------------- 1 | local socket = require 'lib.resty.socket' 2 | 3 | describe('resty.socket', function() 4 | it('fallbacks on LuaSocket outside of ngx_lua', function() 5 | local sock = assert(socket.tcp()) 6 | assert.is_table(getmetatable(sock)) 7 | 8 | finally(function() 9 | sock:close() 10 | end) 11 | 12 | local ok = assert(sock:connect('www.google.com', 80)) 13 | assert.equal(1, ok) 14 | 15 | local bytes = assert(sock:send('HEAD / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n')) 16 | assert.is_number(bytes) 17 | 18 | local status = assert(sock:receive()) 19 | assert.equal('HTTP/1.1 200 OK', status) 20 | end) 21 | 22 | describe('setkeepalive()', function() 23 | it('calls close()', function() 24 | local sock = assert(socket.tcp()) 25 | 26 | local ok = assert(sock:connect('www.google.com', 80)) 27 | assert.equal(1, ok) 28 | 29 | sock:setkeepalive() 30 | 31 | local _, err = sock:send('HEAD / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n') 32 | assert.equal('closed', err) 33 | end) 34 | end) 35 | end) 36 | -------------------------------------------------------------------------------- /t/01-sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | our $HttpConfig = <<_EOC_; 5 | lua_package_path 'lib/?.lua;;'; 6 | _EOC_ 7 | 8 | plan tests => repeat_each() * (blocks() * 3 + 1); 9 | 10 | $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; 11 | 12 | run_tests(); 13 | 14 | __DATA__ 15 | 16 | === TEST 1: _VERSION field 17 | --- http_config eval: $::HttpConfig 18 | --- config 19 | location /t { 20 | content_by_lua_block { 21 | local socket = require 'resty.socket' 22 | ngx.say(socket._VERSION) 23 | } 24 | } 25 | --- request 26 | GET /t 27 | --- response_body_like 28 | [0-9]\.[0-9]\.[0-9] 29 | --- no_error_log 30 | [error] 31 | 32 | 33 | 34 | === TEST 2: expose proxy metatable 35 | --- http_config eval: $::HttpConfig 36 | --- config 37 | location /t { 38 | content_by_lua_block { 39 | local socket = require 'resty.socket' 40 | ngx.say(type(socket.luasocket_mt)) 41 | } 42 | } 43 | --- request 44 | GET /t 45 | --- response_body 46 | table 47 | --- no_error_log 48 | [error] 49 | 50 | 51 | 52 | === TEST 3: warn message on fallback 53 | --- wait: 1 54 | --- http_config eval: $::HttpConfig 55 | --- config 56 | location /t { 57 | return 200; 58 | 59 | log_by_lua_block { 60 | local socket = require 'resty.socket' 61 | socket.tcp() 62 | } 63 | } 64 | --- request 65 | GET /t 66 | --- response_body 67 | 68 | --- error_log eval 69 | qr/\[warn\].*?no support for cosockets in this context, falling back to LuaSocket/ 70 | 71 | 72 | 73 | === TEST 4: cosocket sanity 74 | --- http_config eval: $::HttpConfig 75 | --- config 76 | location /post { 77 | return 201; 78 | } 79 | 80 | location /t { 81 | content_by_lua_block { 82 | local socket = require 'resty.socket' 83 | local sock = socket.tcp() 84 | 85 | local ok, err = sock:connect('127.0.0.1', $TEST_NGINX_SERVER_PORT) 86 | if ok ~= 1 then 87 | ngx.log(ngx.ERR, 'could not connect: ', err) 88 | return 89 | end 90 | 91 | local bytes, err = sock:send('POST /post HTTP/1.1\r\nHost: localhost\r\n\r\n') 92 | if not bytes then 93 | ngx.log(ngx.ERR, 'could not send: ', err) 94 | return 95 | end 96 | 97 | local status, err = sock:receive() 98 | if not status then 99 | ngx.log(ngx.ERR, 'could not receive: ', err) 100 | return 101 | end 102 | 103 | ngx.say(status) 104 | } 105 | } 106 | --- request 107 | GET /t 108 | --- response_body 109 | HTTP/1.1 201 Created 110 | --- no_error_log 111 | [error] 112 | 113 | 114 | 115 | === TEST 5: luasocket fallback sanity 116 | --- wait: 1 117 | --- http_config eval: $::HttpConfig 118 | --- config 119 | resolver $TEST_NGINX_RESOLVER ipv6=off; 120 | location /t { 121 | return 200; 122 | 123 | log_by_lua_block { 124 | local socket = require 'resty.socket' 125 | local sock = socket.tcp() 126 | 127 | local ok, err = sock:connect('www.google.com', 80) 128 | if ok ~= 1 then 129 | ngx.log(ngx.ERR, 'could not connect: ', err) 130 | return 131 | end 132 | 133 | local bytes, err = sock:send('HEAD / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n') 134 | if not bytes then 135 | ngx.log(ngx.ERR, 'could not send: ', err) 136 | return 137 | end 138 | 139 | local res, err = sock:receive() 140 | if not res then 141 | ngx.log(ngx.ERR, 'could not receive: ', err) 142 | return 143 | end 144 | 145 | print(res) 146 | } 147 | } 148 | --- request 149 | GET /t 150 | --- response_body 151 | 152 | --- no_error_log 153 | [error] 154 | --- error_log 155 | HTTP/1.1 200 OK 156 | -------------------------------------------------------------------------------- /t/02-contexts.t: -------------------------------------------------------------------------------- 1 | # vim:set ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | our $HttpConfig = <<_EOC_; 5 | lua_package_path 'lib/?.lua;;'; 6 | _EOC_ 7 | 8 | plan tests => repeat_each() * (blocks() * 5); 9 | 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: luasocket in init 15 | --- http_config eval 16 | "$::HttpConfig 17 | init_by_lua_block { 18 | local socket = require 'resty.socket' 19 | local sock = socket.tcp() 20 | print('is fallback: ', getmetatable(sock) == socket.luasocket_mt) 21 | }" 22 | --- config 23 | location /t { 24 | return 200; 25 | } 26 | --- request 27 | GET /t 28 | --- response_body 29 | 30 | --- no_error_log 31 | [warn] 32 | [error] 33 | --- error_log 34 | is fallback: true 35 | 36 | 37 | 38 | === TEST 2: luasocket in init_worker 39 | --- http_config eval 40 | "$::HttpConfig 41 | init_worker_by_lua_block { 42 | local socket = require 'resty.socket' 43 | local sock = socket.tcp() 44 | print('is fallback: ', getmetatable(sock) == socket.luasocket_mt) 45 | }" 46 | --- config 47 | location /t { 48 | return 200; 49 | } 50 | --- request 51 | GET /t 52 | --- response_body 53 | 54 | --- no_error_log 55 | [error] 56 | --- error_log 57 | is fallback: true 58 | no support for cosockets in this context, falling back to LuaSocket 59 | 60 | 61 | 62 | === TEST 3: luasocket in set 63 | --- http_config eval: $::HttpConfig 64 | --- config 65 | location /t { 66 | set_by_lua_block $res { 67 | local socket = require 'resty.socket' 68 | local sock = socket.tcp() 69 | print('is fallback: ', getmetatable(sock) == socket.luasocket_mt) 70 | return 'ok' 71 | } 72 | echo $res; 73 | } 74 | --- request 75 | GET /t 76 | --- response_body 77 | ok 78 | --- no_error_log 79 | [error] 80 | --- error_log 81 | is fallback: true 82 | no support for cosockets in this context, falling back to LuaSocket 83 | 84 | 85 | 86 | === TEST 4: cosocket in rewrite 87 | --- http_config eval: $::HttpConfig 88 | --- config 89 | location /t { 90 | set $res ""; 91 | rewrite_by_lua_block { 92 | local socket = require 'resty.socket' 93 | local sock = socket.tcp() 94 | print('is fallback: ', getmetatable(sock) == socket.luasocket_mt) 95 | ngx.var.res = "ok" 96 | } 97 | echo $res; 98 | } 99 | --- request 100 | GET /t 101 | --- response_body 102 | ok 103 | --- no_error_log 104 | [error] 105 | [warn] 106 | --- error_log 107 | is fallback: false 108 | 109 | 110 | 111 | === TEST 5: cosocket in access 112 | --- http_config eval: $::HttpConfig 113 | --- config 114 | location /t { 115 | access_by_lua_block { 116 | local socket = require 'resty.socket' 117 | local sock = socket.tcp() 118 | ngx.say('is fallback: ', getmetatable(sock) == socket.luasocket_mt) 119 | } 120 | } 121 | --- request 122 | GET /t 123 | --- response_body 124 | is fallback: false 125 | --- no_error_log 126 | [warn] 127 | [error] 128 | 129 | 130 | 131 | === TEST 6: cosocket in content 132 | --- http_config eval: $::HttpConfig 133 | --- config 134 | location /t { 135 | content_by_lua_block { 136 | local socket = require 'resty.socket' 137 | local sock = socket.tcp() 138 | ngx.say('is fallback: ', getmetatable(sock) == socket.luasocket_mt) 139 | } 140 | } 141 | --- request 142 | GET /t 143 | --- response_body 144 | is fallback: false 145 | --- no_error_log 146 | [warn] 147 | [error] 148 | 149 | 150 | 151 | === TEST 7: luasocket in header_filter 152 | --- http_config eval: $::HttpConfig 153 | --- config 154 | location /t { 155 | return 200; 156 | 157 | header_filter_by_lua_block { 158 | local socket = require 'resty.socket' 159 | local sock = socket.tcp() 160 | print('is fallback: ', getmetatable(sock) == socket.luasocket_mt) 161 | } 162 | } 163 | --- request 164 | GET /t 165 | --- response_body 166 | 167 | --- no_error_log 168 | [error] 169 | --- error_log 170 | is fallback: true 171 | no support for cosockets in this context, falling back to LuaSocket 172 | 173 | 174 | 175 | === TEST 8: luasocket in body_filter 176 | --- http_config eval: $::HttpConfig 177 | --- config 178 | location /t { 179 | return 200; 180 | 181 | body_filter_by_lua_block { 182 | local socket = require 'resty.socket' 183 | local sock = socket.tcp() 184 | print('is fallback: ', getmetatable(sock) == socket.luasocket_mt) 185 | } 186 | } 187 | --- request 188 | GET /t 189 | --- response_body 190 | 191 | --- no_error_log 192 | [error] 193 | --- error_log 194 | is fallback: true 195 | no support for cosockets in this context, falling back to LuaSocket 196 | 197 | 198 | 199 | === TEST 9: luasocket in log 200 | --- wait: 1 201 | --- http_config eval: $::HttpConfig 202 | --- config 203 | location /t { 204 | return 200; 205 | 206 | log_by_lua_block { 207 | local socket = require 'resty.socket' 208 | local sock = socket.tcp() 209 | print('is fallback: ', getmetatable(sock) == socket.luasocket_mt) 210 | } 211 | } 212 | --- request 213 | GET /t 214 | --- response_body 215 | 216 | --- no_error_log 217 | [error] 218 | --- error_log 219 | is fallback: true 220 | no support for cosockets in this context, falling back to LuaSocket 221 | 222 | 223 | 224 | === TEST 10: cosocket in timer 225 | --- wait: 1 226 | --- http_config eval: $::HttpConfig 227 | --- config 228 | location /t { 229 | return 200; 230 | 231 | log_by_lua_block { 232 | ngx.timer.at(0, function() 233 | local socket = require 'resty.socket' 234 | local sock = socket.tcp() 235 | print('is fallback: ', getmetatable(sock) == socket.luasocket_mt) 236 | end) 237 | } 238 | } 239 | --- request 240 | GET /t 241 | --- response_body 242 | 243 | --- no_error_log 244 | [warn] 245 | [error] 246 | --- error_log 247 | is fallback: false 248 | 249 | 250 | 251 | === TEST 11: fallback in non-supported contexts only 252 | --- http_config eval 253 | "$::HttpConfig 254 | init_by_lua_block { 255 | local socket = require 'resty.socket' 256 | local sock = socket.tcp() 257 | print('is fallback in init: ', getmetatable(sock) == socket.luasocket_mt) 258 | }" 259 | --- config 260 | location /t { 261 | content_by_lua_block { 262 | local socket = require 'resty.socket' 263 | local sock = socket.tcp() 264 | print('is fallback in content: ', getmetatable(sock) == socket.luasocket_mt) 265 | } 266 | } 267 | --- request 268 | GET /t 269 | --- response_body 270 | 271 | --- error_log 272 | is fallback in init: true 273 | is fallback in content: false 274 | --- no_error_log 275 | [warn] 276 | [error] 277 | 278 | 279 | 280 | === TEST 12: fallback in non-supported contexts only (bis) 281 | --- http_config eval: $::HttpConfig 282 | --- config 283 | location /t { 284 | content_by_lua_block { 285 | local socket = require 'resty.socket' 286 | local sock = socket.tcp() 287 | print('is fallback in content: ', getmetatable(sock) == socket.luasocket_mt) 288 | } 289 | 290 | header_filter_by_lua_block { 291 | local socket = require 'resty.socket' 292 | local sock = socket.tcp() 293 | print('is fallback in header_filter: ', getmetatable(sock) == socket.luasocket_mt) 294 | } 295 | } 296 | --- request 297 | GET /t 298 | --- response_body 299 | 300 | --- error_log 301 | is fallback in content: false 302 | is fallback in header_filter: true 303 | no support for cosockets in this context, falling back to LuaSocket 304 | --- no_error_log 305 | [error] 306 | -------------------------------------------------------------------------------- /t/03-compat.t: -------------------------------------------------------------------------------- 1 | # vim:set ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | our $HttpConfig = <<_EOC_; 5 | lua_package_path 'lib/?.lua;;'; 6 | _EOC_ 7 | 8 | plan tests => repeat_each() * (blocks() * 3 + 2); 9 | 10 | $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; 11 | 12 | run_tests(); 13 | 14 | __DATA__ 15 | 16 | === TEST 1: luasocket send() 17 | --- wait: 1 18 | --- http_config eval: $::HttpConfig 19 | --- config 20 | resolver $TEST_NGINX_RESOLVER ipv6=off; 21 | location /t { 22 | return 200; 23 | 24 | log_by_lua_block { 25 | local socket = require 'resty.socket' 26 | local sock = socket.tcp() 27 | 28 | local ok, err = sock:connect('www.google.com', 80) 29 | if ok ~= 1 then 30 | ngx.log(ngx.ERR, 'could not connect: ', err) 31 | return 32 | end 33 | 34 | local data = { 35 | 'HEAD ', '/ ', 'HTTP/1.1 ', '\r\n', 'Host: www.google.com', 36 | '\r\n', 'Connection: close', '\r\n\r\n' 37 | } 38 | local bytes, err = sock:send(data) 39 | if not bytes then 40 | ngx.log(ngx.ERR, 'could not send: ', err) 41 | return 42 | end 43 | 44 | local res, err = sock:receive() 45 | if not res then 46 | ngx.log(ngx.ERR, 'could not receive: ', err) 47 | return 48 | end 49 | 50 | print(res) 51 | } 52 | } 53 | --- request 54 | GET /t 55 | --- response_body 56 | 57 | --- no_error_log 58 | [error] 59 | --- error_log 60 | HTTP/1.1 200 OK 61 | 62 | 63 | 64 | === TEST 2: luasocket getreusedtimes() 65 | --- wait: 1 66 | --- http_config eval: $::HttpConfig 67 | --- config 68 | location /t { 69 | return 200; 70 | 71 | log_by_lua_block { 72 | local socket = require 'resty.socket' 73 | local sock = socket.tcp() 74 | local reused_times = sock:getreusedtimes() 75 | print("reused: ", reused_times) 76 | } 77 | } 78 | --- request 79 | GET /t 80 | --- no_error_log 81 | [error] 82 | --- error_log 83 | reused: 0 84 | 85 | 86 | 87 | === TEST 3: luasocket settimeout() compat (ms to seconds conversion) 88 | --- wait: 1 89 | --- http_config eval: $::HttpConfig 90 | --- config 91 | location /get { 92 | content_by_lua_block { 93 | print('sleeping...') 94 | ngx.sleep(2) 95 | ngx.say('ok') 96 | } 97 | } 98 | 99 | location /t { 100 | return 200; 101 | 102 | log_by_lua_block { 103 | local socket = require 'resty.socket' 104 | local sock = socket.tcp() 105 | sock:settimeout(1000) -- should be translated to 1 for LuaSocket 106 | local ok, err = sock:connect('localhost', $TEST_NGINX_SERVER_PORT) 107 | if not ok then 108 | ngx.log(ngx.ERR, 'could not connect: ', err) 109 | return 110 | end 111 | 112 | local bytes, err = sock:send('GET /get HTTP/1.1\r\nHost: localhost\r\n\r\n') 113 | if not bytes then 114 | ngx.log(ngx.ERR, 'could not send: ', err) 115 | return 116 | end 117 | 118 | ngx.timer.at(0, function() 119 | print('receiving...') 120 | local res, err = sock:receive() 121 | if not res then 122 | print('could not receive: ', err) 123 | end 124 | end) 125 | } 126 | } 127 | --- request 128 | GET /t 129 | --- no_error_log 130 | [error] 131 | --- error_log 132 | could not receive: timeout 133 | 134 | 135 | 136 | === TEST 4: luasocket settimeout() nil 137 | --- wait: 1 138 | --- http_config eval: $::HttpConfig 139 | --- config 140 | location /t { 141 | return 200; 142 | 143 | log_by_lua_block { 144 | local socket = require 'resty.socket' 145 | local sock = socket.tcp() 146 | sock:settimeout() -- no errors 147 | ngx.log(ngx.INFO, 'ok') 148 | } 149 | } 150 | --- request 151 | GET /t 152 | --- no_error_log 153 | [error] 154 | --- error_log 155 | ok 156 | 157 | 158 | 159 | === TEST 5: luasocket setkeepalive() compat (close when not supported) 160 | --- wait: 1 161 | --- http_config eval: $::HttpConfig 162 | --- config 163 | resolver $TEST_NGINX_RESOLVER ipv6=off; 164 | location /t { 165 | return 200; 166 | 167 | log_by_lua_block { 168 | local socket = require 'resty.socket' 169 | local sock = socket.tcp() 170 | local ok, err = sock:connect('www.google.com', 80) 171 | if not ok then 172 | ngx.log(ngx.ERR, 'could not connect: ', err) 173 | return 174 | end 175 | 176 | local bytes, err = sock:send('HEAD / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n') 177 | if not bytes then 178 | ngx.log(ngx.ERR, 'could not send: ', err) 179 | return 180 | end 181 | 182 | local status, err = sock:receive() 183 | if not status then 184 | ngx.log(ngx.ERR, 'could not receive: ', err) 185 | return 186 | end 187 | 188 | local ok, err = sock:setkeepalive() 189 | if not ok then 190 | ngx.log(ngx.ERR, 'setkeepalive() proxy should return true') 191 | return 192 | end 193 | 194 | local ok, err = sock:send('HEAD / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n') 195 | if not ok then 196 | print('could not send after keepalive: ', err) 197 | end 198 | } 199 | } 200 | --- request 201 | GET /t 202 | --- no_error_log 203 | [error] 204 | --- error_log 205 | could not send after keepalive: closed 206 | 207 | 208 | 209 | === TEST 6: luasocket sslhandshake() compat 210 | --- wait: 1 211 | --- http_config eval: $::HttpConfig 212 | --- config 213 | resolver $TEST_NGINX_RESOLVER ipv6=off; 214 | location /t { 215 | return 200; 216 | 217 | log_by_lua_block { 218 | local socket = require 'resty.socket' 219 | local sock = socket.tcp() 220 | local ok, err = sock:connect('www.google.com', 443) 221 | if not ok then 222 | ngx.log(ngx.ERR, 'could not connect: ', err) 223 | return 224 | end 225 | 226 | local session, err = sock:sslhandshake(nil, nil, false) 227 | if not session then 228 | ngx.log(ngx.ERR, 'could not handshake: ', err) 229 | return 230 | end 231 | 232 | print('session: ', type(session)) 233 | 234 | local bytes, err = sock:send('HEAD / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n') 235 | if not bytes then 236 | ngx.log(ngx.ERR, 'could not send: ', err) 237 | return 238 | end 239 | 240 | local status, err = sock:receive() 241 | if not status then 242 | ngx.log(ngx.ERR, 'could not receive: ', err) 243 | return 244 | end 245 | 246 | print(status) 247 | } 248 | } 249 | --- request 250 | GET /t 251 | --- no_error_log 252 | [error] 253 | --- error_log eval 254 | [ 255 | qr/\[notice\] .*? session: table/, 256 | qr/\[notice\] .*? HTTP\/1.1 200 OK/ 257 | ] 258 | 259 | 260 | 261 | === TEST 7: luasocket sslhandshake() compat arg #1 false 262 | --- wait: 1 263 | --- http_config eval: $::HttpConfig 264 | --- config 265 | resolver $TEST_NGINX_RESOLVER ipv6=off; 266 | location /t { 267 | return 200; 268 | 269 | log_by_lua_block { 270 | local socket = require 'resty.socket' 271 | local sock = socket.tcp() 272 | local ok, err = sock:connect('www.google.com', 443) 273 | if not ok then 274 | ngx.log(ngx.ERR, 'could not connect: ', err) 275 | return 276 | end 277 | 278 | local session, err = sock:sslhandshake(false, nil, false) 279 | if not session then 280 | ngx.log(ngx.ERR, 'could not handshake: ', err) 281 | return 282 | end 283 | 284 | print('session: ', type(session)) 285 | } 286 | } 287 | --- request 288 | GET /t 289 | --- no_error_log 290 | [error] 291 | --- error_log eval 292 | qr/\[notice\] .*? session: boolean/ 293 | 294 | 295 | 296 | === TEST 8: luasocket close() after sslhandshake() ret values compat (LuaSec wrapper) 297 | --- wait: 1 298 | --- http_config eval: $::HttpConfig 299 | --- config 300 | resolver $TEST_NGINX_RESOLVER ipv6=off; 301 | location /t { 302 | return 200; 303 | 304 | log_by_lua_block { 305 | local socket = require 'resty.socket' 306 | local sock = socket.tcp() 307 | local ok, err = sock:connect('www.google.com', 443) 308 | if not ok then 309 | ngx.log(ngx.ERR, 'could not connect: ', err) 310 | return 311 | end 312 | 313 | local session, err = sock:sslhandshake(false, nil, false) 314 | if not session then 315 | ngx.log(ngx.ERR, 'could not handshake: ', err) 316 | return 317 | end 318 | 319 | local ok, err = sock:close() 320 | 321 | print("ok: ", tostring(ok), " err: ", tostring(err)) 322 | } 323 | } 324 | --- request 325 | GET /t 326 | --- no_error_log 327 | [error] 328 | --- error_log eval 329 | qr/\[notice\] .*? ok: 1 err: nil/ 330 | 331 | 332 | 333 | === TEST 9: luasocket setkeepalive() after sslhandshake() ret values compat (LuaSec wrapper) 334 | --- wait: 1 335 | --- http_config eval: $::HttpConfig 336 | --- config 337 | resolver $TEST_NGINX_RESOLVER ipv6=off; 338 | location /t { 339 | return 200; 340 | 341 | log_by_lua_block { 342 | local socket = require 'resty.socket' 343 | local sock = socket.tcp() 344 | local ok, err = sock:connect('www.google.com', 443) 345 | if not ok then 346 | ngx.log(ngx.ERR, 'could not connect: ', err) 347 | return 348 | end 349 | 350 | local session, err = sock:sslhandshake(false, nil, false) 351 | if not session then 352 | ngx.log(ngx.ERR, 'could not handshake: ', err) 353 | return 354 | end 355 | 356 | local ok, err = sock:setkeepalive() 357 | 358 | print("ok: ", tostring(ok), " err: ", tostring(err)) 359 | } 360 | } 361 | --- request 362 | GET /t 363 | --- no_error_log 364 | [error] 365 | --- error_log eval 366 | qr/\[notice\] .*? ok: true err: nil/ 367 | -------------------------------------------------------------------------------- /t/04-memoized-closures.t: -------------------------------------------------------------------------------- 1 | # vim:set ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | our $HttpConfig = <<_EOC_; 5 | lua_package_path 'lib/?.lua;;'; 6 | _EOC_ 7 | 8 | plan tests => repeat_each() * (blocks() * 3); 9 | 10 | $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; 11 | 12 | run_tests(); 13 | 14 | __DATA__ 15 | 16 | === TEST 1: purge memoized closures 17 | --- wait: 1 18 | --- http_config eval: $::HttpConfig 19 | --- config 20 | resolver $TEST_NGINX_RESOLVER ipv6=off; 21 | location /t { 22 | return 200; 23 | 24 | log_by_lua_block { 25 | local socket = require 'resty.socket' 26 | local sock = socket.tcp() 27 | 28 | local ok, err = sock:connect('www.google.com', 443) 29 | if ok ~= 1 then 30 | ngx.log(ngx.ERR, 'could not connect: ', err) 31 | return 32 | end 33 | 34 | -- create closures 35 | sock.send = function() return nil, 'closure' end 36 | sock.receive = function() return nil, 'closure' end 37 | 38 | local ok, err = sock:sslhandshake(false) 39 | if not ok then 40 | ngx.log(ngx.ERR, 'could not handshake: ', err) 41 | return 42 | end 43 | 44 | local bytes, err = sock:send 'HEAD / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n' 45 | if not bytes then 46 | ngx.log(ngx.ERR, 'could not send: ', err) 47 | return 48 | end 49 | 50 | local status, err = sock:receive() 51 | if not status then 52 | ngx.log(ngx.ERR, 'could not receive: ', err) 53 | return 54 | end 55 | 56 | print(status) 57 | } 58 | } 59 | --- request 60 | GET /t 61 | --- no_error_log 62 | [error] 63 | --- error_log eval 64 | qr/\[notice\] .*? HTTP\/1.1 200 OK/ 65 | -------------------------------------------------------------------------------- /t/05-setting.t: -------------------------------------------------------------------------------- 1 | 2 | # vim:set ts=4 sw=4 et fdm=marker: 3 | use Test::Nginx::Socket::Lua; 4 | 5 | our $HttpConfig = <<_EOC_; 6 | lua_package_path 'lib/?.lua;;'; 7 | _EOC_ 8 | 9 | plan tests => repeat_each() * (blocks() * 5); 10 | 11 | $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; 12 | 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | === TEST 1: force_luasocket() 18 | --- http_config eval: $::HttpConfig 19 | --- config 20 | location /t { 21 | return 200; 22 | 23 | log_by_lua_block { 24 | ngx.timer.at(0, function() 25 | local socket = require 'resty.socket' 26 | socket.force_luasocket('timer', true) 27 | 28 | local sock = socket.tcp() 29 | print('is fallback: ', getmetatable(sock) == socket.luasocket_mt) 30 | end) 31 | } 32 | } 33 | --- request 34 | GET /t 35 | --- response_body 36 | 37 | --- no_error_log 38 | [error] 39 | --- error_log 40 | support for cosocket in this context, but LuaSocket forced 41 | is fallback: true 42 | 43 | 44 | 45 | === TEST 2: disable_luasocket() 46 | --- http_config eval: $::HttpConfig 47 | --- config 48 | location /t { 49 | return 200; 50 | 51 | log_by_lua_block { 52 | local socket = require 'resty.socket' 53 | socket.disable_luasocket('log', true) 54 | 55 | socket.tcp() 56 | } 57 | } 58 | --- request 59 | GET /t 60 | --- response_body 61 | 62 | --- no_error_log 63 | [warn] 64 | [info] 65 | --- error_log 66 | API disabled in the context of log_by_lua* 67 | -------------------------------------------------------------------------------- /t/reindex: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | #: reindex.pl 3 | #: reindex .t files for Test::Base based test files 4 | #: Copyright (c) 2006 Agent Zhang 5 | #: 2006-04-27 2006-05-09 6 | 7 | use strict; 8 | use warnings; 9 | 10 | #use File::Copy; 11 | use Getopt::Std; 12 | 13 | my %opts; 14 | getopts('hb:', \%opts); 15 | if ($opts{h} or ! @ARGV) { 16 | die "Usage: reindex [-b 0] /*.t\n"; 17 | } 18 | 19 | my $init = $opts{b}; 20 | $init = 1 if not defined $init; 21 | 22 | my @files = map glob, @ARGV; 23 | for my $file (@files) { 24 | next if -d $file or $file !~ /\.t_?$/; 25 | reindex($file); 26 | } 27 | 28 | sub reindex { 29 | my $file = $_[0]; 30 | open my $in, $file or 31 | die "Can't open $file for reading: $!"; 32 | my @lines; 33 | my $counter = $init; 34 | my $changed; 35 | while (<$in>) { 36 | s/\r$//; 37 | my $num; 38 | s/ ^ === \s+ TEST \s+ (\d+)/$num=$1; "=== TEST " . $counter++/xie; 39 | next if !defined $num; 40 | if ($num != $counter-1) { 41 | $changed++; 42 | } 43 | } continue { 44 | push @lines, $_; 45 | } 46 | close $in; 47 | my $text = join '', @lines; 48 | $text =~ s/(?x) \n+ === \s+ TEST/\n\n\n\n=== TEST/ixsg; 49 | $text =~ s/__(DATA|END)__\n+=== TEST/__${1}__\n\n=== TEST/; 50 | #$text =~ s/\n+$/\n\n/s; 51 | if (! $changed and $text eq join '', @lines) { 52 | warn "reindex: $file:\tskipped.\n"; 53 | return; 54 | } 55 | #File::Copy::copy( $file, "$file.bak" ); 56 | open my $out, "> $file" or 57 | die "Can't open $file for writing: $!"; 58 | binmode $out; 59 | print $out $text; 60 | close $out; 61 | 62 | warn "reindex: $file:\tdone.\n"; 63 | } 64 | --------------------------------------------------------------------------------