comparison plugins/mod_bosh.lua @ 4797:e239668aa6d2

Merge 0.9->trunk
author Matthew Wild <mwild1@gmail.com>
date Sun, 29 Apr 2012 02:10:55 +0100
parents c91bb217bf79
children 6d96e2e717c1
comparison
equal deleted inserted replaced
4796:04a34287dc12 4797:e239668aa6d2
4 -- 4 --
5 -- This project is MIT/X11 licensed. Please see the 5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information. 6 -- COPYING file in the source package for more information.
7 -- 7 --
8 8
9 module.host = "*" -- Global module 9 module:set_global(); -- Global module
10 10
11 local hosts = _G.hosts; 11 local hosts = _G.hosts;
12 local lxp = require "lxp";
13 local new_xmpp_stream = require "util.xmppstream".new; 12 local new_xmpp_stream = require "util.xmppstream".new;
14 local httpserver = require "net.httpserver";
15 local sm = require "core.sessionmanager"; 13 local sm = require "core.sessionmanager";
16 local sm_destroy_session = sm.destroy_session; 14 local sm_destroy_session = sm.destroy_session;
17 local new_uuid = require "util.uuid".generate; 15 local new_uuid = require "util.uuid".generate;
18 local fire_event = prosody.events.fire_event; 16 local fire_event = prosody.events.fire_event;
19 local core_process_stanza = core_process_stanza; 17 local core_process_stanza = core_process_stanza;
20 local st = require "util.stanza"; 18 local st = require "util.stanza";
21 local logger = require "util.logger"; 19 local logger = require "util.logger";
22 local log = logger.init("mod_bosh"); 20 local log = logger.init("mod_bosh");
23 local timer = require "util.timer";
24 21
25 local xmlns_streams = "http://etherx.jabber.org/streams"; 22 local xmlns_streams = "http://etherx.jabber.org/streams";
26 local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; 23 local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
27 local xmlns_bosh = "http://jabber.org/protocol/httpbind"; -- (hard-coded into a literal in session.send) 24 local xmlns_bosh = "http://jabber.org/protocol/httpbind"; -- (hard-coded into a literal in session.send)
28 25
33 local BOSH_DEFAULT_INACTIVITY = module:get_option_number("bosh_max_inactivity", 60); 30 local BOSH_DEFAULT_INACTIVITY = module:get_option_number("bosh_max_inactivity", 60);
34 local BOSH_DEFAULT_POLLING = module:get_option_number("bosh_max_polling", 5); 31 local BOSH_DEFAULT_POLLING = module:get_option_number("bosh_max_polling", 5);
35 local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2); 32 local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2);
36 33
37 local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure"); 34 local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure");
35 local auto_cork = module:get_option_boolean("bosh_auto_cork", false);
38 36
39 local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" }; 37 local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" };
40 38
41 local cross_domain = module:get_option("cross_domain_bosh", false); 39 local cross_domain = module:get_option("cross_domain_bosh", false);
42 if cross_domain then 40 if cross_domain then
55 end 53 end
56 54
57 local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items; 55 local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items;
58 56
59 local function get_ip_from_request(request) 57 local function get_ip_from_request(request)
60 local ip = request.handler:ip(); 58 local ip = request.conn:ip();
61 local forwarded_for = request.headers["x-forwarded-for"]; 59 local forwarded_for = request.headers["x-forwarded-for"];
62 if forwarded_for then 60 if forwarded_for then
63 forwarded_for = forwarded_for..", "..ip; 61 forwarded_for = forwarded_for..", "..ip;
64 for forwarded_ip in forwarded_for:gmatch("[^%s,]+") do 62 for forwarded_ip in forwarded_for:gmatch("[^%s,]+") do
65 if not trusted_proxies[forwarded_ip] then 63 if not trusted_proxies[forwarded_ip] then
77 local inactive_sessions = {}; -- Sessions which have no open requests 75 local inactive_sessions = {}; -- Sessions which have no open requests
78 76
79 -- Used to respond to idle sessions (those with waiting requests) 77 -- Used to respond to idle sessions (those with waiting requests)
80 local waiting_requests = {}; 78 local waiting_requests = {};
81 function on_destroy_request(request) 79 function on_destroy_request(request)
80 log("debug", "Request destroyed: %s", tostring(request));
82 waiting_requests[request] = nil; 81 waiting_requests[request] = nil;
83 local session = sessions[request.sid]; 82 local session = sessions[request.context.sid];
84 if session then 83 if session then
85 local requests = session.requests; 84 local requests = session.requests;
86 for i,r in ipairs(requests) do 85 for i, r in ipairs(requests) do
87 if r == request then 86 if r == request then
88 t_remove(requests, i); 87 t_remove(requests, i);
89 break; 88 break;
90 end 89 end
91 end 90 end
92 91
93 -- If this session now has no requests open, mark it as inactive 92 -- If this session now has no requests open, mark it as inactive
94 if #requests == 0 and session.bosh_max_inactive and not inactive_sessions[session] then 93 local max_inactive = session.bosh_max_inactive;
95 inactive_sessions[session] = os_time(); 94 if max_inactive and #requests == 0 then
96 (session.log or log)("debug", "BOSH session marked as inactive at %d", inactive_sessions[session]); 95 inactive_sessions[session] = os_time() + max_inactive;
97 end 96 (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive);
98 end 97 end
99 end 98 end
100 99 end
101 function handle_request(method, body, request) 100
102 if (not body) or request.method ~= "POST" then 101 local function handle_GET(request)
103 if request.method == "OPTIONS" then 102 return [[<html><body>
104 local headers = {}; 103 <p>It works! Now point your BOSH client to this URL to connect to Prosody.</p>
105 for k,v in pairs(default_headers) do headers[k] = v; end 104 <p>For more information see <a href="http://prosody.im/doc/setting_up_bosh">Prosody: Setting up BOSH</a>.</p>
106 headers["Content-Type"] = nil; 105 </body></html>]];
107 return { headers = headers, body = "" }; 106 end
108 else 107
109 return "<html><body>You really don't look like a BOSH client to me... what do you want?</body></html>"; 108 function handle_OPTIONS(request)
110 end 109 local headers = {};
111 end 110 for k,v in pairs(default_headers) do headers[k] = v; end
112 if not method then 111 headers["Content-Type"] = nil;
113 log("debug", "Request %s suffered error %s", tostring(request.id), body); 112 return { headers = headers, body = "" };
114 return; 113 end
115 end 114
116 --log("debug", "Handling new request %s: %s\n----------", request.id, tostring(body)); 115 function handle_POST(event)
117 request.notopen = true; 116 log("debug", "Handling new request %s: %s\n----------", tostring(event.request), tostring(event.request.body));
118 request.log = log; 117
119 request.on_destroy = on_destroy_request; 118 local request, response = event.request, event.response;
120 119 response.on_destroy = on_destroy_request;
121 local stream = new_xmpp_stream(request, stream_callbacks); 120 local body = request.body;
121
122 local context = { request = request, response = response, notopen = true };
123 local stream = new_xmpp_stream(context, stream_callbacks);
124 response.context = context;
125
122 -- stream:feed() calls the stream_callbacks, so all stanzas in 126 -- stream:feed() calls the stream_callbacks, so all stanzas in
123 -- the body are processed in this next line before it returns. 127 -- the body are processed in this next line before it returns.
124 local ok, err = stream:feed(body); 128 -- In particular, the streamopened() stream callback is where
125 if not ok then 129 -- much of the session logic happens, because it's where we first
126 log("error", "Failed to parse BOSH payload: %s", err); 130 -- get to see the 'sid' of this request.
127 end 131 stream:feed(body);
128 132
129 local session = sessions[request.sid]; 133 -- Stanzas (if any) in the request have now been processed, and
134 -- we take care of the high-level BOSH logic here, including
135 -- giving a response or putting the request "on hold".
136 local session = sessions[context.sid];
130 if session then 137 if session then
131 -- Session was marked as inactive, since we have 138 -- Session was marked as inactive, since we have
132 -- a request open now, unmark it 139 -- a request open now, unmark it
133 if inactive_sessions[session] and #session.requests > 0 then 140 if inactive_sessions[session] and #session.requests > 0 then
134 inactive_sessions[session] = nil; 141 inactive_sessions[session] = nil;
135 end 142 end
136 143
137 local r = session.requests; 144 local r = session.requests;
138 log("debug", "Session %s has %d out of %d requests open", request.sid, #r, session.bosh_hold); 145 log("debug", "Session %s has %d out of %d requests open", context.sid, #r, session.bosh_hold);
139 log("debug", "and there are %d things in the send_buffer", #session.send_buffer); 146 log("debug", "and there are %d things in the send_buffer:", #session.send_buffer);
147 for i, thing in ipairs(session.send_buffer) do
148 log("debug", " %s", tostring(thing));
149 end
140 if #r > session.bosh_hold then 150 if #r > session.bosh_hold then
141 -- We are holding too many requests, send what's in the buffer, 151 -- We are holding too many requests, send what's in the buffer,
142 log("debug", "We are holding too many requests, so..."); 152 log("debug", "We are holding too many requests, so...");
143 if #session.send_buffer > 0 then 153 if #session.send_buffer > 0 then
144 log("debug", "...sending what is in the buffer") 154 log("debug", "...sending what is in the buffer")
154 local resp = t_concat(session.send_buffer); 164 local resp = t_concat(session.send_buffer);
155 session.send_buffer = {}; 165 session.send_buffer = {};
156 session.send(resp); 166 session.send(resp);
157 end 167 end
158 168
159 if not request.destroyed then 169 if not response.finished then
160 -- We're keeping this request open, to respond later 170 -- We're keeping this request open, to respond later
161 log("debug", "Have nothing to say, so leaving request unanswered for now"); 171 log("debug", "Have nothing to say, so leaving request unanswered for now");
162 if session.bosh_wait then 172 if session.bosh_wait then
163 request.reply_before = os_time() + session.bosh_wait; 173 waiting_requests[response] = os_time() + session.bosh_wait;
164 waiting_requests[request] = true;
165 end 174 end
166 end 175 end
167 176
168 if session.bosh_terminate then 177 if session.bosh_terminate then
169 session.log("debug", "Closing session with %d requests open", #session.requests); 178 session.log("debug", "Closing session with %d requests open", #session.requests);
170 session:close(); 179 session:close();
171 return nil; 180 return nil;
172 else 181 else
173 return true; -- Inform httpserver we shall reply later 182 return true; -- Inform http server we shall reply later
174 end 183 end
175 end 184 end
176 end 185 end
177 186
178 187
207 end 216 end
208 end 217 end
209 log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply)); 218 log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply));
210 end 219 end
211 220
212 local session_close_response = { headers = default_headers, body = tostring(close_reply) }; 221 local response_body = tostring(close_reply);
213
214 for _, held_request in ipairs(session.requests) do 222 for _, held_request in ipairs(session.requests) do
215 held_request:send(session_close_response); 223 held_request.headers = default_headers;
216 held_request:destroy(); 224 held_request:send(response_body);
217 end 225 end
218 sessions[session.sid] = nil; 226 sessions[session.sid] = nil;
227 inactive_sessions[session] = nil;
219 sm_destroy_session(session); 228 sm_destroy_session(session);
220 end 229 end
221 230
222 function stream_callbacks.streamopened(request, attr) 231 -- Handle the <body> tag in the request payload.
232 function stream_callbacks.streamopened(context, attr)
233 local request, response = context.request, context.response;
223 local sid = attr.sid; 234 local sid = attr.sid;
224 log("debug", "BOSH body open (sid: %s)", sid or "<none>"); 235 log("debug", "BOSH body open (sid: %s)", sid or "<none>");
225 if not sid then 236 if not sid then
226 -- New session request 237 -- New session request
227 request.notopen = nil; -- Signals that we accept this opening tag 238 context.notopen = nil; -- Signals that we accept this opening tag
228 239
229 -- TODO: Sanity checks here (rid, to, known host, etc.) 240 -- TODO: Sanity checks here (rid, to, known host, etc.)
230 if not hosts[attr.to] then 241 if not hosts[attr.to] then
231 -- Unknown host 242 -- Unknown host
232 log("debug", "BOSH client tried to connect to unknown host: %s", tostring(attr.to)); 243 log("debug", "BOSH client tried to connect to unknown host: %s", tostring(attr.to));
233 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", 244 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
234 ["xmlns:stream"] = xmlns_streams, condition = "host-unknown" }); 245 ["xmlns:stream"] = xmlns_streams, condition = "host-unknown" });
235 request:send(tostring(close_reply)); 246 response:send(tostring(close_reply));
236 return; 247 return;
237 end 248 end
238 249
239 -- New session 250 -- New session
240 sid = new_uuid(); 251 sid = new_uuid();
249 }; 260 };
250 sessions[sid] = session; 261 sessions[sid] = session;
251 262
252 session.log("debug", "BOSH session created for request from %s", session.ip); 263 session.log("debug", "BOSH session created for request from %s", session.ip);
253 log("info", "New BOSH session, assigned it sid '%s'", sid); 264 log("info", "New BOSH session, assigned it sid '%s'", sid);
254 local r, send_buffer = session.requests, session.send_buffer; 265 local r = session.requests;
255 local response = { headers = default_headers }
256 function session.send(s) 266 function session.send(s)
257 -- We need to ensure that outgoing stanzas have the jabber:client xmlns 267 -- We need to ensure that outgoing stanzas have the jabber:client xmlns
258 if s.attr and not s.attr.xmlns then 268 if s.attr and not s.attr.xmlns then
259 s = st.clone(s); 269 s = st.clone(s);
260 s.attr.xmlns = "jabber:client"; 270 s.attr.xmlns = "jabber:client";
261 end 271 end
262 --log("debug", "Sending BOSH data: %s", tostring(s)); 272 --log("debug", "Sending BOSH data: %s", tostring(s));
263 local oldest_request = r[1]; 273 local oldest_request = r[1];
264 if oldest_request then 274 if oldest_request and (not(auto_cork) or waiting_requests[oldest_request]) then
265 log("debug", "We have an open request, so sending on that"); 275 log("debug", "We have an open request, so sending on that");
266 response.body = t_concat({ 276 oldest_request.headers = default_headers;
277 oldest_request:send(t_concat({
267 "<body xmlns='http://jabber.org/protocol/httpbind' ", 278 "<body xmlns='http://jabber.org/protocol/httpbind' ",
268 session.bosh_terminate and "type='terminate' " or "", 279 session.bosh_terminate and "type='terminate' " or "",
269 "sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>", 280 "sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>",
270 tostring(s), 281 tostring(s),
271 "</body>" 282 "</body>"
272 }); 283 }));
273 oldest_request:send(response);
274 --log("debug", "Sent");
275 if oldest_request.stayopen then
276 if #r>1 then
277 -- Move front request to back
278 t_insert(r, oldest_request);
279 t_remove(r, 1);
280 end
281 else
282 log("debug", "Destroying the request now...");
283 oldest_request:destroy();
284 end
285 elseif s ~= "" then 284 elseif s ~= "" then
286 log("debug", "Saved to send buffer because there are %d open requests", #r); 285 log("debug", "Saved to send buffer because there are %d open requests", #r);
287 -- Hmm, no requests are open :( 286 -- Hmm, no requests are open :(
288 t_insert(session.send_buffer, tostring(s)); 287 t_insert(session.send_buffer, tostring(s));
289 log("debug", "There are now %d things in the send_buffer", #session.send_buffer); 288 log("debug", "There are now %d things in the send_buffer", #session.send_buffer);
295 294
296 local features = st.stanza("stream:features"); 295 local features = st.stanza("stream:features");
297 hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); 296 hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
298 fire_event("stream-features", session, features); 297 fire_event("stream-features", session, features);
299 --xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh' 298 --xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh'
300 local response = st.stanza("body", { xmlns = xmlns_bosh, 299 local body = st.stanza("body", { xmlns = xmlns_bosh,
301 wait = attr.wait, 300 wait = attr.wait,
302 inactivity = tostring(BOSH_DEFAULT_INACTIVITY), 301 inactivity = tostring(BOSH_DEFAULT_INACTIVITY),
303 polling = tostring(BOSH_DEFAULT_POLLING), 302 polling = tostring(BOSH_DEFAULT_POLLING),
304 requests = tostring(BOSH_DEFAULT_REQUESTS), 303 requests = tostring(BOSH_DEFAULT_REQUESTS),
305 hold = tostring(session.bosh_hold), 304 hold = tostring(session.bosh_hold),
307 ver = '1.6', from = session.host, 306 ver = '1.6', from = session.host,
308 secure = 'true', ["xmpp:version"] = "1.0", 307 secure = 'true', ["xmpp:version"] = "1.0",
309 ["xmlns:xmpp"] = "urn:xmpp:xbosh", 308 ["xmlns:xmpp"] = "urn:xmpp:xbosh",
310 ["xmlns:stream"] = "http://etherx.jabber.org/streams" 309 ["xmlns:stream"] = "http://etherx.jabber.org/streams"
311 }):add_child(features); 310 }):add_child(features);
312 request:send{ headers = default_headers, body = tostring(response) }; 311 response.headers = default_headers;
312 response:send(tostring(body));
313 313
314 request.sid = sid; 314 request.sid = sid;
315 return; 315 return;
316 end 316 end
317 317
318 local session = sessions[sid]; 318 local session = sessions[sid];
319 if not session then 319 if not session then
320 -- Unknown sid 320 -- Unknown sid
321 log("info", "Client tried to use sid '%s' which we don't know about", sid); 321 log("info", "Client tried to use sid '%s' which we don't know about", sid);
322 request:send{ headers = default_headers, body = tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" })) }; 322 response.headers = default_headers;
323 request.notopen = nil; 323 response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" })));
324 context.notopen = nil;
324 return; 325 return;
325 end 326 end
326 327
327 if session.rid then 328 if session.rid then
328 local rid = tonumber(attr.rid); 329 local rid = tonumber(attr.rid);
330 if diff > 1 then 331 if diff > 1 then
331 session.log("warn", "rid too large (means a request was lost). Last rid: %d New rid: %s", session.rid, attr.rid); 332 session.log("warn", "rid too large (means a request was lost). Last rid: %d New rid: %s", session.rid, attr.rid);
332 elseif diff <= 0 then 333 elseif diff <= 0 then
333 -- Repeated, ignore 334 -- Repeated, ignore
334 session.log("debug", "rid repeated (on request %s), ignoring: %s (diff %d)", request.id, session.rid, diff); 335 session.log("debug", "rid repeated (on request %s), ignoring: %s (diff %d)", request.id, session.rid, diff);
335 request.notopen = nil; 336 context.notopen = nil;
336 request.ignore = true; 337 context.ignore = true;
337 request.sid = sid; 338 context.sid = sid;
338 t_insert(session.requests, request); 339 t_insert(session.requests, response);
339 return; 340 return;
340 end 341 end
341 session.rid = rid; 342 session.rid = rid;
342 end 343 end
343 344
345 if attr.type == "terminate" then
346 -- Client wants to end this session, which we'll do
347 -- after processing any stanzas in this request
348 session.bosh_terminate = true;
349 end
350
351 context.notopen = nil; -- Signals that we accept this opening tag
352 t_insert(session.requests, response);
353 context.sid = sid;
354
344 if session.notopen then 355 if session.notopen then
345 local features = st.stanza("stream:features"); 356 local features = st.stanza("stream:features");
346 hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); 357 hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
347 fire_event("stream-features", session, features); 358 fire_event("stream-features", session, features);
348 session.send(features); 359 session.send(features);
349 session.notopen = nil; 360 session.notopen = nil;
350 end 361 end
351 362 end
352 if attr.type == "terminate" then 363
353 -- Client wants to end this session, which we'll do 364 function stream_callbacks.handlestanza(context, stanza)
354 -- after processing any stanzas in this request 365 if context.ignore then return; end
355 session.bosh_terminate = true;
356 end
357
358 request.notopen = nil; -- Signals that we accept this opening tag
359 t_insert(session.requests, request);
360 request.sid = sid;
361 end
362
363 function stream_callbacks.handlestanza(request, stanza)
364 if request.ignore then return; end
365 log("debug", "BOSH stanza received: %s\n", stanza:top_tag()); 366 log("debug", "BOSH stanza received: %s\n", stanza:top_tag());
366 local session = sessions[request.sid]; 367 local session = sessions[context.sid];
367 if session then 368 if session then
368 if stanza.attr.xmlns == xmlns_bosh then 369 if stanza.attr.xmlns == xmlns_bosh then
369 stanza.attr.xmlns = nil; 370 stanza.attr.xmlns = nil;
370 end 371 end
371 core_process_stanza(session, stanza); 372 core_process_stanza(session, stanza);
372 end 373 end
373 end 374 end
374 375
375 function stream_callbacks.error(request, error) 376 function stream_callbacks.error(context, error)
376 log("debug", "Error parsing BOSH request payload; %s", error); 377 log("debug", "Error parsing BOSH request payload; %s", error);
377 if not request.sid then 378 if not context.sid then
378 request:send({ headers = default_headers, status = "400 Bad Request" }); 379 local response = context.response;
380 response.headers = default_headers;
381 response.status_code = 400;
382 response:send();
379 return; 383 return;
380 end 384 end
381 385
382 local session = sessions[request.sid]; 386 local session = sessions[context.sid];
383 if error == "stream-error" then -- Remote stream error, we close normally 387 if error == "stream-error" then -- Remote stream error, we close normally
384 session:close(); 388 session:close();
385 else 389 else
386 session:close({ condition = "bad-format", text = "Error processing stream" }); 390 session:close({ condition = "bad-format", text = "Error processing stream" });
387 end 391 end
390 local dead_sessions = {}; 394 local dead_sessions = {};
391 function on_timer() 395 function on_timer()
392 -- log("debug", "Checking for requests soon to timeout..."); 396 -- log("debug", "Checking for requests soon to timeout...");
393 -- Identify requests timing out within the next few seconds 397 -- Identify requests timing out within the next few seconds
394 local now = os_time() + 3; 398 local now = os_time() + 3;
395 for request in pairs(waiting_requests) do 399 for request, reply_before in pairs(waiting_requests) do
396 if request.reply_before <= now then 400 if reply_before <= now then
397 log("debug", "%s was soon to timeout, sending empty response", request.id); 401 log("debug", "%s was soon to timeout (at %d, now %d), sending empty response", tostring(request), reply_before, now);
398 -- Send empty response to let the 402 -- Send empty response to let the
399 -- client know we're still here 403 -- client know we're still here
400 if request.conn then 404 if request.conn then
401 sessions[request.sid].send(""); 405 sessions[request.context.sid].send("");
402 end 406 end
403 end 407 end
404 end 408 end
405 409
406 now = now - 3; 410 now = now - 3;
407 local n_dead_sessions = 0; 411 local n_dead_sessions = 0;
408 for session, inactive_since in pairs(inactive_sessions) do 412 for session, close_after in pairs(inactive_sessions) do
409 if session.bosh_max_inactive then 413 if close_after < now then
410 if now - inactive_since > session.bosh_max_inactive then 414 (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now);
411 (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now); 415 sessions[session.sid] = nil;
412 sessions[session.sid] = nil;
413 inactive_sessions[session] = nil;
414 n_dead_sessions = n_dead_sessions + 1;
415 dead_sessions[n_dead_sessions] = session;
416 end
417 else
418 inactive_sessions[session] = nil; 416 inactive_sessions[session] = nil;
417 n_dead_sessions = n_dead_sessions + 1;
418 dead_sessions[n_dead_sessions] = session;
419 end 419 end
420 end 420 end
421 421
422 for i=1,n_dead_sessions do 422 for i=1,n_dead_sessions do
423 local session = dead_sessions[i]; 423 local session = dead_sessions[i];
424 dead_sessions[i] = nil; 424 dead_sessions[i] = nil;
425 sm_destroy_session(session, "BOSH client silent for over "..session.bosh_max_inactive.." seconds"); 425 sm_destroy_session(session, "BOSH client silent for over "..session.bosh_max_inactive.." seconds");
426 end 426 end
427 return 1; 427 return 1;
428 end 428 end
429 429 module:add_timer(1, on_timer);
430 430
431 local function setup() 431 function module.add_host(module)
432 local ports = module:get_option_array("bosh_ports") or { 5280 }; 432 module:depends("http");
433 httpserver.new_from_config(ports, handle_request, { base = "http-bind" }); 433 module:provides("http", {
434 timer.add_task(1, on_timer); 434 default_path = "/http-bind";
435 end 435 route = {
436 if prosody.start_time then -- already started 436 ["GET"] = handle_GET;
437 setup(); 437 ["GET /"] = handle_GET;
438 else 438 ["OPTIONS"] = handle_OPTIONS;
439 prosody.events.add_handler("server-started", setup); 439 ["OPTIONS /"] = handle_OPTIONS;
440 end 440 ["POST"] = handle_POST;
441 ["POST /"] = handle_POST;
442 };
443 });
444 end