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