-- COPYING file in the source package for more information.
--
-module.host = "*" -- Global module
+module:set_global(); -- Global module
local hosts = _G.hosts;
-local lxp = require "lxp";
local new_xmpp_stream = require "util.xmppstream".new;
local httpserver = require "net.httpserver";
local sm = require "core.sessionmanager";
local stream_callbacks = {
stream_ns = xmlns_bosh, stream_tag = "body", default_ns = "jabber:client" };
-local BOSH_DEFAULT_HOLD = tonumber(module:get_option("bosh_default_hold")) or 1;
-local BOSH_DEFAULT_INACTIVITY = tonumber(module:get_option("bosh_max_inactivity")) or 60;
-local BOSH_DEFAULT_POLLING = tonumber(module:get_option("bosh_max_polling")) or 5;
-local BOSH_DEFAULT_REQUESTS = tonumber(module:get_option("bosh_max_requests")) or 2;
+local BOSH_DEFAULT_HOLD = module:get_option_number("bosh_default_hold", 1);
+local BOSH_DEFAULT_INACTIVITY = module:get_option_number("bosh_max_inactivity", 60);
+local BOSH_DEFAULT_POLLING = module:get_option_number("bosh_max_polling", 5);
+local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2);
local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure");
+local auto_cork = module:get_option_boolean("bosh_auto_cork", false);
local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" };
-local cross_domain = module:get_option("cross_domain_bosh");
+local cross_domain = module:get_option("cross_domain_bosh", false);
if cross_domain then
default_headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS";
default_headers["Access-Control-Allow-Headers"] = "Content-Type";
local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items;
local function get_ip_from_request(request)
- local ip = request.handler:ip();
+ local ip = request.conn:ip();
local forwarded_for = request.headers["x-forwarded-for"];
if forwarded_for then
forwarded_for = forwarded_for..", "..ip;
end
-- If this session now has no requests open, mark it as inactive
- if #requests == 0 and session.bosh_max_inactive and not inactive_sessions[session] then
- inactive_sessions[session] = os_time();
- (session.log or log)("debug", "BOSH session marked as inactive at %d", inactive_sessions[session]);
+ local max_inactive = session.bosh_max_inactive;
+ if max_inactive and #requests == 0 then
+ inactive_sessions[session] = os_time() + max_inactive;
+ (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive);
end
end
end
request.on_destroy = on_destroy_request;
local stream = new_xmpp_stream(request, stream_callbacks);
+
-- stream:feed() calls the stream_callbacks, so all stanzas in
-- the body are processed in this next line before it returns.
+ -- In particular, the streamopened() stream callback is where
+ -- much of the session logic happens, because it's where we first
+ -- get to see the 'sid' of this request.
stream:feed(body);
+ -- Stanzas (if any) in the request have now been processed, and
+ -- we take care of the high-level BOSH logic here, including
+ -- giving a response or putting the request "on hold".
local session = sessions[request.sid];
if session then
+ -- Session was marked as inactive, since we have
+ -- a request open now, unmark it
+ if inactive_sessions[session] and #session.requests > 0 then
+ inactive_sessions[session] = nil;
+ end
+
local r = session.requests;
log("debug", "Session %s has %d out of %d requests open", request.sid, #r, session.bosh_hold);
log("debug", "and there are %d things in the send_buffer", #session.send_buffer);
request.reply_before = os_time() + session.bosh_wait;
waiting_requests[request] = true;
end
- if inactive_sessions[session] then
- -- Session was marked as inactive, since we have
- -- a request open now, unmark it
- inactive_sessions[session] = nil;
- end
end
- return true; -- Inform httpserver we shall reply later
+ if session.bosh_terminate then
+ session.log("debug", "Closing session with %d requests open", #session.requests);
+ session:close();
+ return nil;
+ else
+ return true; -- Inform httpserver we shall reply later
+ end
end
end
(session.log or log)("info", "BOSH client disconnected");
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
- ["xmlns:streams"] = xmlns_streams });
+ ["xmlns:stream"] = xmlns_streams });
if reason then
local session_close_response = { headers = default_headers, body = tostring(close_reply) };
- --FIXME: Quite sure we shouldn't reply to all requests with the error
for _, held_request in ipairs(session.requests) do
held_request:send(session_close_response);
held_request:destroy();
end
sessions[session.sid] = nil;
+ inactive_sessions[session] = nil;
sm_destroy_session(session);
end
+-- Handle the <body> tag in the request payload.
function stream_callbacks.streamopened(request, attr)
- log("debug", "BOSH body open (sid: %s)", attr.sid);
- local sid = attr.sid
+ local sid = attr.sid;
+ log("debug", "BOSH body open (sid: %s)", sid or "<none>");
if not sid then
-- New session request
request.notopen = nil; -- Signals that we accept this opening tag
-- Unknown host
log("debug", "BOSH client tried to connect to unknown host: %s", tostring(attr.to));
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
- ["xmlns:streams"] = xmlns_streams, condition = "host-unknown" });
+ ["xmlns:stream"] = xmlns_streams, condition = "host-unknown" });
request:send(tostring(close_reply));
return;
end
end
--log("debug", "Sending BOSH data: %s", tostring(s));
local oldest_request = r[1];
- if oldest_request then
+ if oldest_request and (not(auto_cork) or waiting_requests[oldest_request]) then
log("debug", "We have an open request, so sending on that");
- response.body = t_concat{"<body xmlns='http://jabber.org/protocol/httpbind' sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>", tostring(s), "</body>" };
+ response.body = t_concat({
+ "<body xmlns='http://jabber.org/protocol/httpbind' ",
+ session.bosh_terminate and "type='terminate' " or "",
+ "sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>",
+ tostring(s),
+ "</body>"
+ });
oldest_request:send(response);
--log("debug", "Sent");
if oldest_request.stayopen then
t_insert(session.send_buffer, tostring(s));
log("debug", "There are now %d things in the send_buffer", #session.send_buffer);
end
+ return true;
end
-- Send creation response
end
if attr.type == "terminate" then
- -- Client wants to end this session
- session:close();
- request.notopen = nil;
- return;
+ -- Client wants to end this session, which we'll do
+ -- after processing any stanzas in this request
+ session.bosh_terminate = true;
end
-
+
+ request.notopen = nil; -- Signals that we accept this opening tag
+ t_insert(session.requests, request);
+ request.sid = sid;
+
if session.notopen then
local features = st.stanza("stream:features");
hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
session.send(features);
session.notopen = nil;
end
-
- request.notopen = nil; -- Signals that we accept this opening tag
- t_insert(session.requests, request);
- request.sid = sid;
end
function stream_callbacks.handlestanza(request, stanza)
now = now - 3;
local n_dead_sessions = 0;
- for session, inactive_since in pairs(inactive_sessions) do
- if session.bosh_max_inactive then
- if now - inactive_since > session.bosh_max_inactive then
- (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now);
- sessions[session.sid] = nil;
- inactive_sessions[session] = nil;
- n_dead_sessions = n_dead_sessions + 1;
- dead_sessions[n_dead_sessions] = session;
- end
- else
+ for session, close_after in pairs(inactive_sessions) do
+ if close_after < now then
+ (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now);
+ sessions[session.sid] = nil;
inactive_sessions[session] = nil;
+ n_dead_sessions = n_dead_sessions + 1;
+ dead_sessions[n_dead_sessions] = session;
end
end
local function setup()
- local ports = module:get_option("bosh_ports") or { 5280 };
+ local ports = module:get_option_array("bosh_ports") or { 5280 };
httpserver.new_from_config(ports, handle_request, { base = "http-bind" });
timer.add_task(1, on_timer);
end