├── .gitignore ├── COPYRIGHT ├── README.md ├── examples ├── datagram_udp.lua ├── generic_chat_client.lua ├── generic_chat_server.lua ├── http_client.lua ├── http_client_put.lua ├── http_file_contents.lua ├── http_file_upload.lua ├── http_form_posts.lua ├── http_post.lua ├── http_server.lua ├── http_server_file.lua ├── http_zmq_proxy.lua ├── localhost.cert ├── localhost.key ├── tcp_client.lua ├── tcp_server.lua ├── tls_tcp_client.lua ├── tls_tcp_server.lua ├── udp_client.lua ├── udp_server.lua ├── zmq_pair_client.lua ├── zmq_pair_server.lua ├── zmq_pub.lua ├── zmq_pull.lua ├── zmq_push.lua ├── zmq_queue_server.lua ├── zmq_rep_server.lua ├── zmq_req_client.lua ├── zmq_sub.lua ├── zmq_sub_server.lua ├── zmq_tcp_publisher.lua ├── zmq_worker_client.lua ├── zmq_xrep_server.lua └── zmq_xreq_client.lua ├── handler ├── acceptor.lua ├── connection.lua ├── connection │ └── tls_backend.lua ├── datagram.lua ├── http │ ├── chunked.lua │ ├── client.lua │ ├── client │ │ ├── connection.lua │ │ ├── hosts.lua │ │ ├── request.lua │ │ └── response.lua │ ├── file.lua │ ├── form.lua │ ├── headers.lua │ ├── server.lua │ └── server │ │ ├── error_handler.lua │ │ ├── hconnection.lua │ │ ├── request.lua │ │ └── response.lua ├── nixio │ ├── acceptor.lua │ ├── connection.lua │ └── datagram.lua ├── uri.lua └── zmq.lua ├── lua-handler-http-scm-0.rockspec ├── lua-handler-nixio-scm-0.rockspec ├── lua-handler-scm-0.rockspec ├── lua-handler-zmq-scm-0.rockspec └── perf └── zmq ├── local_lat.lua ├── local_thr.lua ├── remote_lat.lua └── remote_thr.lua /.gitignore: -------------------------------------------------------------------------------- 1 | \.*.swp 2 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 by Robert G. Jakabosky 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lua-handlers 2 | ============== 3 | 4 | Provides a set of async. callback based handlers for working with raw TCP/UDP socket, ZeroMQ sockets, or HTTP client/server. 5 | 6 | 7 | Socket connect/listen URI's 8 | --------------------------- 9 | 10 | Different types of sockets can now be created from URI strings like `tcp://localhost:1234/` or `tls:/localhost:443/?key=examples/localhost.key&cert=examples/localhost.cert`. URI's can be used for connecting sockets or listening sockets. 11 | 12 | ### TCP sockets 13 | 14 | tcp://:/ 15 | 16 | ### UDP sockets 17 | 18 | udp://:/ 19 | 20 | ### Unix domain sockets 21 | 22 | unix:// 23 | 24 | ### SSL/TLS sockets over TCP 25 | 26 | tls://:/?mode=&key=&cert= 27 | 28 | ### To force IPv6 sockets 29 | 30 | tcp6://:/ 31 | udp6://:/ 32 | tls6://:/?mode=&key=&cert= 33 | 34 | 35 | ### Example server-side listen URIs: 36 | 37 | -- bind tcp socket to 127.0.0.1 on port 80 with an accept backlog of 1000 38 | tcp://localhost:80/?backlog=1000 39 | 40 | -- bind tcp socket to IPv6 address 2001:db8::1 on port 80 with an accept backlog of 1000 41 | tcp://[2001:db8::1]:80/?backlog=1000 42 | 43 | -- bind TLS wrapped tcp socket to 127.0.0.1 on port 443 with an accept backlog of 1000 44 | -- TLS defaults to mode=server when listening. 45 | tls://localhost:443/?backlog=1000&key=private_key.pem&cert=public_certificate.pem 46 | 47 | -- bind Unix domain socket to file /tmp/unix_server.sock 48 | unix:///tmp/unix_server.sock?backlog=100 49 | 50 | -- bind udp socket to 127.0.0.1 on port 53 51 | udp://localhost:53 52 | 53 | -- bind udp socket to IPv6 loop back address ::1 on port 53 54 | udp://[::1]:53 55 | or 56 | udp6://localhost:53 57 | 58 | ### Example client-side connect URIs: 59 | 60 | -- connect tcp socket to 127.0.0.1 on port 80 61 | tcp://localhost:80 62 | 63 | -- connect tcp socket to IPv6 address 2001:db8::1 on port 80 64 | tcp://[2001:db8::1]:80 65 | 66 | -- connect tcp socket to IPv6 address of hostname ipv6.google.com on port 80 67 | tcp6://ipv6.google.com:80 68 | 69 | -- connect TLS wrapped tcp socket to 127.0.0.1 on port 443 70 | -- TLS defaults to mode=client when connecting. 71 | tls://localhost:443 72 | 73 | -- connect Unix domain socket to file /tmp/unix_server.sock 74 | unix:///tmp/unix_server.sock 75 | 76 | -- connect udp socket to 127.0.0.1 on port 53 77 | udp://localhost:53 78 | 79 | -- connect udp socket to IPv6 loop back address ::1 on port 53 80 | udp://[::1]:53 81 | or 82 | udp6://localhost:53 83 | 84 | 85 | Set local address & port when connecting 86 | ---------------------------------------- 87 | 88 | Sockets can be bound to a local address & port before connecting to the remote host:port. For connecting URIs add parameters `laddr=&lport=`. 89 | 90 | Examples: 91 | 92 | -- connect tcp socket to host www.google.com on port 80 and bind the socket to local address 192.168.0.1 93 | tcp://www.google.com/?laddr=192.168.0.1 94 | 95 | -- connect tcp socket to host www.google.com on port 80 and bind the socket to local address 192.168.0.1 and local port 16384 96 | tcp://www.google.com/?laddr=192.168.0.1&lport=16384 97 | 98 | -- connect udp socket to 10.0.0.10 on port 53 and bind to local address 10.100.100.1 and port 2053 99 | udp://10.0.0.10:53/?laddr=10.100.100.1&lport=2053 100 | 101 | 102 | Example generic socket server & client 103 | -------------------------------------- 104 | 105 | The generic server can listen on any number of sockets with different types. The clients read stdin and send each line to the server which then re-sends the message to all connected clients. 106 | 107 | Start generic socket server: 108 | lua examples/generic_chat_server.lua tcp://127.0.0.1:1080/ "tls://127.0.0.1:4433/?key=examples/localhost.key&cert=examples/localhost.cert" tcp://[::1]:1082/ unix:///tmp/test.sock?backlog=1234 udp6://localhost:2053 109 | 110 | Start generic socket client: 111 | lua examples/generic_chat_client.lua tcp://localhost:1080 112 | -- or 113 | lua examples/generic_chat_client.lua udp6://localhost:2053 114 | -- or 115 | lua examples/generic_chat_client.lua tls://127.0.0.1:4433/ 116 | -- or 117 | lua examples/generic_chat_client.lua tcp://[::1]:1082/ 118 | -- or 119 | lua examples/generic_chat_client.lua unix:///tmp/test.sock 120 | 121 | Installing 122 | ---------- 123 | 124 | ### Install base package lua-handler: 125 | 126 | luarocks install "https://github.com/brimworks/lua-ev/raw/master/rockspec/lua-ev-scm-1.rockspec" 127 | 128 | luarocks install "https://github.com/Neopallium/nixio/raw/master/nixio-scm-0.rockspec" 129 | 130 | luarocks install "https://github.com/Neopallium/lua-handlers/raw/master/lua-handler-scm-0.rockspec" 131 | 132 | ### Install optional sub-package lua-handler-http: 133 | 134 | luarocks install "https://github.com/brimworks/lua-http-parser/raw/master/lua-http-parser-scm-0.rockspec" 135 | 136 | luarocks install "https://github.com/Neopallium/lua-handlers/raw/master/lua-handler-http-scm-0.rockspec" 137 | 138 | 139 | ### Install optional sub-package lua-handler-zmq: 140 | 141 | luarocks install "https://github.com/Neopallium/lua-zmq/raw/master/rockspecs/lua-zmq-scm-1.rockspec" 142 | 143 | luarocks install "https://github.com/Neopallium/lua-handlers/raw/master/lua-handler-zmq-scm-0.rockspec" 144 | 145 | 146 | Dependencies 147 | ------------ 148 | ### Base lua-handler package required dependcies: 149 | 150 | * [Lua-ev](https://github.com/brimworks/lua-ev) 151 | * [Nixio](https://github.com/Neopallium/nixio) 152 | 153 | ### Dependencies for optional lua-handler-http package: 154 | 155 | * [Lua-ev](https://github.com/brimworks/lua-ev) 156 | * [Lua-http-parser](https://github.com/brimworks/lua-http-parser) 157 | * [LuaSocket](http://w3.impa.br/~diego/software/luasocket/), needed for the ltn12 sub-module. 158 | 159 | ### Dependencies for optional lua-handler-zmq package: 160 | 161 | * [Lua-ev](https://github.com/brimworks/lua-ev) 162 | * [ZeroMQ](http://www.zeromq.org/) requires at least 2.1.0 163 | * [ZeroMQ-lua](http://github.com/Neopallium/lua-zmq) 164 | 165 | -------------------------------------------------------------------------------- /examples/datagram_udp.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local datagram = require'handler.datagram' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local udp_client_mt = { 26 | handle_error = function(self, err) 27 | if err ~= 'closed' then 28 | print('udp_client.error:', err) 29 | end 30 | end, 31 | handle_data = function(self, data, ip, port) 32 | print('udp_client.data:',data, ip, port) 33 | self.sock:sendto('hello\n', ip, port) 34 | end, 35 | } 36 | udp_client_mt.__index = udp_client_mt 37 | 38 | -- new udp client 39 | local function new_udp_client(host, port) 40 | local self = setmetatable({}, udp_client_mt) 41 | self.sock = datagram.new(loop, self, host, port) 42 | return self 43 | end 44 | 45 | local host = arg[1] or "*" 46 | local port = arg[2] or 8081 47 | local client = new_udp_client(host, port) 48 | 49 | loop:loop() 50 | 51 | -------------------------------------------------------------------------------- /examples/generic_chat_client.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local connection = require'handler.connection' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local io_in 26 | 27 | local generic_client_mt = { 28 | handle_error = function(self, err) 29 | print('client.error:', err) 30 | io_in:stop(loop) 31 | end, 32 | handle_connected = function(self) 33 | print('client.connected: sending hello message to server') 34 | -- say hi 35 | self.sock:send('hello from client connected to: ' .. self.uri) 36 | end, 37 | handle_data = function(self, data) 38 | print('from server: ' .. data) 39 | end, 40 | send = function(self, data) 41 | self.sock:send(data) 42 | end, 43 | } 44 | generic_client_mt.__index = generic_client_mt 45 | 46 | -- new generic client 47 | local function new_generic_client(uri) 48 | print('Connecting to: ' .. uri) 49 | local self = setmetatable({ uri = uri }, generic_client_mt) 50 | self.sock = connection.uri(loop, self, uri) 51 | return self 52 | end 53 | 54 | local uri = arg[1] or 'tcp://localhost:8081/' 55 | local count = tonumber(arg[2] or 1) 56 | local clients = {} 57 | 58 | for i=1,count do 59 | clients[i] = new_generic_client(uri) 60 | end 61 | 62 | local function client_send(...) 63 | for i=1,count do 64 | clients[i]:send(...) 65 | end 66 | end 67 | 68 | local function io_in_cb() 69 | local line = io.read("*l") 70 | if line and #line > 0 then 71 | client_send(line) 72 | end 73 | end 74 | io_in = ev.IO.new(io_in_cb, 0, ev.READ) 75 | io_in:start(loop) 76 | 77 | loop:loop() 78 | 79 | -------------------------------------------------------------------------------- /examples/generic_chat_server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local tremove = table.remove 22 | local tconcat = table.concat 23 | local print = print 24 | 25 | local acceptor = require'handler.acceptor' 26 | local ev = require'ev' 27 | local loop = ev.Loop.default 28 | 29 | local max_id = 0 30 | local clients = {} 31 | local servers = {} 32 | 33 | local function broadcast(...) 34 | local msg = tconcat({...}, ' ') 35 | msg = msg .. '\n' 36 | print('broadcast:', msg) 37 | --for _,client in pairs(clients) do 38 | for i=1,max_id do 39 | local client = clients[i] 40 | if type(client) == 'table' and client.is_client then 41 | client:send(msg) 42 | end 43 | end 44 | end 45 | 46 | local function add_client(client) 47 | -- get free client id 48 | local id = clients[0] 49 | if id then 50 | -- remove id from free list. 51 | clients[0] = clients[id] 52 | else 53 | -- no free ids, add client to end of the list. 54 | id = #clients + 1 55 | if id > max_id then 56 | max_id = id 57 | end 58 | end 59 | clients[id] = client 60 | client.id = id 61 | -- update max id 62 | broadcast('add_client:', id) 63 | return id 64 | end 65 | 66 | local function remove_client(client) 67 | local id = client.id 68 | assert(clients[id] == client, "Invalid client remove.") 69 | -- add id to free list. 70 | clients[id] = clients[0] 71 | clients[0] = id 72 | broadcast('remove_client:', id) 73 | end 74 | 75 | local generic_client_mt = { 76 | is_client = true, 77 | handle_error = function(self, err) 78 | print('client error:', self.id, ':', err) 79 | self.timer:stop(loop) 80 | -- remove client from list. 81 | remove_client(self) 82 | end, 83 | handle_connected = function(self) 84 | self.sock:send('Hello from server\n') 85 | end, 86 | handle_data = function(self, data) 87 | broadcast('msg from: client.' .. tostring(self.id) .. ':', data) 88 | end, 89 | handle_timer = function(self) 90 | self.sock:send('ping\n') 91 | end, 92 | send = function(self, msg) 93 | self.sock:send(msg) 94 | end, 95 | } 96 | generic_client_mt.__index = generic_client_mt 97 | 98 | -- new generic client 99 | local function new_generic_client(sock) 100 | local self = setmetatable({}, generic_client_mt) 101 | self.sock = sock 102 | sock:sethandler(self) 103 | 104 | -- create timer watcher 105 | self.timer = ev.Timer.new(function() 106 | self:handle_timer() 107 | end, 2.0, 2.0) 108 | self.timer:start(loop) 109 | 110 | -- add client to list. 111 | add_client(self) 112 | return self 113 | end 114 | 115 | -- new generic server 116 | local function new_server(uri, handler) 117 | print('New generic server listen on: ' .. uri) 118 | servers[#servers + 1] = acceptor.uri(loop, handler, uri) 119 | end 120 | 121 | if #arg < 1 then 122 | new_server('tcp://127.0.0.1:8081/', new_generic_client) 123 | else 124 | for i=1,#arg do 125 | new_server(arg[i], new_generic_client) 126 | end 127 | end 128 | 129 | loop:loop() 130 | 131 | -------------------------------------------------------------------------------- /examples/http_client.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local httpclient = require'handler.http.client' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | local tremove = table.remove 25 | 26 | local client = httpclient.new(loop,{ 27 | user_agent = "HTTPClient tester", 28 | }) 29 | 30 | -- check for parallel requests 31 | local parallel = false 32 | if arg[1] == '-p' then 33 | parallel = true 34 | tremove(arg,1) -- pop option from list. 35 | end 36 | 37 | local urls = arg 38 | local count = #urls 39 | 40 | local function on_error(req, resp, err) 41 | print('---- request error =' .. err) 42 | end 43 | 44 | local function on_response(req, resp) 45 | print('---- start response headers: status code =' .. resp.status_code) 46 | for k,v in pairs(resp.headers) do 47 | print(k .. ": " .. v) 48 | end 49 | print('\n---- end response headers') 50 | end 51 | 52 | local function on_data(req, resp, data) 53 | print('---- start response body') 54 | if data then io.write(data) end 55 | print('\n---- end response body') 56 | end 57 | 58 | local function next_url() 59 | local url = tremove(urls, 1) 60 | if not url then return false end 61 | print('---- start request of url: ' .. url) 62 | -- start next request. 63 | local req, err = client:request{ 64 | url = url, 65 | on_error = on_error, 66 | on_response = on_response, 67 | on_data = on_data, 68 | on_finished = function() 69 | count = count - 1 70 | if count == 0 then 71 | -- finished processing urls 72 | loop:unloop() 73 | else 74 | next_url() 75 | end 76 | end, 77 | } 78 | if err then 79 | print('****** Error starting request: ' .. err) 80 | return next_url() 81 | end 82 | return true 83 | end 84 | 85 | if parallel then 86 | -- start parallel request of urls from command line. 87 | repeat 88 | until not next_url() 89 | else 90 | -- start serial request of urls from command line. 91 | next_url() 92 | end 93 | 94 | loop:loop() 95 | 96 | -------------------------------------------------------------------------------- /examples/http_client_put.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local httpclient = require'handler.http.client' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | local tremove = table.remove 25 | 26 | local ltn12 = require("ltn12") 27 | 28 | local client = httpclient.new(loop,{ 29 | user_agent = "HTTPClient tester" 30 | }) 31 | 32 | local function on_response(req, resp) 33 | print('---- start response headers: status code =' .. resp.status_code) 34 | for k,v in pairs(resp.headers) do 35 | print(k .. ": " .. v) 36 | end 37 | print('---- end response headers') 38 | end 39 | 40 | local function on_data(req, resp, data) 41 | print('---- start response body') 42 | if data then 43 | io.write(data) 44 | end 45 | print('---- end response body') 46 | end 47 | 48 | local function on_finished(req, resp) 49 | print('====================== Finished PUT request =================') 50 | loop:unloop() 51 | end 52 | 53 | local put_url = arg[1] or 'http://127.0.0.1:1080/' 54 | 55 | local put_data = arg[2] or "this is a test" 56 | 57 | local use_chunked = (arg[3] or "chunked") == "chunked" 58 | 59 | local headers = { 60 | ["Content-Type"] = "text/plain", 61 | } 62 | 63 | if use_chunked then 64 | print("Use chunked transfer encoding.") 65 | -- convert request body to a ltn12 style function, to use chunked encoding. 66 | put_data = ltn12.source.string(put_data) 67 | end 68 | 69 | local req = client:request{ 70 | method = 'PUT', 71 | url = put_url, 72 | headers = headers, 73 | no_expect_100 = true, -- don't send "Expect: 100-continue" header 74 | body = put_data, 75 | on_response = on_response, 76 | on_data = on_data, 77 | on_finished = on_finished, 78 | } 79 | 80 | loop:loop() 81 | 82 | -------------------------------------------------------------------------------- /examples/http_file_contents.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local httpclient = require'handler.http.client' 22 | local form = require'handler.http.form' 23 | local file = require'handler.http.file' 24 | local ev = require'ev' 25 | local loop = ev.Loop.default 26 | local tremove = table.remove 27 | local verbose = false 28 | 29 | local client = httpclient.new(loop,{ 30 | user_agent = "HTTPClient tester" 31 | }) 32 | 33 | local function on_response(req, resp) 34 | if not verbose then return end 35 | print('---- start response headers: status code =' .. resp.status_code) 36 | for k,v in pairs(resp.headers) do 37 | print(k .. ": " .. v) 38 | end 39 | print('---- end response headers') 40 | end 41 | 42 | local function on_data(req, resp, data) 43 | print('---- start response body') 44 | io.write(data) 45 | print('---- end response body') 46 | end 47 | 48 | local function on_finished(req, resp) 49 | loop:unloop() 50 | if not verbose then return end 51 | print('====================== Finished POST request =================') 52 | end 53 | 54 | local upload_form = form.new{ 55 | upload_file = file.new_string('test.txt', 'text/plain', [[ 56 | this is a test of uploading a file with contents from memory. 57 | ]]) 58 | } 59 | 60 | local req = client:request{ 61 | method = 'POST', 62 | url = 'http://localhost/upload.php', 63 | body = upload_form, 64 | on_response = on_response, 65 | on_data = on_data, 66 | on_finished = on_finished, 67 | } 68 | 69 | loop:loop() 70 | 71 | -------------------------------------------------------------------------------- /examples/http_file_upload.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local httpclient = require'handler.http.client' 22 | local form = require'handler.http.form' 23 | local file = require'handler.http.file' 24 | local ev = require'ev' 25 | local loop = ev.Loop.default 26 | local tremove = table.remove 27 | local verbose = false 28 | 29 | local client = httpclient.new(loop,{ 30 | user_agent = "HTTPClient tester" 31 | }) 32 | 33 | local function on_response(req, resp) 34 | if not verbose then return end 35 | print('---- start response headers: status code =' .. resp.status_code) 36 | for k,v in pairs(resp.headers) do 37 | print(k .. ": " .. v) 38 | end 39 | print('---- end response headers') 40 | end 41 | 42 | local function on_data(req, resp, data) 43 | print('---- start response body') 44 | io.write(data) 45 | print('---- end response body') 46 | end 47 | 48 | local function on_finished(req, resp) 49 | loop:unloop() 50 | if not verbose then return end 51 | print('====================== Finished POST request =================') 52 | end 53 | 54 | local filename = arg[1] 55 | if not filename then 56 | print('Usage: ' .. arg[0] .. ' ') 57 | os.exit(-1) 58 | end 59 | 60 | local upload_form = form.new{ 61 | upload_file = file.new(filename) 62 | } 63 | 64 | local req = client:request{ 65 | method = 'POST', 66 | url = 'http://localhost/upload.php', 67 | body = upload_form, 68 | on_response = on_response, 69 | on_data = on_data, 70 | on_finished = on_finished, 71 | } 72 | 73 | loop:loop() 74 | 75 | -------------------------------------------------------------------------------- /examples/http_form_posts.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local httpclient = require'handler.http.client' 22 | local form = require'handler.http.form' 23 | local file = require'handler.http.file' 24 | local ev = require'ev' 25 | local loop = ev.Loop.default 26 | local tremove = table.remove 27 | 28 | local client = httpclient.new(loop,{ 29 | user_agent = "HTTPClient tester" 30 | }) 31 | 32 | local function on_response(req, resp) 33 | print('---- start response headers: status code =' .. resp.status_code) 34 | for k,v in pairs(resp.headers) do 35 | print(k .. ": " .. v) 36 | end 37 | print('---- end response headers') 38 | end 39 | 40 | local function on_data(req, resp, data) 41 | print('---- start response body') 42 | io.write(data) 43 | print('---- end response body') 44 | end 45 | 46 | local function on_finished(req, resp) 47 | print('====================== Finished POST request =================') 48 | loop:unloop() 49 | end 50 | 51 | local post_data = form.new{ 52 | first_name = "tester", 53 | last_name = "smith", 54 | RequestMethod="RemoveSessions", 55 | SceneID="51985476-fc16-4d25-aadd-be67a2e0f980", 56 | } 57 | 58 | local req = client:request{ 59 | method = 'POST', 60 | host = 'localhost', 61 | path = '/', 62 | body = post_data, 63 | on_response = on_response, 64 | on_data = on_data, 65 | on_finished = on_finished, 66 | } 67 | 68 | loop:loop() 69 | 70 | -------------------------------------------------------------------------------- /examples/http_post.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local httpclient = require'handler.http.client' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | local tremove = table.remove 25 | 26 | local client = httpclient.new(loop,{ 27 | user_agent = "HTTPClient tester" 28 | }) 29 | 30 | local function on_response(req, resp) 31 | print('---- start response headers: status code =' .. resp.status_code) 32 | for k,v in pairs(resp.headers) do 33 | print(k .. ": " .. v) 34 | end 35 | print('---- end response headers') 36 | end 37 | 38 | local function on_data(req, resp, data) 39 | print('---- start response body') 40 | if data then 41 | io.write(data) 42 | end 43 | print('---- end response body') 44 | end 45 | 46 | local function on_finished(req, resp) 47 | print('====================== Finished POST request =================') 48 | loop:unloop() 49 | end 50 | 51 | local post_url = arg[1] or 'http://localhost/' 52 | 53 | local post_data = arg[2] or "this is a test" 54 | 55 | local req = client:request{ 56 | method = 'POST', 57 | url = post_url, 58 | host = 'localhost', 59 | path = '/', 60 | body = post_data, 61 | on_response = on_response, 62 | on_data = on_data, 63 | on_finished = on_finished, 64 | } 65 | 66 | loop:loop() 67 | 68 | -------------------------------------------------------------------------------- /examples/http_server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local httpserver = require'handler.http.server' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | local tremove = table.remove 25 | 26 | local function on_data(req, resp, data) 27 | print('---- start request body') 28 | if data then io.write(data) end 29 | print('---- end request body') 30 | end 31 | 32 | local function on_finished(req, resp) 33 | local html = 'Hello, World!Hello, World!' 34 | print('---- request finished, send response') 35 | resp:set_status(200) 36 | resp:set_header('Content-Type', 'text/html') 37 | resp:set_header('Content-Length', #html) 38 | resp:set_body(html) 39 | resp:send() 40 | end 41 | 42 | local function on_response_sent(resp) 43 | print('---- response sent') 44 | end 45 | 46 | local function on_request(server, req, resp) 47 | print('---- start request headers: method =' .. req.method .. ', url = ' .. req.url) 48 | for k,v in pairs(req.headers) do 49 | print(k .. ": " .. v) 50 | end 51 | print('---- end request headers') 52 | -- check for '/favicon.ico' requests. 53 | if req.url:lower() == '/favicon.ico' then 54 | -- return 404 Not found error 55 | resp:set_status(404) 56 | resp:send() 57 | return 58 | end 59 | -- add callbacks to request. 60 | req.on_data = on_data 61 | req.on_finished = on_finished 62 | -- add response callbacks. 63 | resp.on_response_sent = on_response_sent 64 | end 65 | 66 | local server = httpserver.new(loop,{ 67 | -- set HTTP Server's "name/version" string. 68 | name = string.format("Test-HTTPServer/%f", math.pi), 69 | -- new request callback. 70 | on_request = on_request, 71 | -- timeouts 72 | request_head_timeout = 1.0, 73 | request_body_timeout = 1.0, 74 | write_timeout = 1.0, 75 | keep_alive_timeout = 1.0, 76 | max_keep_alive_requests = 10, 77 | }) 78 | 79 | for i=1,#arg do 80 | print("HTTP server listen on:", arg[i]) 81 | server:listen_uri(arg[i]) 82 | end 83 | 84 | if #arg < 1 then 85 | local default_uri = 'tcp://127.0.0.1:1080/' 86 | print("HTTP server listen on default port:", default_uri) 87 | server:listen_uri(default_uri) 88 | end 89 | 90 | loop:loop() 91 | 92 | -------------------------------------------------------------------------------- /examples/http_server_file.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local httpserver = require'handler.http.server' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local lfs = require'lfs' 26 | 27 | local ltn12 = require'ltn12' 28 | 29 | local tremove = table.remove 30 | 31 | local filename = 'index.html' 32 | local filesize = 0 33 | 34 | local function on_request(server, req, resp) 35 | print('---- request finished, send response') 36 | resp:set_header("Content-Disposition", "attachment; filename=" .. filename) 37 | resp:set_header('Content-Type', 'application/octet-stream') 38 | resp:set_header('Content-Length', filesize) 39 | resp:set_body(ltn12.source.file(io.open(filename, 'rb'))) 40 | resp:set_status(200) 41 | resp:send() 42 | resp.on_error = function(resp, req, err) 43 | print('error sending http response:', err) 44 | end 45 | end 46 | 47 | local server = httpserver.new(loop, { 48 | -- new request callback. 49 | on_request = on_request, 50 | -- timeouts 51 | request_head_timeout = 1.0, 52 | request_body_timeout = 1.0, 53 | write_timeout = 1.0, 54 | keep_alive_timeout = 1.0, 55 | max_keep_alive_requests = 10, 56 | }) 57 | 58 | -- get filename from command line. 59 | if #arg >= 1 then 60 | filename = tremove(arg, 1) 61 | end 62 | 63 | filesize = assert(lfs.attributes(filename, 'size')) 64 | 65 | for i=1,#arg do 66 | print("HTTP server listen on:", arg[i]) 67 | server:listen_uri(arg[i]) 68 | end 69 | 70 | if #arg < 1 then 71 | local default_uri = 'tcp://127.0.0.1:1080/' 72 | print("HTTP server listen on default port:", default_uri) 73 | server:listen_uri(default_uri) 74 | end 75 | 76 | loop:loop() 77 | 78 | -------------------------------------------------------------------------------- /examples/http_zmq_proxy.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local tconcat = table.concat 22 | 23 | local httpserver = require'handler.http.server' 24 | local ev = require'ev' 25 | local loop = ev.Loop.default 26 | local zmq = require'handler.zmq' 27 | 28 | local ctx = zmq.init(loop, 1) 29 | 30 | if #arg < 1 then 31 | print('Usage: ' .. arg[0] .. ' ') 32 | return 33 | end 34 | 35 | local http_uri, zmq_uri = arg[1], arg[2] 36 | 37 | local client_requests = {} 38 | local next_id = 0 39 | 40 | -- define ZMQ response handler 41 | local function zmq_on_msg(sock, data) 42 | if type(data) ~= 'table' then 43 | print('INVALID: Response from ZMQ backend:', data) 44 | return 45 | end 46 | -- get req_id from message. 47 | local req_id = data[1] 48 | -- validate message envelope 49 | if req_id:match("^") 77 | zxreq:connect(zmq_uri) 78 | 79 | local function new_http_post_request(req, resp) 80 | -- create a new client request id 81 | -- TODO: use smaller req_id values and re-use old ids. 82 | local req_id = '' 83 | next_id = next_id + 1 84 | 85 | -- add http response object to 'client_requests' queue. 86 | client_requests[req_id] = resp 87 | 88 | local post_data = '' 89 | local function http_on_data(req, resp, data) 90 | if data then 91 | post_data = post_data .. data 92 | end 93 | end 94 | 95 | local function http_on_finished(req, resp) 96 | print('---- start request body') 97 | print(post_data) 98 | zxreq:send({ req_id, "", post_data }) 99 | print('---- end request body') 100 | end 101 | 102 | local function http_on_close(resp, err) 103 | print('---- http_on_close') 104 | client_requests[req_id] = nil 105 | end 106 | 107 | -- add callbacks to request. 108 | req.on_data = http_on_data 109 | req.on_finished = http_on_finished 110 | req.on_error = http_on_close -- cleanup on in-complete request 111 | -- add response callbacks. 112 | resp.on_error = http_on_close -- cleanup on request abort 113 | end 114 | 115 | local function on_request(server, req, resp) 116 | print('---- start request headers: method =' .. req.method .. ', url = ' .. req.url) 117 | for k,v in pairs(req.headers) do 118 | print(k .. ": " .. v) 119 | end 120 | print('---- end request headers') 121 | -- POST request 122 | if req.method == 'POST' then 123 | new_http_post_request(req, resp) 124 | else 125 | -- return 404 Not found error 126 | resp:set_status(404) 127 | resp:send() 128 | return 129 | end 130 | end 131 | 132 | local server = httpserver.new(loop,{ 133 | -- set HTTP Server's "name/version" string. 134 | name = string.format("ZMQ-HTTPServer/1.0"), 135 | -- new request callback. 136 | on_request = on_request, 137 | -- timeouts 138 | request_head_timeout = 1.0, 139 | request_body_timeout = 1.0, 140 | write_timeout = 1.0, 141 | keep_alive_timeout = 1.0, 142 | max_keep_alive_requests = 10, 143 | }) 144 | 145 | print("HTTP server listen on:", http_uri) 146 | server:listen_uri(http_uri) 147 | 148 | loop:loop() 149 | 150 | -------------------------------------------------------------------------------- /examples/localhost.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICgDCCAemgAwIBAgIJAJa8SR/kSZFtMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xMTAzMDIwOTQ0 5 | NTdaFw0xMjAzMDEwOTQ0NTdaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l 6 | LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV 7 | BAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA3lBKT4qA 8 | tFuUmNwD+dpC5UohV8U80i6bMxXpQQwDtWbCfAfXp3GwM2GYc61o8tXYGJ09Q+97 9 | ircCqyP+HOf8LpBU8pfcxCAM06vVx2DzR9kYmZSfDA8Ow7vZP5r6fv7D8FWKViBx 10 | ZiihgVEB5JS/oIkDmNpx5+SOSlDgGkxVqb0CAwEAAaNQME4wHQYDVR0OBBYEFJM+ 11 | TvnTkNoFNKUkYPIprHeHP15JMB8GA1UdIwQYMBaAFJM+TvnTkNoFNKUkYPIprHeH 12 | P15JMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAQO2klbHFO9fBHuul 13 | rQbA8C5W6B+lcfpev2QD8FSIS9ObaJoBLlcrEmGeOVEYNWDdHAVAc/Aj8LqBIWn0 14 | Bi24XK7DqkLot0rZKGUPZVVFNR9WkrHdokPnWUq0NtdMfgxNJ8V8j5xJShRP++8G 15 | NyOFCnAFklOGW7TjwNHJHNjn2Yc= 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /examples/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWwIBAAKBgQDeUEpPioC0W5SY3AP52kLlSiFXxTzSLpszFelBDAO1ZsJ8B9en 3 | cbAzYZhzrWjy1dgYnT1D73uKtwKrI/4c5/wukFTyl9zEIAzTq9XHYPNH2RiZlJ8M 4 | Dw7Du9k/mvp+/sPwVYpWIHFmKKGBUQHklL+giQOY2nHn5I5KUOAaTFWpvQIDAQAB 5 | AoGACLtCj+kysOK/7VkG1vNZmUPWF2ppvx+RfOopZSKhqqhEjzaHo831S69Tm4d+ 6 | DbuUZzMVyutHAOW7NxmMy3nZD7BhFKBA/pytqcRUeModvv4SGPFImauZoO1cG6aX 7 | oI0M4Bl7R/XRRhFWdyp3+s9BSEI/HxE7DJwJZcr42DnWvcECQQDzs7yralyiJuXA 8 | 6W4VZVf3NROHVfGg9I3HE7EnbPXBZAu3RGHbCiielk1u/7SJE3PQyi3thFpoj/ch 9 | aCO6LCl1AkEA6Yg/I8g/iv++YNq7srySNoRXxFSLl1jQz3jf74wHrjcAbjK3GIZ7 10 | JCXOiys8QV6ltIGw6Fdjzb7jjxT8EpAuKQJAduDJexCySUMSNk1oLrW1+FgCw7TP 11 | 3oUNF/xqIWJMa18DPA32ciP9doa5FRlVFrzPdRz61G8IrxLzKW+kZe+e9QJAIi/Z 12 | 3mkgND8AJbmfpKjKVsTE/G7MCJnt55FwZub+8NgIbhlqiGKXgFEwjVsE9STf6S7b 13 | MQgqCiKZuYZ+6FDHEQJAHixDSsHsc1QTLWBMTAbQb5VjUnh0+8+pO3hhIk1Fg67h 14 | qhrSZJmnU0Nl5dHgKGNZ7GTblZlTMb4ASnXetwmkHg== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /examples/tcp_client.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local connection = require'handler.connection' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local tcp_client_mt = { 26 | handle_error = function(self, err) 27 | print('tcp_client.error:', err) 28 | end, 29 | handle_connected = function(self) 30 | print('tcp_client.connected') 31 | end, 32 | handle_data = function(self, data) 33 | print('tcp_client.data:', data) 34 | end, 35 | } 36 | tcp_client_mt.__index = tcp_client_mt 37 | 38 | -- new tcp client 39 | local function new_tcp_client(host, port) 40 | local self = setmetatable({}, tcp_client_mt) 41 | self.sock = connection.tcp(loop, self, host, port) 42 | return self 43 | end 44 | 45 | local host, port = (arg[1] or 'localhost:8081'):match('^([^:]*):(.*)$') 46 | local client = new_tcp_client(host, port) 47 | 48 | loop:loop() 49 | 50 | -------------------------------------------------------------------------------- /examples/tcp_server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local acceptor = require'handler.acceptor' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local tcp_client_mt = { 26 | handle_error = function(self, err) 27 | print('tcp_client.error:', self, err) 28 | self.timer:stop(loop) 29 | end, 30 | handle_connected = function(self) 31 | print('tcp_client.connected:', self) 32 | end, 33 | handle_data = function(self, data) 34 | print('tcp_client.data:', self, data) 35 | end, 36 | handle_timer = function(self) 37 | self.sock:send('ping\n') 38 | end, 39 | } 40 | tcp_client_mt.__index = tcp_client_mt 41 | 42 | -- new tcp client 43 | local function new_tcp_client(sock) 44 | print('new_tcp_client:', sock) 45 | local self = setmetatable({}, tcp_client_mt) 46 | sock:sethandler(self) 47 | self.sock = sock 48 | 49 | -- create timer watcher 50 | self.timer = ev.Timer.new(function() 51 | self:handle_timer() 52 | end, 1.0, 1.0) 53 | self.timer:start(loop) 54 | return self 55 | end 56 | 57 | -- new tcp server 58 | local function new_server(port, handler) 59 | print('New tcp server listen on: ' .. port) 60 | return acceptor.tcp(loop, handler, '*', port, 1024) 61 | end 62 | 63 | local port = arg[1] or 8081 64 | local server = new_server(port, new_tcp_client) 65 | 66 | loop:loop() 67 | 68 | -------------------------------------------------------------------------------- /examples/tls_tcp_client.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local nixio = require'nixio' 22 | local connection = require'handler.connection' 23 | local ev = require'ev' 24 | local loop = ev.Loop.default 25 | 26 | -- create client-side TLS context 27 | local tls = nixio.tls'client' 28 | 29 | local tcp_client_mt = { 30 | handle_error = function(self, err) 31 | print('tcp_client.error:', err) 32 | end, 33 | handle_connected = function(self) 34 | print('tcp_client.connected') 35 | end, 36 | handle_data = function(self, data) 37 | print('tcp_client.data:', data) 38 | -- echo data 39 | --self.sock:send(data) 40 | end, 41 | } 42 | tcp_client_mt.__index = tcp_client_mt 43 | 44 | -- new tcp client 45 | local function new_tcp_client(host, port) 46 | local self = setmetatable({}, tcp_client_mt) 47 | self.sock = connection.tls_tcp(loop, self, host, port, tls) 48 | return self 49 | end 50 | 51 | local host, port = (arg[1] or 'localhost:4081'):match('^([^:]*):(.*)$') 52 | local client = new_tcp_client(host, port) 53 | 54 | loop:loop() 55 | 56 | -------------------------------------------------------------------------------- /examples/tls_tcp_server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local acceptor = require'handler.acceptor' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local tcp_client_mt = { 26 | handle_error = function(self, err) 27 | print('tcp_client.error:', self, err) 28 | self.timer:stop(loop) 29 | end, 30 | handle_connected = function(self) 31 | print('tcp_client.connected:', self) 32 | end, 33 | handle_data = function(self, data) 34 | print('tcp_client.data:', self, data) 35 | end, 36 | handle_timer = function(self) 37 | self.sock:send('ping\n') 38 | end, 39 | } 40 | tcp_client_mt.__index = tcp_client_mt 41 | 42 | -- new tcp client 43 | local function new_tcp_client(sock) 44 | print('new_tcp_client:', sock) 45 | local self = setmetatable({ sock = sock }, tcp_client_mt) 46 | sock:sethandler(self) 47 | 48 | -- create timer watcher 49 | self.timer = ev.Timer.new(function() 50 | self:handle_timer() 51 | end, 1.0, 1.0) 52 | self.timer:start(loop) 53 | return self 54 | end 55 | 56 | -- new tcp server 57 | local function new_server(port, handler, tls) 58 | print('New tcp server listen on: ' .. port) 59 | if tls then 60 | return acceptor.tls_tcp(loop, handler, '*', port, tls, 1024) 61 | else 62 | return acceptor.tcp(loop, handler, '*', port, 1024) 63 | end 64 | end 65 | 66 | local port = arg[1] or 4081 67 | local key = arg[2] or 'examples/localhost.key' 68 | local cert = arg[3] or 'examples/localhost.cert' 69 | 70 | -- create server-side TLS Context. 71 | local tls = nixio.tls'server' 72 | assert(tls:set_key(key)) 73 | assert(tls:set_cert(cert)) 74 | 75 | local server = new_server(port, new_tcp_client, tls) 76 | 77 | loop:loop() 78 | 79 | -------------------------------------------------------------------------------- /examples/udp_client.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local connection = require'handler.connection' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local udp_client_mt = { 26 | handle_error = function(self, err) 27 | if err ~= 'closed' then 28 | print('udp_client.error:', err) 29 | end 30 | end, 31 | handle_connected = function(self) 32 | print('udp_client.connected') 33 | self.sock:send('hello world!\n') 34 | end, 35 | handle_data = function(self, data) 36 | print('udp_client.data:',data) 37 | self.sock:send('echo:' .. data .. '\n') 38 | end, 39 | } 40 | udp_client_mt.__index = udp_client_mt 41 | 42 | -- new udp client 43 | local function new_udp_client(host, port) 44 | local self = setmetatable({}, udp_client_mt) 45 | self.sock = connection.udp(loop, self, host, port) 46 | return self 47 | end 48 | 49 | local host = arg[1] or "localhost" 50 | local port = arg[2] or 8081 51 | local client = new_udp_client(host, port) 52 | 53 | loop:loop() 54 | 55 | -------------------------------------------------------------------------------- /examples/udp_server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local acceptor = require'handler.acceptor' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local udp_client_mt = { 26 | handle_error = function(self, err) 27 | print('udp_client.error:', self, err) 28 | self.timer:stop(loop) 29 | end, 30 | handle_connected = function(self) 31 | print('udp_client.connected:', self) 32 | end, 33 | handle_data = function(self, data) 34 | print('udp_client.data:', self, data) 35 | end, 36 | handle_timer = function(self) 37 | self.sock:send('ping\n') 38 | end, 39 | } 40 | udp_client_mt.__index = udp_client_mt 41 | 42 | -- new udp client 43 | local function new_udp_client(sock) 44 | print('new_udp_client:', sock) 45 | local self = setmetatable({}, udp_client_mt) 46 | sock:sethandler(self) 47 | self.sock = sock 48 | 49 | -- create timer watcher 50 | self.timer = ev.Timer.new(function() 51 | self:handle_timer() 52 | end, 1.0, 1.0) 53 | self.timer:start(loop) 54 | return self 55 | end 56 | 57 | -- new udp server 58 | local function new_server(port, handler) 59 | print('New udp server listen on: ' .. port) 60 | return acceptor.udp(loop, handler, '*', port, 1024) 61 | end 62 | 63 | local port = arg[1] or 8081 64 | local server = new_server(port, new_udp_client) 65 | 66 | loop:loop() 67 | 68 | -------------------------------------------------------------------------------- /examples/zmq_pair_client.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | -- define PAIR worker 28 | local function handle_msg(sock, data) 29 | print(data) 30 | end 31 | 32 | -- create PAIR worker 33 | local zpair = ctx:pair(handle_msg) 34 | 35 | zpair:connect("tcp://localhost:5555") 36 | 37 | local function io_in_cb() 38 | local line = io.read("*l") 39 | zpair:send(line) 40 | end 41 | local io_in = ev.IO.new(io_in_cb, 0, ev.READ) 42 | io_in:start(loop) 43 | 44 | loop:loop() 45 | 46 | -------------------------------------------------------------------------------- /examples/zmq_pair_server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | -- define PAIR worker 28 | local function handle_msg(sock, data) 29 | print(data) 30 | end 31 | 32 | -- create PAIR worker 33 | local zpair = ctx:pair(handle_msg) 34 | 35 | zpair:bind("tcp://lo:5555") 36 | 37 | local function io_in_cb() 38 | local line = io.read("*l") 39 | zpair:send(line) 40 | end 41 | local io_in = ev.IO.new(io_in_cb, 0, ev.READ) 42 | io_in:start(loop) 43 | 44 | loop:loop() 45 | 46 | -------------------------------------------------------------------------------- /examples/zmq_pub.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | local zpub = ctx:pub() 28 | 29 | zpub:bind("tcp://lo:5555") 30 | zpub:connect("tcp://localhost:5556") -- you can bind & connect from the same zmq socket. 31 | 32 | local msg_id = 1 33 | 34 | local function timer_cb() 35 | zpub:send(tostring(msg_id)) 36 | msg_id = msg_id + 1 37 | end 38 | local timer = ev.Timer.new(timer_cb, 0.5, 0.5) 39 | timer:start(loop) 40 | 41 | loop:loop() 42 | 43 | -------------------------------------------------------------------------------- /examples/zmq_pull.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | -- define PULL worker 28 | local function handle_msg(sock, data) 29 | print(data) 30 | end 31 | 32 | -- create PULL worker 33 | local zpull = ctx:pull(handle_msg) 34 | 35 | zpull:connect("tcp://localhost:5555") 36 | 37 | loop:loop() 38 | 39 | -------------------------------------------------------------------------------- /examples/zmq_push.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | local zpush = ctx:push() 28 | 29 | zpush:bind("tcp://lo:5555") 30 | 31 | local msg_id = 1 32 | 33 | local function timer_cb() 34 | zpush:send(tostring(msg_id)) 35 | msg_id = msg_id + 1 36 | end 37 | local timer = ev.Timer.new(timer_cb, 0.5, 0.5) 38 | timer:start(loop) 39 | 40 | loop:loop() 41 | 42 | -------------------------------------------------------------------------------- /examples/zmq_queue_server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, replish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | local tinsert = table.insert 28 | local tremove = table.remove 29 | local work_requests = {} 30 | 31 | --[[ 32 | TODO: 33 | * should have job pull & job results messages that the workers can send, that way 34 | workers can send the jobs results, then exit or do after job clean-up work before 35 | requesting next job 36 | ]] 37 | 38 | -- define request handler 39 | local function handle_msg(sock, msg) 40 | print('server:', unpack(msg)) 41 | local addr = {} 42 | -- get address parts of message 43 | for i=1,#msg do 44 | local part = msg[i] 45 | addr[i] = part 46 | if part == '' then break end 47 | end 48 | -- queue work request 49 | print('server: queue worker address:', unpack(addr)) 50 | tinsert(work_requests, addr) 51 | end 52 | 53 | -- create response worker 54 | local zxrep = ctx:xrep(handle_msg) 55 | 56 | zxrep:identity("") 57 | zxrep:bind("tcp://lo:5555") 58 | 59 | local function io_in_cb() 60 | -- get job 61 | local line = io.read("*l") 62 | -- send job to first queued worker 63 | local addr = tremove(work_requests) 64 | if addr then 65 | -- add job to message address 66 | tinsert(addr, line) 67 | zxrep:send(addr) 68 | else 69 | print('WARNING: no workers waiting for jobs. TODO: implement job wait queue.') 70 | end 71 | end 72 | local io_in = ev.IO.new(io_in_cb, 0, ev.READ) 73 | io_in:start(loop) 74 | 75 | -- create PUB socket for broadcast of status. 76 | local zpub = ctx:pub() 77 | zpub:bind("tcp://lo:5556") 78 | 79 | -- use server start time to allow workers to detect server restarts. 80 | -- TODO: use a better time source or a true random number source 81 | local start_time = tostring(os.time()) 82 | 83 | local function status_update_cb() 84 | zpub:send(start_time) 85 | end 86 | local status_update = ev.Timer.new(status_update_cb, 1.0, 1.0) 87 | status_update:start(loop) 88 | 89 | loop:loop() 90 | 91 | -------------------------------------------------------------------------------- /examples/zmq_rep_server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, replish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | -- define request handler 28 | local function handle_msg(sock, data) 29 | print("client request:\n", unpack(data)) 30 | assert(sock:send(data)) 31 | end 32 | 33 | -- create response worker 34 | local zrep = ctx:rep(handle_msg) 35 | 36 | zrep:identity("") 37 | zrep:bind("tcp://lo:5555") 38 | 39 | loop:loop() 40 | 41 | -------------------------------------------------------------------------------- /examples/zmq_req_client.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | -- define response handler 28 | local function handle_msg(sock, data) 29 | print("server response:\n", unpack(data)) 30 | end 31 | 32 | -- create REQ worker 33 | local zreq = ctx:req(handle_msg) 34 | 35 | zreq:identity("") 36 | zreq:connect("tcp://localhost:5555") 37 | 38 | local function io_in_cb() 39 | local line = io.read("*l") 40 | -- send request message. 41 | assert(zreq:send({"request:", line})) 42 | end 43 | local io_in = ev.IO.new(io_in_cb, 0, ev.READ) 44 | io_in:start(loop) 45 | 46 | loop:loop() 47 | 48 | -------------------------------------------------------------------------------- /examples/zmq_sub.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | -- define SUB worker 28 | local function handle_msg(sock, data) 29 | print(data) 30 | end 31 | 32 | -- create SUB worker 33 | local zsub = ctx:sub(handle_msg) 34 | 35 | zsub:sub("") 36 | zsub:connect("tcp://localhost:5555") 37 | 38 | loop:loop() 39 | 40 | -------------------------------------------------------------------------------- /examples/zmq_sub_server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | -- define SUB worker 28 | local function handle_msg(sock, data) 29 | print(data) 30 | end 31 | 32 | -- create SUB worker 33 | local zsub = ctx:sub(handle_msg) 34 | 35 | zsub:sub("") 36 | zsub:bind("tcp://lo:5556") 37 | 38 | loop:loop() 39 | 40 | -------------------------------------------------------------------------------- /examples/zmq_tcp_publisher.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local acceptor = require'handler.acceptor' 22 | local zmq = require'handler.zmq' 23 | local ev = require'ev' 24 | local loop = ev.Loop.default 25 | 26 | local ctx = zmq.init(loop, 1) 27 | 28 | local zpub = ctx:pub() 29 | 30 | zpub:bind("tcp://lo:5555") 31 | 32 | local msg_id = 1 33 | 34 | local tcp_client_mt = { 35 | handle_error = function(self, err) 36 | if err ~= 'closed' then 37 | print('tcp_client:', err) 38 | end 39 | end, 40 | handle_connected = function(self) 41 | end, 42 | handle_data = function(self, data) 43 | zpub:send(tostring(msg_id) .. ':' .. data) 44 | msg_id = msg_id + 1 45 | end, 46 | } 47 | tcp_client_mt.__index = tcp_client_mt 48 | 49 | -- new tcp client 50 | local function new_tcp_client(sock) 51 | local self = setmetatable({}, tcp_client_mt) 52 | sock:sethandler(self) 53 | self.sock = sock 54 | return self 55 | end 56 | 57 | -- new tcp server 58 | local function new_server(port, handler) 59 | print('New tcp server listen on: ' .. port) 60 | return acceptor.tcp(loop, handler, '*', port, 1024) 61 | end 62 | 63 | local port = arg[1] or 8081 64 | local server = new_server(port, new_tcp_client) 65 | 66 | loop:loop() 67 | 68 | -------------------------------------------------------------------------------- /examples/zmq_worker_client.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | local socket = require'socket' 25 | 26 | local ctx = zmq.init(loop, 1) 27 | 28 | local function get_next_job(sock, last_job_response) 29 | -- send job request 30 | print('send job request') 31 | assert(sock:send({last_job_response})) 32 | end 33 | 34 | -- define response handler 35 | local function handle_job(sock, data) 36 | print("got job:\n", data) 37 | -- DO WORK 38 | socket.sleep(1) 39 | -- get next job 40 | get_next_job(sock, 'echo job:' .. data) 41 | end 42 | 43 | -- create socket for requesting jobs 44 | local zreq = nil 45 | math.randomseed(os.time()) 46 | 47 | local function start_request_socket() 48 | -- close old socket 49 | if zreq then 50 | print('closing old request socket.') 51 | zreq:close() 52 | end 53 | 54 | print('connect request socket to queue server.') 55 | zreq = ctx:req(handle_job) 56 | 57 | zreq:identity(string.format("",math.floor(100000 * math.random()))) 58 | zreq:connect("tcp://localhost:5555") 59 | 60 | -- request first job. 61 | get_next_job(zreq, '') 62 | end 63 | 64 | local queue_start_time = nil 65 | -- subscribe to queue server to detect server restarts. 66 | local function handle_sub(sock, data) 67 | if not queue_start_time then 68 | print('Got first queue start time message.') 69 | -- we just started so this is our first message from the server. 70 | -- so store the server's start time. 71 | queue_start_time = data 72 | -- and connect request socket. 73 | start_request_socket() 74 | elseif queue_start_time ~= data then 75 | print('Got NEW queue start time message, queue server must have restarted.') 76 | -- detected different queue server, the old one must have died. 77 | queue_start_time = data 78 | -- and re-connect request socket. 79 | start_request_socket() 80 | end 81 | end 82 | 83 | -- subscribe to queue server 84 | local zsub = ctx:sub(handle_sub) 85 | 86 | zsub:sub("") 87 | zsub:connect("tcp://localhost:5556") 88 | 89 | loop:loop() 90 | 91 | -------------------------------------------------------------------------------- /examples/zmq_xrep_server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, replish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | -- define request handler 28 | local function handle_msg(sock, data) 29 | print("client request:", unpack(data)) 30 | sock:send(data) 31 | end 32 | 33 | -- create response worker 34 | local zxrep = ctx:xrep(handle_msg) 35 | 36 | zxrep:identity("") 37 | zxrep:bind("tcp://lo:5555") 38 | 39 | loop:loop() 40 | 41 | -------------------------------------------------------------------------------- /examples/zmq_xreq_client.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local zmq = require'handler.zmq' 22 | local ev = require'ev' 23 | local loop = ev.Loop.default 24 | 25 | local ctx = zmq.init(loop, 1) 26 | 27 | -- define response handler 28 | local function handle_msg(sock, data) 29 | print("server response:", unpack(data)) 30 | end 31 | 32 | -- create PAIR worker 33 | local zxreq = ctx:xreq(handle_msg) 34 | 35 | zxreq:identity("") 36 | zxreq:connect("tcp://localhost:5555") 37 | 38 | local function io_in_cb() 39 | local line = io.read("*l") 40 | -- send request message. 41 | zxreq:send({"", "request:", line}) 42 | end 43 | local io_in = ev.IO.new(io_in_cb, 0, ev.READ) 44 | io_in:start(loop) 45 | 46 | loop:loop() 47 | 48 | -------------------------------------------------------------------------------- /handler/acceptor.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | local print = print 23 | local assert = assert 24 | local tostring = tostring 25 | local tonumber = tonumber 26 | local error = error 27 | 28 | local ev = require"ev" 29 | 30 | local nixio = require"nixio" 31 | local new_socket = nixio.socket 32 | 33 | local connection = require"handler.connection" 34 | local wrap_connected = connection.wrap_connected 35 | local tls_connection = require"handler.connection.tls_backend" 36 | local tls_wrap_connected = connection.tls_wrap_connected 37 | 38 | local uri_mod = require"handler.uri" 39 | local uri_parse = uri_mod.parse 40 | local query_parse = uri_mod.parse_query 41 | 42 | local function n_assert(test, errno, msg) 43 | return assert(test, msg) 44 | end 45 | 46 | local acceptor_mt = { 47 | set_accept_max = function(self, max) 48 | self.accept_max = max 49 | end, 50 | close = function(self) 51 | self.io:stop(self.loop) 52 | self.server:close() 53 | end, 54 | } 55 | acceptor_mt.__index = acceptor_mt 56 | 57 | local function sock_new_bind_listen(loop, handler, domain, _type, host, port, tls, backlog) 58 | local is_dgram = (_type == 'dgram') 59 | -- nixio uses nil to mean any local address. 60 | if host == '*' then host = nil end 61 | -- 'backlog' is optional, it defaults to 256 62 | backlog = backlog or 256 63 | -- create nixio socket 64 | local server = new_socket(domain, _type) 65 | 66 | -- create acceptor 67 | local self = { 68 | loop = loop, 69 | handler = handler, 70 | server = server, 71 | host = host, 72 | port = port, 73 | tls = tls, 74 | -- max sockets to try to accept on one event 75 | accept_max = 100, 76 | backlog = backlog, 77 | } 78 | setmetatable(self, acceptor_mt) 79 | 80 | -- make nixio socket non-blocking 81 | server:setblocking(false) 82 | -- create callback closure 83 | local accept_cb 84 | if is_dgram then 85 | local udp_clients = setmetatable({},{__mode="v"}) 86 | accept_cb = function() 87 | local max = self.accept_max 88 | local count = 0 89 | repeat 90 | local data, c_ip, c_port = server:recvfrom(8192) 91 | if not data then 92 | if data ~= false then 93 | print('dgram_accept.error:', c_ip, c_port) 94 | end 95 | break 96 | else 97 | local client 98 | local c_key = c_ip .. tostring(c_port) 99 | -- look for existing client socket. 100 | local sock = udp_clients[c_key] 101 | -- check if socket is still valid. 102 | if sock and sock:is_closed() then 103 | sock = nil 104 | end 105 | -- if no cached socket, make a new one. 106 | if not sock then 107 | -- make a duplicate server socket 108 | sock = new_socket(domain, _type) 109 | n_assert(sock:setsockopt('socket', 'reuseaddr', 1)) 110 | n_assert(sock:bind(host, port)) 111 | -- connect dupped socket to client's ip:port 112 | n_assert(sock:connect(c_ip, c_port)) 113 | -- wrap nixio socket 114 | sock = wrap_connected(loop, nil, sock) 115 | udp_clients[c_key] = sock 116 | -- pass client socket to new connection handler. 117 | if handler(sock) == nil then 118 | -- connect handler returned nil, maybe they are rejecting connections. 119 | break 120 | end 121 | -- get socket handler object from socket 122 | client = sock.handler 123 | else 124 | -- get socket handler object from socket 125 | client = sock.handler 126 | end 127 | -- handle first data block from udp client 128 | client:handle_data(data) 129 | end 130 | count = count + 1 131 | until count >= max 132 | end 133 | else 134 | accept_cb = function() 135 | local max = self.accept_max 136 | local count = 0 137 | repeat 138 | local sock, errno, err = server:accept() 139 | if not sock then 140 | if sock ~= false then 141 | print('stream_accept.error:', errno, err) 142 | end 143 | break 144 | else 145 | -- wrap nixio socket 146 | if tls then 147 | sock = tls_wrap_connected(loop, nil, sock, tls) 148 | else 149 | sock = wrap_connected(loop, nil, sock) 150 | end 151 | if handler(sock) == nil then 152 | -- connect handler returned nil, maybe they are rejecting connections. 153 | break 154 | end 155 | end 156 | count = count + 1 157 | until count >= max 158 | end 159 | end 160 | -- create IO watcher. 161 | local fd = server:fileno() 162 | self.io = ev.IO.new(accept_cb, fd, ev.READ) 163 | 164 | self.io:start(loop) 165 | 166 | -- allow the address to be re-used. 167 | n_assert(server:setsockopt('socket', 'reuseaddr', 1)) 168 | -- bind socket to local host:port 169 | n_assert(server:bind(host, port)) 170 | if not is_dgram then 171 | -- set the socket to listening mode 172 | n_assert(server:listen(backlog)) 173 | end 174 | 175 | return self 176 | end 177 | 178 | module(...) 179 | 180 | function tcp6(loop, handler, host, port, backlog) 181 | -- remove '[]' from IPv6 addresses 182 | if host:sub(1,1) == '[' then 183 | host = host:sub(2,-2) 184 | end 185 | return sock_new_bind_listen(loop, handler, 'inet6', 'stream', host, port, nil, backlog) 186 | end 187 | 188 | function tcp(loop, handler, host, port, backlog) 189 | if host:sub(1,1) == '[' then 190 | return tcp6(loop, handler, host, port, backlog) 191 | else 192 | return sock_new_bind_listen(loop, handler, 'inet', 'stream', host, port, nil, backlog) 193 | end 194 | end 195 | 196 | function tls_tcp6(loop, handler, host, port, tls, backlog) 197 | -- remove '[]' from IPv6 addresses 198 | if host:sub(1,1) == '[' then 199 | host = host:sub(2,-2) 200 | end 201 | return sock_new_bind_listen(loop, handler, 'inet6', 'stream', host, port, tls, backlog) 202 | end 203 | 204 | function tls_tcp(loop, handler, host, port, tls, backlog) 205 | if host:sub(1,1) == '[' then 206 | return tls_tcp6(loop, handler, host, port, tls, backlog) 207 | else 208 | return sock_new_bind_listen(loop, handler, 'inet', 'stream', host, port, tls, backlog) 209 | end 210 | end 211 | 212 | function udp6(loop, handler, host, port, backlog) 213 | -- remove '[]' from IPv6 addresses 214 | if host:sub(1,1) == '[' then 215 | host = host:sub(2,-2) 216 | end 217 | return sock_new_bind_listen(loop, handler, 'inet6', 'dgram', host, port, nil, backlog) 218 | end 219 | 220 | function udp(loop, handler, host, port, backlog) 221 | if host:sub(1,1) == '[' then 222 | return udp6(loop, handler, host, port, backlog) 223 | else 224 | return sock_new_bind_listen(loop, handler, 'inet', 'dgram', host, port, nil, backlog) 225 | end 226 | end 227 | 228 | function unix(loop, handler, path, backlog) 229 | -- check if socket already exists. 230 | local stat, errno, err = nixio.fs.lstat(path) 231 | if stat then 232 | -- socket already exists, try to delete it. 233 | local stat, errno, err = nixio.fs.unlink(path) 234 | if not stat then 235 | print('Warning failed to delete old Unix domain socket: ', err) 236 | end 237 | end 238 | return sock_new_bind_listen(loop, handler, 'unix', 'stream', path, nil, nil, backlog) 239 | end 240 | 241 | function uri(loop, handler, uri, backlog, default_port) 242 | local orig_uri = uri 243 | -- parse uri 244 | uri = uri_parse(uri) 245 | local scheme = uri.scheme 246 | assert(scheme, "Invalid listen URI: " .. orig_uri) 247 | local q = query_parse(uri.query) 248 | -- check if query has a 'backlog' parameter 249 | if q.backlog then 250 | backlog = tonumber(q.backlog) 251 | end 252 | -- use scheme to select socket type. 253 | if scheme == 'unix' then 254 | return unix(loop, handler, uri.path, backlog) 255 | else 256 | local host, port = uri.host, uri.port or default_port 257 | if scheme == 'tcp' then 258 | return tcp(loop, handler, host, port, backlog) 259 | elseif scheme == 'tcp6' then 260 | return tcp6(loop, handler, host, port, backlog) 261 | elseif scheme == 'udp' then 262 | return udp(loop, handler, host, port, backlog) 263 | elseif scheme == 'udp6' then 264 | return udp6(loop, handler, host, port, backlog) 265 | else 266 | -- create TLS context 267 | local tls = nixio.tls(q.mode or 'server') -- default to server-side 268 | -- set key 269 | if q.key then 270 | tls:set_key(q.key) 271 | end 272 | -- set certificate 273 | if q.cert then 274 | tls:set_cert(q.cert) 275 | end 276 | -- set ciphers 277 | if q.ciphers then 278 | tls:set_ciphers(q.ciphers) 279 | end 280 | if scheme == 'tls' then 281 | return tls_tcp(loop, handler, host, port, tls, backlog) 282 | elseif scheme == 'tls6' then 283 | return tls_tcp6(loop, handler, host, port, tls, backlog) 284 | end 285 | end 286 | end 287 | error("Unknown listen URI scheme: " .. scheme) 288 | end 289 | 290 | -------------------------------------------------------------------------------- /handler/connection.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | local print = print 23 | local assert = assert 24 | local error = error 25 | 26 | local ev = require"ev" 27 | local nixio = require"nixio" 28 | local new_socket = nixio.socket 29 | 30 | local tls_backend = require"handler.connection.tls_backend" 31 | local sock_tls_wrap = tls_backend.wrap 32 | 33 | local uri_mod = require"handler.uri" 34 | local uri_parse = uri_mod.parse 35 | local query_parse = uri_mod.parse_query 36 | 37 | local function n_assert(test, errno, msg) 38 | return assert(test, msg) 39 | end 40 | 41 | -- important errors 42 | local EINPROGRESS = nixio.const.EINPROGRESS 43 | 44 | local function sock_setsockopt(self, level, option, value) 45 | return self.sock:setsockopt(level, option, value) 46 | end 47 | 48 | local function sock_getsockopt(self, level, option) 49 | return self.sock:getsockopt(level, option) 50 | end 51 | 52 | local function sock_getpeername(self) 53 | return self.sock:getpeername() 54 | end 55 | 56 | local function sock_getsockname(self) 57 | return self.sock:getsockname() 58 | end 59 | 60 | local function sock_block_read(self, block) 61 | -- block/unblock read 62 | if block ~= self.read_blocked then 63 | self.read_blocked = block 64 | if block then 65 | self.io_read:stop(self.loop) 66 | else 67 | self.io_read:start(self.loop) 68 | end 69 | end 70 | end 71 | 72 | local function sock_shutdown(self, read, write) 73 | local how = '' 74 | if read then 75 | how = 'rd' 76 | -- stop reading from socket, we don't want any more data. 77 | sock_block_read(self, true) 78 | end 79 | if write then 80 | how = how .. 'wr' 81 | end 82 | return self.sock:shutdown(how) 83 | end 84 | 85 | local function sock_close(self) 86 | local sock = self.sock 87 | if not sock then return end 88 | self.is_closing = true 89 | self.read_blocked = true 90 | if not self.write_buf or self.has_error then 91 | local loop = self.loop 92 | if self.write_timer then 93 | self.write_timer:stop(loop) 94 | end 95 | self.io_write:stop(loop) 96 | self.io_read:stop(loop) 97 | sock:close() 98 | self.sock = nil 99 | end 100 | end 101 | 102 | local function sock_handle_error(self, err) 103 | self.has_error = true -- mark socket as bad. 104 | sock_close(self) 105 | local handler = self.handler 106 | if handler then 107 | local errFunc = handler.handle_error 108 | if errFunc then 109 | errFunc(handler, err) 110 | else 111 | print('socket error:', err) 112 | end 113 | end 114 | end 115 | 116 | local function sock_set_write_timeout(self, timeout) 117 | local timer = self.write_timer 118 | -- default to no write timeout. 119 | timeout = timeout or -1 120 | self.write_timeout = timeout 121 | -- enable/disable timeout 122 | local is_disable = (timeout <= 0) 123 | -- create the write timer if one is needed. 124 | if not timer then 125 | -- don't create a disabled timer. 126 | if is_disable then return end 127 | timer = ev.Timer.new(function() 128 | sock_handle_error(self, 'write timeout') 129 | end, timeout, timeout) 130 | self.write_timer = timer 131 | -- enable timer if socket is write blocked. 132 | if self.write_blocked then 133 | timer:start(self.loop) 134 | end 135 | return 136 | end 137 | -- if the timer should be disabled. 138 | if is_disable then 139 | -- then disable the timer 140 | timer:stop(self.loop) 141 | return 142 | end 143 | -- update timeout interval and start the timer if socket is write blocked. 144 | if self.write_blocked then 145 | timer:again(self.loop, timeout) 146 | end 147 | end 148 | 149 | local function sock_reset_write_timeout(self) 150 | local timeout = self.write_timeout 151 | local timer = self.write_timer 152 | -- write timeout is disabled. 153 | if timeout < 0 or timer == nil then return end 154 | -- update timeout interval 155 | timer:again(self.loop, timeout) 156 | end 157 | 158 | local function sock_send_data(self, buf) 159 | local sock = self.sock 160 | local is_blocked = false 161 | 162 | local num, errno, err = sock:send(buf) 163 | if not num then 164 | -- got timeout error block writes. 165 | if num == false then 166 | -- got EAGAIN 167 | is_blocked = true 168 | else -- data == nil 169 | -- report error 170 | sock_handle_error(self, err) 171 | return nil, err 172 | end 173 | else 174 | -- trim sent data. 175 | if num < #buf then 176 | -- remove sent bytes from buffer. 177 | buf = buf:sub(num+1) 178 | -- partial send, not enough socket buffer space, so blcok writes. 179 | is_blocked = true 180 | else 181 | self.write_buf = nil 182 | if self.is_closing then 183 | -- write buffer is empty, finish closing socket. 184 | sock_close(self) 185 | return num, 'closed' 186 | end 187 | end 188 | end 189 | -- block/un-block write events. 190 | if is_blocked ~= self.write_blocked then 191 | self.write_blocked = is_blocked 192 | if is_blocked then 193 | self.write_buf = buf 194 | self.io_write:start(self.loop) 195 | -- socket is write blocked, start write timeout 196 | sock_reset_write_timeout(self) 197 | return num, 'blocked' 198 | else 199 | local loop = self.loop 200 | self.io_write:stop(loop) 201 | -- no data to write, so stop timer. 202 | if self.write_timer then 203 | self.write_timer:stop(loop) 204 | end 205 | end 206 | elseif is_blocked then 207 | -- reset write timeout, since some data was written and the socket is still write blocked. 208 | sock_reset_write_timeout(self) 209 | end 210 | return num 211 | end 212 | 213 | local function sock_send(self, data) 214 | -- only process send when given data to send. 215 | if data == nil or #data == 0 then return end 216 | local num, err 217 | local buf = self.write_buf 218 | if buf then 219 | buf = buf .. data 220 | else 221 | buf = data 222 | end 223 | if not self.write_blocked then 224 | num, err = sock_send_data(self, buf) 225 | else 226 | self.write_buf = buf 227 | -- let the caller know that the socket is blocked and data is being buffered 228 | err = 'blocked' 229 | end 230 | -- always return the size of the data passed in, since un-sent data will be buffered 231 | -- for sending later. 232 | return #data, err 233 | end 234 | 235 | local function sock_handle_connected(self) 236 | local handler = self.handler 237 | self.is_connecting = false 238 | if handler then 239 | local handle_connected = handler.handle_connected 240 | if handle_connected then 241 | handle_connected(handler) 242 | end 243 | end 244 | end 245 | 246 | local function sock_recv_data(self) 247 | local read_len = self.read_len 248 | local read_max = self.read_max 249 | local handler = self.handler 250 | local sock = self.sock 251 | local len = 0 252 | local is_connecting = self.is_connecting 253 | 254 | repeat 255 | local data, errno, err = sock:recv(read_len) 256 | if not data then 257 | if data == false then 258 | -- check if we where in the connecting state. 259 | if is_connecting then 260 | is_connecting = false 261 | sock_handle_connected(self) 262 | end 263 | -- no data 264 | return true 265 | else -- data == nil 266 | -- report error 267 | sock_handle_error(self, err) 268 | return false, err 269 | end 270 | end 271 | -- check if the other side shutdown there send stream 272 | if #data == 0 then 273 | -- report socket closed 274 | sock_handle_error(self, 'closed') 275 | return false, 'closed' 276 | end 277 | -- check if we where in the connecting state. 278 | if is_connecting then 279 | is_connecting = false 280 | sock_handle_connected(self) 281 | end 282 | -- pass read data to handler 283 | len = len + #data 284 | err = handler:handle_data(data) 285 | if err then 286 | -- report error 287 | sock_handle_error(self, err) 288 | return false, err 289 | end 290 | until len >= read_max or self.read_blocked 291 | 292 | return true 293 | end 294 | 295 | local function sock_sethandler(self, handler) 296 | self.handler = handler 297 | if handler and not self.is_connecting then 298 | -- raise 'connected' event for the new handler 299 | sock_handle_connected(self) 300 | end 301 | end 302 | 303 | local function sock_is_closed(self) 304 | return self.is_closing 305 | end 306 | 307 | local sock_mt = { 308 | is_tls = false, 309 | send = sock_send, 310 | getsockopt = sock_getsockopt, 311 | setsockopt = sock_setsockopt, 312 | getsockname = sock_getsockname, 313 | getpeername = sock_getpeername, 314 | shutdown = sock_shutdown, 315 | close = sock_close, 316 | block_read = sock_block_read, 317 | set_write_timeout = sock_set_write_timeout, 318 | sethandler = sock_sethandler, 319 | is_closed = sock_is_closed, 320 | } 321 | sock_mt.__index = sock_mt 322 | 323 | local function sock_wrap(loop, handler, sock, is_connected) 324 | -- create socket object 325 | local self = { 326 | loop = loop, 327 | handler = handler, 328 | sock = sock, 329 | is_connecting = true, 330 | write_blocked = false, 331 | write_timeout = -1, 332 | read_blocked = false, 333 | read_len = 8192, 334 | read_max = 65536, 335 | is_closing = false, 336 | } 337 | setmetatable(self, sock_mt) 338 | 339 | -- make nixio socket non-blocking 340 | sock:setblocking(false) 341 | -- get socket FD 342 | local fd = sock:fileno() 343 | -- create callback closure 344 | local write_cb = function() 345 | local num, err = sock_send_data(self, self.write_buf) 346 | if self.write_buf == nil and not self.is_closing then 347 | -- write buffer is empty and socket is still open, 348 | -- call drain callback. 349 | local handler = self.handler 350 | local drain = handler.handle_drain 351 | if drain then 352 | local err = drain(handler) 353 | if err then 354 | -- report error 355 | sock_handle_error(self, err) 356 | end 357 | end 358 | end 359 | end 360 | local read_cb = function() 361 | sock_recv_data(self) 362 | end 363 | 364 | -- create IO watchers. 365 | if is_connected then 366 | self.io_write = ev.IO.new(write_cb, fd, ev.WRITE) 367 | self.is_connecting = false 368 | else 369 | local connected_cb = function(loop, io, revents) 370 | if not self.write_blocked then 371 | io:stop(loop) 372 | end 373 | -- change callback to write_cb 374 | io:callback(write_cb) 375 | -- check for connect errors by tring to read from the socket. 376 | sock_recv_data(self) 377 | end 378 | self.io_write = ev.IO.new(connected_cb, fd, ev.WRITE) 379 | self.io_write:start(loop) 380 | end 381 | self.io_read = ev.IO.new(read_cb, fd, ev.READ) 382 | self.io_read:start(loop) 383 | 384 | return self 385 | end 386 | 387 | local function sock_new_connect(loop, handler, domain, _type, host, port, laddr, lport) 388 | -- create nixio socket 389 | local sock = new_socket(domain, _type) 390 | -- wrap socket 391 | local self = sock_wrap(loop, handler, sock) 392 | -- bind to local laddr/lport 393 | if laddr then 394 | n_assert(sock:setsockopt('socket', 'reuseaddr', 1)) 395 | n_assert(sock:bind(laddr, tonumber(lport or 0))) 396 | end 397 | -- connect to host:port 398 | local ret, errno, err = sock:connect(host, port) 399 | if not ret and errno ~= EINPROGRESS then 400 | -- report error 401 | sock_handle_error(self, err) 402 | return nil, err 403 | end 404 | return self 405 | end 406 | 407 | -- remove '[]' from IPv6 addresses 408 | local function strip_ipv6(ip6) 409 | if ip6 and ip6:sub(1,1) == '[' then 410 | return ip6:sub(2,-2) 411 | end 412 | return ip6 413 | end 414 | 415 | module(...) 416 | 417 | -- 418 | -- TCP/UDP/Unix sockets (non-tls) 419 | -- 420 | function tcp6(loop, handler, host, port, laddr, lport) 421 | host = strip_ipv6(host) 422 | laddr = strip_ipv6(laddr) 423 | return sock_new_connect(loop, handler, 'inet6', 'stream', host, port, laddr, lport) 424 | end 425 | 426 | function tcp(loop, handler, host, port, laddr, lport) 427 | if host:sub(1,1) == '[' then 428 | return tcp6(loop, handler, host, port, laddr, lport) 429 | else 430 | return sock_new_connect(loop, handler, 'inet', 'stream', host, port, laddr, lport) 431 | end 432 | end 433 | 434 | function udp6(loop, handler, host, port, laddr, lport) 435 | host = strip_ipv6(host) 436 | laddr = strip_ipv6(laddr) 437 | return sock_new_connect(loop, handler, 'inet6', 'dgram', host, port, laddr, lport) 438 | end 439 | 440 | function udp(loop, handler, host, port, laddr, lport) 441 | if host:sub(1,1) == '[' then 442 | return udp6(loop, handler, host, port, laddr, lport) 443 | else 444 | return sock_new_connect(loop, handler, 'inet', 'dgram', host, port, laddr, lport) 445 | end 446 | end 447 | 448 | function unix(loop, handler, path) 449 | return sock_new_connect(loop, handler, 'unix', 'stream', path) 450 | end 451 | 452 | function wrap_connected(loop, handler, sock) 453 | -- wrap socket 454 | return sock_wrap(loop, handler, sock, true) 455 | end 456 | 457 | -- 458 | -- TCP TLS sockets 459 | -- 460 | function tls_tcp(loop, handler, host, port, tls, is_client, laddr, lport) 461 | local self = tcp(loop, handler, host, port, laddr, lport) 462 | -- default to client-side TLS 463 | if is_client == nil then is_client = true end 464 | return sock_tls_wrap(self, tls, is_client) 465 | end 466 | 467 | function tls_tcp6(loop, handler, host, port, tls, is_client, laddr, lport) 468 | local self = tcp6(loop, handler, host, port, laddr, lport) 469 | -- default to client-side TLS 470 | if is_client == nil then is_client = true end 471 | return sock_tls_wrap(self, tls, is_client) 472 | end 473 | 474 | function tls_wrap_connected(loop, handler, sock, tls, is_client) 475 | -- wrap socket 476 | local self = sock_wrap(loop, handler, sock, false) 477 | -- default to server-side TLS 478 | if is_client == nil then is_client = false end 479 | return sock_tls_wrap(self, tls, is_client) 480 | end 481 | 482 | -- 483 | -- URI 484 | -- 485 | function uri(loop, handler, uri) 486 | local orig_uri = uri 487 | -- parse uri 488 | uri = uri_parse(uri) 489 | local scheme = uri.scheme 490 | assert(scheme, "Invalid listen URI: " .. orig_uri) 491 | local q = query_parse(uri.query) 492 | -- use scheme to select socket type. 493 | if scheme == 'unix' then 494 | return unix(loop, handler, uri.path) 495 | else 496 | local host, port = uri.host, uri.port or default_port 497 | if scheme == 'tcp' then 498 | return tcp(loop, handler, host, port, q.laddr, q.lport) 499 | elseif scheme == 'tcp6' then 500 | return tcp6(loop, handler, host, port, q.laddr, q.lport) 501 | elseif scheme == 'udp' then 502 | return udp(loop, handler, host, port, q.laddr, q.lport) 503 | elseif scheme == 'udp6' then 504 | return udp6(loop, handler, host, port, q.laddr, q.lport) 505 | else 506 | local mode = q.mode or 'client' 507 | local is_client = (mode == 'client') 508 | -- default to client-side 509 | -- create TLS context 510 | local tls = nixio.tls(mode) 511 | -- set key 512 | if q.key then 513 | tls:set_key(q.key) 514 | end 515 | -- set certificate 516 | if q.cert then 517 | tls:set_cert(q.cert) 518 | end 519 | -- set ciphers 520 | if q.ciphers then 521 | tls:set_ciphers(q.ciphers) 522 | end 523 | if scheme == 'tls' then 524 | return tls_tcp(loop, handler, host, port, tls, is_client, q.laddr, q.lport) 525 | elseif scheme == 'tls6' then 526 | return tls_tcp6(loop, handler, host, port, tls, is_client, q.laddr, q.lport) 527 | end 528 | end 529 | end 530 | error("Unknown listen URI scheme: " .. scheme) 531 | end 532 | 533 | -- export 534 | wrap = sock_wrap 535 | 536 | -------------------------------------------------------------------------------- /handler/connection/tls_backend.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | local print = print 23 | 24 | local nixio = require"nixio" 25 | 26 | local ev = require"ev" 27 | 28 | -- important SSL error codes 29 | local SSL_ERROR_SSL = 1 30 | local SSL_ERROR_WANT_READ = 2 31 | local SSL_ERROR_WANT_WRITE = 3 32 | local SSL_ERROR_WANT_X509_LOOKUP = 4 33 | local SSL_ERROR_SYSCALL = 5 34 | local SSL_ERROR_ZERO_RETURN = 6 35 | local SSL_ERROR_WANT_CONNECT = 7 36 | local SSL_ERROR_WANT_ACCEPT = 8 37 | 38 | local function sock_setsockopt(self, level, option, value) 39 | return self.sock.socket:setsockopt(level, option, value) 40 | end 41 | 42 | local function sock_getsockopt(self, level, option) 43 | return self.sock.socket:getsockopt(level, option) 44 | end 45 | 46 | local function sock_getpeername(self) 47 | return self.sock.socket:getpeername() 48 | end 49 | 50 | local function sock_getsockname(self) 51 | return self.sock.socket:getsockname() 52 | end 53 | 54 | local function sock_shutdown(self, read, write) 55 | if read then 56 | -- stop reading from socket, we don't want any more data. 57 | self.io_read:stop(self.loop) 58 | end 59 | end 60 | 61 | local function sock_close(self) 62 | self.is_closing = true 63 | if not self.write_buf or self.has_error then 64 | local loop = self.loop 65 | if self.write_timer then 66 | self.write_timer:stop(loop) 67 | end 68 | self.io_write:stop(loop) 69 | self.io_read:stop(loop) 70 | self.sock:shutdown() 71 | end 72 | end 73 | 74 | local function sock_block_read(self, block) 75 | -- block/unblock read 76 | if block ~= self.read_blocked then 77 | self.read_blocked = block 78 | if block then 79 | self.io_read:stop(self.loop) 80 | else 81 | self.io_read:start(self.loop) 82 | end 83 | end 84 | end 85 | 86 | local function sock_handle_error(self, err, errno) 87 | local handler = self.handler 88 | local errFunc = handler and handler.handle_error 89 | self.has_error = true -- mark socket as bad. 90 | sock_close(self) 91 | if err == nil then 92 | if errno == SSL_ERROR_SYSCALL then 93 | errno = nixio.errno() 94 | end 95 | err = 'TLS error code: ' .. tostring(errno) 96 | end 97 | if errFunc then 98 | errFunc(handler, err) 99 | else 100 | print('socket error:', err) 101 | end 102 | end 103 | 104 | local function sock_set_write_timeout(self, timeout) 105 | local timer = self.write_timer 106 | -- default to no write timeout. 107 | timeout = timeout or -1 108 | self.write_timeout = timeout 109 | -- enable/disable timeout 110 | local is_disable = (timeout <= 0) 111 | -- create the write timer if one is needed. 112 | if not timer then 113 | -- don't create a disabled timer. 114 | if is_disable then return end 115 | timer = ev.Timer.new(function() 116 | sock_handle_error(self, 'write timeout') 117 | end, timeout, timeout) 118 | self.write_timer = timer 119 | -- enable timer if socket is write blocked. 120 | if self.write_blocked then 121 | timer:start(self.loop) 122 | end 123 | return 124 | end 125 | -- if the timer should be disabled. 126 | if is_disable then 127 | -- then disable the timer 128 | timer:stop(self.loop) 129 | return 130 | end 131 | -- update timeout interval and start the timer if socket is write blocked. 132 | if self.write_blocked then 133 | timer:again(self.loop, timeout) 134 | end 135 | end 136 | 137 | local function sock_reset_write_timeout(self) 138 | local timeout = self.write_timeout 139 | local timer = self.write_timer 140 | -- write timeout is disabled. 141 | if timeout < 0 or timer == nil then return end 142 | -- update timeout interval 143 | timer:again(self.loop, timeout) 144 | end 145 | 146 | local function sock_send_data(self, buf) 147 | local sock = self.sock 148 | local is_blocked = false 149 | 150 | local num, errno, err = sock:send(buf) 151 | if not num then 152 | -- got timeout error block writes. 153 | if errno == SSL_ERROR_WANT_READ or errno == SSL_ERROR_WANT_WRITE then 154 | -- got EAGAIN 155 | is_blocked = true 156 | else 157 | -- report error 158 | sock_handle_error(self, err, errno) 159 | return nil, err 160 | end 161 | else 162 | -- trim sent data. 163 | if num < #buf then 164 | -- remove sent bytes from buffer. 165 | buf = buf:sub(num+1) 166 | -- partial send, not enough socket buffer space, so blcok writes. 167 | is_blocked = true 168 | else 169 | self.write_buf = nil 170 | if self.is_closing then 171 | -- write buffer is empty, finish closing socket. 172 | sock_close(self) 173 | return num, 'closed' 174 | end 175 | end 176 | end 177 | -- block/un-block write events. 178 | if is_blocked ~= self.write_blocked then 179 | self.write_blocked = is_blocked 180 | if is_blocked then 181 | self.write_buf = buf 182 | self.io_write:start(self.loop) 183 | -- socket is write blocked, start write timeout 184 | sock_reset_write_timeout(self) 185 | return num, 'blocked' 186 | else 187 | local loop = self.loop 188 | self.io_write:stop(loop) 189 | -- no data to write, so stop timer. 190 | if self.write_timer then 191 | self.write_timer:stop(loop) 192 | end 193 | end 194 | elseif is_blocked then 195 | -- reset write timeout, since some data was written and the socket is still write blocked. 196 | sock_reset_write_timeout(self) 197 | end 198 | return num 199 | end 200 | 201 | local function sock_send(self, data) 202 | -- only process send when given data to send. 203 | if data == nil or #data == 0 then return end 204 | local num, err 205 | local buf = self.write_buf 206 | if buf then 207 | buf = buf .. data 208 | else 209 | buf = data 210 | end 211 | if not self.write_blocked then 212 | num, err = sock_send_data(self, buf) 213 | else 214 | self.write_buf = buf 215 | -- let the caller know that the socket is blocked and data is being buffered 216 | err = 'blocked' 217 | end 218 | -- always return the size of the data passed in, since un-sent data will be buffered 219 | -- for sending later. 220 | return #data, err 221 | end 222 | 223 | local function sock_handle_connected(self) 224 | local handler = self.handler 225 | self.is_connecting = false 226 | if handler then 227 | local handle_connected = handler.handle_connected 228 | if handle_connected then 229 | handle_connected(handler) 230 | end 231 | end 232 | end 233 | 234 | local function sock_recv_data(self) 235 | local read_len = self.read_len 236 | local read_max = self.read_max 237 | local handler = self.handler 238 | local sock = self.sock 239 | local len = 0 240 | local is_connecting = self.is_connecting 241 | 242 | repeat 243 | local data, errno, err = sock:recv(read_len) 244 | if not data then 245 | if data == false then 246 | -- check if we where in the connecting state. 247 | if is_connecting then 248 | is_connecting = false 249 | sock_handle_connected(self) 250 | end 251 | elseif errno ~= SSL_ERROR_WANT_READ and errno ~= SSL_ERROR_WANT_WRITE then 252 | -- report error 253 | sock_handle_error(self, err, errno) 254 | return false, err 255 | end 256 | -- no data 257 | return true 258 | end 259 | -- check if the other side shutdown there send stream 260 | if #data == 0 then 261 | -- report socket closed 262 | sock_handle_error(self, 'closed') 263 | return false, 'closed' 264 | end 265 | -- check if we where in the connecting state. 266 | if is_connecting then 267 | is_connecting = false 268 | sock_handle_connected(self) 269 | end 270 | -- pass read data to handler 271 | len = len + #data 272 | err = handler:handle_data(data) 273 | if err then 274 | -- report error 275 | sock_handle_error(self, err) 276 | return false, err 277 | end 278 | until len >= read_max and not self.read_blocked 279 | 280 | return true 281 | end 282 | 283 | local function sock_sethandler(self, handler) 284 | self.handler = handler 285 | if handler and not self.is_connecting then 286 | -- raise 'connected' event for the new handler 287 | sock_handle_connected(self) 288 | end 289 | end 290 | 291 | local function sock_is_closed(self) 292 | return self.is_closing 293 | end 294 | 295 | local function sock_handshake(self, is_client) 296 | local stat, code 297 | if is_client then 298 | stat, code = self.sock:connect() 299 | else 300 | stat, code = self.sock:accept() 301 | end 302 | if stat then 303 | self.is_handshake_complete = true 304 | return true, code 305 | else 306 | self.is_handshake_complete = false 307 | return false, code 308 | end 309 | end 310 | 311 | local tls_sock_mt = { 312 | is_tls = true, 313 | send = sock_send, 314 | getsockopt = sock_getsockopt, 315 | setsockopt = sock_setsockopt, 316 | getsockname = sock_getsockname, 317 | getpeername = sock_getpeername, 318 | shutdown = sock_shutdown, 319 | close = sock_close, 320 | block_read = sock_block_read, 321 | set_write_timeout = sock_set_write_timeout, 322 | sethandler = sock_sethandler, 323 | is_closed = sock_is_closed, 324 | } 325 | tls_sock_mt.__index = tls_sock_mt 326 | 327 | local function sock_tls_wrap(self, tls, is_client) 328 | local loop = self.loop 329 | -- create TLS context 330 | tls = tls or nixio.tls(is_client and 'client' or 'server') 331 | -- convertion normal socket to TLS 332 | setmetatable(self, tls_sock_mt) 333 | self.sock = tls:create(self.sock) 334 | 335 | -- create callback closure 336 | local write_cb = function() 337 | local num, err = sock_send_data(self, self.write_buf) 338 | if self.write_buf == nil and not self.is_closing then 339 | -- write buffer is empty and socket is still open, 340 | -- call drain callback. 341 | local handler = self.handler 342 | local drain = handler.handle_drain 343 | if drain then 344 | local err = drain(handler) 345 | if err then 346 | -- report error 347 | sock_handle_error(self, err) 348 | end 349 | end 350 | end 351 | end 352 | local read_cb = function() 353 | sock_recv_data(self) 354 | end 355 | 356 | -- create callback for TLS handshake 357 | self.is_handshake_complete = false 358 | -- block writes until handshake is completed, to force queueing of sent data. 359 | self.write_blocked = true 360 | self.read_blocked = false -- no need to block reads. 361 | local handshake_cb = function() 362 | local is_handshake_complete, code = sock_handshake(self, is_client) 363 | if is_handshake_complete then 364 | self.write_blocked = false 365 | self.io_write:stop(loop) 366 | -- install normal read/write callbacks 367 | self.io_write:callback(write_cb) 368 | self.io_read:callback(read_cb) 369 | -- check if we where in the connecting state. 370 | if self.is_connecting then 371 | sock_handle_connected(self) 372 | end 373 | -- check for pending write data. 374 | local buf = self.write_buf 375 | if buf then 376 | sock_send_data(self, buf) 377 | end 378 | else 379 | if code == SSL_ERROR_WANT_WRITE then 380 | self.io_write:start(loop) 381 | elseif code == SSL_ERROR_WANT_READ then 382 | self.io_write:stop(loop) 383 | else 384 | -- report error 385 | sock_handle_error(self, "SSL_Error: code=" .. code) 386 | end 387 | end 388 | end 389 | self.io_write:callback(handshake_cb) 390 | self.io_read:callback(handshake_cb) 391 | -- start TLS handshake 392 | handshake_cb() 393 | 394 | -- always keep read events enabled. 395 | self.io_read:start(loop) 396 | 397 | return self 398 | end 399 | 400 | module(...) 401 | 402 | -- export 403 | wrap = sock_tls_wrap 404 | 405 | -------------------------------------------------------------------------------- /handler/datagram.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | local print = print 23 | local assert = assert 24 | 25 | local ev = require"ev" 26 | local nixio = require"nixio" 27 | local new_socket = nixio.socket 28 | 29 | local function dgram_setsockopt(self, level, option, value) 30 | return self.sock:setsockopt(level, option, value) 31 | end 32 | 33 | local function dgram_getsockopt(self, level, option) 34 | return self.sock:getsockopt(level, option) 35 | end 36 | 37 | local function dgram_setsockname(self, addr, port) 38 | return self.sock:setsockname(addr, port) 39 | end 40 | 41 | local function dgram_getsockname(self) 42 | return self.sock:getsockname() 43 | end 44 | 45 | local function dgram_setpeername(self, addr, port) 46 | return self.sock:setpeername(addr, port) 47 | end 48 | 49 | local function dgram_getpeername(self) 50 | return self.sock:getpeername() 51 | end 52 | 53 | local function dgram_close(self) 54 | self.io_read:stop(self.loop) 55 | self.sock:close() 56 | end 57 | 58 | local function dgram_handle_error(self, err) 59 | local handler = self.handler 60 | local errFunc = handler.handle_error 61 | self.has_error = true -- mark socket as bad. 62 | if errFunc then 63 | errFunc(handler, err) 64 | else 65 | print('udp socket error:', err) 66 | end 67 | dgram_close(self) 68 | end 69 | 70 | local function dgram_sendto(self, data, ip, port) 71 | return self.sock:sendto(data, ip, port) 72 | end 73 | 74 | local function dgram_recvfrom_data(self) 75 | local max_packet = self.max_packet 76 | local read_max = self.read_max 77 | local handler = self.handler 78 | local sock = self.sock 79 | local len = 0 80 | local err 81 | 82 | repeat 83 | local data, ip, port = sock:recvfrom(max_packet) 84 | if not data then 85 | if data == false then 86 | -- no data 87 | return true 88 | else 89 | err = port 90 | -- report error 91 | dgram_handle_error(self, err) 92 | return false, err 93 | end 94 | end 95 | -- check if the other side shutdown there send stream 96 | if #data == 0 then 97 | -- report socket closed 98 | dgram_handle_error(self, 'closed') 99 | return false, 'closed' 100 | end 101 | -- pass read data to handler 102 | err = handler:handle_data(data, ip, port) 103 | if err then 104 | -- report error 105 | dgram_handle_error(self, err) 106 | return false, err 107 | end 108 | len = len + #data 109 | until len >= read_max 110 | 111 | return true 112 | end 113 | 114 | local dgram_mt = { 115 | sendto = dgram_sendto, 116 | getsockopt = dgram_getsockopt, 117 | setsockopt = dgram_setsockopt, 118 | getsockname = dgram_getsockname, 119 | setsockname = dgram_setsockname, 120 | getpeername = dgram_getpeername, 121 | setpeername = dgram_setpeername, 122 | close = dgram_close, 123 | } 124 | dgram_mt.__index = dgram_mt 125 | 126 | local function dgram_wrap(loop, handler, sock) 127 | -- create udp socket object 128 | local self = { 129 | loop = loop, 130 | handler = handler, 131 | sock = sock, 132 | max_packet = 8192, 133 | read_max = 65536, 134 | } 135 | setmetatable(self, dgram_mt) 136 | 137 | -- make nixio socket non-blocking 138 | sock:setblocking(false) 139 | -- get socket FD 140 | local fd = sock:fileno() 141 | 142 | -- create callback closure 143 | local read_cb = function() 144 | dgram_recvfrom_data(self) 145 | end 146 | -- create IO watcher. 147 | self.io_read = ev.IO.new(read_cb, fd, ev.READ) 148 | 149 | self.io_read:start(loop) 150 | 151 | return self 152 | end 153 | 154 | local function dgram_new_bind(loop, handler, domain, host, port) 155 | -- nixio uses nil to mena any local address 156 | if host == '*' then host = nil end 157 | -- create nixio socket 158 | local sock = new_socket(domain, 'dgram') 159 | -- wrap socket 160 | local self = dgram_wrap(loop, handler, sock) 161 | -- connect to host:port 162 | local ret, errno, err = sock:bind(host, port) 163 | if not ret then 164 | -- report error 165 | dgram_handle_error(self, err) 166 | return nil, err 167 | end 168 | return self 169 | end 170 | 171 | module(...) 172 | 173 | function udp(loop, handler, host, port) 174 | return dgram_new_bind(loop, handler, 'inet', host, port) 175 | end 176 | 177 | -- default datagram type to udp. 178 | new = udp 179 | 180 | function udp6(loop, handler, host, port) 181 | return dgram_new_bind(loop, handler, 'inet6', host, port) 182 | end 183 | 184 | function unix(loop, handler, path) 185 | return dgram_new_bind(loop, handler, 'unix', path) 186 | end 187 | 188 | -------------------------------------------------------------------------------- /handler/http/chunked.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | local format = string.format 23 | 24 | local ltn12 = require"ltn12" 25 | 26 | -- 27 | -- Chunked Transfer Encoding filter 28 | -- 29 | local function chunked() 30 | local is_eof = false 31 | return function(chunk) 32 | if chunk == "" then 33 | return "" 34 | elseif chunk then 35 | local len = #chunk 36 | -- prepend chunk length 37 | return format('%x\r\n', len) .. chunk .. '\r\n' 38 | elseif not is_eof then 39 | -- nil chunk, mark stream EOF 40 | is_eof = true 41 | -- return zero-length chunk. 42 | return '0\r\n\r\n' 43 | end 44 | return nil 45 | end 46 | end 47 | 48 | module(...) 49 | 50 | function new(src) 51 | return ltn12.filter.chain(src, chunked()) 52 | end 53 | 54 | setmetatable(_M, { __call = function(tab, ...) return new(...) end }) 55 | 56 | -------------------------------------------------------------------------------- /handler/http/client.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | local print = print 23 | local assert = assert 24 | 25 | local nixio = require"nixio" 26 | 27 | local request = require"handler.http.client.request" 28 | local hosts = require"handler.http.client.hosts" 29 | local headers = require"handler.http.headers" 30 | local headers_new = headers.new 31 | 32 | local ev = require"ev" 33 | 34 | local client_mt = {} 35 | client_mt.__index = client_mt 36 | 37 | function client_mt:request(req) 38 | local req, err = request.new(self, req) 39 | if req == nil then return req, err end 40 | 41 | -- queue request to be processed. 42 | local stat, err = self.hosts:queue_request(req) 43 | if not stat then return nil, err end 44 | 45 | return req 46 | end 47 | 48 | function client_mt:get_tls_context() 49 | local tls = self.tls 50 | if not tls then 51 | -- make default client-side TLS context. 52 | tls = nixio.tls'client' 53 | self.tls = tls 54 | end 55 | return tls 56 | end 57 | 58 | module(...) 59 | 60 | function new(loop, client) 61 | client = client or {} 62 | client.loop = loop 63 | client.hosts = hosts.new(client) 64 | -- normalize http headers 65 | client.headers = headers_new(client.headers) 66 | 67 | -- set User-Agent header 68 | client.headers['User-Agent'] = 69 | client.headers['User-Agent'] or client.user_agent or "Lua-Handler HTTPClient/0.1" 70 | 71 | return setmetatable(client, client_mt) 72 | end 73 | 74 | local default_client = nil 75 | -- get default http client. 76 | function default() 77 | if not default_client then 78 | -- create a http client. 79 | default_client = new(ev.Loop.default) 80 | end 81 | return default_client 82 | end 83 | 84 | -- initialize default http client. 85 | function init(loop, client) 86 | default_client = new(loop, client) 87 | end 88 | 89 | -------------------------------------------------------------------------------- /handler/http/client/connection.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local pairs = pairs 22 | local format = string.format 23 | local setmetatable = setmetatable 24 | local assert = assert 25 | local tconcat = table.concat 26 | 27 | local ltn12 = require"ltn12" 28 | 29 | local lhp = require"http.parser" 30 | 31 | local connection = require"handler.connection" 32 | 33 | local chunked = require"handler.http.chunked" 34 | local chunked = chunked.new 35 | 36 | local response = require"handler.http.client.response" 37 | local new_response = response.new 38 | 39 | local function call_callback(obj, cb, ...) 40 | local meth_cb = obj[cb] 41 | if meth_cb then 42 | return meth_cb(obj, ...) 43 | end 44 | end 45 | 46 | local client_mt = {} 47 | client_mt.__index = client_mt 48 | 49 | local function pool_remove(self) 50 | local pool = self.pool 51 | -- remove connection from the pool. 52 | if pool then 53 | pool:remove_connection(self) 54 | end 55 | end 56 | 57 | function client_mt:close() 58 | local sock = self.sock 59 | if sock then 60 | self.sock = nil 61 | sock:close() 62 | end 63 | pool_remove(self) 64 | end 65 | 66 | function client_mt:handle_error(err) 67 | local req = self.cur_req 68 | local resp = self.resp 69 | -- flush http-parser 70 | self:handle_data('') 71 | -- close connection on all errors. 72 | self.is_closed = true 73 | if req then 74 | -- if connection was closed before we received a response 75 | if not resp then 76 | local pool = self.pool 77 | -- then re-queue request in a new connection. 78 | if pool then 79 | pool:retry_request(req, true) 80 | end 81 | -- don't send closed error to request. 82 | req = nil 83 | else 84 | -- request is active, signal it that there was an error 85 | call_callback(req, 'on_error', resp, err) 86 | end 87 | end 88 | pool_remove(self) 89 | end 90 | 91 | function client_mt:handle_connected() 92 | end 93 | 94 | function client_mt:handle_data(data) 95 | local parser = self.parser 96 | local bytes_parsed = parser:execute(data) 97 | if parser:is_upgrade() then 98 | -- protocol changing. 99 | return 100 | elseif #data ~= bytes_parsed then 101 | -- failed to parse response. 102 | self:handle_error(format("http-parser: failed to parse all received data=%d, parsed=%d", 103 | #data, bytes_parsed)) 104 | end 105 | end 106 | 107 | function client_mt:handle_drain() 108 | -- write buffer is empty, send more of the request body. 109 | self:send_body() 110 | end 111 | 112 | local function gen_headers(data, headers) 113 | local offset=#data 114 | for k,v in pairs(headers) do 115 | offset = offset + 1 116 | data[offset] = k 117 | offset = offset + 1 118 | data[offset] = ": " 119 | offset = offset + 1 120 | data[offset] = v 121 | offset = offset + 1 122 | data[offset] = "\r\n" 123 | end 124 | return offset 125 | end 126 | 127 | function client_mt:queue_request(req) 128 | assert(self.cur_req == nil, "TODO: no pipeline support yet!") 129 | self.cur_req = req 130 | -- request needs a reference to the connection, so it can cancel the request early. 131 | req.connection = self 132 | -- gen: Request-Line 133 | local data = { req.method, " ", req.path, " ", req.http_version, "\r\n" } 134 | -- preprocess request body 135 | self:preprocess_body() 136 | -- gen: Request-Headers 137 | local offset = gen_headers(data, req.headers) 138 | offset = offset + 1 139 | data[offset] = "\r\n" 140 | -- send request. 141 | self.sock:send(tconcat(data)) 142 | -- send request body 143 | if not self.expect_100 then 144 | self:send_body() 145 | end 146 | 147 | return true 148 | end 149 | 150 | -- this need to be called before writting headers. 151 | function client_mt:preprocess_body() 152 | local req = self.cur_req 153 | local body = req.body 154 | -- if no request body, then we are finished. 155 | if not body then 156 | -- make sure there is no body_src left-over from previous request. 157 | self.body_src = nil 158 | -- call request_sent callback. 159 | call_callback(req, 'on_request_sent') 160 | return 161 | end 162 | 163 | if not req.no_expect_100 then 164 | -- set "Expect: 100-continue" header 165 | req.headers.Expect = "100-continue" 166 | self.expect_100 = true 167 | else 168 | self.expect_100 = false 169 | end 170 | 171 | local body_type = req.body_type 172 | local src 173 | if body_type == 'string' then 174 | src = ltn12.source.string(body) 175 | elseif body_type == 'object' then 176 | src = body:get_source() 177 | else 178 | -- body is the LTN12 source 179 | src = body 180 | end 181 | 182 | -- if no Content-Length, then use chunked transfer encoding. 183 | if not req.headers['Content-Length'] then 184 | req.headers['Transfer-Encoding'] = 'chunked' 185 | -- add chunked filter. 186 | src = chunked(src) 187 | end 188 | 189 | self.body_src = src 190 | end 191 | 192 | function client_mt:send_body() 193 | local body_src = self.body_src 194 | local sock = self.sock 195 | -- check if there is anything to send 196 | if body_src == nil then return end 197 | 198 | -- send chunks until socket blocks. 199 | local chunk, num, err 200 | local len = 0 201 | repeat 202 | -- get next chunk 203 | chunk, err = body_src() 204 | if chunk == nil then 205 | -- finished sending request body. 206 | self.body_src = nil 207 | -- call request_sent callback. 208 | call_callback(self.cur_req, 'on_request_sent') 209 | return 210 | end 211 | if chunk ~= "" then 212 | -- send chunk 213 | num, err = sock:send(chunk) 214 | if num then len = len + num end 215 | end 216 | until err 217 | end 218 | 219 | local function create_response_parser(self) 220 | local resp 221 | local headers 222 | local parser 223 | local body 224 | local need_close = false 225 | 226 | function self.on_message_begin() 227 | -- setup response object. 228 | resp = new_response() 229 | headers = resp.headers 230 | self.resp = resp 231 | body = nil 232 | need_close = false 233 | end 234 | 235 | function self.on_header(header, val) 236 | headers[header] = val 237 | end 238 | 239 | function self.on_headers_complete() 240 | -- check if we need to close the connection at the end of the response. 241 | if not parser:should_keep_alive() then 242 | need_close = true 243 | end 244 | local status_code = parser:status_code() 245 | -- check for expect_100 246 | if self.expect_100 and status_code == 100 then 247 | -- send request body now. 248 | self:send_body() 249 | -- don't process message complete event from the "100 Continue" response. 250 | self.skip_complete = true 251 | return 252 | end 253 | -- save response status. 254 | resp.status_code = status_code 255 | -- call request's on_response callback 256 | return call_callback(self.cur_req, 'on_response', resp) 257 | end 258 | 259 | function self.on_body(data) 260 | if self.skip_complete then return end 261 | local req = self.cur_req 262 | if req.stream_response then 263 | -- call request's on_stream_data callback 264 | call_callback(req, 'on_data', resp, data) 265 | else 266 | -- call request's on_data callback 267 | if data == nil then 268 | call_callback(req, 'on_data', resp, body) 269 | body = nil 270 | else 271 | body = (body or '') .. data 272 | end 273 | end 274 | end 275 | 276 | function self.on_message_complete() 277 | if self.skip_complete then 278 | self.expect_100 = false 279 | self.skip_complete = false 280 | return 281 | end 282 | local cur_resp = resp 283 | local req = self.cur_req 284 | -- dis-connect request object from connection. 285 | req.connection = nil 286 | self.cur_req = nil 287 | -- clean-up parser and make it ready for next request 288 | resp = nil 289 | headers = nil 290 | self.resp = nil 291 | body = nil 292 | -- put connection back into the pool. 293 | local pool = self.pool 294 | if pool and not need_close then 295 | pool:put_idle_connection(self) 296 | else 297 | self:close() 298 | end 299 | -- call request's on_finished callback 300 | call_callback(req, 'on_finished', cur_resp) 301 | end 302 | 303 | parser = lhp.response(self) 304 | self.parser = parser 305 | end 306 | 307 | module(...) 308 | 309 | function new(loop, pool) 310 | local conn = setmetatable({ 311 | is_closed = false, 312 | pool = pool, 313 | expect_100 = false, 314 | skip_complete = false, 315 | }, client_mt) 316 | 317 | create_response_parser(conn) 318 | 319 | local sock, err 320 | if pool.is_https then 321 | sock, err = connection.tls_tcp(loop, conn, pool.address, pool.port, tls, true) 322 | else 323 | sock, err = connection.tcp(loop, conn, pool.address, pool.port) 324 | end 325 | if sock == nil then return nil, err end 326 | conn.sock = sock 327 | 328 | return conn 329 | end 330 | 331 | setmetatable(_M, { __call = function(tab, ...) return new(...) end }) 332 | 333 | -------------------------------------------------------------------------------- /handler/http/client/hosts.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | local print = print 23 | local assert = assert 24 | local tinsert = table.insert 25 | local tremove = table.remove 26 | 27 | local httpconnection = require"handler.http.client.connection" 28 | 29 | local MAX_CONNECTIONS_PER_HOST = 8 30 | 31 | local host_mt = {} 32 | host_mt.__index = host_mt 33 | 34 | local function remove_connection(connections, dead) 35 | -- remove dead connection from pool 36 | for i=1,#connections do 37 | if connections[i] == dead then 38 | -- found dead connection. 39 | tremove(connections, i) 40 | break 41 | end 42 | end 43 | end 44 | 45 | function host_mt:remove_connection(dead) 46 | -- remove dead connection from pool 47 | remove_connection(self.connections, dead) 48 | remove_connection(self.idle, dead) 49 | -- check for queued requests 50 | local req = tremove(self.requests, 1) -- pop requests in the order they where queued 51 | if req then 52 | -- re-process request to create a new connection (since we are now below the max limit). 53 | return self:queue_request(req) 54 | end 55 | -- if we have no more connections to this host 56 | if #self.connections == 0 then 57 | -- then remove this host from the cache of host objects. 58 | self.cache:remove_host(self) 59 | end 60 | end 61 | 62 | function host_mt:put_idle_connection(conn) 63 | if conn.is_closed then return end 64 | -- check for queued requests 65 | local req 66 | repeat 67 | req = tremove(self.requests, 1) -- pop requests in the order they where queued 68 | -- skip cancelled requests. 69 | if req and not req.is_cancelled then 70 | return conn:queue_request(req) 71 | end 72 | until req == nil 73 | tinsert(self.idle, conn) 74 | end 75 | 76 | function host_mt:retry_request(req, is_push_back) 77 | -- make sure request is valid. 78 | if req.is_cancelled then return end 79 | -- increase retry count 80 | local count = (req.retries or 0) + 1 81 | req.retries = count 82 | if count > 4 then 83 | -- reached max request retries 84 | return 85 | end 86 | -- queue request 87 | if is_push_back then 88 | tinsert(self.requests, 1, req) -- insert at head of request queue 89 | else 90 | tinsert(self.requests, req) -- insert at end of request queue 91 | end 92 | end 93 | 94 | function host_mt:queue_request(req) 95 | -- make sure request is valid. 96 | if req.is_cancelled then return end 97 | -- remove a connection from the idle list. 98 | local conn = tremove(self.idle) 99 | -- already have an open connection. 100 | if not conn then 101 | if #self.connections >= MAX_CONNECTIONS_PER_HOST then 102 | -- queue request 103 | tinsert(self.requests, req) 104 | return 105 | end 106 | -- no pooled connection, create a new connection. 107 | local err 108 | conn, err = httpconnection(self.client.loop, self) 109 | if conn == nil then return false, err end 110 | tinsert(self.connections, conn) 111 | end 112 | return conn:queue_request(req) 113 | end 114 | 115 | function host_mt:get_tls_context() 116 | return self.client:get_tls_context() 117 | end 118 | 119 | local function new_host(cache, client, scheme, address, port) 120 | return setmetatable({ 121 | cache = cache, 122 | client = client, 123 | is_https = (scheme == 'https'), 124 | scheme = scheme, 125 | address = address, 126 | port = port, 127 | open = 0, -- number of open connections (either in the pool or handling a request). 128 | idle = {}, 129 | connections = {}, 130 | requests = {}, 131 | }, host_mt) 132 | end 133 | 134 | local hosts_cache_mt = {} 135 | hosts_cache_mt.__index = hosts_cache_mt 136 | 137 | local function host_key(scheme, address, port) 138 | -- key format "scheme:address:port" 139 | return scheme .. ':' .. address .. ':' .. port 140 | end 141 | 142 | function hosts_cache_mt:remove_host(host) 143 | local key = host_key(host.scheme, host.address, host.port) 144 | self.hosts[key] = nil 145 | end 146 | 147 | function hosts_cache_mt:get_host(scheme, address, port) 148 | local key = host_key(scheme, address, port) 149 | local host = self.hosts[key] 150 | if not host then 151 | -- create new host object. 152 | host = new_host(self, self.client, scheme, address, port) 153 | self.hosts[key] = host 154 | end 155 | return host 156 | end 157 | 158 | function hosts_cache_mt:queue_request(req) 159 | local host = self:get_host(req.scheme, req.host, req.port) 160 | assert(host,'failed to create host object.') 161 | return host:queue_request(req) 162 | end 163 | 164 | module(...) 165 | 166 | function new(client) 167 | return setmetatable({ 168 | client = client, 169 | hosts = {}, 170 | }, hosts_cache_mt) 171 | end 172 | 173 | function set_max_connections_per_host(max) 174 | MAX_CONNECTIONS_PER_HOST = max 175 | end 176 | 177 | -------------------------------------------------------------------------------- /handler/http/client/request.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | local tonumber = tonumber 23 | local tostring = tostring 24 | local print = print 25 | local assert = assert 26 | local type = type 27 | local pairs = pairs 28 | local error = error 29 | 30 | local http_headers = require'handler.http.headers' 31 | local new_headers = http_headers.new 32 | 33 | local uri = require"handler.uri" 34 | local uri_parse = uri.parse 35 | 36 | local request_mt = {} 37 | request_mt.__index = request_mt 38 | 39 | function request_mt:close() 40 | self.is_cancelled = true 41 | if self.connection then 42 | self.connection:close() 43 | end 44 | end 45 | 46 | local function process_request_body(req) 47 | local body = req.body 48 | -- if no request body, then we don't need to do anything. 49 | if not body then return end 50 | 51 | -- default method to POST when there is a request body. 52 | req.method = req.method or 'POST' 53 | 54 | -- check if request body is a complex object. 55 | local b_type = type(body) 56 | if b_type == 'table' then 57 | assert(body.is_content_object, "Can't encode generic tables.") 58 | -- if request body is a form 59 | if body.object_type == 'form' then 60 | -- force method to POST and set headers Content-Type & Content-Length 61 | req.method = 'POST' 62 | req.headers['Content-Type'] = body:get_content_type() 63 | end 64 | -- get content-type from object 65 | if not req.headers['Content-Type'] then 66 | req.headers['Content-Type'] = body:get_content_type() 67 | end 68 | req.headers['Content-Length'] = body:get_content_length() 69 | -- mark request body as an object 70 | req.body_type = 'object' 71 | elseif b_type == 'string' then 72 | -- simple string body 73 | req.headers['Content-Length'] = #body 74 | -- mark request body as an simple string 75 | req.body_type = 'string' 76 | elseif b_type == 'function' then 77 | -- if the body is a function it should be a LTN12 source 78 | -- mark request body as an source 79 | req.body_type = 'source' 80 | else 81 | assert(false, "Unsupported request body type: " .. b_type) 82 | end 83 | 84 | end 85 | 86 | module(...) 87 | 88 | function new(client, req, body) 89 | if type(req) == 'string' then 90 | req = { url = req, body = body, headers = http_headers.dup(client.headers) } 91 | else 92 | req.headers = http_headers.copy_defaults(req.headers, client.headers) 93 | -- convert port to number 94 | req.port = tonumber(req.port) 95 | end 96 | 97 | -- mark request as non-cancelled. 98 | req.is_cancelled = false 99 | 100 | -- default to version 1.1 101 | req.http_version = req.http_version or 'HTTP/1.1' 102 | 103 | local url = req.url 104 | if url then 105 | if type(url) ~= 'string' then 106 | return nil, "Invalid request URL: " .. tostring(url) 107 | end 108 | -- parse url 109 | uri_parse(url, req, true) -- parsed parts of url are saved into the 'req' table. 110 | else 111 | req.path = req.path or '/' 112 | end 113 | -- validate scheme 114 | local scheme = req.scheme or 'http' 115 | local default_port 116 | scheme = scheme:lower() 117 | if scheme == 'http' then 118 | default_port = 80 119 | elseif scheme == 'https' then 120 | default_port = 443 121 | else 122 | error("Unknown protocol scheme in URL: " .. scheme) 123 | end 124 | if req.port == nil then 125 | req.port = default_port 126 | end 127 | -- validate request. 128 | if req.host == nil then 129 | return nil, "Invalid request missing host or URL." 130 | end 131 | 132 | -- check if Host header needs to be set. 133 | if not req.headers.Host and req.http_version == "HTTP/1.1" then 134 | local host = req.host 135 | local port = req.port 136 | if port and port ~= default_port then 137 | -- none default port add it to the authority 138 | host = host .. ":" .. tostring(port) 139 | end 140 | req.headers.Host = host 141 | end 142 | 143 | -- 144 | -- Process request body. 145 | -- 146 | process_request_body(req) 147 | 148 | -- default to GET method. 149 | req.method = req.method or 'GET' 150 | 151 | return setmetatable(req, request_mt) 152 | end 153 | 154 | -------------------------------------------------------------------------------- /handler/http/client/response.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | 23 | local http_headers = require'handler.http.headers' 24 | local new_headers = http_headers.new 25 | 26 | local response_mt = {} 27 | response_mt.__index = response_mt 28 | 29 | module(...) 30 | 31 | function new() 32 | return setmetatable({ 33 | headers = new_headers(), 34 | }, response_mt) 35 | end 36 | 37 | setmetatable(_M, { __call = function(tab, ...) return new(...) end }) 38 | 39 | -------------------------------------------------------------------------------- /handler/http/file.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local print = print 22 | local setmetatable = setmetatable 23 | local assert = assert 24 | local io = io 25 | local tconcat = table.concat 26 | local ltn12 = require'ltn12' 27 | 28 | local file_mt = { is_content_object = true, object_type = 'file' } 29 | file_mt.__index = file_mt 30 | 31 | function file_mt:get_content_type() 32 | return self.content_type 33 | end 34 | 35 | function file_mt:get_content_length() 36 | return self.size 37 | end 38 | 39 | function file_mt:get_source() 40 | return self.src 41 | end 42 | 43 | function file_mt:get_content() 44 | local data = {} 45 | local src = self:get_source() 46 | local sink = ltn12.sink.table(data) 47 | ltn12.pump.all(src, sink) 48 | return tconcat(data) 49 | end 50 | 51 | module(...) 52 | 53 | function new(filename, content_type, upload_name) 54 | local file = assert(io.open(filename)) 55 | 56 | -- get file size. 57 | local size = file:seek('end') 58 | file:seek('set', 0) 59 | 60 | -- make sure there is a content type. 61 | content_type = content_type or "application/octet-stream" 62 | 63 | -- check if we where given an upload name 64 | if not upload_name then 65 | -- default upload name to same as filename without the path. 66 | upload_name = filename:match('([^/]*)$') 67 | -- TOD: add support for windows 68 | end 69 | 70 | local self = { 71 | upload_name = upload_name, 72 | size = size, 73 | content_type = content_type, 74 | src = ltn12.source.file(file) 75 | } 76 | return setmetatable(self, file_mt) 77 | end 78 | 79 | function new_string(name, content_type, content) 80 | assert(content, "Missing content for file.") 81 | -- make sure there is a content type. 82 | content_type = content_type or "application/octet-stream" 83 | 84 | local self = { 85 | upload_name = name, 86 | size = #content, 87 | content_type = content_type, 88 | src = ltn12.source.string(content) 89 | } 90 | return setmetatable(self, file_mt) 91 | end 92 | 93 | -------------------------------------------------------------------------------- /handler/http/form.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local socket = require"socket" 22 | local url = require"socket.url" 23 | local urlencode = url.escape 24 | local ltn12 = require"ltn12" 25 | 26 | local type = type 27 | local print = print 28 | local pairs = pairs 29 | local tostring = tostring 30 | local time = socket.gettime 31 | local randomseed = math.randomseed 32 | local random = math.random 33 | local floor = math.floor 34 | local format = string.format 35 | local setmetatable = setmetatable 36 | local assert = assert 37 | local tconcat = table.concat 38 | 39 | local valid_types = { 40 | number = true, 41 | boolean = true, 42 | string = true, 43 | table = true, 44 | -- we don't support these types 45 | ['function'] = false, 46 | userdata = false, 47 | thread = false, 48 | } 49 | 50 | local simple_types = { 51 | number = true, 52 | boolean = true, 53 | string = true, 54 | table = false, 55 | } 56 | 57 | local function check_form_format(data) 58 | local is_simple = true 59 | for k,v in pairs(data) do 60 | local t = type(v) 61 | assert(valid_types[t], 'Invalid value type for field: ' .. k) 62 | if not simple_types[t] then 63 | is_simple = false 64 | break 65 | end 66 | end 67 | return is_simple 68 | end 69 | 70 | local function content_source(t) 71 | local last_src 72 | local i = 1 73 | return function(...) 74 | local chunk 75 | -- check for eof 76 | if t == nil then 77 | return nil 78 | end 79 | repeat 80 | if last_src then 81 | local err 82 | -- process current sub-source 83 | chunk, err = last_src() 84 | if chunk ~= nil then 85 | break 86 | else 87 | -- check for sub-source error 88 | if err then 89 | return nil, err 90 | end 91 | -- sub-source finished 92 | last_src = nil 93 | end 94 | end 95 | -- get next chunk from table 96 | chunk = t[i] 97 | i = i + 1 98 | if chunk == nil then 99 | t = nil 100 | return nil 101 | elseif type(chunk) == 'function' then 102 | last_src = chunk 103 | chunk = nil 104 | end 105 | until chunk 106 | return chunk 107 | end 108 | end 109 | 110 | local form_mt = { is_content_object = true, object_type = 'form' } 111 | form_mt.__index = form_mt 112 | 113 | function form_mt:add(field, value) 114 | assert(type(field) == 'string', 'field name must be a string') 115 | -- check if field value is complex 116 | local t = type(value) 117 | assert(valid_types[t], 'Invalid value type for field: ' .. field) 118 | if not simple_types[t] then 119 | self.is_simple = false 120 | end 121 | self.data[field] = value 122 | end 123 | 124 | function form_mt:remove(field) 125 | self.data[field] = nil 126 | end 127 | 128 | local function append(t,idx, len, data) 129 | idx = idx + 1 130 | t[idx] = data 131 | return idx, len + #data 132 | end 133 | 134 | local function fixate_form_content(self) 135 | -- check if already generated the form content. 136 | if self.content then 137 | return 138 | end 139 | local content = {} 140 | local c_length = 0 141 | local c_type 142 | local parts = 0 143 | -- generate content now. 144 | if self.is_simple then 145 | for k,v in pairs(self.data) do 146 | parts, c_length = append(content, parts, c_length, 147 | urlencode(k) .. '=' .. urlencode(v)) 148 | end 149 | -- concat fields together. 150 | if parts > 1 then 151 | content = tconcat(content, '&') 152 | c_length = c_length + (1 * parts) - 1 153 | else 154 | content = tconcat(content) 155 | end 156 | -- Content-Type 157 | c_type = "application/x-www-form-urlencoded" 158 | -- create content ltn12 source 159 | content = ltn12.source.string(content) 160 | else 161 | -- generate a boundry value 162 | local boundry = '---------------------------' 163 | -- gen. long random number for boundry 164 | randomseed(time()) 165 | for i=1,15 do 166 | boundry = boundry .. format('%x',floor(10000 * random())) 167 | end 168 | -- limit boundry length to 65 chars max 169 | if #boundry > 65 then 170 | boundry = boundry:sub(1,65) 171 | end 172 | -- Content-Type 173 | c_type = 'multipart/form-data; boundary=' .. boundry .. '' 174 | -- pre-append '--' to boundry string. 175 | boundry = '--' .. boundry 176 | 177 | -- encode form data 178 | local boundry_len = #boundry 179 | for key,val in pairs(self.data) do 180 | -- add boundry 181 | parts, c_length = append(content, parts, c_length, boundry) 182 | -- field headers 183 | parts, c_length = append(content, parts, c_length, 184 | '\r\nContent-Disposition: form-data; name="' .. key .. '"') 185 | -- field value 186 | if type(val) == 'string' then 187 | -- no extra headers, just append the value 188 | parts, c_length = append(content, parts, c_length, 189 | '\r\n\r\n' .. val .. '\r\n') 190 | else 191 | -- check if the value is a file object. 192 | if val.object_type == 'file' then 193 | local d 194 | -- append filename to headers 195 | parts, c_length = append(content, parts, c_length, 196 | '; filename="' .. val.upload_name) 197 | -- append Content-Type header 198 | parts, c_length = append(content, parts, c_length, 199 | '"\r\nContent-Type: ' .. val:get_content_type() .. '\r\n\r\n') 200 | -- append file contents 201 | parts = parts + 1 202 | c_length = c_length + val:get_content_length() 203 | --content[parts] = val:get_content() 204 | content[parts] = val:get_source() 205 | -- value end 206 | parts, c_length = append(content, parts, c_length, '\r\n') 207 | else 208 | assert(false, 'un-handled form value.') 209 | end 210 | end 211 | end 212 | -- mark end 213 | parts, c_length = append(content, parts, c_length, boundry .. '--\r\n') 214 | -- create content ltn12 source 215 | content = content_source(content) 216 | end 217 | self.content_length = c_length 218 | self.content = content 219 | self.content_type = c_type 220 | end 221 | 222 | function form_mt:get_content_type() 223 | fixate_form_content(self) 224 | return self.content_type 225 | end 226 | 227 | function form_mt:get_content_length() 228 | fixate_form_content(self) 229 | return self.content_length 230 | end 231 | 232 | function form_mt:get_source() 233 | fixate_form_content(self) 234 | return self.content 235 | end 236 | 237 | function form_mt:get_content() 238 | local data = {} 239 | local src = self:get_source() 240 | local sink = ltn12.sink.table(data) 241 | ltn12.pump.all(src, sink) 242 | return tconcat(data) 243 | end 244 | 245 | module(...) 246 | 247 | function new(data) 248 | local self = { } 249 | if data then 250 | self.is_simple = check_form_format(data) 251 | self.data = data 252 | else 253 | self.is_simple = true 254 | self.data = {} 255 | end 256 | return setmetatable(self, form_mt) 257 | end 258 | 259 | function parser() 260 | assert(false, "Not implemented yet!") 261 | end 262 | 263 | -------------------------------------------------------------------------------- /handler/http/headers.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local pairs = pairs 22 | local rawget = rawget 23 | local rawset = rawset 24 | local print = print 25 | local setmetatable = setmetatable 26 | local getmetatable = getmetatable 27 | local assert = assert 28 | 29 | local common_headers = { 30 | "Accept", 31 | "Accept-Charset", 32 | "Accept-Encoding", 33 | "Accept-Language", 34 | "Accept-Ranges", 35 | "Age", 36 | "Allow", 37 | "Authorization", 38 | "Cache-Control", 39 | "Connection", 40 | "Content-Disposition", 41 | "Content-Encoding", 42 | "Content-Language", 43 | "Content-Length", 44 | "Content-Location", 45 | "Content-MD5", 46 | "Content-Range", 47 | "Content-Type", 48 | "Cookie", 49 | "Date", 50 | "ETag", 51 | "Expect", 52 | "Expires", 53 | "From", 54 | "Host", 55 | "If-Match", 56 | "If-Modified-Since", 57 | "If-None-Match", 58 | "If-Range", 59 | "If-Unmodified-Since", 60 | "Last-Modified", 61 | "Link", 62 | "Location", 63 | "Max-Forwards", 64 | "Pragma", 65 | "Proxy-Authenticate", 66 | "Proxy-Authorization", 67 | "Range", 68 | "Referer", 69 | "Refresh", 70 | "Retry-After", 71 | "Server", 72 | "Set-Cookie", 73 | "TE", 74 | "Trailer", 75 | "Transfer-Encoding", 76 | "Upgrade", 77 | "User-Agent", 78 | "Vary", 79 | "Via", 80 | "WWW-Authenticate", 81 | "Warning", 82 | } 83 | -- create header normalize table. 84 | local normalized = {} 85 | for i=1,#common_headers do 86 | local name = common_headers[i] 87 | normalized[name:lower()] = name 88 | end 89 | 90 | local headers_mt = {} 91 | 92 | function headers_mt.__index(headers, name) 93 | -- normalize header name 94 | local norm = normalized[name:lower()] 95 | -- if normalized name is nil or the same as name 96 | if norm == nil or norm == name then 97 | -- then no value exists for this header. 98 | return nil 99 | end 100 | -- get normalized header's value. 101 | return rawget(headers, norm) 102 | end 103 | 104 | function headers_mt.__newindex(headers, name, value) 105 | -- normalize header name 106 | local norm = normalized[name:lower()] or name 107 | rawset(headers, norm, value) 108 | end 109 | 110 | module(...) 111 | 112 | function new(headers) 113 | -- check if 'headers' has the same metatable already. 114 | if getmetatable(headers) == headers_mt then 115 | -- no need to re-convert this table. 116 | return headers 117 | end 118 | 119 | -- normalize existing headers 120 | if headers then 121 | for name,val in pairs(headers) do 122 | -- get normalized name 123 | local norm = normalized[name:lower()] 124 | -- if normalized name is different then current name. 125 | if norm and norm ~= name then 126 | -- then move value to normalized name. 127 | headers[norm] = val 128 | headers[name] = nil 129 | end 130 | end 131 | else 132 | headers = {} 133 | end 134 | 135 | return setmetatable(headers, headers_mt) 136 | end 137 | 138 | function dup(src) 139 | local dst = new() 140 | -- copy headers from src 141 | for name,val in pairs(src) do 142 | dst[name] = val 143 | end 144 | return dst 145 | end 146 | 147 | function copy_defaults(dst, src) 148 | if dst == nil then 149 | return dup(src) 150 | end 151 | -- make sure 'dst' is a headers object 152 | dst = new(dst) 153 | -- copy headers from src 154 | for name,val in pairs(src) do 155 | if not dst[name] then 156 | dst[name] = val 157 | end 158 | end 159 | return dst 160 | end 161 | 162 | 163 | -------------------------------------------------------------------------------- /handler/http/server.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | local print = print 23 | local assert = assert 24 | local date = os.date 25 | local floor = math.floor 26 | 27 | local ev = require"ev" 28 | 29 | local acceptor = require"handler.acceptor" 30 | 31 | local headers = require"handler.http.headers" 32 | local headers_new = headers.new 33 | 34 | local connection = require"handler.http.server.hconnection" 35 | 36 | local error_handler = require"handler.http.server.error_handler" 37 | 38 | local server_mt = {} 39 | server_mt.__index = server_mt 40 | 41 | function server_mt:new_connection(sock) 42 | local conn = connection(self, sock) 43 | end 44 | 45 | function server_mt:add_acceptor(accept) 46 | local list = self.acceptors 47 | list[#list+1] = accept 48 | return true 49 | end 50 | 51 | function server_mt:listen_unix(path, backlog) 52 | assert(path, "You must provide a port/path.") 53 | return self:add_acceptor(acceptor.unix(self.loop, self.accept_handler, path, backlog)) 54 | end 55 | 56 | local function check_port_addr(port, addr) 57 | assert(port, "You must provide a port/path.") 58 | if type(port) == 'string' then 59 | local path = port 60 | port = tonumber(path) 61 | if port == nil then 62 | return self:listen_path(path) 63 | end 64 | end 65 | addr = adrr or '0.0.0.0' 66 | return port, addr 67 | end 68 | 69 | function server_mt:listen(port, addr, backlog) 70 | port, addr = check_port_addr(port, addr) 71 | return self:add_acceptor(acceptor.tcp(self.loop, self.accept_handler, addr, port, backlog)) 72 | end 73 | 74 | function server_mt:listen6(port, addr, backlog) 75 | port, addr = check_port_addr(port, addr) 76 | return self:add_acceptor(acceptor.tcp6(self.loop, self.accept_handler, addr, port, backlog)) 77 | end 78 | 79 | function server_mt:tls_listen(tls, port, addr, backlog) 80 | port, addr = check_port_addr(port, addr) 81 | return self:add_acceptor( 82 | acceptor.tls_tcp(self.loop, self.accept_handler, addr, port, tls, backlog)) 83 | end 84 | 85 | function server_mt:tls_listen6(tls, port, addr, backlog) 86 | port, addr = check_port_addr(port, addr) 87 | return self:add_acceptor( 88 | acceptor.tls_tcp6(self.loop, self.accept_handler, addr, port, tls, backlog)) 89 | end 90 | 91 | function server_mt:listen_uri(uri, backlog) 92 | -- we don't support HTTP over UDP. 93 | assert(not uri:match('^udp'), "Can't accept HTTP connections from UDP socket.") 94 | -- default port 95 | local port = 80 96 | if uri:match('^tls') then 97 | port = 443 -- default port for https 98 | end 99 | return self:add_acceptor( 100 | acceptor.uri(self.loop, self.accept_handler, uri, backlog, port)) 101 | end 102 | 103 | local function server_update_cached_date(self, now) 104 | local cached_date 105 | -- get new date 106 | cached_date = date('!%a, %d %b %Y %T GMT') 107 | self.cached_date = cached_date 108 | self.cached_now = floor(now) -- only cache now as whole seconds. 109 | return cached_date 110 | end 111 | 112 | function server_mt:update_cached_date() 113 | return server_update_cached_date(self, self.loop:now()) 114 | end 115 | 116 | function server_mt:get_cached_date() 117 | local now = floor(self.loop:now()) 118 | if self.cached_now ~= now then 119 | return server_update_cached_date(self, now) 120 | end 121 | return self.cached_date 122 | end 123 | 124 | module(...) 125 | 126 | local function default_on_check_continue(self, req, resp) 127 | -- default to always sending the '100 Continue' response. 128 | resp:send_continue() 129 | return self:on_request(req, resp) 130 | end 131 | 132 | function new(loop, self) 133 | self = self or {} 134 | self.acceptors = {} 135 | self.loop = loop 136 | -- default timeouts 137 | -- maximum time to wait from the start of the request to the end of the headers. 138 | self.request_head_timeout = self.request_head_timeout or 1.0 139 | -- maximum time to wait from the end of the request headers to the end of the request body. 140 | self.request_body_timeout = self.request_body_timeout or -1 141 | -- maximum time to wait on a blocked write (i.e. with pending data to write). 142 | self.write_timeout = self.write_timeout or 30.0 143 | -- maximum time to wait for next request on a connection. 144 | self.keep_alive_timeout = self.keep_alive_timeout or 5.0 145 | -- maximum number of requests to allow on one connection. 146 | self.max_keep_alive_requests = self.max_keep_alive_requests or 128 147 | -- normalize http headers 148 | self.headers = headers_new(self.headers) 149 | 150 | -- create accept callback function. 151 | self.accept_handler = function(sock) 152 | return self:new_connection(sock) 153 | end 154 | 155 | -- add a default error_handler if none exists. 156 | local custom_handler = self.on_error_response 157 | if custom_handler then 158 | -- wrap custom handler. 159 | self.on_error_response = function(self, resp) 160 | local stat = custom_handler(self, resp) 161 | -- check if custom error handler added a response body. 162 | if not stat or resp.body == nil then 163 | -- try default handler. 164 | return error_handler(self, resp) 165 | end 166 | return stat 167 | end 168 | else 169 | -- no handler, use default 170 | self.on_error_response = error_handler 171 | end 172 | 173 | -- add a default on_check_continue handler if none exists. 174 | if not self.on_check_continue then 175 | self.on_check_continue = default_on_check_continue 176 | end 177 | 178 | -- set Server header 179 | if self.name ~= '' then 180 | self.headers['Server'] = 181 | self.headers['Server'] or self.name or "Lua-Handler HTTPServer/0.1" 182 | end 183 | 184 | return setmetatable(self, server_mt) 185 | end 186 | 187 | local default_server = nil 188 | -- get default http server. 189 | function default() 190 | if not default_server then 191 | -- create a http server. 192 | default_server = new(ev.Loop.default) 193 | end 194 | return default_server 195 | end 196 | 197 | -- initialize default http server. 198 | function init(loop, server) 199 | default_server = new(loop, server) 200 | end 201 | 202 | -------------------------------------------------------------------------------- /handler/http/server/error_handler.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local http_error_codes = { 22 | -- Redirection 3xx 23 | [300] = "Multiple Choices", 24 | [301] = "Moved Permanently", 25 | [302] = "Found", 26 | [303] = "See Other", 27 | [304] = "Not Modified", 28 | [305] = "Use Proxy", 29 | [306] = "(Unused)", 30 | [307] = "Temporary Redirect", 31 | -- Client Error 4xx 32 | [400] = "Bad Request", 33 | [401] = "Unauthorized", 34 | [402] = "Payment Required", 35 | [403] = "Forbidden", 36 | [404] = "Not Found", 37 | [405] = "Method Not Allowed", 38 | [406] = "Not Acceptable", 39 | [407] = "Proxy Authentication Required", 40 | [408] = "Request Timeout", 41 | [409] = "Conflict", 42 | [410] = "Gone", 43 | [411] = "Length Required", 44 | [412] = "Precondition Failed", 45 | [413] = "Request Entity Too Large", 46 | [414] = "Request-URI Too Long", 47 | [415] = "Unsupported Media Type", 48 | [416] = "Requested Range Not Satisfiable", 49 | [417] = "Expectation Failed", 50 | -- Server Error 5xx 51 | [500] = "Internal Server Error", 52 | [501] = "Not Implemented", 53 | [502] = "Bad Gateway", 54 | [503] = "Service Unavailable", 55 | [504] = "Gateway Timeout", 56 | [505] = "HTTP Version Not Supported", 57 | } 58 | 59 | local function render_response(title, message) 60 | return [[]] .. 61 | title .. [[]] .. 62 | message .. [[]] 63 | end 64 | 65 | local function handle_redirection(resp, status) 66 | local reason = http_error_codes[status] or '' 67 | local msg = "Redirection " .. status .. " " .. reason 68 | return render_response(msg, msg) 69 | end 70 | 71 | local function handle_error(resp, status) 72 | local reason = http_error_codes[status] or '' 73 | local msg = "Error " .. status .. " " .. reason 74 | return render_response(msg, msg) 75 | end 76 | 77 | return function(server, resp) 78 | -- check for a response body. 79 | if resp.body ~= nil then 80 | -- don't replace a custom response body. 81 | return false 82 | end 83 | local status = resp.status 84 | -- check for redirection response. 85 | local body 86 | if status >= 300 and status < 400 then 87 | body = handle_redirection(resp, status) 88 | else 89 | body = handle_error(resp, status) 90 | end 91 | resp:set_header('Content-Type', 'text/html') 92 | resp:set_body(body) 93 | return true 94 | end 95 | 96 | -------------------------------------------------------------------------------- /handler/http/server/request.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | 23 | local http_headers = require'handler.http.headers' 24 | local new_headers = http_headers.new 25 | 26 | local request_mt = {} 27 | request_mt.__index = request_mt 28 | 29 | module(...) 30 | 31 | function new() 32 | return setmetatable({ 33 | headers = new_headers(), 34 | }, request_mt) 35 | end 36 | 37 | setmetatable(_M, { __call = function(tab, ...) return new(...) end }) 38 | 39 | -------------------------------------------------------------------------------- /handler/http/server/response.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | 23 | local http_headers = require'handler.http.headers' 24 | local dup_headers = http_headers.dup 25 | 26 | local response_mt = {} 27 | response_mt.__index = response_mt 28 | 29 | function response_mt:set_status(status, reason) 30 | self.status = status 31 | self.reason = reason 32 | end 33 | 34 | function response_mt:set_header(key, value) 35 | self.headers[key] = value 36 | end 37 | 38 | function response_mt:get_header(key, value) 39 | self.headers[key] = value 40 | end 41 | 42 | function response_mt:set_body(body) 43 | -- if no response body, then we don't need to do anything. 44 | if not body then return end 45 | self.body = body 46 | 47 | -- check if response body is a complex object. 48 | local b_type = type(body) 49 | if b_type == 'table' then 50 | assert(body.is_content_object, "Can't encode generic tables.") 51 | -- get content-type from object 52 | if not self.headers['Content-Type'] then 53 | self.headers['Content-Type'] = body:get_content_type() 54 | end 55 | self.headers['Content-Length'] = body:get_content_length() 56 | -- mark response body as an object 57 | self.body_type = 'object' 58 | elseif b_type == 'string' then 59 | -- simple string body 60 | self.headers['Content-Length'] = #body 61 | -- mark response body as an simple string 62 | self.body_type = 'string' 63 | elseif b_type == 'function' then 64 | -- if the body is a function it should be a LTN12 source 65 | -- mark response body as an source 66 | self.body_type = 'source' 67 | else 68 | assert(false, "Unsupported response body type: " .. b_type) 69 | end 70 | 71 | end 72 | 73 | function response_mt:send(status, headers) 74 | if status then 75 | self:set_status(status) 76 | end 77 | if type(headers) == 'table' then 78 | local resp_headers = self.headers 79 | for key,value in pairs(headers) do 80 | resp_headers[key] = value 81 | end 82 | end 83 | -- signal the HTTP connection that this response is ready. 84 | return self.connection:send_response(self) 85 | end 86 | 87 | function response_mt:send_continue() 88 | return self.connection:send_continue(self) 89 | end 90 | 91 | module(...) 92 | 93 | function new(conn, req, default_headers) 94 | return setmetatable({ 95 | connection = conn, 96 | request = req, 97 | headers = dup_headers(default_headers), 98 | }, response_mt) 99 | end 100 | 101 | setmetatable(_M, { __call = function(tab, ...) return new(...) end }) 102 | 103 | -------------------------------------------------------------------------------- /handler/nixio/acceptor.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local acceptor = require"handler.acceptor" 22 | return acceptor 23 | -------------------------------------------------------------------------------- /handler/nixio/connection.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local connection = require"handler.connection" 22 | return connection 23 | -------------------------------------------------------------------------------- /handler/nixio/datagram.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local datagram = require"handler.datagram" 22 | return datagram 23 | -------------------------------------------------------------------------------- /handler/uri.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | local print = print 23 | 24 | local function parse_scheme(uri, off) 25 | local scheme, off = uri:match('^(%a[%w.+-]*):()', off) 26 | -- force scheme to canonical form 27 | scheme = scheme:lower() 28 | return scheme, off 29 | end 30 | 31 | local function parse_authority(uri, off) 32 | local authority, auth_end = uri:match('([^/]*)()', off) 33 | local userinfo, host, port 34 | -- check if authority has userinfo 35 | off = authority:find('@', 1, true) 36 | if off then 37 | -- parser userinfo 38 | userinfo = authority:sub(1,off) 39 | off = off + 1 40 | else 41 | off = 1 42 | end 43 | -- check if host is an IPv6 address 44 | if authority:sub(off, off) == '[' then 45 | -- parse IPv6 address 46 | host, off = authority:match('(%[[%x:]*%])()', off) 47 | else 48 | -- parse IPv4 address or hostname 49 | host, off = authority:match('([^:]*)()', off) 50 | end 51 | -- parse port 52 | port, off = authority:match(':(%d*)', off) 53 | port = tonumber(port) 54 | return userinfo, host, port, auth_end 55 | end 56 | 57 | local function parse_path_query_fragment(uri, off) 58 | local path, query, fragment 59 | -- parse path 60 | path, off = uri:match('([^?#]*)()', off) 61 | -- parse query 62 | if uri:sub(off, off) == '?' then 63 | query, off = uri:match('([^#]*)()', off + 1) 64 | end 65 | -- parse fragment 66 | if uri:sub(off, off) == '#' then 67 | fragment = uri:sub(off + 1) 68 | off = #uri 69 | end 70 | return path or '/', query, fragment, off 71 | end 72 | 73 | module(...) 74 | 75 | function parse(uri, info, path_only) 76 | local off 77 | info = info or {} 78 | -- parse scheme 79 | info.scheme, off = parse_scheme(uri, off) 80 | -- check if uri has an authority 81 | if uri:sub(off, off + 1) == '//' then 82 | -- parse authority 83 | info.userinfo, info.host, info.port, off = parse_authority(uri, off + 2) 84 | if path_only then 85 | -- don't split path/query/fragment, keep them whole. 86 | info.path = uri:sub(off) 87 | else 88 | -- parse path, query, and fragment 89 | info.path, info.query, info.fragment, off = parse_path_query_fragment(uri, off) 90 | end 91 | else 92 | -- uri has no authority the rest of the uri is the path. 93 | info.path = uri:sub(off) 94 | end 95 | -- check for zero-length path 96 | if #info.path == 0 then 97 | info.path = "/" 98 | end 99 | -- return parsed uri 100 | return info 101 | end 102 | 103 | function parse_query(query, values) 104 | query = query or '' 105 | values = values or {} 106 | -- parse name/value pairs 107 | for k,v in query:gmatch('([^=]+)=([^&]*)&?') do 108 | values[k] = v 109 | end 110 | return values 111 | end 112 | 113 | setmetatable(_M, { __call = function(tab, ...) return parse(...) end }) 114 | 115 | -------------------------------------------------------------------------------- /handler/zmq.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010-2011 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local setmetatable = setmetatable 22 | local print = print 23 | local tinsert = table.insert 24 | local tremove = table.remove 25 | local pairs = pairs 26 | local error = error 27 | local type = type 28 | 29 | local ev = require"ev" 30 | 31 | assert(ev.Idle,"handler.zmq requires a version of lua-ev > 1.3 that supports Idle watchers.") 32 | 33 | local zmq = require"zmq" 34 | local z_SUBSCRIBE = zmq.SUBSCRIBE 35 | local z_UNSUBSCRIBE = zmq.UNSUBSCRIBE 36 | local z_IDENTITY = zmq.IDENTITY 37 | local z_NOBLOCK = zmq.NOBLOCK 38 | local z_RCVMORE = zmq.RCVMORE 39 | local z_SNDMORE = zmq.SNDMORE 40 | local z_EVENTS = zmq.EVENTS 41 | local z_POLLIN = zmq.POLLIN 42 | local z_POLLOUT = zmq.POLLOUT 43 | local z_POLLIN_OUT = z_POLLIN + z_POLLOUT 44 | 45 | local mark_SNDMORE = {} 46 | 47 | local default_send_max = 50 48 | local default_recv_max = 50 49 | 50 | local function zsock_getopt(self, ...) 51 | return self.socket:getopt(...) 52 | end 53 | 54 | local function zsock_setopt(self, ...) 55 | return self.socket:setopt(...) 56 | end 57 | 58 | local function zsock_sub(self, filter) 59 | return self.socket:setopt(z_SUBSCRIBE, filter) 60 | end 61 | 62 | local function zsock_unsub(self, filter) 63 | return self.socket:setopt(z_UNSUBSCRIBE, filter) 64 | end 65 | 66 | local function zsock_identity(self, filter) 67 | return self.socket:setopt(z_IDENTITY, filter) 68 | end 69 | 70 | local function zsock_bind(self, ...) 71 | return self.socket:bind(...) 72 | end 73 | 74 | local function zsock_connect(self, ...) 75 | return self.socket:connect(...) 76 | end 77 | 78 | local function zsock_close(self) 79 | local send_queue = self.send_queue 80 | self.is_closing = true 81 | if not send_queue or #send_queue == 0 or self.has_error then 82 | if self.io_recv then 83 | self.io_recv:stop(self.loop) 84 | end 85 | self.io_idle:stop(self.loop) 86 | if self.socket then 87 | self.socket:close() 88 | self.socket = nil 89 | end 90 | end 91 | end 92 | 93 | local function zsock_handle_error(self, err) 94 | local handler = self.handler 95 | local errFunc = handler.handle_error 96 | self.has_error = true -- mark socket as bad. 97 | if errFunc then 98 | errFunc(self, err) 99 | else 100 | print('zmq socket: error ' .. err) 101 | end 102 | zsock_close(self) 103 | end 104 | 105 | local function zsock_enable_idle(self, enable) 106 | if enable == self.idle_enabled then return end 107 | self.idle_enabled = enable 108 | if enable then 109 | self.io_idle:start(self.loop) 110 | else 111 | self.io_idle:stop(self.loop) 112 | end 113 | end 114 | 115 | local function zsock_send_data(self, data, more) 116 | local s = self.socket 117 | 118 | local flags = z_NOBLOCK 119 | -- check for send more marker 120 | if more then 121 | flags = flags + z_SNDMORE 122 | end 123 | local sent, err = s:send(data, flags) 124 | if not sent then 125 | -- got timeout error block writes. 126 | if err == 'timeout' then 127 | -- block sending, data will queue until we can send again. 128 | self.send_blocked = true 129 | -- data in queue, mark socket for sending. 130 | self.need_send = true 131 | -- make sure idle watcher is running. 132 | zsock_enable_idle(self, true) 133 | else 134 | -- report error 135 | zsock_handle_error(self, err) 136 | end 137 | return false 138 | end 139 | if not more and self.state == "SEND_ONLY" then 140 | -- sent whole message, switch to receiving state 141 | self.state = "RECV_ONLY" 142 | -- make sure the idle callback is started 143 | zsock_enable_idle(self, true) 144 | end 145 | return true 146 | end 147 | 148 | local function zsock_send_queue(self) 149 | local send_max = self.send_max 150 | local count = 0 151 | local s = self.socket 152 | local queue = self.send_queue 153 | 154 | repeat 155 | local data = queue[1] 156 | -- check for send more marker 157 | local more = (queue[2] == mark_SNDMORE) 158 | local sent = zsock_send_data(self, data, more) 159 | if not sent then 160 | return 161 | end 162 | -- pop sent data from queue 163 | tremove(queue, 1) 164 | -- pop send more marker 165 | if more then 166 | tremove(queue, 1) 167 | else 168 | -- whole message sent 169 | count = count + 1 170 | end 171 | -- check if queue is empty 172 | if #queue == 0 then 173 | -- un-block socket 174 | self.need_send = false 175 | self.send_blocked = false 176 | -- finished queue is empty 177 | return 178 | end 179 | until count >= send_max 180 | -- hit max send and still have more data to send 181 | self.need_send = true 182 | -- make sure idle watcher is running. 183 | zsock_enable_idle(self, true) 184 | return 185 | end 186 | 187 | local function zsock_receive_data(self) 188 | local recv_max = self.recv_max 189 | local count = 0 190 | local s = self.socket 191 | local handler = self.handler 192 | local msg = self.recv_msg 193 | self.recv_msg = nil 194 | 195 | repeat 196 | local data, err = s:recv(z_NOBLOCK) 197 | if err then 198 | -- check for blocking. 199 | if err == 'timeout' then 200 | -- store any partial message we may have received. 201 | self.recv_msg = msg 202 | -- recv blocked 203 | self.recv_enabled = false 204 | else 205 | -- report error 206 | zsock_handle_error(self, err) 207 | end 208 | return 209 | end 210 | -- check for more message parts. 211 | local more = s:getopt(z_RCVMORE) 212 | if msg ~= nil then 213 | tinsert(msg, data) 214 | else 215 | if more == 1 then 216 | -- create multipart message 217 | msg = { data } 218 | else 219 | -- simple one part message 220 | msg = data 221 | end 222 | end 223 | if more == 0 then 224 | -- finished receiving whole message 225 | if self.state == "RECV_ONLY" then 226 | -- switch to sending state 227 | self.state = "SEND_ONLY" 228 | end 229 | -- pass read message to handler 230 | err = handler.handle_msg(self, msg) 231 | if err then 232 | -- report error 233 | zsock_handle_error(self, err) 234 | return 235 | end 236 | -- can't receive any more messages when in send_only state 237 | if self.state == "SEND_ONLY" then 238 | self.recv_enabled = false 239 | return 240 | end 241 | msg = nil 242 | count = count + 1 243 | end 244 | until count >= recv_max or self.is_closing 245 | 246 | -- save any partial message. 247 | self.recv_msg = msg 248 | 249 | -- hit max receive and we are not blocked on receiving. 250 | self.recv_enabled = true 251 | -- make sure idle watcher is running. 252 | zsock_enable_idle(self, true) 253 | end 254 | 255 | local function zsock_dispatch_events(self) 256 | local s = self.socket 257 | local readable = false 258 | local writeable = false 259 | 260 | -- check ZMQ_EVENTS 261 | local events = s:getopt(z_EVENTS) 262 | if events == z_POLLIN_OUT then 263 | readable = true 264 | writeable = true 265 | elseif events == z_POLLIN then 266 | readable = true 267 | elseif events == z_POLLOUT and self.need_send then 268 | writeable = true 269 | else 270 | -- no events read block until next read event. 271 | return zsock_enable_idle(self, false) 272 | end 273 | 274 | -- always read when the socket is readable 275 | if readable then 276 | zsock_receive_data(self) 277 | else 278 | -- recv is blocked 279 | self.recv_enabled = false 280 | end 281 | -- if socket is writeable and the send queue is not empty 282 | if writeable and self.need_send then 283 | zsock_send_queue(self) 284 | end 285 | end 286 | 287 | local function _queue_msg(queue, msg, offset, more) 288 | local parts = #msg 289 | -- queue first part of message 290 | tinsert(queue, msg[offset]) 291 | for i=offset+1,parts do 292 | -- queue more marker flag 293 | tinsert(queue, mark_SNDMORE) 294 | -- queue part of message 295 | tinsert(queue, msg[i]) 296 | end 297 | if more then 298 | -- queue more marker flag 299 | tinsert(queue, mark_SNDMORE) 300 | end 301 | end 302 | 303 | local function zsock_send(self, data, more) 304 | if type(data) == 'table' then 305 | local i = 1 306 | -- if socket is not blocked 307 | if not self.send_blocked then 308 | local parts = #data 309 | -- try sending message 310 | while zsock_send_data(self, data[i], true) do 311 | i = i + 1 312 | -- send last part of the message with the value from 'more' 313 | if i == parts then 314 | -- try sending last part of message 315 | if zsock_send_data(self, data[i], more) then 316 | return true, nil 317 | end 318 | -- failed to send last chunk, it will be queued 319 | break 320 | end 321 | end 322 | end 323 | -- queue un-sent parts of message 324 | _queue_msg(self.queue, data, i, more) 325 | else 326 | -- if socket is not blocked 327 | if not self.send_blocked then 328 | if zsock_send_data(self, data, more) then 329 | -- data sent we are finished 330 | return true, nil 331 | end 332 | end 333 | -- queue un-sent data 334 | local queue = self.send_queue 335 | -- queue simple data. 336 | tinsert(queue, data) 337 | -- check if there is more data to send 338 | if more then 339 | -- queue a marker flag 340 | tinsert(queue, mark_SNDMORE) 341 | end 342 | end 343 | return true, nil 344 | end 345 | 346 | local zsock_mt = { 347 | send = zsock_send, 348 | setopt = zsock_setopt, 349 | getopt = zsock_getopt, 350 | identity = zsock_identity, 351 | bind = zsock_bind, 352 | connect = zsock_connect, 353 | close = zsock_close, 354 | } 355 | zsock_mt.__index = zsock_mt 356 | 357 | local zsock_no_send_mt = { 358 | setopt = zsock_setopt, 359 | getopt = zsock_getopt, 360 | identity = zsock_identity, 361 | bind = zsock_bind, 362 | connect = zsock_connect, 363 | close = zsock_close, 364 | } 365 | zsock_no_send_mt.__index = zsock_no_send_mt 366 | 367 | local zsock_sub_mt = { 368 | setopt = zsock_setopt, 369 | getopt = zsock_getopt, 370 | sub = zsock_sub, 371 | unsub = zsock_unsub, 372 | identity = zsock_identity, 373 | bind = zsock_bind, 374 | connect = zsock_connect, 375 | close = zsock_close, 376 | } 377 | zsock_sub_mt.__index = zsock_sub_mt 378 | 379 | local type_info = { 380 | -- publish/subscribe sockets 381 | [zmq.PUB] = { mt = zsock_mt, recv = false, send = true }, 382 | [zmq.SUB] = { mt = zsock_sub_mt, recv = true, send = false }, 383 | -- push/pull sockets 384 | [zmq.PUSH] = { mt = zsock_mt, recv = false, send = true }, 385 | [zmq.PULL] = { mt = zsock_no_send_mt, recv = true, send = false }, 386 | -- two-way pair socket 387 | [zmq.PAIR] = { mt = zsock_mt, recv = true, send = true }, 388 | -- request/response sockets 389 | [zmq.REQ] = { mt = zsock_mt, recv = true, send = true, state = "SEND_ONLY" }, 390 | [zmq.REP] = { mt = zsock_mt, recv = true, send = true, state = "RECV_ONLY" }, 391 | -- extended request/response sockets 392 | [zmq.XREQ] = { mt = zsock_mt, recv = true, send = true }, 393 | [zmq.XREP] = { mt = zsock_mt, recv = true, send = true }, 394 | } 395 | 396 | local function zsock_wrap(s, s_type, loop, msg_cb, err_cb) 397 | local tinfo = type_info[s_type] 398 | local handler = { handle_msg = msg_cb, handle_error = err_cb} 399 | -- create zmq socket 400 | local self = { 401 | s_type = s_type, 402 | socket = s, 403 | loop = loop, 404 | handler = handler, 405 | need_send = false, 406 | recv_enabled = false, 407 | idle_enabled = false, 408 | is_closing = false, 409 | state = tinfo.state, -- copy initial socket state. 410 | } 411 | setmetatable(self, tinfo.mt) 412 | 413 | local fd = s:getopt(zmq.FD) 414 | -- create IO watcher. 415 | if tinfo.send then 416 | self.send_blocked = false 417 | self.send_queue = {} 418 | self.send_max = default_send_max 419 | end 420 | if tinfo.recv then 421 | local recv_cb = function() 422 | -- check for the real events. 423 | zsock_dispatch_events(self) 424 | end 425 | self.io_recv = ev.IO.new(recv_cb, fd, ev.READ) 426 | self.recv_max = default_recv_max 427 | self.io_recv:start(loop) 428 | end 429 | local idle_cb = function() 430 | -- dispatch events. 431 | zsock_dispatch_events(self) 432 | end 433 | -- this Idle watcher is used to convert ZeroMQ FD's edge-triggered fashion to level-triggered 434 | self.io_idle = ev.Idle.new(idle_cb) 435 | if self.state == nil or self.state == 'RECV_ONLY' then 436 | zsock_enable_idle(self, true) 437 | end 438 | 439 | return self 440 | end 441 | 442 | local function create(self, s_type, msg_cb, err_cb) 443 | -- create ZeroMQ socket 444 | local s, err = self.ctx:socket(s_type) 445 | if not s then return nil, err end 446 | 447 | -- wrap socket. 448 | return zsock_wrap(s, s_type, self.loop, msg_cb, err_cb) 449 | end 450 | 451 | module(...) 452 | 453 | -- copy constants 454 | for k,v in pairs(zmq) do 455 | -- only copy upper-case string values. 456 | if type(k) == 'string' and k == k:upper() then 457 | _M[k] = v 458 | end 459 | end 460 | 461 | local meta = {} 462 | meta.__index = meta 463 | local function no_recv_cb() 464 | error("Invalid this type of ZeroMQ socket shouldn't receive data.") 465 | end 466 | function meta:pub(err_cb) 467 | return create(self, zmq.PUB, no_recv_cb, err_cb) 468 | end 469 | 470 | function meta:sub(msg_cb, err_cb) 471 | return create(self, zmq.SUB, msg_cb, err_cb) 472 | end 473 | 474 | function meta:push(err_cb) 475 | return create(self, zmq.PUSH, no_recv_cb, err_cb) 476 | end 477 | 478 | function meta:pull(msg_cb, err_cb) 479 | return create(self, zmq.PULL, msg_cb, err_cb) 480 | end 481 | 482 | function meta:pair(msg_cb, err_cb) 483 | return create(self, zmq.PAIR, msg_cb, err_cb) 484 | end 485 | 486 | function meta:req(msg_cb, err_cb) 487 | return create(self, zmq.REQ, msg_cb, err_cb) 488 | end 489 | 490 | function meta:rep(msg_cb, err_cb) 491 | return create(self, zmq.REP, msg_cb, err_cb) 492 | end 493 | 494 | function meta:xreq(msg_cb, err_cb) 495 | return create(self, zmq.XREQ, msg_cb, err_cb) 496 | end 497 | 498 | function meta:xrep(msg_cb, err_cb) 499 | return create(self, zmq.XREP, msg_cb, err_cb) 500 | end 501 | 502 | function meta:term() 503 | return self.ctx:term() 504 | end 505 | 506 | function init(loop, io_threads) 507 | -- create ZeroMQ context 508 | local ctx, err = zmq.init(io_threads) 509 | if not ctx then return nil, err end 510 | 511 | return setmetatable({ ctx = ctx, loop = loop }, meta) 512 | end 513 | 514 | -------------------------------------------------------------------------------- /lua-handler-http-scm-0.rockspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | package = 'lua-handler-http' 4 | version = 'scm-0' 5 | source = { 6 | url = 'git://github.com/Neopallium/lua-handlers.git' 7 | } 8 | description = { 9 | summary = "HTTP client handler class.", 10 | detailed = '', 11 | homepage = 'https://github.com/Neopallium/lua-handlers', 12 | license = 'MIT', 13 | } 14 | dependencies = { 15 | 'lua-handler', 16 | 'lua-http-parser', 17 | 'luasocket', 18 | } 19 | build = { 20 | type = 'none', 21 | install = { 22 | lua = { 23 | ['handler.http.client'] = "handler/http/client.lua", 24 | ['handler.http.client.connection'] = "handler/http/client/connection.lua", 25 | ['handler.http.client.hosts'] = "handler/http/client/hosts.lua", 26 | ['handler.http.client.request'] = "handler/http/client/request.lua", 27 | ['handler.http.client.response'] = "handler/http/client/response.lua", 28 | ['handler.http.chunked'] = "handler/http/chunked.lua", 29 | ['handler.http.server'] = "handler/http/server.lua", 30 | ['handler.http.server.hconnection'] = "handler/http/server/hconnection.lua", 31 | ['handler.http.server.error_handler'] = "handler/http/server/error_handler.lua", 32 | ['handler.http.server.request'] = "handler/http/server/request.lua", 33 | ['handler.http.server.response'] = "handler/http/server/response.lua", 34 | ['handler.http.headers'] = "handler/http/headers.lua", 35 | ['handler.http.file'] = "handler/http/file.lua", 36 | ['handler.http.form'] = "handler/http/form.lua", 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lua-handler-nixio-scm-0.rockspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | package = 'lua-handler-nixio' 4 | version = 'scm-0' 5 | source = { 6 | url = 'git://github.com/Neopallium/lua-handlers.git' 7 | } 8 | description = { 9 | summary = "Depercated module. Nixio is now the main socket backend.", 10 | detailed = '', 11 | homepage = 'https://github.com/Neopallium/lua-handlers', 12 | license = 'MIT', 13 | } 14 | dependencies = { 15 | 'nixio', 16 | 'lua-ev', 17 | } 18 | build = { 19 | type = 'none', 20 | install = { 21 | lua = { 22 | ['handler.nixio.acceptor'] = "handler/nixio/acceptor.lua", 23 | ['handler.nixio.connection'] = "handler/nixio/connection.lua", 24 | ['handler.nixio.datagram'] = "handler/nixio/datagram.lua", 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lua-handler-scm-0.rockspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | package = 'lua-handler' 4 | version = 'scm-0' 5 | source = { 6 | url = 'git://github.com/Neopallium/lua-handlers.git' 7 | } 8 | description = { 9 | summary = "Socket handler class that wrap lua-ev/nixio.", 10 | detailed = '', 11 | homepage = 'https://github.com/Neopallium/lua-handlers', 12 | license = 'MIT', 13 | } 14 | dependencies = { 15 | 'nixio', 16 | 'lua-ev', 17 | } 18 | build = { 19 | type = 'none', 20 | install = { 21 | lua = { 22 | ['handler.acceptor'] = "handler/acceptor.lua", 23 | ['handler.connection'] = "handler/connection.lua", 24 | ['handler.connection.tls_backend'] = "handler/connection/tls_backend.lua", 25 | ['handler.datagram'] = "handler/datagram.lua", 26 | ['handler.uri'] = "handler/uri.lua", 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lua-handler-zmq-scm-0.rockspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | package = 'lua-handler-zmq' 4 | version = 'scm-0' 5 | source = { 6 | url = 'git://github.com/Neopallium/lua-handlers.git' 7 | } 8 | description = { 9 | summary = "ZeroMQ async. handler class.", 10 | detailed = '', 11 | homepage = 'https://github.com/Neopallium/lua-handlers', 12 | license = 'MIT', 13 | } 14 | dependencies = { 15 | 'lua-ev', 16 | 'lua-zmq', 17 | } 18 | build = { 19 | type = 'none', 20 | install = { 21 | lua = { 22 | ['handler.zmq'] = "handler/zmq.lua", 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /perf/zmq/local_lat.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, replish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local format = string.format 22 | local function printf(fmt, ...) 23 | print(format(fmt, ...)) 24 | end 25 | 26 | if #arg ~= 3 then 27 | printf("usage: %s ", arg[0]) 28 | os.exit() 29 | end 30 | 31 | local bind_to = arg[1] 32 | local message_size = tonumber(arg[2]) 33 | local roundtrip_count = tonumber(arg[3]) 34 | 35 | local zmq = require'handler.zmq' 36 | local ev = require'ev' 37 | local loop = ev.Loop.default 38 | 39 | local ctx = zmq.init(loop, 1) 40 | 41 | local i = 0 42 | -- define request handler 43 | local function handle_msg(sock, data) 44 | sock:send(data) 45 | i = i + 1 46 | if i == roundtrip_count then 47 | loop:unloop() 48 | end 49 | end 50 | 51 | -- create response worker 52 | local zrep = ctx:rep(handle_msg) 53 | 54 | zrep.recv_max = 1000 55 | zrep:bind(bind_to) 56 | 57 | loop:loop() 58 | 59 | zrep:close() 60 | ctx:term() 61 | 62 | -------------------------------------------------------------------------------- /perf/zmq/local_thr.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, replish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local format = string.format 22 | local function printf(fmt, ...) 23 | print(format(fmt, ...)) 24 | end 25 | 26 | if #arg ~= 3 then 27 | printf("usage: %s ", arg[0]) 28 | os.exit() 29 | end 30 | 31 | local bind_to = arg[1] 32 | local message_size = tonumber(arg[2]) 33 | local message_count = tonumber(arg[3]) 34 | 35 | local socket = require'socket' 36 | local time = socket.gettime 37 | local zmq = require'handler.zmq' 38 | local ev = require'ev' 39 | local loop = ev.Loop.default 40 | 41 | local ctx = zmq.init(loop, 1) 42 | 43 | local start_time, end_time 44 | local i = 0 45 | -- define SUB worker 46 | local function handle_msg(sock, data) 47 | if i == 0 then 48 | start_time = time() 49 | end 50 | i = i + 1 51 | if i == message_count then 52 | end_time = time() 53 | loop:unloop() 54 | end 55 | end 56 | 57 | -- create SUB worker 58 | local zsub = ctx:sub(handle_msg) 59 | 60 | zsub.recv_max = 200000 --math.max(message_count / 1000, 20) 61 | zsub:sub("") 62 | zsub:bind(bind_to) 63 | 64 | loop:loop() 65 | 66 | local elapsed = end_time - start_time 67 | if elapsed == 0 then 68 | elapsed = 1 69 | end 70 | 71 | printf("elapsed: %d secs", elapsed) 72 | local throughput = message_count / elapsed 73 | local megabits = throughput * message_size * 8 / 1000000 74 | 75 | printf("message size: %d [B]", message_size) 76 | printf("message count: %d", message_count) 77 | printf("mean throughput: %d [msg/s]", throughput) 78 | printf("mean throughput: %.3f [Mb/s]", megabits) 79 | 80 | zsub:close() 81 | ctx:term() 82 | 83 | -------------------------------------------------------------------------------- /perf/zmq/remote_lat.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, replish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local format = string.format 22 | local function printf(fmt, ...) 23 | print(format(fmt, ...)) 24 | end 25 | 26 | if #arg ~= 3 then 27 | printf("usage: %s ", arg[0]) 28 | os.exit() 29 | end 30 | 31 | local connect_to = arg[1] 32 | local message_size = tonumber(arg[2]) 33 | local roundtrip_count = tonumber(arg[3]) 34 | 35 | local socket = require'socket' 36 | local time = socket.gettime 37 | local zmq = require'handler.zmq' 38 | local ev = require'ev' 39 | local loop = ev.Loop.default 40 | 41 | local ctx = zmq.init(loop, 1) 42 | 43 | local msg = string.rep('0', message_size) 44 | local start_time, end_time 45 | local i = 0 46 | -- define request handler 47 | local function handle_msg(sock, data) 48 | i = i + 1 49 | if i == roundtrip_count then 50 | end_time = time() 51 | loop:unloop() 52 | return 53 | end 54 | sock:send(msg) 55 | end 56 | 57 | -- create response worker 58 | local zreq = ctx:req(handle_msg) 59 | 60 | zreq:connect(connect_to) 61 | 62 | start_time = time() 63 | -- send first message 64 | zreq:send(msg) 65 | 66 | loop:loop() 67 | 68 | local elapsed = end_time - start_time 69 | 70 | local latency = elapsed * 1000000 / roundtrip_count / 2 71 | 72 | printf("message size: %d [B]", message_size) 73 | printf("roundtrip count: %d", roundtrip_count) 74 | printf("mean latency: %.3f [us]", latency) 75 | 76 | zreq:close() 77 | ctx:term() 78 | 79 | -------------------------------------------------------------------------------- /perf/zmq/remote_thr.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2010 by Robert G. Jakabosky 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to deal 5 | -- in the Software without restriction, including without limitation the rights 6 | -- to use, copy, modify, merge, replish, distribute, sublicense, and/or sell 7 | -- copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | -- THE SOFTWARE. 20 | 21 | local format = string.format 22 | local function printf(fmt, ...) 23 | print(format(fmt, ...)) 24 | end 25 | 26 | if #arg ~= 3 then 27 | printf("usage: %s ", arg[0]) 28 | os.exit() 29 | end 30 | 31 | local connect_to = arg[1] 32 | local message_size = tonumber(arg[2]) 33 | local message_count = tonumber(arg[3]) 34 | 35 | local zmq = require'handler.zmq' 36 | local ev = require'ev' 37 | local loop = ev.Loop.default 38 | 39 | local ctx = zmq.init(loop, 1) 40 | 41 | -- create PUB worker 42 | local zpub = ctx:pub() 43 | 44 | zpub.send_max = 200 --math.max(message_count / 1000, 20) 45 | zpub:connect(connect_to) 46 | 47 | local msg = string.rep('0', message_size) 48 | for i=1,message_count do 49 | zpub:send(msg) 50 | end 51 | 52 | local timer = ev.Timer.new(function() 53 | loop:unloop() 54 | end, 1) 55 | timer:start(loop, true) 56 | 57 | loop:loop() 58 | 59 | zpub:close() 60 | ctx:term() 61 | 62 | --------------------------------------------------------------------------------