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