X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=net%2Fserver.lua;h=1c1a63a4743651ea6120a582dd070a584cdb8233;hb=6a529b2e925a4bd641d5139af95d7cd36783b832;hp=55afcf3f777d5a90cf863047709ba3de7ca1ba13;hpb=92df1c99b4e8bdc9ae85809377a97930d3af6b75;p=prosody.git diff --git a/net/server.lua b/net/server.lua index 55afcf3f..1c1a63a4 100644 --- a/net/server.lua +++ b/net/server.lua @@ -1,841 +1,56 @@ ---[[ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- - server.lua by blastbeat of the luadch project - - re-used here under the MIT/X Consortium License - - Modifications (C) 2008 Matthew Wild, Waqas Hussain -]]-- +local use_luaevent = prosody and require "core.configmanager".get("*", "core", "use_libevent"); -----------------------------------// DECLARATION //-- - ---// constants //-- - -local STAT_UNIT = 1 / ( 1024 * 1024 ) -- mb - ---// lua functions //-- - -local function use( what ) return _G[ what ] end - -local type = use "type" -local pairs = use "pairs" -local ipairs = use "ipairs" -local tostring = use "tostring" -local collectgarbage = use "collectgarbage" - ---// lua libs //-- - -local table = use "table" -local coroutine = use "coroutine" - ---// lua lib methods //-- - -local table_concat = table.concat -local table_remove = table.remove -local string_sub = use'string'.sub -local coroutine_wrap = coroutine.wrap -local coroutine_yield = coroutine.yield -local print = print; -local out_put = function () end --print; -local out_error = print; - ---// extern libs //-- - -local luasec = select(2, pcall(require, "ssl")) -local luasocket = require "socket" - ---// extern lib methods //-- - -local ssl_wrap = ( luasec and luasec.wrap ) -local socket_bind = luasocket.bind -local socket_select = luasocket.select -local ssl_newcontext = ( luasec and luasec.newcontext ) - ---// functions //-- - -local loop -local stats -local addtimer -local closeall -local addserver -local firetimer -local closesocket -local removesocket -local wrapserver -local wraptcpclient -local wrapsslclient - ---// tables //-- - -local listener -local readlist -local writelist -local socketlist -local timelistener - ---// simple data types //-- - -local _ -local readlen = 0 -- length of readlist -local writelen = 0 -- lenght of writelist - -local sendstat= 0 -local receivestat = 0 - -----------------------------------// DEFINITION //-- - -listener = { } -- key = port, value = table -readlist = { } -- array with sockets to read from -writelist = { } -- arrary with sockets to write to -socketlist = { } -- key = socket, value = wrapped socket -timelistener = { } - -stats = function( ) - return receivestat, sendstat -end - -wrapserver = function( listener, socket, ip, serverport, mode, sslctx ) -- this function wraps a server - - local dispatch, disconnect = listener.listener, listener.disconnect -- dangerous - - local wrapclient, err - - if sslctx then - if not ssl_newcontext then - return nil, "luasec not found" - end - if type( sslctx ) ~= "table" then - out_error "server.lua: wrong server sslctx" - return nil, "wrong server sslctx" - end - sslctx, err = ssl_newcontext( sslctx ) - if not sslctx then - err = err or "wrong sslctx parameters" - out_error( "server.lua: ", err ) - return nil, err - end - wrapclient = wrapsslclient - wrapclient = wraptlsclient - else - wrapclient = wraptcpclient - end - - local accept = socket.accept - local close = socket.close - - --// public methods of the object //-- - - local handler = { } - - handler.shutdown = function( ) end - - --[[handler.listener = function( data, err ) - return ondata( handler, data, err ) - end]] - handler.ssl = function( ) - return sslctx and true or false - end - handler.close = function( closed ) - _ = not closed and close( socket ) - writelen = removesocket( writelist, socket, writelen ) - readlen = removesocket( readlist, socket, readlen ) - socketlist[ socket ] = nil - handler = nil - end - handler.ip = function( ) - return ip - end - handler.serverport = function( ) - return serverport - end - handler.socket = function( ) - return socket - end - handler.receivedata = function( ) - local client, err = accept( socket ) -- try to accept - if client then - local ip, clientport = client:getpeername( ) - client:settimeout( 0 ) - local handler, client, err = wrapclient( listener, client, ip, serverport, clientport, mode, sslctx ) -- wrap new client socket - if err then -- error while wrapping ssl socket - return false - end - out_put( "server.lua: accepted new client connection from ", ip, ":", clientport ) - return dispatch( handler ) - elseif err then -- maybe timeout or something else - out_put( "server.lua: error with new client connection: ", err ) - return false - end - end - return handler -end - -wrapsslclient = function( listener, socket, ip, serverport, clientport, mode, sslctx ) -- this function wraps a ssl cleint - - local dispatch, disconnect = listener.listener, listener.disconnect - - --// transform socket to ssl object //-- - - local err - socket, err = ssl_wrap( socket, sslctx ) -- wrap socket - if err then - out_put( "server.lua: ssl error: ", err ) - return nil, nil, err -- fatal error +if use_luaevent then + use_luaevent = pcall(require, "luaevent.core"); + if not use_luaevent then + log("error", "libevent not found, falling back to select()"); end - socket:settimeout( 0 ) - - --// private closures of the object //-- - - local writequeue = { } -- buffer for messages to send - - local eol, fatal_send_error -- end of buffer - - local sstat, rstat = 0, 0 - - --// local import of socket methods //-- - - local send = socket.send - local receive = socket.receive - local close = socket.close - --local shutdown = socket.shutdown - - --// public methods of the object //-- - - local handler = { } - - handler.getstats = function( ) - return rstat, sstat - end - - handler.listener = function( data, err ) - return listener( handler, data, err ) - end - handler.ssl = function( ) - return true - end - handler.send = function( _, data, i, j ) - return send( socket, data, i, j ) - end - handler.receive = function( pattern, prefix ) - return receive( socket, pattern, prefix ) - end - handler.shutdown = function( pattern ) - --return shutdown( socket, pattern ) - end - handler.close = function( closed ) - if eol and not fatal_send_error then handler._dispatchdata(); end - close( socket ) - writelen = ( eol and removesocket( writelist, socket, writelen ) ) or writelen - readlen = removesocket( readlist, socket, readlen ) - socketlist[ socket ] = nil - out_put "server.lua: closed handler and removed socket from list" - end - handler.ip = function( ) - return ip - end - handler.serverport = function( ) - return serverport - end - handler.clientport = function( ) - return clientport - end - - handler.write = function( data ) - if not eol then - writelen = writelen + 1 - writelist[ writelen ] = socket - eol = 0 - end - eol = eol + 1 - writequeue[ eol ] = data - end - handler.writequeue = function( ) - return writequeue - end - handler.socket = function( ) - return socket - end - handler.mode = function( ) - return mode - end - handler._receivedata = function( ) - local data, err, part = receive( socket, mode ) -- receive data in "mode" - if not err or ( err == "timeout" or err == "wantread" ) then -- received something - local data = data or part or "" - local count = #data * STAT_UNIT - rstat = rstat + count - receivestat = receivestat + count - --out_put( "server.lua: read data '", data, "', error: ", err ) - return dispatch( handler, data, err ) - else -- connections was closed or fatal error - out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) - handler.close( ) - disconnect( handler, err ) - writequeue = nil - handler = nil - return false - end - end - handler._dispatchdata = function( ) -- this function writes data to handlers - local buffer = table_concat( writequeue, "", 1, eol ) - local succ, err, byte = send( socket, buffer ) - local count = ( succ or 0 ) * STAT_UNIT - sstat = sstat + count - sendstat = sendstat + count - out_put( "server.lua: sended '", buffer, "', bytes: ", succ, ", error: ", err, ", part: ", byte, ", to: ", ip, ":", clientport ) - if succ then -- sending succesful - --writequeue = { } - eol = nil - writelen = removesocket( writelist, socket, writelen ) -- delete socket from writelist - return true - elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write - buffer = string_sub( buffer, byte + 1, -1 ) -- new buffer - writequeue[ 1 ] = buffer -- insert new buffer in queue - eol = 1 - return true - else -- connection was closed during sending or fatal error - fatal_send_error = true; - out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) - handler.close( ) - disconnect( handler, err ) - writequeue = nil - handler = nil - return false - end - end - - -- // COMPAT // -- - - handler.getIp = handler.ip - handler.getPort = handler.clientport - - --// handshake //-- - - local wrote - - handler.handshake = coroutine_wrap( function( client ) - local err - for i = 1, 10 do -- 10 handshake attemps - _, err = client:dohandshake( ) - if not err then - out_put( "server.lua: ssl handshake done" ) - writelen = ( wrote and removesocket( writelist, socket, writelen ) ) or writelen - handler.receivedata = handler._receivedata -- when handshake is done, replace the handshake function with regular functions - handler.dispatchdata = handler._dispatchdata - return dispatch( handler ) - else - out_put( "server.lua: error during ssl handshake: ", err ) - if err == "wantwrite" then - if wrote == nil then - writelen = writelen + 1 - writelist[ writelen ] = client - wrote = true - end - end - coroutine_yield( handler, nil, err ) -- handshake not finished - end - end - _ = err ~= "closed" and close( socket ) - handler.close( ) - disconnect( handler, err ) - writequeue = nil - handler = nil - return false -- handshake failed - end - ) - handler.receivedata = handler.handshake - handler.dispatchdata = handler.handshake - - handler.handshake( socket ) -- do handshake - - socketlist[ socket ] = handler - readlen = readlen + 1 - readlist[ readlen ] = socket - - return handler, socket end -wraptlsclient = function( listener, socket, ip, serverport, clientport, mode, sslctx ) -- this function wraps a tls cleint - - local dispatch, disconnect = listener.listener, listener.disconnect - - --// transform socket to ssl object //-- - - local err - - socket:settimeout( 0 ) - --// private closures of the object //-- - - local writequeue = { } -- buffer for messages to send - - local eol, fatal_send_error -- end of buffer - - local sstat, rstat = 0, 0 - - --// local import of socket methods //-- - - local send = socket.send - local receive = socket.receive - local close = socket.close - --local shutdown = socket.shutdown - - --// public methods of the object //-- +local server; - local handler = { } - - handler.getstats = function( ) - return rstat, sstat - end - - handler.listener = function( data, err ) - return listener( handler, data, err ) - end - handler.ssl = function( ) - return false - end - handler.send = function( _, data, i, j ) - return send( socket, data, i, j ) - end - handler.receive = function( pattern, prefix ) - return receive( socket, pattern, prefix ) - end - handler.shutdown = function( pattern ) - --return shutdown( socket, pattern ) - end - handler.close = function( closed ) - if eol and not fatal_send_error then handler._dispatchdata(); end - close( socket ) - writelen = ( eol and removesocket( writelist, socket, writelen ) ) or writelen - readlen = removesocket( readlist, socket, readlen ) - socketlist[ socket ] = nil - out_put "server.lua: closed handler and removed socket from list" - end - handler.ip = function( ) - return ip - end - handler.serverport = function( ) - return serverport - end - handler.clientport = function( ) - return clientport - end - - handler.write = function( data ) - if not eol then - writelen = writelen + 1 - writelist[ writelen ] = socket - eol = 0 - end - eol = eol + 1 - writequeue[ eol ] = data - end - handler.writequeue = function( ) - return writequeue - end - handler.socket = function( ) - return socket - end - handler.mode = function( ) - return mode - end - handler._receivedata = function( ) - local data, err, part = receive( socket, mode ) -- receive data in "mode" - if not err or ( err == "timeout" or err == "wantread" ) then -- received something - local data = data or part or "" - local count = #data * STAT_UNIT - rstat = rstat + count - receivestat = receivestat + count - --out_put( "server.lua: read data '", data, "', error: ", err ) - return dispatch( handler, data, err ) - else -- connections was closed or fatal error - out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) - handler.close( ) - disconnect( handler, err ) - writequeue = nil - handler = nil - return false - end - end - handler._dispatchdata = function( ) -- this function writes data to handlers - local buffer = table_concat( writequeue, "", 1, eol ) - local succ, err, byte = send( socket, buffer ) - local count = ( succ or 0 ) * STAT_UNIT - sstat = sstat + count - sendstat = sendstat + count - out_put( "server.lua: sended '", buffer, "', bytes: ", succ, ", error: ", err, ", part: ", byte, ", to: ", ip, ":", clientport ) - if succ then -- sending succesful - --writequeue = { } - eol = nil - writelen = removesocket( writelist, socket, writelen ) -- delete socket from writelist - if handler.need_tls then - out_put("server.lua: connection is ready for tls handshake"); - handler.starttls(true); - end - return true - elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write - buffer = string_sub( buffer, byte + 1, -1 ) -- new buffer - writequeue[ 1 ] = buffer -- insert new buffer in queue - eol = 1 - return true - else -- connection was closed during sending or fatal error - fatal_send_error = true; -- :( - out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) - handler.close( ) - disconnect( handler, err ) - writequeue = nil - handler = nil - return false - end - end - - handler.receivedata, handler.dispatchdata = handler._receivedata, handler._dispatchdata; - -- // COMPAT // -- - - handler.getIp = handler.ip - handler.getPort = handler.clientport - - --// handshake //-- - - local wrote, read +if use_luaevent then + server = require "net.server_event"; + -- util.timer requires "net.server", so instead of having + -- Lua look for, and load us again (causing a loop) - set this here + -- (usually it isn't set until we return, look down there...) + package.loaded["net.server"] = server; - handler.starttls = function (now) - if not now then out_put("server.lua: we need to do tls, but delaying until later"); handler.need_tls = true; return; end - out_put( "server.lua: attempting to start tls on "..tostring(socket) ) - local oldsocket = socket; - socket, err = ssl_wrap( socket, sslctx ) -- wrap socket - out_put("sslwrapped socket is "..tostring(socket)); - if err then - out_put( "server.lua: ssl error: ", err ) - return nil, nil, err -- fatal error - end - socket:settimeout(0); - - -- Add the new socket to our system - socketlist[ socket ] = handler - readlen = readlen + 1 - readlist[ readlen ] = socket - - -- Remove traces of the old socket - readlen = removesocket( readlist, oldsocket, readlen ) - socketlist [ oldsocket ] = nil; - - send = socket.send - receive = socket.receive - close = socket.close - handler.ssl = function( ) - return true - end - handler.send = function( _, data, i, j ) - return send( socket, data, i, j ) - end - handler.receive = function( pattern, prefix ) - return receive( socket, pattern, prefix ) - end - - handler.starttls = nil; - handler.need_tls = nil - - handler.handshake = coroutine_wrap( function( client ) - local err - for i = 1, 10 do -- 10 handshake attemps - _, err = client:dohandshake( ) - if not err then - out_put( "server.lua: ssl handshake done" ) - writelen = ( wrote and removesocket( writelist, socket, writelen ) ) or writelen - handler.receivedata = handler._receivedata -- when handshake is done, replace the handshake function with regular functions - handler.dispatchdata = handler._dispatchdata; - return true; - else - out_put( "server.lua: error during ssl handshake: ", err ) - if err == "wantwrite" then - if wrote == nil then - writelen = writelen + 1 - writelist[ writelen ] = client - wrote = true - end - end - coroutine_yield( handler, nil, err ) -- handshake not finished - end - end - _ = err ~= "closed" and close( socket ) - handler.close( ) - disconnect( handler, err ) - writequeue = nil - handler = nil - return false -- handshake failed - end - ) - handler.receivedata = handler.handshake - handler.dispatchdata = handler.handshake - - handler.handshake( socket ) -- do handshake - end - socketlist[ socket ] = handler - readlen = readlen + 1 - readlist[ readlen ] = socket - - return handler, socket -end - -wraptcpclient = function( listener, socket, ip, serverport, clientport, mode ) -- this function wraps a socket - - local dispatch, disconnect = listener.listener, listener.disconnect - - --// private closures of the object //-- - - local writequeue = { } -- list for messages to send - - local eol, fatal_send_error - - local rstat, sstat = 0, 0 - - --// local import of socket methods //-- - - local send = socket.send - local receive = socket.receive - local close = socket.close - local shutdown = socket.shutdown - - --// public methods of the object //-- - - local handler = { } - - handler.getstats = function( ) - return rstat, sstat - end - - handler.listener = function( data, err ) - return listener( handler, data, err ) - end - handler.ssl = function( ) - return false - end - handler.send = function( _, data, i, j ) - return send( socket, data, i, j ) - end - handler.receive = function( pattern, prefix ) - return receive( socket, pattern, prefix ) - end - handler.shutdown = function( pattern ) - return shutdown( socket, pattern ) - end - handler.close = function( closed ) - if eol and not fatal_send_error then handler.dispatchdata(); end - _ = not closed and shutdown( socket ) - _ = not closed and close( socket ) - writelen = ( eol and removesocket( writelist, socket, writelen ) ) or writelen - readlen = removesocket( readlist, socket, readlen ) - socketlist[ socket ] = nil - out_put "server.lua: closed handler and removed socket from list" - end - handler.ip = function( ) - return ip - end - handler.serverport = function( ) - return serverport - end - handler.clientport = function( ) - return clientport - end - handler.write = function( data ) - if not eol then - writelen = writelen + 1 - writelist[ writelen ] = socket - eol = 0 - end - eol = eol + 1 - writequeue[ eol ] = data - end - handler.writequeue = function( ) - return writequeue - end - handler.socket = function( ) - return socket - end - handler.mode = function( ) - return mode - end - - handler.receivedata = function( ) - local data, err, part = receive( socket, mode ) -- receive data in "mode" - if not err or ( err == "timeout" or err == "wantread" ) then -- received something - local data = data or part or "" - local count = #data * STAT_UNIT - rstat = rstat + count - receivestat = receivestat + count - --out_put( "server.lua: read data '", data, "', error: ", err ) - return dispatch( handler, data, err ) - else -- connections was closed or fatal error - out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) - handler.close( ) - disconnect( handler, err ) - writequeue = nil - handler = nil - return false - end + -- Backwards compatibility for timers, addtimer + -- called a function roughly every second + local add_task = require "util.timer".add_task; + function server.addtimer(f) + return add_task(1, function (...) f(...); return 1; end); end - handler.dispatchdata = function( ) -- this function writes data to handlers - local buffer = table_concat( writequeue, "", 1, eol ) - local succ, err, byte = send( socket, buffer ) - local count = ( succ or 0 ) * STAT_UNIT - sstat = sstat + count - sendstat = sendstat + count - out_put( "server.lua: sended '", buffer, "', bytes: ", succ, ", error: ", err, ", part: ", byte, ", to: ", ip, ":", clientport ) - if succ then -- sending succesful - --writequeue = { } - eol = nil - writelen = removesocket( writelist, socket, writelen ) -- delete socket from writelist - return true - elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write - buffer = string_sub( buffer, byte + 1, -1 ) -- new buffer - writequeue[ 1 ] = buffer -- insert new buffer in queue - eol = 1 - return true - else -- connection was closed during sending or fatal error - fatal_send_error = true; -- :'-( - out_put( "server.lua: client ", ip, ":", clientport, " error: ", err ) - handler.close( ) - disconnect( handler, err ) - writequeue = nil - handler = nil - return false - end - end - - -- // COMPAT // -- - - handler.getIp = handler.ip - handler.getPort = handler.clientport - - socketlist[ socket ] = handler - readlen = readlen + 1 - readlist[ readlen ] = socket - - return handler, socket -end - -addtimer = function( listener ) - timelistener[ #timelistener + 1 ] = listener -end - -firetimer = function( listener ) - for i, listener in ipairs( timelistener ) do - listener( ) - end -end - -addserver = function( listeners, port, addr, mode, sslctx ) -- this function provides a way for other scripts to reg a server - local err - if type( listeners ) ~= "table" then - err = "invalid listener table" - else - for name, func in pairs( listeners ) do - if type( func ) ~= "function" then - --err = "invalid listener function" - break - end - end - end - if not type( port ) == "number" or not ( port >= 0 and port <= 65535 ) then - err = "invalid port" - elseif listener[ port ] then - err= "listeners on port '" .. port .. "' already exist" - elseif sslctx and not luasec then - err = "luasec not found" - end - if err then - out_error( "server.lua: ", err ) - return nil, err - end - addr = addr or "*" - local server, err = socket_bind( addr, port ) - if err then - out_error( "server.lua: ", err ) - return nil, err - end - local handler, err = wrapserver( listeners, server, addr, port, mode, sslctx ) -- wrap new server socket - if not handler then - server:close( ) - return nil, err - end - server:settimeout( 0 ) - readlen = readlen + 1 - readlist[ readlen ] = server - listener[ port ] = listeners - socketlist[ server ] = handler - out_put( "server.lua: new server listener on ", addr, ":", port ) - return true -end - -removesocket = function( tbl, socket, len ) -- this function removes sockets from a list - for i, target in ipairs( tbl ) do - if target == socket then - len = len - 1 - table_remove( tbl, i ) - return len - end - end - return len -end - -closeall = function( ) - for sock, handler in pairs( socketlist ) do - handler.shutdown( ) - handler.close( ) - socketlist[ sock ] = nil - end - writelist, readlist, socketlist = { }, { }, { } -end - -closesocket = function( socket ) - writelen = removesocket( writelist, socket, writelen ) - readlen = removesocket( readlist, socket, readlen ) - socketlist[ socket ] = nil - socket:close( ) -end - -loop = function( ) -- this is the main loop of the program - --signal_set( "hub", "run" ) - repeat - --[[print(readlen, writelen) - for _, s in ipairs(readlist) do print("R:", tostring(s)) end - for _, s in ipairs(writelist) do print("W:", tostring(s)) end - out_put("select()"..os.time())]] - local read, write, err = socket_select( readlist, writelist, 1 ) -- 1 sec timeout, nice for timers - for i, socket in ipairs( write ) do -- send data waiting in writequeues - local handler = socketlist[ socket ] - if handler then - handler.dispatchdata( ) - else - closesocket( socket ) - out_put "server.lua: found no handler and closed socket (writelist)" -- this should not happen + -- Overwrite signal.signal() because we need to ask libevent to + -- handle them instead + local ok, signal = pcall(require, "util.signal"); + if ok and signal then + local _signal_signal = signal.signal; + function signal.signal(signal_id, handler) + if type(signal_id) == "string" then + signal_id = signal[signal_id:upper()]; end - end - for i, socket in ipairs( read ) do -- receive data - local handler = socketlist[ socket ] - if handler then - handler.receivedata( ) - else - closesocket( socket ) - out_put "server.lua: found no handler and closed socket (readlist)" -- this can happen + if type(signal_id) ~= "number" then + return false, "invalid-signal"; end + return server.hook_signal(signal_id, handler); end - firetimer( ) - until false - return + end +else + server = require "net.server_select"; + package.loaded["net.server"] = server; end -----------------------------------// BEGIN //-- - -----------------------------------// PUBLIC INTERFACE //-- - -return { - - add = addserver, - loop = loop, - stats = stats, - closeall = closeall, - addtimer = addtimer, - wraptlsclient = wraptlsclient, -} +-- require "net.server" shall now forever return this, +-- ie. server_select or server_event as chosen above. +return server;