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