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