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