Merge with 0.4
[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 getserver\r
81 local wrapserver\r
82 local getsettings\r
83 local closesocket\r
84 local removesocket\r
85 local removeserver\r
86 local changetimeout\r
87 local wrapconnection\r
88 local changesettings\r
89 \r
90 --// tables //--\r
91 \r
92 local _server\r
93 local _readlist\r
94 local _timerlist\r
95 local _sendlist\r
96 local _socketlist\r
97 local _closelist\r
98 local _readtimes\r
99 local _writetimes\r
100 \r
101 --// simple data types //--\r
102 \r
103 local _\r
104 local _readlistlen\r
105 local _sendlistlen\r
106 local _timerlistlen\r
107 \r
108 local _sendtraffic\r
109 local _readtraffic\r
110 \r
111 local _selecttimeout\r
112 local _sleeptime\r
113 \r
114 local _starttime\r
115 local _currenttime\r
116 \r
117 local _maxsendlen\r
118 local _maxreadlen\r
119 \r
120 local _checkinterval\r
121 local _sendtimeout\r
122 local _readtimeout\r
123 \r
124 local _cleanqueue\r
125 \r
126 local _timer\r
127 \r
128 local _maxclientsperserver\r
129 \r
130 ----------------------------------// DEFINITION //--\r
131 \r
132 _server = { }    -- key = port, value = table; list of listening servers\r
133 _readlist = { }    -- array with sockets to read from\r
134 _sendlist = { }    -- arrary with sockets to write to\r
135 _timerlist = { }    -- array of timer functions\r
136 _socketlist = { }    -- key = socket, value = wrapped socket (handlers)\r
137 _readtimes = { }   -- key = handler, value = timestamp of last data reading\r
138 _writetimes = { }   -- key = handler, value = timestamp of last data writing/sending\r
139 _closelist = { }    -- handlers to close\r
140 \r
141 _readlistlen = 0    -- length of readlist\r
142 _sendlistlen = 0    -- length of sendlist\r
143 _timerlistlen = 0    -- lenght of timerlist\r
144 \r
145 _sendtraffic = 0    -- some stats\r
146 _readtraffic = 0\r
147 \r
148 _selecttimeout = 1    -- timeout of socket.select\r
149 _sleeptime = 0    -- time to wait at the end of every loop\r
150 \r
151 _maxsendlen = 51000 * 1024    -- max len of send buffer\r
152 _maxreadlen = 25000 * 1024    -- max len of read buffer\r
153 \r
154 _checkinterval = 1200000    -- interval in secs to check idle clients\r
155 _sendtimeout = 60000    -- allowed send idle time in secs\r
156 _readtimeout = 6 * 60 * 60    -- allowed read idle time in secs\r
157 \r
158 _cleanqueue = false    -- clean bufferqueue after using\r
159 \r
160 _maxclientsperserver = 1000\r
161 \r
162 ----------------------------------// PRIVATE //--\r
163 \r
164 wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxconnections, startssl )    -- this function wraps a server\r
165 \r
166     maxconnections = maxconnections or _maxclientsperserver\r
167 \r
168     local connections = 0\r
169 \r
170     local dispatch, disconnect = listeners.incoming or listeners.listener, listeners.disconnect\r
171 \r
172     local err\r
173 \r
174     local ssl = false\r
175 \r
176     if sslctx then\r
177         if not ssl_newcontext then\r
178             return nil, "luasec not found"\r
179         end\r
180         if type( sslctx ) ~= "table" then\r
181             out_error "server.lua: wrong server sslctx"\r
182             return nil, "wrong server sslctx"\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             return nil, err\r
189         end\r
190         ssl = true\r
191     else\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.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         else\r
543             handler.starttls = function( now )\r
544                 if not now then\r
545                     --out_put "server.lua: we need to do tls, but delaying until later"\r
546                     needtls = true\r
547                     return\r
548                 end\r
549                 --out_put( "server.lua: attempting to start tls on " .. tostring( socket ) )\r
550                 local oldsocket, err = socket\r
551                 socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
552                 --out_put( "server.lua: sslwrapped socket is " .. tostring( socket ) )\r
553                 if err then\r
554                     out_put( "server.lua: error while starting tls on client: ", tostring(err) )\r
555                     return nil, err    -- fatal error\r
556                 end\r
557 \r
558                 socket:settimeout( 0 )\r
559 \r
560                 -- add the new socket to our system\r
561 \r
562                 send = socket.send\r
563                 receive = socket.receive\r
564                 shutdown = id\r
565 \r
566                 _socketlist[ socket ] = handler\r
567                 _readlistlen = _readlistlen + 1\r
568                 _readlist[ _readlistlen ] = socket\r
569                 _readlist[ socket ] = _readlistlen\r
570 \r
571                 -- remove traces of the old socket\r
572 \r
573                 _readlistlen = removesocket( _readlist, oldsocket, _readlistlen )\r
574                 _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen )\r
575                 _socketlist[ oldsocket ] = nil\r
576 \r
577                 handler.starttls = nil\r
578                 needtls = nil\r
579 \r
580                 handler.readbuffer = handshake\r
581                 handler.sendbuffer = handshake\r
582                 handshake( socket )    -- do handshake\r
583             end\r
584             handler.readbuffer = _readbuffer\r
585             handler.sendbuffer = _sendbuffer\r
586         end\r
587     else    -- normal connection\r
588         ssl = false\r
589         handler.readbuffer = _readbuffer\r
590         handler.sendbuffer = _sendbuffer\r
591     end\r
592 \r
593     send = socket.send\r
594     receive = socket.receive\r
595     shutdown = ( ssl and id ) or socket.shutdown\r
596 \r
597     _socketlist[ socket ] = handler\r
598     _readlistlen = _readlistlen + 1\r
599     _readlist[ _readlistlen ] = socket\r
600     _readlist[ socket ] = _readlistlen\r
601 \r
602     return handler, socket\r
603 end\r
604 \r
605 id = function( )\r
606 end\r
607 \r
608 idfalse = function( )\r
609     return false\r
610 end\r
611 \r
612 removesocket = function( list, socket, len )    -- this function removes sockets from a list ( copied from copas )\r
613     local pos = list[ socket ]\r
614     if pos then\r
615         list[ socket ] = nil\r
616         local last = list[ len ]\r
617         list[ len ] = nil\r
618         if last ~= socket then\r
619             list[ last ] = pos\r
620             list[ pos ] = last\r
621         end\r
622         return len - 1\r
623     end\r
624     return len\r
625 end\r
626 \r
627 closesocket = function( socket )\r
628     _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
629     _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
630     _socketlist[ socket ] = nil\r
631     socket:close( )\r
632     mem_free( )\r
633 end\r
634 \r
635 ----------------------------------// PUBLIC //--\r
636 \r
637 addserver = function( listeners, port, addr, pattern, sslctx, maxconnections, startssl )    -- this function provides a way for other scripts to reg a server\r
638     local err\r
639     --out_put("server.lua: autossl on ", port, " is ", startssl)\r
640     if type( listeners ) ~= "table" then\r
641         err = "invalid listener table"\r
642     end\r
643     if not type( port ) == "number" or not ( port >= 0 and port <= 65535 ) then\r
644         err = "invalid port"\r
645     elseif _server[ port ] then\r
646         err =  "listeners on port '" .. port .. "' already exist"\r
647     elseif sslctx and not luasec then\r
648         err = "luasec not found"\r
649     end\r
650     if err then\r
651         out_error( "server.lua: ", err )\r
652         return nil, err\r
653     end\r
654     addr = addr or "*"\r
655     local server, err = socket_bind( addr, port )\r
656     if err then\r
657         out_error( "server.lua: ", err )\r
658         return nil, err\r
659     end\r
660     local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, maxconnections, startssl )    -- wrap new server socket\r
661     if not handler then\r
662         server:close( )\r
663         return nil, err\r
664     end\r
665     server:settimeout( 0 )\r
666     _readlistlen = _readlistlen + 1\r
667     _readlist[ _readlistlen ] = server\r
668     _server[ port ] = handler\r
669     _socketlist[ server ] = handler\r
670     out_put( "server.lua: new server listener on '", addr, ":", port, "'" )\r
671     return handler\r
672 end\r
673 \r
674 getserver = function ( port )\r
675         return _server[ port ];\r
676 end\r
677 \r
678 removeserver = function( port )\r
679     local handler = _server[ port ]\r
680     if not handler then\r
681         return nil, "no server found on port '" .. tostring( port ) "'"\r
682     end\r
683     handler.close( )\r
684     return true\r
685 end\r
686 \r
687 closeall = function( )\r
688     for _, handler in pairs( _socketlist ) do\r
689         handler.close( )\r
690         _socketlist[ _ ] = nil\r
691     end\r
692     _readlistlen = 0\r
693     _sendlistlen = 0\r
694     _timerlistlen = 0\r
695     _server = { }\r
696     _readlist = { }\r
697     _sendlist = { }\r
698     _timerlist = { }\r
699     _socketlist = { }\r
700     mem_free( )\r
701 end\r
702 \r
703 getsettings = function( )\r
704     return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver\r
705 end\r
706 \r
707 changesettings = function( new )\r
708     if type( new ) ~= "table" then\r
709         return nil, "invalid settings table"\r
710     end\r
711     _selecttimeout = tonumber( new.timeout ) or _selecttimeout\r
712     _sleeptime = tonumber( new.sleeptime ) or _sleeptime\r
713     _maxsendlen = tonumber( new.maxsendlen ) or _maxsendlen\r
714     _maxreadlen = tonumber( new.maxreadlen ) or _maxreadlen\r
715     _checkinterval = tonumber( new.checkinterval ) or _checkinterval\r
716     _sendtimeout = tonumber( new.sendtimeout ) or _sendtimeout\r
717     _readtimeout = tonumber( new.readtimeout ) or _readtimeout\r
718     _cleanqueue = new.cleanqueue\r
719     _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver\r
720     return true\r
721 end\r
722 \r
723 addtimer = function( listener )\r
724     if type( listener ) ~= "function" then\r
725         return nil, "invalid listener function"\r
726     end\r
727     _timerlistlen = _timerlistlen + 1\r
728     _timerlist[ _timerlistlen ] = listener\r
729     return true\r
730 end\r
731 \r
732 stats = function( )\r
733     return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen\r
734 end\r
735 \r
736 local dontstop = true; -- thinking about tomorrow, ...\r
737 \r
738 setquitting = function (quit)\r
739         dontstop = not quit;\r
740         return;\r
741 end\r
742 \r
743 loop = function( )    -- this is the main loop of the program\r
744     while dontstop do\r
745         local read, write, err = socket_select( _readlist, _sendlist, _selecttimeout )\r
746         for i, socket in ipairs( write ) do    -- send data waiting in writequeues\r
747             local handler = _socketlist[ socket ]\r
748             if handler then\r
749                 handler.sendbuffer( )\r
750             else\r
751                 closesocket( socket )\r
752                 out_put "server.lua: found no handler and closed socket (writelist)"    -- this should not happen\r
753             end\r
754         end\r
755         for i, socket in ipairs( read ) do    -- receive data\r
756             local handler = _socketlist[ socket ]\r
757             if handler then\r
758                 handler.readbuffer( )\r
759             else\r
760                 closesocket( socket )\r
761                 out_put "server.lua: found no handler and closed socket (readlist)"    -- this can happen\r
762             end\r
763         end\r
764         for handler, err in pairs( _closelist ) do\r
765             handler.disconnect( )( handler, err )\r
766             handler.close( true )    -- forced disconnect\r
767         end\r
768         clean( _closelist )\r
769         _currenttime = os_time( )\r
770         if os_difftime( _currenttime - _timer ) >= 1 then\r
771             for i = 1, _timerlistlen do\r
772                 _timerlist[ i ]( )    -- fire timers\r
773             end\r
774             _timer = _currenttime\r
775         end\r
776         socket_sleep( _sleeptime )    -- wait some time\r
777         --collectgarbage( )\r
778     end\r
779     return "quitting"\r
780 end\r
781 \r
782 --// EXPERIMENTAL //--\r
783 \r
784 local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, startssl )\r
785     local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, startssl )\r
786     _socketlist[ socket ] = handler\r
787     _sendlistlen = _sendlistlen + 1\r
788     _sendlist[ _sendlistlen ] = socket\r
789     _sendlist[ socket ] = _sendlistlen\r
790     return handler, socket\r
791 end\r
792 \r
793 local addclient = function( address, port, listeners, pattern, sslctx, startssl )\r
794     local client, err = luasocket.tcp( )\r
795     if err then\r
796         return nil, err\r
797     end\r
798     client:settimeout( 0 )\r
799     _, err = client:connect( address, port )\r
800     if err then    -- try again\r
801         local handler = wrapclient( client, address, port, listeners )\r
802     else\r
803         wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx, startssl )\r
804     end\r
805 end\r
806 \r
807 --// EXPERIMENTAL //--\r
808 \r
809 ----------------------------------// BEGIN //--\r
810 \r
811 use "setmetatable" ( _socketlist, { __mode = "k" } )\r
812 use "setmetatable" ( _readtimes, { __mode = "k" } )\r
813 use "setmetatable" ( _writetimes, { __mode = "k" } )\r
814 \r
815 _timer = os_time( )\r
816 _starttime = os_time( )\r
817 \r
818 addtimer( function( )\r
819         local difftime = os_difftime( _currenttime - _starttime )\r
820         if difftime > _checkinterval then\r
821             _starttime = _currenttime\r
822             for handler, timestamp in pairs( _writetimes ) do\r
823                 if os_difftime( _currenttime - timestamp ) > _sendtimeout then\r
824                     --_writetimes[ handler ] = nil\r
825                     handler.disconnect( )( handler, "send timeout" )\r
826                     handler.close( true )    -- forced disconnect\r
827                 end\r
828             end\r
829             for handler, timestamp in pairs( _readtimes ) do\r
830                 if os_difftime( _currenttime - timestamp ) > _readtimeout then\r
831                     --_readtimes[ handler ] = nil\r
832                     handler.disconnect( )( handler, "read timeout" )\r
833                     handler.close( )    -- forced disconnect?\r
834                 end\r
835             end\r
836         end\r
837     end\r
838 )\r
839 \r
840 ----------------------------------// PUBLIC INTERFACE //--\r
841 \r
842 return {\r
843 \r
844     addclient = addclient,\r
845     wrapclient = wrapclient,\r
846     \r
847     loop = loop,\r
848     stats = stats,\r
849     closeall = closeall,\r
850     addtimer = addtimer,\r
851     addserver = addserver,\r
852     getserver = getserver,\r
853     getsettings = getsettings,\r
854     setquitting = setquitting,\r
855     removeserver = removeserver,\r
856     changesettings = changesettings,\r
857 }\r