├── test.lua ├── test_ssl_config.lua ├── test_server.key ├── test_server.cert ├── rockspecs ├── tango-zmq-0.1.1-1.rockspec ├── tango-zmq-0.2.1-1.rockspec ├── tango-ev-0.1.1-1.rockspec ├── tango-copas-0.1.1-1.rockspec ├── tango-ev-0.2.1-1.rockspec ├── tango-copas-0.2.1-1.rockspec ├── tango-complete-0.1.1-1.rockspec └── tango-complete-0.2.1-1.rockspec ├── tango ├── config.lua ├── client │ ├── zmq.lua │ └── socket.lua ├── utils │ ├── socket_message.lua │ └── serialization.lua ├── proxy.lua ├── server │ ├── zmq.lua │ ├── ev_socket.lua │ └── copas_socket.lua └── dispatcher.lua ├── COPYRIGHT ├── test_server.lua ├── tango.lua ├── test_client.lua └── README.md /test.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | local run_client_test = 3 | function(server_backend,client_backend,option) 4 | local cmd = 'lua test_client.lua %s %s %s' 5 | os.execute(cmd:format(server_backend,client_backend,option or '')) 6 | end 7 | 8 | run_client_test('copas_socket','socket') 9 | if pcall(require,'ssl') then 10 | run_client_test('copas_socket','socket','ssl') 11 | end 12 | run_client_test('ev_socket','socket') 13 | run_client_test('zmq','zmq') 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test_ssl_config.lua: -------------------------------------------------------------------------------- 1 | return { 2 | client = { 3 | mode = 'client', 4 | protocol = 'tlsv1', 5 | verify = 'none', 6 | options = 'all', 7 | ciphers = 'ALL:!ADH:@STRENGTH' 8 | }, 9 | server = { 10 | mode = 'server', 11 | protocol = 'tlsv1', 12 | options = {'all'}, 13 | verify = {'peer'}, 14 | options = {'all'}, 15 | ciphers = 'ALL:!ADH:@STRENGTH', 16 | certificate = './test_server.cert', 17 | key = './test_server.key' 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test_server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIBOgIBAAJBAJ1cMvh69CSRorVeNQA/LMVUzvH9TqkOiRnBN1e625XSHJ1rfZEQ 3 | aLuAG6tDNhExFBBedb1LRjT8urQb7IQchjkCAwEAAQJAe5/2j04RRjWALZrAatw2 4 | 8SSKnIST6q73uNsZ/ntXjeBTcXst2rJCULlgKD+VLtLWyWja025kuzCcX9HXagPe 5 | XQIhANBuURuIcCB6J1qfuwDvWHUToBDuHdlWTeOgFX9A1sazAiEAwUYL3NdG8/Kr 6 | VPAbakwTCRnpeeJ89COBZut2Hpq7FWMCIF5LWjQ7kIaQ3Nb55m8w2PL2cvbV0vkt 7 | O0Wceb09Ry1TAiAxNhOpLItdbAmh++0PGMW0CIwBQ+ELDMtTGFsgGcfO/wIhAK9c 8 | jjzrA0gvv+i/Ax6ExoqlT1hlw1z8lyULEwEbkhVi 9 | -----END RSA PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /test_server.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB0DCCAXoCCQCHQGKIXEEwbTANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJV 3 | UzERMA8GA1UECBMIS2VudHVja3kxEzARBgNVBAcTCkxvdWlzdmlsbGUxHTAbBgNV 4 | BAoTFE5vbi1FeGlzdGFudC1PcmcgTExDMRkwFwYDVQQDExB0YW5nby1zc2wgc2Vy 5 | dmVyMB4XDTEyMDExNjE1NTM1NFoXDTIyMDExMzE1NTM1NFowbzELMAkGA1UEBhMC 6 | VVMxETAPBgNVBAgTCEtlbnR1Y2t5MRMwEQYDVQQHEwpMb3Vpc3ZpbGxlMR0wGwYD 7 | VQQKExROb24tRXhpc3RhbnQtT3JnIExMQzEZMBcGA1UEAxMQdGFuZ28tc3NsIHNl 8 | cnZlcjBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCdXDL4evQkkaK1XjUAPyzFVM7x 9 | /U6pDokZwTdXutuV0hyda32REGi7gBurQzYRMRQQXnW9S0Y0/Lq0G+yEHIY5AgMB 10 | AAEwDQYJKoZIhvcNAQEFBQADQQAJHqSYxM/xhvwRHXpkDJk8TNkvE5IpP6PBqRX9 11 | T+QLXsep3Yng6kOcJQErsWphu9168fXjLdjAfZGPktER/IC/ 12 | -----END CERTIFICATE----- 13 | -------------------------------------------------------------------------------- /rockspecs/tango-zmq-0.1.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'tango-zmq' 2 | version = '0.1.1-1' 3 | source = { 4 | url = 'git://github.com/lipp/tango.git', 5 | branch = '0.1.1' 6 | } 7 | description = { 8 | summary = 'Remote procedure calls (RPC) for Lua.', 9 | homepage = 'http://github.com/lipp/tango', 10 | license = 'MIT/X11' 11 | } 12 | dependencies = { 13 | 'lua >= 5.1', 14 | 'lua-zmq >= 1.0' 15 | } 16 | build = { 17 | type = 'builtin', 18 | modules = { 19 | ['tango.proxy'] = 'tango/proxy.lua', 20 | ['tango.dispatch'] = 'tango/dispatch.lua', 21 | ['tango.utils.serialization'] = 'tango/utils/serialization.lua', 22 | ['tango.client.zmq'] = 'tango/client/zmq.lua', 23 | ['tango.server.zmq'] = 'tango/server/zmq.lua' 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rockspecs/tango-zmq-0.2.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'tango-zmq' 2 | version = '0.2.1-1' 3 | source = { 4 | url = 'git://github.com/lipp/tango.git', 5 | branch = '0.2.1' 6 | } 7 | description = { 8 | summary = 'Remote procedure calls (RPC) for Lua.', 9 | homepage = 'http://github.com/lipp/tango', 10 | license = 'MIT/X11' 11 | } 12 | dependencies = { 13 | 'lua >= 5.1', 14 | 'lua-zmq >= 1.0' 15 | } 16 | build = { 17 | type = 'builtin', 18 | modules = { 19 | ['tango'] = 'tango.lua', 20 | ['tango.proxy'] = 'tango/proxy.lua', 21 | ['tango.dispatcher'] = 'tango/dispatcher.lua', 22 | ['tango.config'] = 'tango/config.lua', 23 | ['tango.utils.serialization'] = 'tango/utils/serialization.lua', 24 | ['tango.client.zmq'] = 'tango/client/zmq.lua', 25 | ['tango.server.zmq'] = 'tango/server/zmq.lua' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rockspecs/tango-ev-0.1.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'tango-ev' 2 | version = '0.1.1-1' 3 | source = { 4 | url = 'git://github.com/lipp/tango.git', 5 | branch = '0.1.1' 6 | } 7 | description = { 8 | summary = 'Remote procedure calls (RPC) for Lua.', 9 | homepage = 'http://github.com/lipp/tango', 10 | license = 'MIT/X11' 11 | } 12 | dependencies = { 13 | 'lua >= 5.1', 14 | 'luasocket >= 2.0.2', 15 | 'lua-ev' 16 | } 17 | build = { 18 | type = 'builtin', 19 | modules = { 20 | ['tango.proxy'] = 'tango/proxy.lua', 21 | ['tango.dispatch'] = 'tango/dispatch.lua', 22 | ['tango.utils.serialization'] = 'tango/utils/serialization.lua', 23 | ['tango.utils.socket_message'] = 'tango/utils/socket_message.lua', 24 | ['tango.client.socket'] = 'tango/client/socket.lua', 25 | ['tango.server.ev_socket'] = 'tango/server/ev_socket.lua' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rockspecs/tango-copas-0.1.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'tango-copas' 2 | version = '0.1.1-1' 3 | source = { 4 | url = 'git://github.com/lipp/tango.git', 5 | branch = '0.1.1' 6 | } 7 | description = { 8 | summary = 'Remote procedure calls (RPC) for Lua.', 9 | homepage = 'http://github.com/lipp/tango', 10 | license = 'MIT/X11' 11 | } 12 | dependencies = { 13 | 'lua >= 5.1', 14 | 'luasocket >= 2.0.2', 15 | 'copas >= 1.1.6' 16 | } 17 | build = { 18 | type = 'builtin', 19 | modules = { 20 | ['tango.proxy'] = 'tango/proxy.lua', 21 | ['tango.dispatch'] = 'tango/dispatch.lua', 22 | ['tango.utils.serialization'] = 'tango/utils/serialization.lua', 23 | ['tango.utils.socket_message'] = 'tango/utils/socket_message.lua', 24 | ['tango.client.socket'] = 'tango/client/socket.lua', 25 | ['tango.server.copas_socket'] = 'tango/server/copas_socket.lua' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rockspecs/tango-ev-0.2.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'tango-ev' 2 | version = '0.2.1-1' 3 | source = { 4 | url = 'git://github.com/lipp/tango.git', 5 | branch = '0.2.1' 6 | } 7 | description = { 8 | summary = 'Remote procedure calls (RPC) for Lua.', 9 | homepage = 'http://github.com/lipp/tango', 10 | license = 'MIT/X11' 11 | } 12 | dependencies = { 13 | 'lua >= 5.1', 14 | 'luasocket >= 2.0.2', 15 | 'lua-ev' 16 | } 17 | build = { 18 | type = 'builtin', 19 | modules = { 20 | ['tango'] = 'tango.lua', 21 | ['tango.proxy'] = 'tango/proxy.lua', 22 | ['tango.dispatcher'] = 'tango/dispatcher.lua', 23 | ['tango.config'] = 'tango/config.lua', 24 | ['tango.utils.serialization'] = 'tango/utils/serialization.lua', 25 | ['tango.utils.socket_message'] = 'tango/utils/socket_message.lua', 26 | ['tango.client.socket'] = 'tango/client/socket.lua', 27 | ['tango.server.ev_socket'] = 'tango/server/ev_socket.lua' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rockspecs/tango-copas-0.2.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'tango-copas' 2 | version = '0.2.1-1' 3 | source = { 4 | url = 'git://github.com/lipp/tango.git', 5 | branch = '0.2.1' 6 | } 7 | description = { 8 | summary = 'Remote procedure calls (RPC) for Lua.', 9 | homepage = 'http://github.com/lipp/tango', 10 | license = 'MIT/X11' 11 | } 12 | dependencies = { 13 | 'lua >= 5.1', 14 | 'luasocket >= 2.0.2', 15 | 'copas >= 1.1.6' 16 | } 17 | build = { 18 | type = 'builtin', 19 | modules = { 20 | ['tango'] = 'tango.lua', 21 | ['tango.proxy'] = 'tango/proxy.lua', 22 | ['tango.dispatcher'] = 'tango/dispatcher.lua', 23 | ['tango.config'] = 'tango/config.lua', 24 | ['tango.utils.serialization'] = 'tango/utils/serialization.lua', 25 | ['tango.utils.socket_message'] = 'tango/utils/socket_message.lua', 26 | ['tango.client.socket'] = 'tango/client/socket.lua', 27 | ['tango.server.copas_socket'] = 'tango/server/copas_socket.lua' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tango/config.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | local globals = _G 3 | local pcall = pcall 4 | 5 | module('tango.config') 6 | 7 | server_default = 8 | function(config) 9 | config = config or {} 10 | config.functab = config.functab or globals 11 | config.serialize = config.serialize or require'tango.utils.serialization'.serialize 12 | config.unserialize = config.unserialize or require'tango.utils.serialization'.unserialize 13 | config.pcall = pcall 14 | if config.write_access == nil then 15 | config.write_access = true 16 | end 17 | if config.read_access == nil then 18 | config.read_access = true 19 | end 20 | return config 21 | end 22 | 23 | client_default = 24 | function(config) 25 | config = config or {} 26 | config.serialize = config.serialize or require'tango.utils.serialization'.serialize 27 | config.unserialize = config.unserialize or require'tango.utils.serialization'.unserialize 28 | return config 29 | end 30 | 31 | return { 32 | server_default = server_default, 33 | client_default = client_default 34 | } 35 | 36 | -------------------------------------------------------------------------------- /rockspecs/tango-complete-0.1.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'tango-complete' 2 | version = '0.1.1-1' 3 | source = { 4 | url = 'git://github.com/lipp/tango.git', 5 | branch = '0.1.1' 6 | } 7 | description = { 8 | summary = 'Remote procedure calls (RPC) for Lua.', 9 | homepage = 'http://github.com/lipp/tango', 10 | license = 'MIT/X11' 11 | } 12 | dependencies = { 13 | 'lua >= 5.1', 14 | 'luasocket >= 2.0.2', 15 | 'copas >= 1.1.6', 16 | 'lua-ev', 17 | 'lua-zmq >= 1.0' 18 | } 19 | build = { 20 | type = 'builtin', 21 | modules = { 22 | ['tango.proxy'] = 'tango/proxy.lua', 23 | ['tango.dispatch'] = 'tango/dispatch.lua', 24 | ['tango.utils.serialization'] = 'tango/utils/serialization.lua', 25 | ['tango.utils.socket_message'] = 'tango/utils/socket_message.lua', 26 | ['tango.client.socket'] = 'tango/client/socket.lua', 27 | ['tango.client.zmq'] = 'tango/client/zmq.lua', 28 | ['tango.server.copas_socket'] = 'tango/server/copas_socket.lua', 29 | ['tango.server.ev_socket'] = 'tango/server/ev_socket.lua', 30 | ['tango.server.zmq'] = 'tango/server/zmq.lua' 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 by Gerhard Lipp 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. -------------------------------------------------------------------------------- /tango/client/zmq.lua: -------------------------------------------------------------------------------- 1 | local zmq = require"zmq" 2 | require'tango' -- automatically import tango.ref ans tango.unref 3 | local proxy = require'tango.proxy' 4 | local default = require'tango.config'.client_default 5 | 6 | module('tango.client.zmq') 7 | 8 | local context 9 | 10 | connect = 11 | function(config) 12 | config = default(config) 13 | config.url = config.url or 'tcp://localhost:12345' 14 | -- prevent multiple calls to zmq.init! 15 | config.context = config.context or context or zmq.init(1) 16 | context = config.context 17 | local socket = config.context:socket(zmq.REQ) 18 | socket:connect(config.url) 19 | local serialize = config.serialize 20 | local unserialize = config.unserialize 21 | local send_request = 22 | function(request) 23 | local request_str = serialize(request) 24 | socket:send(request_str) 25 | end 26 | local recv_response = 27 | function() 28 | local response_str = socket:recv() 29 | return unserialize(response_str) 30 | end 31 | return proxy.new(send_request,recv_response) 32 | end 33 | 34 | return { 35 | connect = connect 36 | } 37 | -------------------------------------------------------------------------------- /rockspecs/tango-complete-0.2.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'tango-complete' 2 | version = '0.2.1-1' 3 | source = { 4 | url = 'git://github.com/lipp/tango.git', 5 | branch = '0.2.1' 6 | } 7 | description = { 8 | summary = 'Remote procedure calls (RPC) for Lua.', 9 | homepage = 'http://github.com/lipp/tango', 10 | license = 'MIT/X11' 11 | } 12 | dependencies = { 13 | 'lua >= 5.1', 14 | 'luasocket >= 2.0.2', 15 | 'copas >= 1.1.6', 16 | 'lua-ev', 17 | 'lua-zmq >= 1.0' 18 | } 19 | build = { 20 | type = 'builtin', 21 | modules = { 22 | ['tango'] = 'tango.lua', 23 | ['tango.proxy'] = 'tango/proxy.lua', 24 | ['tango.dispatcher'] = 'tango/dispatcher.lua', 25 | ['tango.config'] = 'tango/config.lua', 26 | ['tango.utils.serialization'] = 'tango/utils/serialization.lua', 27 | ['tango.utils.socket_message'] = 'tango/utils/socket_message.lua', 28 | ['tango.client.socket'] = 'tango/client/socket.lua', 29 | ['tango.client.zmq'] = 'tango/client/zmq.lua', 30 | ['tango.server.copas_socket'] = 'tango/server/copas_socket.lua', 31 | ['tango.server.ev_socket'] = 'tango/server/ev_socket.lua', 32 | ['tango.server.zmq'] = 'tango/server/zmq.lua' 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test_server.lua: -------------------------------------------------------------------------------- 1 | local backend = arg[1] 2 | local mode = arg[2] or 'rw' 3 | local option = arg[3] 4 | 5 | local config = {} 6 | if option then 7 | if option == 'ssl' then 8 | config.sslparams = require'test_ssl_config'.server 9 | end 10 | end 11 | config.write_access = mode:find('w') ~= nil 12 | config.read_access = mode:find('r') ~= nil 13 | 14 | add = 15 | function(a,b) 16 | return a+b 17 | end 18 | 19 | echo = 20 | function(...) 21 | return ... 22 | end 23 | 24 | strerror = 25 | function() 26 | error('testmessage') 27 | end 28 | 29 | customerror = 30 | function(err) 31 | error(err) 32 | end 33 | 34 | nested = { 35 | method = { 36 | name = function()return true end 37 | } 38 | } 39 | 40 | person = 41 | function(name) 42 | local p = { 43 | _name = name 44 | } 45 | 46 | function p:name(name) 47 | if name then 48 | self._name = name 49 | else 50 | return self._name 51 | end 52 | end 53 | 54 | return p 55 | end 56 | 57 | double_x = 58 | function() 59 | return 2*x 60 | end 61 | 62 | data = { 63 | x = 0, 64 | y = 3 65 | } 66 | 67 | local tango = require'tango' 68 | local server = tango.server[backend] 69 | server.loop(config) 70 | 71 | -------------------------------------------------------------------------------- /tango/utils/socket_message.lua: -------------------------------------------------------------------------------- 1 | local tonumber = tonumber 2 | local tostring = tostring 3 | local error = error 4 | 5 | module('tango.utils.socket_message') 6 | 7 | local send = 8 | function(socket,message) 9 | -- send length of the string as ascii line 10 | local sent,err = socket:send(tostring(#message)..'\n') 11 | if not sent then 12 | error(err) 13 | end 14 | -- send the actual message data 15 | sent,err = socket:send(message) 16 | if not sent then 17 | error(err) 18 | end 19 | end 20 | 21 | local receive = 22 | function(socket,on_error) 23 | local responselen,err = socket:receive('*l') 24 | if not responselen then 25 | error(err) 26 | end 27 | -- convert ascii len to number of bytes 28 | responselen = tonumber(responselen) 29 | if not responselen then 30 | error('length as ascii number string expected') 31 | end 32 | -- receive the actual response table dataa 33 | local response,err = socket:receive(responselen) 34 | if not response then 35 | error(err) 36 | end 37 | return response 38 | end 39 | 40 | return { 41 | send = send, 42 | receive = receive 43 | } 44 | -------------------------------------------------------------------------------- /tango/proxy.lua: -------------------------------------------------------------------------------- 1 | local rawget = rawget 2 | local rawset = rawset 3 | local type = type 4 | local error = error 5 | local unpack = unpack 6 | local setmetatable = setmetatable 7 | local print = print 8 | 9 | module('tango.proxy') 10 | 11 | new = 12 | function(send_request,recv_response,method_name) 13 | return setmetatable( 14 | { 15 | method_name = method_name, 16 | send_request = send_request, 17 | recv_response = recv_response 18 | }, 19 | { 20 | __index= 21 | function(self,sub_method_name) 22 | -- look up if proxy already exists 23 | local proxy = rawget(self,sub_method_name) 24 | if not proxy then 25 | local new_method_name 26 | if not method_name then 27 | new_method_name = sub_method_name 28 | else 29 | new_method_name = method_name..'.'..sub_method_name 30 | end 31 | -- create new call proxy 32 | proxy = new(send_request,recv_response,new_method_name) 33 | -- store for subsequent access 34 | rawset(self,sub_method_name,proxy) 35 | end 36 | return proxy 37 | end, 38 | __call= 39 | function(self,...) 40 | send_request({method_name,...}) 41 | local response = recv_response() 42 | if response[1] == true then 43 | return unpack(response,2) 44 | else 45 | error(response[2],2) 46 | end 47 | end 48 | }) 49 | end 50 | 51 | return { 52 | new = new 53 | } 54 | 55 | -------------------------------------------------------------------------------- /tango/server/zmq.lua: -------------------------------------------------------------------------------- 1 | local zmq = require"zmq" 2 | zmq.poller = require'zmq.poller' 3 | local serialize = require'tango.utils.serialization'.serialize 4 | local unserialize = require'tango.utils.serialization'.unserialize 5 | local dispatcher = require'tango.dispatcher' 6 | local default = require'tango.config'.server_default 7 | local print = print 8 | 9 | module('tango.server.zmq') 10 | 11 | local new = 12 | function(config) 13 | config = default(config) 14 | config.url = config.url or 'tcp://*:12345' 15 | config.context = config.context or zmq.init(1) 16 | config.poller = config.poller or zmq.poller(2) 17 | local serialize = config.serialize 18 | local unserialize = config.unserialize 19 | local dispatcher = dispatcher.new(config) 20 | local socket = config.context:socket(zmq.REP) 21 | socket:bind(config.url) 22 | local poller = config.poller 23 | local response_str 24 | local handle_request 25 | local send_response = 26 | function() 27 | socket:send(response_str) 28 | poller:modify(socket,zmq.POLLIN,handle_request) 29 | end 30 | handle_request = 31 | function() 32 | local request_str = socket:recv() 33 | if not request_str then 34 | socket:close() 35 | return 36 | end 37 | local request = unserialize(request_str) 38 | local response = dispatcher:dispatch(request) 39 | response_str = serialize(response) 40 | poller:modify(socket,zmq.POLLOUT,send_response) 41 | end 42 | 43 | poller:add(socket,zmq.POLLIN,handle_request) 44 | return config 45 | end 46 | 47 | 48 | local loop = 49 | function(config) 50 | local server = new(config) 51 | server.poller:start() 52 | server.context:term() 53 | end 54 | 55 | return { 56 | loop = loop, 57 | new = new 58 | } 59 | -------------------------------------------------------------------------------- /tango/utils/serialization.lua: -------------------------------------------------------------------------------- 1 | local tinsert = table.insert 2 | local tconcat = table.concat 3 | local tremove = table.remove 4 | local smatch = string.match 5 | local sgmatch = string.gmatch 6 | local sgsub = string.gsub 7 | local ipairs = ipairs 8 | local pairs = pairs 9 | local type = type 10 | local tostring = tostring 11 | local tonumber = tonumber 12 | local loadstring = loadstring 13 | local print = print 14 | 15 | --- The default tango serialization module. 16 | -- Uses table serialization from http://lua/users.org/wiki/TableUtils and loadstring for unserialize. 17 | module('tango.utils.serialization') 18 | 19 | serialize = nil 20 | 21 | local converters = { 22 | string = function(v) 23 | v = sgsub(v,"\n","\\n") 24 | if smatch(sgsub(v,"[^'\"]",""),'^"+$') then 25 | return "'"..v.."'" 26 | end 27 | return '"'..sgsub(v,'"','\\"')..'"' 28 | end, 29 | table = function(v) 30 | return serialize(v) 31 | end, 32 | number = function(v) 33 | return tostring(v) 34 | end, 35 | boolean = function(v) 36 | return tostring(v) 37 | end 38 | } 39 | 40 | local valtostr = 41 | function(v) 42 | local conv = converters[type(v)] 43 | if conv then 44 | return conv(v) 45 | else 46 | return 'nil' 47 | end 48 | end 49 | 50 | local keytostr = 51 | function(k) 52 | if 'string' == type(k) and smatch(k,"^[_%a][_%a%d]*$") then 53 | return k 54 | else 55 | return '['..valtostr(k)..']' 56 | end 57 | end 58 | 59 | serialize = 60 | function(tbl) 61 | local result,done = {},{} 62 | for k,v in ipairs(tbl) do 63 | tinsert(result,valtostr(v)) 64 | done[k] = true 65 | end 66 | for k,v in pairs(tbl) do 67 | if not done[k] then 68 | tinsert(result,keytostr(k)..'='..valtostr(v)) 69 | end 70 | end 71 | return '{'..tconcat(result,',')..'}' 72 | end 73 | 74 | unserialize = 75 | function(strtab) 76 | return loadstring('return '..strtab)() 77 | end 78 | 79 | return { 80 | serialize = serialize, 81 | unserialize = unserialize 82 | } 83 | -------------------------------------------------------------------------------- /tango.lua: -------------------------------------------------------------------------------- 1 | local rawget = rawget 2 | local rawset = rawset 3 | local setmetatable = setmetatable 4 | local print = print 5 | local require = require 6 | local pcall = pcall 7 | local new = require'tango.proxy'.new 8 | 9 | module('tango') 10 | 11 | local rproxies = {} 12 | 13 | local root = 14 | function(proxy) 15 | local method_name = rawget(proxy,'method_name') 16 | local send_request = rawget(proxy,'send_request') 17 | local rproxy 18 | if not rproxies[send_request] then 19 | local recv_response = rawget(proxy,'recv_response') 20 | rproxy = new(send_request,recv_response) 21 | rproxies[send_request] = rproxy 22 | end 23 | return rproxies[send_request],method_name 24 | end 25 | 26 | ref = 27 | function(proxy,...) 28 | local rproxy,create_method = root(proxy) 29 | return setmetatable( 30 | { 31 | id = rproxy.tango.ref_create(create_method,...), 32 | proxy = rproxy 33 | }, 34 | { 35 | __index = 36 | function(self,method_name) 37 | return setmetatable( 38 | { 39 | }, 40 | { 41 | __call = 42 | function(_,ref,...) 43 | local proxy = rawget(ref,'proxy') 44 | return proxy.tango.ref_call(rawget(self,'id'),method_name,...) 45 | end 46 | }) 47 | end 48 | }) 49 | end 50 | 51 | unref = 52 | function(ref) 53 | local proxy = rawget(ref,'proxy') 54 | local id = rawget(ref,'id') 55 | proxy.tango.ref_release(id) 56 | end 57 | 58 | local try_require = 59 | function(module) 60 | local ok,mod = pcall(require,module) 61 | if ok then 62 | return mod 63 | else 64 | return nil 65 | end 66 | end 67 | 68 | return { 69 | ref = ref, 70 | unref = unref, 71 | client = { 72 | socket = try_require('tango.client.socket'), 73 | zmq = try_require('tango.client.zmq') 74 | }, 75 | server = { 76 | copas_socket = try_require('tango.server.copas_socket'), 77 | ev_socket = try_require('tango.server.ev_socket'), 78 | zmq = try_require('tango.server.zmq') 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /tango/server/ev_socket.lua: -------------------------------------------------------------------------------- 1 | local socket = require'socket' 2 | local ev = require'ev' 3 | local default_loop = ev.Loop.default 4 | local send_message = require'tango.utils.socket_message'.send 5 | local receive_message = require'tango.utils.socket_message'.receive 6 | local dispatcher = require'tango.dispatcher' 7 | local default = require'tango.config'.server_default 8 | local pcall = pcall 9 | local print = print 10 | 11 | module('tango.server.ev_socket') 12 | 13 | new = 14 | function(config) 15 | config = default(config) 16 | local serialize = config.serialize 17 | local unserialize = config.unserialize 18 | local dispatcher = dispatcher.new(config) 19 | local server = socket.bind(config.interfaces or "*", 20 | config.port or 12345) 21 | return ev.IO.new( 22 | function(loop) 23 | local client = server:accept() 24 | local response_str 25 | local send_response = ev.IO.new( 26 | function(loop, send_response) 27 | local ok,err = pcall( 28 | function() 29 | send_response:stop(loop) 30 | send_message(client,response_str) 31 | end) 32 | if not ok then 33 | print(err) 34 | end 35 | end, 36 | client:getfd(), 37 | ev.WRITE) 38 | 39 | ev.IO.new( 40 | function(loop, receive_request) 41 | receive_request:stop(loop) 42 | local ok,err = pcall( 43 | function() 44 | local request_str = receive_message(client) 45 | local request = unserialize(request_str) 46 | local response = dispatcher:dispatch(request) 47 | response_str = serialize(response) 48 | send_response:start(loop) 49 | receive_request:start(loop) 50 | end) 51 | if not ok then 52 | print(err) 53 | end 54 | end, 55 | client:getfd(), 56 | ev.READ):start(loop) 57 | end, 58 | server:getfd(), 59 | ev.READ) 60 | end 61 | 62 | loop = function(config) 63 | new(config):start(default_loop) 64 | default_loop:loop() 65 | end 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /tango/client/socket.lua: -------------------------------------------------------------------------------- 1 | local error = error 2 | local pcall = pcall 3 | local socket = require'socket' 4 | require'tango' -- automatically import tango.ref ans tango.unref 5 | local proxy = require'tango.proxy' 6 | local send_message = require'tango.utils.socket_message'.send 7 | local receive_message = require'tango.utils.socket_message'.receive 8 | local default = require'tango.config'.client_default 9 | local require = require 10 | local ssl = nil 11 | 12 | module('tango.client.socket') 13 | 14 | connect = 15 | function(config) 16 | config = default(config) 17 | config.timeout = config.timeout or 5000 18 | config.address = config.address or 'localhost' 19 | config.port = config.port or 12345 20 | if config.sslparams then 21 | ok,ssl = pcall(require,'ssl') 22 | if not ok then 23 | error(ssl) 24 | end 25 | end 26 | 27 | local sock = socket.tcp() 28 | sock:settimeout(config.timeout) 29 | sock:setoption('tcp-nodelay',true) 30 | local connected,err = sock:connect(config.address, 31 | config.port) 32 | if not connected then 33 | error(err) 34 | end 35 | 36 | if config.sslparams then 37 | sock = ssl.wrap(sock, config.sslparams) 38 | ok,message = sock:dohandshake() 39 | if not ok then 40 | error('tango ssl handshake failed with: ' .. message) 41 | return 42 | end 43 | end 44 | 45 | local serialize = config.serialize 46 | local unserialize = config.unserialize 47 | local close_and_rethrow = 48 | function(err) 49 | sock:shutdown() 50 | sock:close() 51 | error(err,3) 52 | end 53 | local send_request = 54 | function(request) 55 | local req_str = serialize(request) 56 | local ok,err = pcall(send_message,sock,req_str) 57 | if ok == false then 58 | close_and_rethrow(err) 59 | end 60 | end 61 | local recv_response = 62 | function() 63 | local ok,result = pcall(receive_message,sock) 64 | if ok == true then 65 | return unserialize(result) 66 | else 67 | close_and_rethrow(result) 68 | end 69 | end 70 | return proxy.new(send_request,recv_response) 71 | end 72 | 73 | return { 74 | connect = connect 75 | } 76 | -------------------------------------------------------------------------------- /tango/server/copas_socket.lua: -------------------------------------------------------------------------------- 1 | local socket = require'socket' 2 | local copas = require'copas' 3 | local coxpcall = require'coxpcall' 4 | local copcall = copcall 5 | local pcall = pcall 6 | local print = print 7 | local send_message = require'tango.utils.socket_message'.send 8 | local receive_message = require'tango.utils.socket_message'.receive 9 | local dispatcher = require'tango.dispatcher' 10 | local default = require'tango.config'.server_default 11 | local require = require 12 | local ssl = nil 13 | 14 | module('tango.server.copas_socket') 15 | 16 | new = 17 | function(config) 18 | config = default(config) 19 | config.pcall = copcall 20 | config.interface = config.interface or '*' 21 | config.port = config.port or 12345 22 | config.ssl_timeout = config.ssl_timeout or 10 23 | 24 | if config.sslparams then 25 | ok,ssl = pcall(require,'ssl') 26 | if not ok then 27 | error(ssl) 28 | end 29 | end 30 | 31 | local request_loop = 32 | function(sock) 33 | sock:setoption('tcp-nodelay',true) 34 | 35 | if config.sslparams then 36 | sock = ssl.wrap(sock, config.sslparams) 37 | sock:settimeout(config.ssl_timeout, 't') 38 | ok,message = sock:dohandshake() 39 | if not ok then 40 | print('tango ssl handshake failed with:' .. message) 41 | return 42 | end 43 | end 44 | 45 | local wrapsock = copas.wrap(sock) 46 | local dispatcher = dispatcher.new(config) 47 | local serialize = config.serialize 48 | local unserialize = config.unserialize 49 | 50 | local ok,err = copcall( 51 | function() 52 | while true do 53 | local request_str = receive_message(wrapsock) 54 | local request = unserialize(request_str) 55 | local response = dispatcher:dispatch(request) 56 | local response_str = serialize(response) 57 | send_message(wrapsock,response_str) 58 | wrapsock:flush() 59 | end 60 | end) 61 | if not ok then 62 | print(err) 63 | end 64 | end 65 | return socket.bind(config.interface,config.port),request_loop 66 | end 67 | 68 | loop = 69 | function(config) 70 | copas.addserver(new(config)) 71 | copas.loop() 72 | end 73 | 74 | return { 75 | new = new, 76 | loop = loop 77 | } 78 | 79 | -------------------------------------------------------------------------------- /tango/dispatcher.lua: -------------------------------------------------------------------------------- 1 | local type = type 2 | local error = error 3 | local unpack = unpack 4 | local tostring = tostring 5 | local print = print 6 | 7 | module('tango.dispatcher') 8 | 9 | local error_msg = 10 | function(var_name,err) 11 | local msg = 'tango server error "%s": %s' 12 | return msg:format(var_name,err) 13 | end 14 | 15 | local new = 16 | function(config) 17 | local d = { 18 | functab = config.functab, 19 | pcall = config.pcall, 20 | read_access = config.read_access, 21 | write_access = config.write_access, 22 | dispatch = 23 | function(self,request) 24 | local var = self.functab 25 | local var_name = request[1] 26 | local last_part 27 | local last_var 28 | for part in var_name:gmatch('[%w_]+') do 29 | last_part = part 30 | last_var = var 31 | if type(var) == 'table' then 32 | var = var[part] 33 | else 34 | return {false,error_msg(var_name,'no such variable')} 35 | end 36 | end 37 | if type(var) == 'function' then 38 | return {self.pcall(var,unpack(request,2))} 39 | else 40 | local val = request[2] 41 | if val then 42 | if not self.write_access then 43 | return {false,error_msg(var_name,'no write_access')} 44 | else 45 | return {self.pcall( 46 | function() 47 | last_var[last_part] = val 48 | end)} 49 | end 50 | else 51 | if not self.read_access then 52 | return {false,error_msg(var_name,'no read_access')} 53 | else 54 | return {true,var} 55 | end 56 | end 57 | end 58 | 59 | end 60 | } 61 | 62 | d.refs = {} 63 | d.functab.tango = d.functab.tango or {} 64 | 65 | d.functab.tango.ref_create = 66 | function(create_method,...) 67 | local result = d:dispatch({create_method,...}) 68 | if result[1] == true then 69 | local obj = result[2] 70 | if type(obj) == 'table' or type(obj) == 'userdata' then 71 | local id = tostring(obj) 72 | d.refs[id] = obj 73 | return id 74 | else 75 | error('tango.ref proxy did not create table nor userdata') 76 | end 77 | else 78 | error(result[2]) 79 | end 80 | end 81 | 82 | d.functab.tango.ref_release = 83 | function(refid) 84 | d.refs[refid] = nil 85 | end 86 | 87 | d.functab.tango.ref_call = 88 | function(refid,method_name,...) 89 | local obj = d.refs[refid] 90 | if obj then 91 | return obj[method_name](obj,...) 92 | else 93 | error('tango.ref invalid id' .. refid) 94 | end 95 | end 96 | 97 | return d 98 | end 99 | 100 | return { 101 | new = new 102 | } 103 | -------------------------------------------------------------------------------- /test_client.lua: -------------------------------------------------------------------------------- 1 | local server_backend = arg[1] 2 | local client_backend = arg[2] 3 | local option = arg[3] 4 | 5 | local tango = require'tango' 6 | local config = {} 7 | if option then 8 | if option == 'ssl' then 9 | config.sslparams = require'test_ssl_config'.client 10 | end 11 | end 12 | 13 | local connect = tango.client[client_backend].connect 14 | 15 | local spawn_server = 16 | function(backend,access_str) 17 | local cmd = [[ 18 | lua test_server.lua %s %s %s & 19 | echo $! 20 | ]] 21 | cmd = cmd:format(backend,access_str,option or '') 22 | local process = io.popen(cmd) 23 | local pid = process:read() 24 | if backend ~= 'zmq' then 25 | os.execute('sleep 1') 26 | end 27 | return { 28 | process = process, 29 | pid = pid, 30 | kill = function() 31 | os.execute('kill '..pid) 32 | end 33 | } 34 | end 35 | 36 | local tests = 0 37 | local failed = 0 38 | local ok = 0 39 | local test = function(txt,f) 40 | tests = tests + 1 41 | io.write(txt..' ... ') 42 | local ret = f() 43 | if ret and ret ~= false then 44 | ok = ok + 1 45 | io.write('ok\n') 46 | else 47 | failed = failed + 1 48 | io.write('failed\n') 49 | end 50 | end 51 | 52 | local server = spawn_server(server_backend,'rw') 53 | local client = connect(config) 54 | 55 | print('==============================') 56 | print('running tests with:') 57 | print('server backend:',server_backend) 58 | print('client backend:',client_backend) 59 | if option then 60 | print('option:',option) 61 | end 62 | print('------------------------------') 63 | 64 | test('add test', 65 | function() 66 | return client.add(1,2)==3 67 | end) 68 | 69 | test('echo test', 70 | function() 71 | local tab = {number=444,name='horst',bool=true} 72 | local tab2 = client.echo(tab) 73 | return tab.number==tab2.number and tab.name==tab2.name and tab.bool==tab2.bool 74 | end) 75 | 76 | test('multiple return values', 77 | function() 78 | local a,b,c = 1.234,true,{el=11} 79 | local a2,b2,c2 = client.echo(a,b,c) 80 | return a==a2 and b==b2 and c.el==c2.el 81 | end) 82 | 83 | test('string error test', 84 | function() 85 | local status,msg = pcall(function()client.strerror()end) 86 | return status==false and type(msg) == 'string' and msg:find('testmessage') 87 | end) 88 | 89 | test('custom error test', 90 | function() 91 | local errtab = {code=117} 92 | local status,errtab2 = pcall(function()client.customerror(errtab)end) 93 | return status==false and type(errtab2) == 'table' and errtab2.code==errtab.code 94 | end) 95 | 96 | test('nested method name test', 97 | function() 98 | return client.nested.method.name()==true 99 | end) 100 | 101 | test('tango.ref with io.popen', 102 | function() 103 | local pref = tango.ref(client.io.popen,'echo hello') 104 | local match = pref:read('*a'):find('hello') 105 | pref:close() 106 | tango.unref(pref) 107 | return match 108 | end) 109 | 110 | test('tango.ref with person', 111 | function() 112 | local pref = tango.ref(client.person,'horst') 113 | pref:name('peter') 114 | local match = pref:name() == 'peter' 115 | tango.unref(pref) 116 | return match 117 | end) 118 | 119 | test('creating and accessing variables with number', 120 | function() 121 | client.x(4) 122 | return client.x() == 4 and client.double_x() == 8 123 | end) 124 | 125 | test('creating and accessing variables with tables', 126 | function() 127 | client.abc({sub='horst',tab={}}) 128 | client.abc.tab.num(1234) 129 | local abc = client.abc() 130 | return type(abc) == 'table' and abc.sub == 'horst' and abc.tab.num == 1234 131 | end) 132 | 133 | test('accessing not existing tables causes error', 134 | function() 135 | local ok,err = pcall( 136 | function() 137 | client.horst.dieter() 138 | end) 139 | return ok == false and err:find('horst.dieter') 140 | end) 141 | 142 | server:kill() 143 | server = spawn_server(server_backend,'r') 144 | client = connect(config) 145 | 146 | test('reading remote variable', 147 | function() 148 | local d = client.data() 149 | return d.x == 0 and d.y == 3 150 | end) 151 | 152 | test('writing remote variable causes error', 153 | function() 154 | local ok,err = pcall( 155 | function() 156 | client.data(33) 157 | end) 158 | return ok == false 159 | end) 160 | 161 | server:kill() 162 | server = spawn_server(server_backend,'w') 163 | client = connect(config) 164 | 165 | test('reading remote variable causes error', 166 | function() 167 | local ok,err = pcall( 168 | function() 169 | client.data() 170 | end) 171 | return ok == false 172 | end) 173 | 174 | test('writing remote variable', 175 | function() 176 | local ok,err = pcall( 177 | function() 178 | client.data(33) 179 | end) 180 | return ok == true 181 | end) 182 | 183 | server:kill() 184 | 185 | print('--------------------------------') 186 | print('#TESTS',tests) 187 | print('#OK',ok) 188 | print('#FAILED',failed) 189 | print('================================') 190 | 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ======= 3 | 4 | tango is a small, simple and customizable RPC (remote procedure call) 5 | module for Lua. 6 | 7 | Its main features are: 8 | 9 | * a generic transparent 10 | [proxy](https://github.com/lipp/tango/tree/master/tango/proxy.lua) 11 | for call invocations 12 | * support of remote objects (tables with functions, userdata etc, see tango.ref) 13 | * a generic [dispatch](https://github.com/lipp/tango/tree/master/tango/dispatch.lua) routine for servers 14 | * several server implementations for different protocols, message formats and event/io 15 | frameworks, further called backends 16 | * several client implementations for different protocols and message formats 17 | 18 | 19 | Backends included 20 | --------------------- 21 | 22 | * copas 23 | * [lua-zmq](https://github.com/Neopallium/lua-zmq) 24 | * [lua-ev](https://github.com/brimworks/lua-ev) 25 | 26 | Tutorial (copas_socket server + socket client) 27 | ============================ 28 | 29 | Greetings! 30 | ---------- 31 | 32 | The greet server code 33 | 34 | ```lua 35 | -- load tango module 36 | local tango = require'tango' 37 | -- define a nice greeting function 38 | greet = function(...) 39 | print(...) 40 | end 41 | -- start listening for client connections 42 | tango.server.copas_socket.loop{ 43 | port = 12345 44 | } 45 | ``` 46 | 47 | The client code calling the remote server function `greet` 48 | 49 | ```lua 50 | -- load tango module 51 | local tango = require'tango' 52 | -- connect to server 53 | local con = tango.client.socket.connect{ 54 | address = 'localhost', 55 | port = 12345 56 | } 57 | -- call the remote greeting function 58 | con.greet('Hello','Horst') 59 | ``` 60 | 61 | Access anything? 62 | ---------------- 63 | 64 | Since the server exposes the global table `_G` per default, the client may even 65 | directly call `print`,let the server sleep a bit remotely 66 | (`os.execute`) or calc some stuff (`math.sqrt`). 67 | 68 | ```lua 69 | -- variable argument count is supported 70 | con.print('I','call','print','myself') 71 | -- any function or variable in the server's _G can be accessed by default 72 | con.os.execute('sleep 1') 73 | con.math.sqrt(4) 74 | ``` 75 | 76 | One can limit the server exposed functions by specifying a `functab` 77 | like this (to expose only methods of he math table/module): 78 | 79 | ```lua 80 | local tango = require'tango' 81 | -- just pass a table to the functab to limit the access to this table 82 | tango.server.copas_socket.loop{ 83 | port = 12345, 84 | functab = math 85 | } 86 | ``` 87 | 88 | As the global table `_G` is not available any more, the client can 89 | only call methods from the math module: 90 | 91 | ```lua 92 | con.sqrt(4) 93 | ``` 94 | 95 | Remote Variables 96 | ----------------- 97 | 98 | Sometimes you need to get some data from the server, as 99 | enumaration-like-constants for instance. Instead of creating a mess of 100 | remote getters and setters, just treat the value of interest as a 101 | function... 102 | 103 | Let's read the remote table friends from the server 104 | 105 | ```lua 106 | local tango = require'tango' 107 | -- connect to server as usual 108 | local con = tango.client.socket.connect() 109 | -- friends is a remote table but could be of any other type 110 | local friends = con.friends() 111 | ``` 112 | 113 | To change the servers state, just pass the new value as 114 | argument: 115 | 116 | ```lua 117 | local tango = require'tango' 118 | local con = tango.client.socket.connect() 119 | -- read the remote variable 120 | local friends = con.friends() 121 | -- modify it 122 | table.insert(friends,'Horst') 123 | -- and write back the new value 124 | client.friends(friends) 125 | ``` 126 | 127 | If you are worried about security concerns, just do not allow 128 | read and/or write access: 129 | 130 | ```lua 131 | local tango = require'tango' 132 | -- write_access and read_access can be set independently 133 | -- accessing variables from the client side will now cause errors. 134 | tango.server.copas_socket.loop{ 135 | write_access = false, 136 | read_access = false 137 | } 138 | ``` 139 | 140 | Using classes/tables/objects remotely (tango.ref) 141 | ----------------------------------------- 142 | 143 | Even if Lua does not come with a class model, semi-object-oriented 144 | programming is broadly used via the semicolon operator, e.g.: 145 | 146 | ```lua 147 | -- assume you open a pipe locally 148 | local p = io.popen('ls') 149 | -- and read some stuff from it, ... note the : operator 150 | local line = p:read('*l') 151 | ... 152 | p:close() 153 | ``` 154 | 155 | To allow such construct remotely via tango, one has to use the 156 | `tango.ref`: 157 | 158 | ```lua 159 | local con = tango.client.socket.connect() 160 | -- pass in the remote function and all arguments required (optionally) 161 | local p = tango.ref(con.io.popen,'ls') 162 | -- now proceed as if p was a local object 163 | local line = p:read('*l') 164 | ... 165 | p:close() 166 | -- unref it locally to let the server release it 167 | tango.unref(p) 168 | ``` 169 | 170 | This may seem a bit awkward, but it is certainly less hassle, then 171 | writing non-object-oriented counterparts on the server side. 172 | 173 | 174 | Tests 175 | ===== 176 | 177 | You can run test by the following sh call in the *project root* 178 | directory: 179 | 180 | ./test.lua 181 | 182 | tango does not need to be installed. 183 | 184 | Client/Server compatibilities 185 | ----------------------------- 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 |
tango.client.sockettango.client.zmq
tango.server.copas_socketX
tango.server.ev_socketX
tango.server.zmqX
201 | 202 | 203 | Serialization 204 | ------------- 205 | tango provides a default (lua-only) table serialization which should 206 | meet most common use cases. 207 | 208 | Anyhow, the table serialization is neither exceedingly fast nor 209 | compact in output or memory consumption. If this is a problem for your application, you can 210 | customize the serialization by assigning your serialize/unserialize 211 | methods to the clients and servers respectively. 212 | 213 | Socket client with customized serialization: 214 | 215 | ```lua 216 | local tango = require'tango' 217 | local cjson = require'cjson' 218 | -- set serialization on the client side 219 | local con = tango.client.socket.connect{ 220 | serialize = cjson.encode, 221 | unserialize = cjson.decode 222 | } 223 | ``` 224 | 225 | Copas socket server with customized serialization: 226 | 227 | ```lua 228 | local tango = require'tango' 229 | local cjson = require'cjson' 230 | -- set serialization on the server side 231 | tango.server.copas_socket.loop{ 232 | serialize = cjson.encode, 233 | unserialize = cjson.decode 234 | } 235 | ``` 236 | 237 | Some alternatives are: 238 | 239 | * [lua-marshal](https://github.com/richardhundt/lua-marshal) 240 | * [lua-cjson](http://www.kyne.com.au/~mark/software/lua-cjson.php) 241 | * [luabins](https://github.com/agladysh/luabins) 242 | * [luatexts](https://github.com/agladysh/luatexts) 243 | 244 | Requirements 245 | ------------ 246 | 247 | The requirements depend on the desired i/o backend, see the 248 | corresponding [rockspecs](https://github.com/lipp/tango/tree/master/rockspecs) for details. 249 | 250 | 251 | Installation 252 | ------------- 253 | With LuaRocks: 254 | Directly from the its repository: 255 | 256 | $ sudo luarocks install tango-copas 257 | 258 | or tango-complete, which requires lua-zmq and lua-ev (and the 259 | corresponding C-libs: 260 | 261 | $ sudo luarocks install tango-complete 262 | 263 | or a specific rock from 264 | 265 | $ sudo luarocks install https://raw.github.com/lipp/tango/master/rockspecs/SPECIFIC_ROCKSPEC 266 | 267 | Note: [luarocks](http://www.luarocks.org) must be >= 2.0.4.1 and requires luasec for doing https requests! 268 | 269 | $ sudo apt-get install libssl-dev 270 | $ sudo luarocks install luasec 271 | --------------------------------------------------------------------------------