net.xmppclient_listener: Add small comment
[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             -- We're not automatically doing SSL, so we're not secure (yet)\r
544             ssl = false\r
545             handler.starttls = function( now )\r
546                 if not now then\r
547                     --out_put "server.lua: we need to do tls, but delaying until later"\r
548                     needtls = true\r
549                     return\r
550                 end\r
551                 --out_put( "server.lua: attempting to start tls on " .. tostring( socket ) )\r
552                 local oldsocket, err = socket\r
553                 socket, err = ssl_wrap( socket, sslctx )    -- wrap socket\r
554                 --out_put( "server.lua: sslwrapped socket is " .. tostring( socket ) )\r
555                 if err then\r
556                     out_put( "server.lua: error while starting tls on client: ", tostring(err) )\r
557                     return nil, err    -- fatal error\r
558                 end\r
559 \r
560                 socket:settimeout( 0 )\r
561 \r
562                 -- add the new socket to our system\r
563 \r
564                 send = socket.send\r
565                 receive = socket.receive\r
566                 shutdown = id\r
567 \r
568                 _socketlist[ socket ] = handler\r
569                 _readlistlen = _readlistlen + 1\r
570                 _readlist[ _readlistlen ] = socket\r
571                 _readlist[ socket ] = _readlistlen\r
572 \r
573                 -- remove traces of the old socket\r
574 \r
575                 _readlistlen = removesocket( _readlist, oldsocket, _readlistlen )\r
576                 _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen )\r
577                 _socketlist[ oldsocket ] = nil\r
578 \r
579                 handler.starttls = nil\r
580                 needtls = nil\r
581                 \r
582                 -- Secure now\r
583                 ssl = true\r
584 \r
585                 handler.readbuffer = handshake\r
586                 handler.sendbuffer = handshake\r
587                 handshake( socket )    -- do handshake\r
588             end\r
589             handler.readbuffer = _readbuffer\r
590             handler.sendbuffer = _sendbuffer\r
591         end\r
592     else    -- normal connection\r
593         ssl = false\r
594         handler.readbuffer = _readbuffer\r
595         handler.sendbuffer = _sendbuffer\r
596     end\r
597 \r
598     send = socket.send\r
599     receive = socket.receive\r
600     shutdown = ( ssl and id ) or socket.shutdown\r
601 \r
602     _socketlist[ socket ] = handler\r
603     _readlistlen = _readlistlen + 1\r
604     _readlist[ _readlistlen ] = socket\r
605     _readlist[ socket ] = _readlistlen\r
606 \r
607     return handler, socket\r
608 end\r
609 \r
610 id = function( )\r
611 end\r
612 \r
613 idfalse = function( )\r
614     return false\r
615 end\r
616 \r
617 removesocket = function( list, socket, len )    -- this function removes sockets from a list ( copied from copas )\r
618     local pos = list[ socket ]\r
619     if pos then\r
620         list[ socket ] = nil\r
621         local last = list[ len ]\r
622         list[ len ] = nil\r
623         if last ~= socket then\r
624             list[ last ] = pos\r
625             list[ pos ] = last\r
626         end\r
627         return len - 1\r
628     end\r
629     return len\r
630 end\r
631 \r
632 closesocket = function( socket )\r
633     _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )\r
634     _readlistlen = removesocket( _readlist, socket, _readlistlen )\r
635     _socketlist[ socket ] = nil\r
636     socket:close( )\r
637     mem_free( )\r
638 end\r
639 \r
640 ----------------------------------// PUBLIC //--\r
641 \r
642 addserver = function( listeners, port, addr, pattern, sslctx, maxconnections, startssl )    -- this function provides a way for other scripts to reg a server\r
643     local err\r
644     --out_put("server.lua: autossl on ", port, " is ", startssl)\r
645     if type( listeners ) ~= "table" then\r
646         err = "invalid listener table"\r
647     end\r
648     if not type( port ) == "number" or not ( port >= 0 and port <= 65535 ) then\r
649         err = "invalid port"\r
650     elseif _server[ port ] then\r
651         err =  "listeners on port '" .. port .. "' already exist"\r
652     elseif sslctx and not luasec then\r
653         err = "luasec not found"\r
654     end\r
655     if err then\r
656         out_error( "server.lua: ", err )\r
657         return nil, err\r
658     end\r
659     addr = addr or "*"\r
660     local server, err = socket_bind( addr, port )\r
661     if err then\r
662         out_error( "server.lua: ", err )\r
663         return nil, err\r
664     end\r
665     local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, maxconnections, startssl )    -- wrap new server socket\r
666     if not handler then\r
667         server:close( )\r
668         return nil, err\r
669     end\r
670     server:settimeout( 0 )\r
671     _readlistlen = _readlistlen + 1\r
672     _readlist[ _readlistlen ] = server\r
673     _server[ port ] = handler\r
674     _socketlist[ server ] = handler\r
675     out_put( "server.lua: new server listener on '", addr, ":", port, "'" )\r
676     return handler\r
677 end\r
678 \r
679 getserver = function ( port )\r
680         return _server[ port ];\r
681 end\r
682 \r
683 removeserver = function( port )\r
684     local handler = _server[ port ]\r
685     if not handler then\r
686         return nil, "no server found on port '" .. tostring( port ) "'"\r
687     end\r
688     handler.close( )\r
689     return true\r
690 end\r
691 \r
692 closeall = function( )\r
693     for _, handler in pairs( _socketlist ) do\r
694         handler.close( )\r
695         _socketlist[ _ ] = nil\r
696     end\r
697     _readlistlen = 0\r
698     _sendlistlen = 0\r
699     _timerlistlen = 0\r
700     _server = { }\r
701     _readlist = { }\r
702     _sendlist = { }\r
703     _timerlist = { }\r
704     _socketlist = { }\r
705     mem_free( )\r
706 end\r
707 \r
708 getsettings = function( )\r
709     return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver\r
710 end\r
711 \r
712 changesettings = function( new )\r
713     if type( new ) ~= "table" then\r
714         return nil, "invalid settings table"\r
715     end\r
716     _selecttimeout = tonumber( new.timeout ) or _selecttimeout\r
717     _sleeptime = tonumber( new.sleeptime ) or _sleeptime\r
718     _maxsendlen = tonumber( new.maxsendlen ) or _maxsendlen\r
719     _maxreadlen = tonumber( new.maxreadlen ) or _maxreadlen\r
720     _checkinterval = tonumber( new.checkinterval ) or _checkinterval\r
721     _sendtimeout = tonumber( new.sendtimeout ) or _sendtimeout\r
722     _readtimeout = tonumber( new.readtimeout ) or _readtimeout\r
723     _cleanqueue = new.cleanqueue\r
724     _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver\r
725     return true\r
726 end\r
727 \r
728 addtimer = function( listener )\r
729     if type( listener ) ~= "function" then\r
730         return nil, "invalid listener function"\r
731     end\r
732     _timerlistlen = _timerlistlen + 1\r
733     _timerlist[ _timerlistlen ] = listener\r
734     return true\r
735 end\r
736 \r
737 stats = function( )\r
738     return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen\r
739 end\r
740 \r
741 local dontstop = true; -- thinking about tomorrow, ...\r
742 \r
743 setquitting = function (quit)\r
744         dontstop = not quit;\r
745         return;\r
746 end\r
747 \r
748 loop = function( )    -- this is the main loop of the program\r
749     while dontstop do\r
750         local read, write, err = socket_select( _readlist, _sendlist, _selecttimeout )\r
751         for i, socket in ipairs( write ) do    -- send data waiting in writequeues\r
752             local handler = _socketlist[ socket ]\r
753             if handler then\r
754                 handler.sendbuffer( )\r
755             else\r
756                 closesocket( socket )\r
757                 out_put "server.lua: found no handler and closed socket (writelist)"    -- this should not happen\r
758             end\r
759         end\r
760         for i, socket in ipairs( read ) do    -- receive data\r
761             local handler = _socketlist[ socket ]\r
762             if handler then\r
763                 handler.readbuffer( )\r
764             else\r
765                 closesocket( socket )\r
766                 out_put "server.lua: found no handler and closed socket (readlist)"    -- this can happen\r
767             end\r
768         end\r
769         for handler, err in pairs( _closelist ) do\r
770             handler.disconnect( )( handler, err )\r
771             handler.close( true )    -- forced disconnect\r
772         end\r
773         clean( _closelist )\r
774         _currenttime = os_time( )\r
775         if os_difftime( _currenttime - _timer ) >= 1 then\r
776             for i = 1, _timerlistlen do\r
777                 _timerlist[ i ]( )    -- fire timers\r
778             end\r
779             _timer = _currenttime\r
780         end\r
781         socket_sleep( _sleeptime )    -- wait some time\r
782         --collectgarbage( )\r
783     end\r
784     return "quitting"\r
785 end\r
786 \r
787 --// EXPERIMENTAL //--\r
788 \r
789 local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, startssl )\r
790     local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, startssl )\r
791     _socketlist[ socket ] = handler\r
792     _sendlistlen = _sendlistlen + 1\r
793     _sendlist[ _sendlistlen ] = socket\r
794     _sendlist[ socket ] = _sendlistlen\r
795     return handler, socket\r
796 end\r
797 \r
798 local addclient = function( address, port, listeners, pattern, sslctx, startssl )\r
799     local client, err = luasocket.tcp( )\r
800     if err then\r
801         return nil, err\r
802     end\r
803     client:settimeout( 0 )\r
804     _, err = client:connect( address, port )\r
805     if err then    -- try again\r
806         local handler = wrapclient( client, address, port, listeners )\r
807     else\r
808         wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx, startssl )\r
809     end\r
810 end\r
811 \r
812 --// EXPERIMENTAL //--\r
813 \r
814 ----------------------------------// BEGIN //--\r
815 \r
816 use "setmetatable" ( _socketlist, { __mode = "k" } )\r
817 use "setmetatable" ( _readtimes, { __mode = "k" } )\r
818 use "setmetatable" ( _writetimes, { __mode = "k" } )\r
819 \r
820 _timer = os_time( )\r
821 _starttime = os_time( )\r
822 \r
823 addtimer( function( )\r
824         local difftime = os_difftime( _currenttime - _starttime )\r
825         if difftime > _checkinterval then\r
826             _starttime = _currenttime\r
827             for handler, timestamp in pairs( _writetimes ) do\r
828                 if os_difftime( _currenttime - timestamp ) > _sendtimeout then\r
829                     --_writetimes[ handler ] = nil\r
830                     handler.disconnect( )( handler, "send timeout" )\r
831                     handler.close( true )    -- forced disconnect\r
832                 end\r
833             end\r
834             for handler, timestamp in pairs( _readtimes ) do\r
835                 if os_difftime( _currenttime - timestamp ) > _readtimeout then\r
836                     --_readtimes[ handler ] = nil\r
837                     handler.disconnect( )( handler, "read timeout" )\r
838                     handler.close( )    -- forced disconnect?\r
839                 end\r
840             end\r
841         end\r
842     end\r
843 )\r
844 \r
845 ----------------------------------// PUBLIC INTERFACE //--\r
846 \r
847 return {\r
848 \r
849     addclient = addclient,\r
850     wrapclient = wrapclient,\r
851     \r
852     loop = loop,\r
853     stats = stats,\r
854     closeall = closeall,\r
855     addtimer = addtimer,\r
856     addserver = addserver,\r
857     getserver = getserver,\r
858     getsettings = getsettings,\r
859     setquitting = setquitting,\r
860     removeserver = removeserver,\r
861     changesettings = changesettings,\r
862 }\r