773a4232d23c572337c2eb71a0ebffc74afc26b6
[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         _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
322         _readtimes[ handler ] = nil\r
323         if bufferqueuelen ~= 0 then\r
324             if not ( forced or fatalerror ) then\r
325                 handler.sendbuffer( )\r
326                 if bufferqueuelen ~= 0 then   -- try again...\r
327                     handler.write = nil    -- ... but no further writing allowed\r
328                     toclose = true\r
329                     return false\r
330                 end\r
331             else\r
332                 send( socket, table_concat( bufferqueue, "", 1, bufferqueuelen ), 1, bufferlen )    -- forced send\r
333             end\r
334         end\r
335         shutdown( socket )\r
336         socket:close( )\r
337         _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
338         _socketlist[ socket ] = nil\r
339         _writetimes[ handler ] = nil\r
340         _closelist[ handler ] = nil\r
341         handler = nil\r
342         socket = nil\r
343         mem_free( )\r
344         if server then\r
345                 server.remove( )\r
346         end\r
347         out_put "server.lua: closed client handler and removed socket from list"\r
348         return true\r
349     end\r
350     handler.ip = function( )\r
351         return ip\r
352     end\r
353     handler.serverport = function( )\r
354         return serverport\r
355     end\r
356     handler.clientport = function( )\r
357         return clientport\r
358     end\r
359     local write = function( data )\r
360         bufferlen = bufferlen + string_len( data )\r
361         if bufferlen > maxsendlen then\r
362             _closelist[ handler ] = "send buffer exceeded"   -- cannot close the client at the moment, have to wait to the end of the cycle\r
363             handler.write = idfalse    -- dont write anymore\r
364             return false\r
365         elseif not _sendlist[ socket ] then\r
366             _sendlistlen = _sendlistlen + 1\r
367             _sendlist[ _sendlistlen ] = socket\r
368             _sendlist[ socket ] = _sendlistlen\r
369         end\r
370         bufferqueuelen = bufferqueuelen + 1\r
371         bufferqueue[ bufferqueuelen ] = data\r
372         _writetimes[ handler ] = _writetimes[ handler ] or _currenttime\r
373         return true\r
374     end\r
375     handler.write = write\r
376     handler.bufferqueue = function( )\r
377         return bufferqueue\r
378     end\r
379     handler.socket = function( )\r
380         return socket\r
381     end\r
382     handler.pattern = function( new )\r
383         pattern = new or pattern\r
384         return pattern\r
385     end\r
386     handler.bufferlen = function( readlen, sendlen )\r
387         maxsendlen = sendlen or maxsendlen\r
388         maxreadlen = readlen or maxreadlen\r
389         return maxreadlen, maxsendlen\r
390     end\r
391     handler.lock = function( switch )\r
392         if switch == true then\r
393             handler.write = idfalse\r
394             local tmp = _sendlistlen\r
395             _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
396             _writetimes[ handler ] = nil\r
397             if _sendlistlen ~= tmp then\r
398                 nosend = true\r
399             end\r
400             tmp = _readlistlen\r
401             _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
402             _readtimes[ handler ] = nil\r
403             if _readlistlen ~= tmp then\r
404                 noread = true\r
405             end\r
406         elseif switch == false then\r
407             handler.write = write\r
408             if noread then\r
409                 noread = false\r
410                 _readlistlen = _readlistlen + 1\r
411                 _readlist[ socket ] = _readlistlen\r
412                 _readlist[ _readlistlen ] = socket\r
413                 _readtimes[ handler ] = _currenttime\r
414             end\r
415             if nosend then\r
416                 nosend = false\r
417                 write( "" )\r
418             end\r
419         end\r
420         return noread, nosend\r
421     end\r
422     local _readbuffer = function( )    -- this function reads data\r
423         local buffer, err, part = receive( socket, pattern )    -- receive buffer with "pattern"\r
424         if not err or ( err == "timeout" or err == "wantread" ) then    -- received something\r
425             local buffer = buffer or part or ""\r
426             local len = string_len( buffer )\r
427             if len > maxreadlen then\r
428                 disconnect( handler, "receive buffer exceeded" )\r
429                 handler.close( true )\r
430                 return false\r
431             end\r
432             local count = len * STAT_UNIT\r
433             readtraffic = readtraffic + count\r
434             _readtraffic = _readtraffic + count\r
435             _readtimes[ handler ] = _currenttime\r
436             --out_put( "server.lua: read data '", buffer, "', error: ", err )\r
437             return dispatch( handler, buffer, err )\r
438         else    -- connections was closed or fatal error\r
439             out_put( "server.lua: client ", ip, ":", clientport, " error: ", err )\r
440             fatalerror = true\r
441             disconnect( handler, err )\r
442             _ = handler and handler.close( )\r
443             return false\r
444         end\r
445     end\r
446     local _sendbuffer = function( )    -- this function sends data\r
447         local buffer = table_concat( bufferqueue, "", 1, bufferqueuelen )\r
448         local succ, err, byte = send( socket, buffer, 1, bufferlen )\r
449         local count = ( succ or byte or 0 ) * STAT_UNIT\r
450         sendtraffic = sendtraffic + count\r
451         _sendtraffic = _sendtraffic + count\r
452         _ = _cleanqueue and clean( bufferqueue )\r
453         --out_put( "server.lua: sended '", buffer, "', bytes: ", succ, ", error: ", err, ", part: ", byte, ", to: ", ip, ":", clientport )\r
454         if succ then    -- sending succesful\r
455             bufferqueuelen = 0\r
456             bufferlen = 0\r
457             _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )    -- delete socket from writelist\r
458             _ = needtls and handler.starttls(true)\r
459             _writetimes[ handler ] = nil\r
460             _ = toclose and handler.close( )\r
461             return true\r
462         elseif byte and ( err == "timeout" or err == "wantwrite" ) then    -- want write\r
463             buffer = string_sub( buffer, byte + 1, bufferlen )    -- new buffer\r
464             bufferqueue[ 1 ] = buffer    -- insert new buffer in queue\r
465             bufferqueuelen = 1\r
466             bufferlen = bufferlen - byte\r
467             _writetimes[ handler ] = _currenttime\r
468             return true\r
469         else    -- connection was closed during sending or fatal error\r
470             out_put( "server.lua: client ", ip, ":", clientport, " error: ", err )\r
471             fatalerror = true\r
472             disconnect( handler, err )\r
473             handler.close( )\r
474             return false\r
475         end\r
476     end\r
477 \r
478     if sslctx then    -- ssl?\r
479         ssl = true\r
480         local wrote\r
481         local handshake = coroutine_wrap( function( client )    -- create handshake coroutine\r
482                 local err\r
483                 for i = 1, 10 do    -- 10 handshake attemps\r
484                     _, err = client:dohandshake( )\r
485                     if not err then\r
486                         --out_put( "server.lua: ssl handshake done" )\r
487                         _sendlistlen = ( wrote and removesocket( _sendlist, socket, _sendlistlen ) ) or _sendlistlen\r
488                         handler.readbuffer = _readbuffer    -- when handshake is done, replace the handshake function with regular functions\r
489                         handler.sendbuffer = _sendbuffer\r
490                         --return dispatch( handler )\r
491                         return true\r
492                     else\r
493                         out_put( "server.lua: error during ssl handshake: ", err )\r
494                         if err == "wantwrite" and not wrote then\r
495                             _sendlistlen = _sendlistlen + 1\r
496                             _sendlist[ _sendlistlen ] = client\r
497                             wrote = true\r
498                         end\r
499                         --coroutine_yield( handler, nil, err )    -- handshake not finished\r
500                         coroutine_yield( )\r
501                     end\r
502                 end\r
503                 disconnect( handler, "max handshake attemps exceeded" )\r
504                 handler.close( true )    -- forced disconnect\r
505                 return false    -- handshake failed\r
506             end\r
507         )\r
508         if startssl then    -- ssl now?\r
509             --out_put("server.lua: ", "starting ssl handshake")\r
510             local err\r
511             socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
512             if err then\r
513                 out_put( "server.lua: ssl error: ", err )\r
514                 mem_free( )\r
515                 return nil, nil, err    -- fatal error\r
516             end\r
517             socket:settimeout( 0 )\r
518             handler.readbuffer = handshake\r
519             handler.sendbuffer = handshake\r
520             handshake( socket )    -- do handshake\r
521         else\r
522             handler.starttls = function( now )\r
523                 if not now then\r
524                     --out_put "server.lua: we need to do tls, but delaying until later"\r
525                     needtls = true\r
526                     return\r
527                 end\r
528                 --out_put( "server.lua: attempting to start tls on " .. tostring( socket ) )\r
529                 local oldsocket, err = socket\r
530                 socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
531                 --out_put( "server.lua: sslwrapped socket is " .. tostring( socket ) )\r
532                 if err then\r
533                     out_put( "server.lua: error while starting tls on client: ", err )\r
534                     return nil, err    -- fatal error\r
535                 end\r
536 \r
537                 socket:settimeout( 0 )\r
538 \r
539                 -- add the new socket to our system\r
540 \r
541                 send = socket.send\r
542                 receive = socket.receive\r
543                 shutdown = id\r
544 \r
545                 _socketlist[ socket ] = handler\r
546                 _readlistlen = _readlistlen + 1\r
547                 _readlist[ _readlistlen ] = socket\r
548                 _readlist[ socket ] = _readlistlen\r
549 \r
550                 -- remove traces of the old socket\r
551 \r
552                 _readlistlen = removesocket( _readlist, oldsocket, _readlistlen )\r
553                 _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen )\r
554                 _socketlist[ oldsocket ] = nil\r
555 \r
556                 handler.starttls = nil\r
557                 needtls = nil\r
558 \r
559                 handler.receivedata = handler.handshake\r
560                 handler.dispatchdata = handler.handshake\r
561                 handshake( socket )    -- do handshake\r
562             end\r
563             handler.readbuffer = _readbuffer\r
564             handler.sendbuffer = _sendbuffer\r
565         end\r
566     else    -- normal connection\r
567         ssl = false\r
568         handler.readbuffer = _readbuffer\r
569         handler.sendbuffer = _sendbuffer\r
570     end\r
571 \r
572     send = socket.send\r
573     receive = socket.receive\r
574     shutdown = ( ssl and id ) or socket.shutdown\r
575 \r
576     _socketlist[ socket ] = handler\r
577     _readlistlen = _readlistlen + 1\r
578     _readlist[ _readlistlen ] = socket\r
579     _readlist[ socket ] = _readlistlen\r
580 \r
581     return handler, socket\r
582 end\r
583 \r
584 id = function( )\r
585 end\r
586 \r
587 idfalse = function( )\r
588     return false\r
589 end\r
590 \r
591 removesocket = function( list, socket, len )    -- this function removes sockets from a list ( copied from copas )\r
592     local pos = list[ socket ]\r
593     if pos then\r
594         list[ socket ] = nil\r
595         local last = list[ len ]\r
596         list[ len ] = nil\r
597         if last ~= socket then\r
598             list[ last ] = pos\r
599             list[ pos ] = last\r
600         end\r
601         return len - 1\r
602     end\r
603     return len\r
604 end\r
605 \r
606 closesocket = function( socket )\r
607     _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
608     _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
609     _socketlist[ socket ] = nil\r
610     socket:close( )\r
611     mem_free( )\r
612 end\r
613 \r
614 ----------------------------------// PUBLIC //--\r
615 \r
616 addserver = function( listeners, port, addr, pattern, sslctx, maxconnections, startssl )    -- this function provides a way for other scripts to reg a server\r
617     local err\r
618     --out_put("server.lua: autossl on ", port, " is ", startssl)\r
619     if type( listeners ) ~= "table" then\r
620         err = "invalid listener table"\r
621     end\r
622     if not type( port ) == "number" or not ( port >= 0 and port <= 65535 ) then\r
623         err = "invalid port"\r
624     elseif _server[ port ] then\r
625         err =  "listeners on port '" .. port .. "' already exist"\r
626     elseif sslctx and not luasec then\r
627         err = "luasec not found"\r
628     end\r
629     if err then\r
630         out_error( "server.lua: ", err )\r
631         return nil, err\r
632     end\r
633     addr = addr or "*"\r
634     local server, err = socket_bind( addr, port )\r
635     if err then\r
636         out_error( "server.lua: ", err )\r
637         return nil, err\r
638     end\r
639     local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, maxconnections, startssl )    -- wrap new server socket\r
640     if not handler then\r
641         server:close( )\r
642         return nil, err\r
643     end\r
644     server:settimeout( 0 )\r
645     _readlistlen = _readlistlen + 1\r
646     _readlist[ _readlistlen ] = server\r
647     _server[ port ] = handler\r
648     _socketlist[ server ] = handler\r
649     out_put( "server.lua: new server listener on '", addr, ":", port, "'" )\r
650     return handler\r
651 end\r
652 \r
653 removeserver = function( port )\r
654     local handler = _server[ port ]\r
655     if not handler then\r
656         return nil, "no server found on port '" .. tostring( port ) "'"\r
657     end\r
658     handler.close( )\r
659     return true\r
660 end\r
661 \r
662 closeall = function( )\r
663     for _, handler in pairs( _socketlist ) do\r
664         handler.close( )\r
665         _socketlist[ _ ] = nil\r
666     end\r
667     _readlistlen = 0\r
668     _sendlistlen = 0\r
669     _timerlistlen = 0\r
670     _server = { }\r
671     _readlist = { }\r
672     _sendlist = { }\r
673     _timerlist = { }\r
674     _socketlist = { }\r
675     mem_free( )\r
676 end\r
677 \r
678 getsettings = function( )\r
679     return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver\r
680 end\r
681 \r
682 changesettings = function( new )\r
683     if type( new ) ~= "table" then\r
684         return nil, "invalid settings table"\r
685     end\r
686     _selecttimeout = tonumber( new.timeout ) or _selecttimeout\r
687     _sleeptime = tonumber( new.sleeptime ) or _sleeptime\r
688     _maxsendlen = tonumber( new.maxsendlen ) or _maxsendlen\r
689     _maxreadlen = tonumber( new.maxreadlen ) or _maxreadlen\r
690     _checkinterval = tonumber( new.checkinterval ) or _checkinterval\r
691     _sendtimeout = tonumber( new.sendtimeout ) or _sendtimeout\r
692     _readtimeout = tonumber( new.readtimeout ) or _readtimeout\r
693     _cleanqueue = new.cleanqueue\r
694     _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver\r
695     return true\r
696 end\r
697 \r
698 addtimer = function( listener )\r
699     if type( listener ) ~= "function" then\r
700         return nil, "invalid listener function"\r
701     end\r
702     _timerlistlen = _timerlistlen + 1\r
703     _timerlist[ _timerlistlen ] = listener\r
704     return true\r
705 end\r
706 \r
707 stats = function( )\r
708     return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen\r
709 end\r
710 \r
711 loop = function( )    -- this is the main loop of the program\r
712     while true do\r
713         local read, write, err = socket_select( _readlist, _sendlist, _selecttimeout )\r
714         for i, socket in ipairs( write ) do    -- send data waiting in writequeues\r
715             local handler = _socketlist[ socket ]\r
716             if handler then\r
717                 handler.sendbuffer( )\r
718             else\r
719                 closesocket( socket )\r
720                 out_put "server.lua: found no handler and closed socket (writelist)"    -- this should not happen\r
721             end\r
722         end\r
723         for i, socket in ipairs( read ) do    -- receive data\r
724             local handler = _socketlist[ socket ]\r
725             if handler then\r
726                 handler.readbuffer( )\r
727             else\r
728                 closesocket( socket )\r
729                 out_put "server.lua: found no handler and closed socket (readlist)"    -- this can happen\r
730             end\r
731         end\r
732         for handler, err in pairs( _closelist ) do\r
733             handler.disconnect( )( handler, err )\r
734             handler.close( true )    -- forced disconnect\r
735         end\r
736         clean( _closelist )\r
737         _currenttime = os_time( )\r
738         if os_difftime( _currenttime - _timer ) >= 1 then\r
739             for i = 1, _timerlistlen do\r
740                 _timerlist[ i ]( )    -- fire timers\r
741             end\r
742             _timer = _currenttime\r
743         end\r
744         socket_sleep( _sleeptime )    -- wait some time\r
745         --collectgarbage( )\r
746     end\r
747 end\r
748 \r
749 --// EXPERIMENTAL //--\r
750 \r
751 local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, startssl )\r
752     local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, startssl )\r
753     _socketlist[ socket ] = handler\r
754     _sendlistlen = _sendlistlen + 1\r
755     _sendlist[ _sendlistlen ] = socket\r
756     _sendlist[ socket ] = _sendlistlen\r
757     return handler, socket\r
758 end\r
759 \r
760 local addclient = function( address, port, listeners, pattern, sslctx, startssl )\r
761     local client, err = socket.tcp( )\r
762     if err then\r
763         return nil, err\r
764     end\r
765     client:settimeout( 0 )\r
766     _, err = client:connect( address, port )\r
767     if err then    -- try again\r
768         local handler = wrapclient( client, address, port, listeners )\r
769     else\r
770         wrapconnection( server, listeners, socket, address, port, "clientport", pattern, sslctx, startssl )\r
771     end\r
772 end\r
773 \r
774 --// EXPERIMENTAL //--\r
775 \r
776 ----------------------------------// BEGIN //--\r
777 \r
778 use "setmetatable" ( _socketlist, { __mode = "k" } )\r
779 use "setmetatable" ( _readtimes, { __mode = "k" } )\r
780 use "setmetatable" ( _writetimes, { __mode = "k" } )\r
781 \r
782 _timer = os_time( )\r
783 _starttime = os_time( )\r
784 \r
785 addtimer( function( )\r
786         local difftime = os_difftime( _currenttime - _starttime )\r
787         if difftime > _checkinterval then\r
788             _starttime = _currenttime\r
789             for handler, timestamp in pairs( _writetimes ) do\r
790                 if os_difftime( _currenttime - timestamp ) > _sendtimeout then\r
791                     --_writetimes[ handler ] = nil\r
792                     handler.disconnect( )( handler, "send timeout" )\r
793                     handler.close( true )    -- forced disconnect\r
794                 end\r
795             end\r
796             for handler, timestamp in pairs( _readtimes ) do\r
797                 if os_difftime( _currenttime - timestamp ) > _readtimeout then\r
798                     --_readtimes[ handler ] = nil\r
799                     handler.disconnect( )( handler, "read timeout" )\r
800                     handler.close( )    -- forced disconnect?\r
801                 end\r
802             end\r
803         end\r
804     end\r
805 )\r
806 \r
807 ----------------------------------// PUBLIC INTERFACE //--\r
808 \r
809 return {\r
810 \r
811     addclient = addclient,\r
812     wrapclient = wrapclient,\r
813     \r
814     loop = loop,\r
815     stats = stats,\r
816     closeall = closeall,\r
817     addtimer = addtimer,\r
818     addserver = addserver,\r
819     getsettings = getsettings,\r
820     removeserver = removeserver,\r
821     changesettings = changesettings,\r
822 \r
823 }\r