12502412f29a31ce7df2fe493d89de9ab2b50901
[prosody.git] / net / server.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2009 Matthew Wild
3 -- Copyright (C) 2008-2009 Waqas Hussain
4 -- 
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9 --[[\r
10 \r
11         server.lua by blastbeat\r
12 \r
13         - this script contains the server loop of the program\r
14         - other scripts can reg a server here\r
15 \r
16 ]]--\r
17 \r
18 -- // wrapping luadch stuff // --\r
19 \r
20 local use = function( what )\r
21     return _G[ what ]\r
22 end\r
23 local clean = function( tbl )\r
24     for i, k in pairs( tbl ) do\r
25         tbl[ i ] = nil\r
26     end\r
27 end\r
28 \r
29 local log, table_concat = require ("util.logger").init("socket"), table.concat;\r
30 local out_put = function (...) return log("debug", table_concat{...}); end\r
31 local out_error = function (...) return log("warn", table_concat{...}); end\r
32 local mem_free = collectgarbage\r
33 \r
34 ----------------------------------// DECLARATION //--\r
35 \r
36 --// constants //--\r
37 \r
38 local STAT_UNIT = 1    -- byte\r
39 \r
40 --// lua functions //--\r
41 \r
42 local type = use "type"\r
43 local pairs = use "pairs"\r
44 local ipairs = use "ipairs"\r
45 local tostring = use "tostring"\r
46 local collectgarbage = use "collectgarbage"\r
47 \r
48 --// lua libs //--\r
49 \r
50 local os = use "os"\r
51 local table = use "table"\r
52 local string = use "string"\r
53 local coroutine = use "coroutine"\r
54 \r
55 --// lua lib methods //--\r
56 \r
57 local os_time = os.time\r
58 local os_difftime = os.difftime\r
59 local table_concat = table.concat\r
60 local table_remove = table.remove\r
61 local string_len = string.len\r
62 local string_sub = string.sub\r
63 local coroutine_wrap = coroutine.wrap\r
64 local coroutine_yield = coroutine.yield\r
65 \r
66 --// extern libs //--\r
67 \r
68 local luasec = select( 2, pcall( require, "ssl" ) )\r
69 local luasocket = require "socket"\r
70 \r
71 --// extern lib methods //--\r
72 \r
73 local ssl_wrap = ( luasec and luasec.wrap )\r
74 local socket_bind = luasocket.bind\r
75 local socket_sleep = luasocket.sleep\r
76 local socket_select = luasocket.select\r
77 local ssl_newcontext = ( luasec and luasec.newcontext )\r
78 \r
79 --// functions //--\r
80 \r
81 local id\r
82 local loop\r
83 local stats\r
84 local idfalse\r
85 local addtimer\r
86 local closeall\r
87 local addserver\r
88 local getserver\r
89 local wrapserver\r
90 local getsettings\r
91 local closesocket\r
92 local removesocket\r
93 local removeserver\r
94 local changetimeout\r
95 local wrapconnection\r
96 local changesettings\r
97 \r
98 --// tables //--\r
99 \r
100 local _server\r
101 local _readlist\r
102 local _timerlist\r
103 local _sendlist\r
104 local _socketlist\r
105 local _closelist\r
106 local _readtimes\r
107 local _writetimes\r
108 \r
109 --// simple data types //--\r
110 \r
111 local _\r
112 local _readlistlen\r
113 local _sendlistlen\r
114 local _timerlistlen\r
115 \r
116 local _sendtraffic\r
117 local _readtraffic\r
118 \r
119 local _selecttimeout\r
120 local _sleeptime\r
121 \r
122 local _starttime\r
123 local _currenttime\r
124 \r
125 local _maxsendlen\r
126 local _maxreadlen\r
127 \r
128 local _checkinterval\r
129 local _sendtimeout\r
130 local _readtimeout\r
131 \r
132 local _cleanqueue\r
133 \r
134 local _timer\r
135 \r
136 local _maxclientsperserver\r
137 \r
138 ----------------------------------// DEFINITION //--\r
139 \r
140 _server = { }    -- key = port, value = table; list of listening servers\r
141 _readlist = { }    -- array with sockets to read from\r
142 _sendlist = { }    -- arrary with sockets to write to\r
143 _timerlist = { }    -- array of timer functions\r
144 _socketlist = { }    -- key = socket, value = wrapped socket (handlers)\r
145 _readtimes = { }   -- key = handler, value = timestamp of last data reading\r
146 _writetimes = { }   -- key = handler, value = timestamp of last data writing/sending\r
147 _closelist = { }    -- handlers to close\r
148 \r
149 _readlistlen = 0    -- length of readlist\r
150 _sendlistlen = 0    -- length of sendlist\r
151 _timerlistlen = 0    -- lenght of timerlist\r
152 \r
153 _sendtraffic = 0    -- some stats\r
154 _readtraffic = 0\r
155 \r
156 _selecttimeout = 1    -- timeout of socket.select\r
157 _sleeptime = 0    -- time to wait at the end of every loop\r
158 \r
159 _maxsendlen = 51000 * 1024    -- max len of send buffer\r
160 _maxreadlen = 25000 * 1024    -- max len of read buffer\r
161 \r
162 _checkinterval = 1200000    -- interval in secs to check idle clients\r
163 _sendtimeout = 60000    -- allowed send idle time in secs\r
164 _readtimeout = 6 * 60 * 60    -- allowed read idle time in secs\r
165 \r
166 _cleanqueue = false    -- clean bufferqueue after using\r
167 \r
168 _maxclientsperserver = 1000\r
169 \r
170 ----------------------------------// PRIVATE //--\r
171 \r
172 wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxconnections, startssl )    -- this function wraps a server\r
173 \r
174     maxconnections = maxconnections or _maxclientsperserver\r
175 \r
176     local connections = 0\r
177 \r
178     local dispatch, disconnect = listeners.incoming or listeners.listener, listeners.disconnect\r
179 \r
180     local err\r
181 \r
182     local ssl = false\r
183 \r
184     if sslctx then\r
185         if not ssl_newcontext then\r
186             return nil, "luasec not found"\r
187         end\r
188         if type( sslctx ) ~= "table" then\r
189             out_error "server.lua: wrong server sslctx"\r
190             return nil, "wrong server sslctx"\r
191         end\r
192         sslctx, err = ssl_newcontext( sslctx )\r
193         if not sslctx then\r
194             err = err or "wrong sslctx parameters"\r
195             out_error( "server.lua: ", err )\r
196             return nil, err\r
197         end\r
198         ssl = true\r
199     else\r
200         out_put("server.lua: ", "ssl not enabled on ", serverport);\r
201     end\r
202 \r
203     local accept = socket.accept\r
204 \r
205     --// public methods of the object //--\r
206 \r
207     local handler = { }\r
208 \r
209     handler.shutdown = function( ) end\r
210 \r
211     handler.ssl = function( )\r
212         return ssl\r
213     end\r
214     handler.remove = function( )\r
215         connections = connections - 1\r
216     end\r
217     handler.close = function( )\r
218         for _, handler in pairs( _socketlist ) do\r
219             if handler.serverport == serverport then\r
220                 handler.disconnect( handler, "server closed" )\r
221                 handler.close( true )\r
222             end\r
223         end\r
224         socket:close( )\r
225         _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
226         _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
227         _socketlist[ socket ] = nil\r
228         handler = nil\r
229         socket = nil\r
230         mem_free( )\r
231         out_put "server.lua: closed server handler and removed sockets from list"\r
232     end\r
233     handler.ip = function( )\r
234         return ip\r
235     end\r
236     handler.serverport = function( )\r
237         return serverport\r
238     end\r
239     handler.socket = function( )\r
240         return socket\r
241     end\r
242     handler.readbuffer = function( )\r
243         if connections > maxconnections then\r
244             out_put( "server.lua: refused new client connection: server full" )\r
245             return false\r
246         end\r
247         local client, err = accept( socket )    -- try to accept\r
248         if client then\r
249             local ip, clientport = client:getpeername( )\r
250             client:settimeout( 0 )\r
251             local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx, startssl )    -- wrap new client socket\r
252             if err then    -- error while wrapping ssl socket\r
253                 return false\r
254             end\r
255             connections = connections + 1\r
256             out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport))\r
257             return dispatch( handler )\r
258         elseif err then    -- maybe timeout or something else\r
259             out_put( "server.lua: error with new client connection: ", tostring(err) )\r
260             return false\r
261         end\r
262     end\r
263     return handler\r
264 end\r
265 \r
266 wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx, startssl )    -- this function wraps a client to a handler object\r
267 \r
268     socket:settimeout( 0 )\r
269 \r
270     --// local import of socket methods //--\r
271 \r
272     local send\r
273     local receive\r
274     local shutdown\r
275 \r
276     --// private closures of the object //--\r
277 \r
278     local ssl\r
279 \r
280     local dispatch = listeners.incoming or listeners.listener\r
281     local disconnect = listeners.disconnect\r
282 \r
283     local bufferqueue = { }    -- buffer array\r
284     local bufferqueuelen = 0    -- end of buffer array\r
285 \r
286     local toclose\r
287     local fatalerror\r
288     local needtls\r
289 \r
290     local bufferlen = 0\r
291 \r
292     local noread = false\r
293     local nosend = false\r
294 \r
295     local sendtraffic, readtraffic = 0, 0\r
296 \r
297     local maxsendlen = _maxsendlen\r
298     local maxreadlen = _maxreadlen\r
299 \r
300     --// public methods of the object //--\r
301 \r
302     local handler = bufferqueue    -- saves a table ^_^\r
303 \r
304     handler.dispatch = function( )\r
305         return dispatch\r
306     end\r
307     handler.disconnect = function( )\r
308         return disconnect\r
309     end\r
310     handler.setlistener = function( listeners )\r
311         dispatch = listeners.incoming\r
312         disconnect = listeners.disconnect\r
313     end\r
314     handler.getstats = function( )\r
315         return readtraffic, sendtraffic\r
316     end\r
317     handler.ssl = function( )\r
318         return ssl\r
319     end\r
320     handler.send = function( _, data, i, j )\r
321         return send( socket, data, i, j )\r
322     end\r
323     handler.receive = function( pattern, prefix )\r
324         return receive( socket, pattern, prefix )\r
325     end\r
326     handler.shutdown = function( pattern )\r
327         return shutdown( socket, pattern )\r
328     end\r
329     handler.close = function( forced )\r
330         if not handler then return true; end\r
331         _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
332         _readtimes[ handler ] = nil\r
333         if bufferqueuelen ~= 0 then\r
334             if not ( forced or fatalerror ) then\r
335                 handler.sendbuffer( )\r
336                 if bufferqueuelen ~= 0 then   -- try again...\r
337                     if handler then\r
338                         handler.write = nil    -- ... but no further writing allowed\r
339                     end\r
340                     toclose = true\r
341                     return false\r
342                 end\r
343             else\r
344                 send( socket, table_concat( bufferqueue, "", 1, bufferqueuelen ), 1, bufferlen )    -- forced send\r
345             end\r
346         end\r
347         _ = shutdown and shutdown( socket )\r
348         socket:close( )\r
349         _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
350         _socketlist[ socket ] = nil\r
351         if handler then\r
352             _writetimes[ handler ] = nil\r
353             _closelist[ handler ] = nil\r
354             handler = nil\r
355         end\r
356         socket = nil\r
357         mem_free( )\r
358         if server then\r
359                 server.remove( )\r
360         end\r
361         out_put "server.lua: closed client handler and removed socket from list"\r
362         return true\r
363     end\r
364     handler.ip = function( )\r
365         return ip\r
366     end\r
367     handler.serverport = function( )\r
368         return serverport\r
369     end\r
370     handler.clientport = function( )\r
371         return clientport\r
372     end\r
373     local write = function( data )\r
374         bufferlen = bufferlen + string_len( data )\r
375         if bufferlen > maxsendlen then\r
376             _closelist[ handler ] = "send buffer exceeded"   -- cannot close the client at the moment, have to wait to the end of the cycle\r
377             handler.write = idfalse    -- dont write anymore\r
378             return false\r
379         elseif socket and not _sendlist[ socket ] then\r
380             _sendlistlen = _sendlistlen + 1\r
381             _sendlist[ _sendlistlen ] = socket\r
382             _sendlist[ socket ] = _sendlistlen\r
383         end\r
384         bufferqueuelen = bufferqueuelen + 1\r
385         bufferqueue[ bufferqueuelen ] = data\r
386         if handler then\r
387                 _writetimes[ handler ] = _writetimes[ handler ] or _currenttime\r
388         end\r
389         return true\r
390     end\r
391     handler.write = write\r
392     handler.bufferqueue = function( )\r
393         return bufferqueue\r
394     end\r
395     handler.socket = function( )\r
396         return socket\r
397     end\r
398     handler.pattern = function( new )\r
399         pattern = new or pattern\r
400         return pattern\r
401     end\r
402     handler.setsend = function ( newsend )\r
403         send = newsend or send\r
404         return send\r
405     end\r
406     handler.bufferlen = function( readlen, sendlen )\r
407         maxsendlen = sendlen or maxsendlen\r
408         maxreadlen = readlen or maxreadlen\r
409         return maxreadlen, maxsendlen\r
410     end\r
411     handler.lock = function( switch )\r
412         if switch == true then\r
413             handler.write = idfalse\r
414             local tmp = _sendlistlen\r
415             _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
416             _writetimes[ handler ] = nil\r
417             if _sendlistlen ~= tmp then\r
418                 nosend = true\r
419             end\r
420             tmp = _readlistlen\r
421             _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
422             _readtimes[ handler ] = nil\r
423             if _readlistlen ~= tmp then\r
424                 noread = true\r
425             end\r
426         elseif switch == false then\r
427             handler.write = write\r
428             if noread then\r
429                 noread = false\r
430                 _readlistlen = _readlistlen + 1\r
431                 _readlist[ socket ] = _readlistlen\r
432                 _readlist[ _readlistlen ] = socket\r
433                 _readtimes[ handler ] = _currenttime\r
434             end\r
435             if nosend then\r
436                 nosend = false\r
437                 write( "" )\r
438             end\r
439         end\r
440         return noread, nosend\r
441     end\r
442     local _readbuffer = function( )    -- this function reads data\r
443         local buffer, err, part = receive( socket, pattern )    -- receive buffer with "pattern"\r
444         if not err or ( err == "timeout" or err == "wantread" ) then    -- received something\r
445             local buffer = buffer or part or ""\r
446             local len = string_len( buffer )\r
447             if len > maxreadlen then\r
448                 disconnect( handler, "receive buffer exceeded" )\r
449                 handler.close( true )\r
450                 return false\r
451             end\r
452             local count = len * STAT_UNIT\r
453             readtraffic = readtraffic + count\r
454             _readtraffic = _readtraffic + count\r
455             _readtimes[ handler ] = _currenttime\r
456             --out_put( "server.lua: read data '", buffer, "', error: ", err )\r
457             return dispatch( handler, buffer, err )\r
458         else    -- connections was closed or fatal error\r
459             out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " error: ", tostring(err) )\r
460             fatalerror = true\r
461             disconnect( handler, err )\r
462             _ = handler and handler.close( )\r
463             return false\r
464         end\r
465     end\r
466     local _sendbuffer = function( )    -- this function sends data\r
467         local buffer = table_concat( bufferqueue, "", 1, bufferqueuelen )\r
468         local succ, err, byte = send( socket, buffer, 1, bufferlen )\r
469         local count = ( succ or byte or 0 ) * STAT_UNIT\r
470         sendtraffic = sendtraffic + count\r
471         _sendtraffic = _sendtraffic + count\r
472         _ = _cleanqueue and clean( bufferqueue )\r
473         --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) )\r
474         if succ then    -- sending succesful\r
475             bufferqueuelen = 0\r
476             bufferlen = 0\r
477             _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )    -- delete socket from writelist\r
478             _ = needtls and handler.starttls(true)\r
479             _writetimes[ handler ] = nil\r
480             _ = toclose and handler.close( )\r
481             return true\r
482         elseif byte and ( err == "timeout" or err == "wantwrite" ) then    -- want write\r
483             buffer = string_sub( buffer, byte + 1, bufferlen )    -- new buffer\r
484             bufferqueue[ 1 ] = buffer    -- insert new buffer in queue\r
485             bufferqueuelen = 1\r
486             bufferlen = bufferlen - byte\r
487             _writetimes[ handler ] = _currenttime\r
488             return true\r
489         else    -- connection was closed during sending or fatal error\r
490             out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " error: ", tostring(err) )\r
491             fatalerror = true\r
492             disconnect( handler, err )\r
493             _ = handler and handler.close( )\r
494             return false\r
495         end\r
496     end\r
497 \r
498     if sslctx then    -- ssl?\r
499         ssl = true\r
500         local wrote\r
501         local read\r
502         local handshake = coroutine_wrap( function( client )    -- create handshake coroutine\r
503                 local err\r
504                 for i = 1, 10 do    -- 10 handshake attemps\r
505                     _sendlistlen = ( wrote and removesocket( _sendlist, socket, _sendlistlen ) ) or _sendlistlen\r
506                     _readlistlen = ( read and removesocket( _readlist, socket, _readlistlen ) ) or _readlistlen\r
507                     read, wrote = nil, nil\r
508                     _, err = client:dohandshake( )\r
509                     if not err then\r
510                         out_put( "server.lua: ssl handshake done" )\r
511                         handler.readbuffer = _readbuffer    -- when handshake is done, replace the handshake function with regular functions\r
512                         handler.sendbuffer = _sendbuffer\r
513                         -- return dispatch( handler )\r
514                         return true\r
515                     else\r
516                         out_put( "server.lua: error during ssl handshake: ", tostring(err) )\r
517                         if err == "wantwrite" and not wrote then\r
518                             _sendlistlen = _sendlistlen + 1\r
519                             _sendlist[ _sendlistlen ] = client\r
520                             wrote = true\r
521                         elseif err == "wantread" and not read then\r
522                                 _readlistlen = _readlistlen + 1\r
523                                 _readlist [ _readlistlen ] = client\r
524                                 read = true\r
525                         else\r
526                                 break;\r
527                         end\r
528                         --coroutine_yield( handler, nil, err )    -- handshake not finished\r
529                         coroutine_yield( )\r
530                     end\r
531                 end\r
532                 disconnect( handler, "ssl handshake failed" )\r
533                 _ = handler and handler.close( true )    -- forced disconnect\r
534                 return false    -- handshake failed\r
535             end\r
536         )\r
537         if startssl then    -- ssl now?\r
538             --out_put("server.lua: ", "starting ssl handshake")\r
539             local err\r
540             socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
541             if err then\r
542                 out_put( "server.lua: ssl error: ", tostring(err) )\r
543                 mem_free( )\r
544                 return nil, nil, err    -- fatal error\r
545             end\r
546             socket:settimeout( 0 )\r
547             handler.readbuffer = handshake\r
548             handler.sendbuffer = handshake\r
549             if not socket then   -- do handshake\r
550                 return nil, nil, "ssl handshake failed";\r
551             end\r
552         else\r
553             -- We're not automatically doing SSL, so we're not secure (yet)\r
554             ssl = false\r
555             handler.starttls = function( now )\r
556                 if not now then\r
557                     --out_put "server.lua: we need to do tls, but delaying until later"\r
558                     needtls = true\r
559                     return\r
560                 end\r
561                 --out_put( "server.lua: attempting to start tls on " .. tostring( socket ) )\r
562                 local oldsocket, err = socket\r
563                 socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
564                 --out_put( "server.lua: sslwrapped socket is " .. tostring( socket ) )\r
565                 if err then\r
566                     out_put( "server.lua: error while starting tls on client: ", tostring(err) )\r
567                     return nil, err    -- fatal error\r
568                 end\r
569 \r
570                 socket:settimeout( 0 )\r
571 \r
572                 -- add the new socket to our system\r
573 \r
574                 send = socket.send\r
575                 receive = socket.receive\r
576                 shutdown = id\r
577 \r
578                 _socketlist[ socket ] = handler\r
579                 _readlistlen = _readlistlen + 1\r
580                 _readlist[ _readlistlen ] = socket\r
581                 _readlist[ socket ] = _readlistlen\r
582 \r
583                 -- remove traces of the old socket\r
584 \r
585                 _readlistlen = removesocket( _readlist, oldsocket, _readlistlen )\r
586                 _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen )\r
587                 _socketlist[ oldsocket ] = nil\r
588 \r
589                 handler.starttls = nil\r
590                 needtls = nil\r
591                 \r
592                 -- Secure now\r
593                 ssl = true\r
594 \r
595                 handler.readbuffer = handshake\r
596                 handler.sendbuffer = handshake\r
597                 handshake( socket )    -- do handshake\r
598             end\r
599             handler.readbuffer = _readbuffer\r
600             handler.sendbuffer = _sendbuffer\r
601         end\r
602     else    -- normal connection\r
603         ssl = false\r
604         handler.readbuffer = _readbuffer\r
605         handler.sendbuffer = _sendbuffer\r
606     end\r
607 \r
608     send = socket.send\r
609     receive = socket.receive\r
610     shutdown = ( ssl and id ) or socket.shutdown\r
611 \r
612     _socketlist[ socket ] = handler\r
613     _readlistlen = _readlistlen + 1\r
614     _readlist[ _readlistlen ] = socket\r
615     _readlist[ socket ] = _readlistlen\r
616 \r
617     return handler, socket\r
618 end\r
619 \r
620 id = function( )\r
621 end\r
622 \r
623 idfalse = function( )\r
624     return false\r
625 end\r
626 \r
627 removesocket = function( list, socket, len )    -- this function removes sockets from a list ( copied from copas )\r
628     local pos = list[ socket ]\r
629     if pos then\r
630         list[ socket ] = nil\r
631         local last = list[ len ]\r
632         list[ len ] = nil\r
633         if last ~= socket then\r
634             list[ last ] = pos\r
635             list[ pos ] = last\r
636         end\r
637         return len - 1\r
638     end\r
639     return len\r
640 end\r
641 \r
642 closesocket = function( socket )\r
643     _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
644     _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
645     _socketlist[ socket ] = nil\r
646     socket:close( )\r
647     mem_free( )\r
648 end\r
649 \r
650 ----------------------------------// PUBLIC //--\r
651 \r
652 addserver = function( listeners, port, addr, pattern, sslctx, maxconnections, startssl )    -- this function provides a way for other scripts to reg a server\r
653     local err\r
654     --out_put("server.lua: autossl on ", port, " is ", startssl)\r
655     if type( listeners ) ~= "table" then\r
656         err = "invalid listener table"\r
657     end\r
658     if not type( port ) == "number" or not ( port >= 0 and port <= 65535 ) then\r
659         err = "invalid port"\r
660     elseif _server[ port ] then\r
661         err =  "listeners on port '" .. port .. "' already exist"\r
662     elseif sslctx and not luasec then\r
663         err = "luasec not found"\r
664     end\r
665     if err then\r
666         out_error( "server.lua, port ", port, ": ", err )\r
667         return nil, err\r
668     end\r
669     addr = addr or "*"\r
670     local server, err = socket_bind( addr, port )\r
671     if err then\r
672         out_error( "server.lua, port ", port, ": ", err )\r
673         return nil, err\r
674     end\r
675     local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, maxconnections, startssl )    -- wrap new server socket\r
676     if not handler then\r
677         server:close( )\r
678         return nil, err\r
679     end\r
680     server:settimeout( 0 )\r
681     _readlistlen = _readlistlen + 1\r
682     _readlist[ _readlistlen ] = server\r
683     _server[ port ] = handler\r
684     _socketlist[ server ] = handler\r
685     out_put( "server.lua: new server listener on '", addr, ":", port, "'" )\r
686     return handler\r
687 end\r
688 \r
689 getserver = function ( port )\r
690         return _server[ port ];\r
691 end\r
692 \r
693 removeserver = function( port )\r
694     local handler = _server[ port ]\r
695     if not handler then\r
696         return nil, "no server found on port '" .. tostring( port ) "'"\r
697     end\r
698     handler.close( )\r
699     return true\r
700 end\r
701 \r
702 closeall = function( )\r
703     for _, handler in pairs( _socketlist ) do\r
704         handler.close( )\r
705         _socketlist[ _ ] = nil\r
706     end\r
707     _readlistlen = 0\r
708     _sendlistlen = 0\r
709     _timerlistlen = 0\r
710     _server = { }\r
711     _readlist = { }\r
712     _sendlist = { }\r
713     _timerlist = { }\r
714     _socketlist = { }\r
715     mem_free( )\r
716 end\r
717 \r
718 getsettings = function( )\r
719     return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver\r
720 end\r
721 \r
722 changesettings = function( new )\r
723     if type( new ) ~= "table" then\r
724         return nil, "invalid settings table"\r
725     end\r
726     _selecttimeout = tonumber( new.timeout ) or _selecttimeout\r
727     _sleeptime = tonumber( new.sleeptime ) or _sleeptime\r
728     _maxsendlen = tonumber( new.maxsendlen ) or _maxsendlen\r
729     _maxreadlen = tonumber( new.maxreadlen ) or _maxreadlen\r
730     _checkinterval = tonumber( new.checkinterval ) or _checkinterval\r
731     _sendtimeout = tonumber( new.sendtimeout ) or _sendtimeout\r
732     _readtimeout = tonumber( new.readtimeout ) or _readtimeout\r
733     _cleanqueue = new.cleanqueue\r
734     _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver\r
735     return true\r
736 end\r
737 \r
738 addtimer = function( listener )\r
739     if type( listener ) ~= "function" then\r
740         return nil, "invalid listener function"\r
741     end\r
742     _timerlistlen = _timerlistlen + 1\r
743     _timerlist[ _timerlistlen ] = listener\r
744     return true\r
745 end\r
746 \r
747 stats = function( )\r
748     return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen\r
749 end\r
750 \r
751 local dontstop = true; -- thinking about tomorrow, ...\r
752 \r
753 setquitting = function (quit)\r
754         dontstop = not quit;\r
755         return;\r
756 end\r
757 \r
758 loop = function( )    -- this is the main loop of the program\r
759     while dontstop do\r
760         local read, write, err = socket_select( _readlist, _sendlist, _selecttimeout )\r
761         for i, socket in ipairs( write ) do    -- send data waiting in writequeues\r
762             local handler = _socketlist[ socket ]\r
763             if handler then\r
764                 handler.sendbuffer( )\r
765             else\r
766                 closesocket( socket )\r
767                 out_put "server.lua: found no handler and closed socket (writelist)"    -- this should not happen\r
768             end\r
769         end\r
770         for i, socket in ipairs( read ) do    -- receive data\r
771             local handler = _socketlist[ socket ]\r
772             if handler then\r
773                 handler.readbuffer( )\r
774             else\r
775                 closesocket( socket )\r
776                 out_put "server.lua: found no handler and closed socket (readlist)"    -- this can happen\r
777             end\r
778         end\r
779         for handler, err in pairs( _closelist ) do\r
780             handler.disconnect( )( handler, err )\r
781             handler.close( true )    -- forced disconnect\r
782         end\r
783         clean( _closelist )\r
784         _currenttime = os_time( )\r
785         if os_difftime( _currenttime - _timer ) >= 1 then\r
786             for i = 1, _timerlistlen do\r
787                 _timerlist[ i ]( )    -- fire timers\r
788             end\r
789             _timer = _currenttime\r
790         end\r
791         socket_sleep( _sleeptime )    -- wait some time\r
792         --collectgarbage( )\r
793     end\r
794     return "quitting"\r
795 end\r
796 \r
797 --// EXPERIMENTAL //--\r
798 \r
799 local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, startssl )\r
800     local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, startssl )\r
801     _socketlist[ socket ] = handler\r
802     _sendlistlen = _sendlistlen + 1\r
803     _sendlist[ _sendlistlen ] = socket\r
804     _sendlist[ socket ] = _sendlistlen\r
805     return handler, socket\r
806 end\r
807 \r
808 local addclient = function( address, port, listeners, pattern, sslctx, startssl )\r
809     local client, err = luasocket.tcp( )\r
810     if err then\r
811         return nil, err\r
812     end\r
813     client:settimeout( 0 )\r
814     _, err = client:connect( address, port )\r
815     if err then    -- try again\r
816         local handler = wrapclient( client, address, port, listeners )\r
817     else\r
818         wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx, startssl )\r
819     end\r
820 end\r
821 \r
822 --// EXPERIMENTAL //--\r
823 \r
824 ----------------------------------// BEGIN //--\r
825 \r
826 use "setmetatable" ( _socketlist, { __mode = "k" } )\r
827 use "setmetatable" ( _readtimes, { __mode = "k" } )\r
828 use "setmetatable" ( _writetimes, { __mode = "k" } )\r
829 \r
830 _timer = os_time( )\r
831 _starttime = os_time( )\r
832 \r
833 addtimer( function( )\r
834         local difftime = os_difftime( _currenttime - _starttime )\r
835         if difftime > _checkinterval then\r
836             _starttime = _currenttime\r
837             for handler, timestamp in pairs( _writetimes ) do\r
838                 if os_difftime( _currenttime - timestamp ) > _sendtimeout then\r
839                     --_writetimes[ handler ] = nil\r
840                     handler.disconnect( )( handler, "send timeout" )\r
841                     handler.close( true )    -- forced disconnect\r
842                 end\r
843             end\r
844             for handler, timestamp in pairs( _readtimes ) do\r
845                 if os_difftime( _currenttime - timestamp ) > _readtimeout then\r
846                     --_readtimes[ handler ] = nil\r
847                     handler.disconnect( )( handler, "read timeout" )\r
848                     handler.close( )    -- forced disconnect?\r
849                 end\r
850             end\r
851         end\r
852     end\r
853 )\r
854 \r
855 ----------------------------------// PUBLIC INTERFACE //--\r
856 \r
857 return {\r
858 \r
859     addclient = addclient,\r
860     wrapclient = wrapclient,\r
861     \r
862     loop = loop,\r
863     stats = stats,\r
864     closeall = closeall,\r
865     addtimer = addtimer,\r
866     addserver = addserver,\r
867     getserver = getserver,\r
868     getsettings = getsettings,\r
869     setquitting = setquitting,\r
870     removeserver = removeserver,\r
871     changesettings = changesettings,\r
872 }\r