X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=plugins%2Fmod_bosh.lua;h=d9c8defd90ead6d891c5948ede45d90e04ce98a0;hb=8551bf5a2ae3d1c8bc47f052f531902fe4f0d919;hp=13cea911f0681efaeb523a7a01afb0ac7ef988a5;hpb=7d24814e220b88429bbb63d446e7792829f24a13;p=prosody.git diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index 13cea911..d9c8defd 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -10,16 +10,18 @@ module:set_global(); -- Global module local hosts = _G.hosts; local new_xmpp_stream = require "util.xmppstream".new; -local httpserver = require "net.httpserver"; local sm = require "core.sessionmanager"; local sm_destroy_session = sm.destroy_session; local new_uuid = require "util.uuid".generate; local fire_event = prosody.events.fire_event; -local core_process_stanza = core_process_stanza; +local core_process_stanza = prosody.core_process_stanza; local st = require "util.stanza"; local logger = require "util.logger"; local log = logger.init("mod_bosh"); -local timer = require "util.timer"; +local initialize_filters = require "util.filters".initialize; +local math_min = math.min; +local xpcall, tostring, type = xpcall, tostring, type; +local traceback = debug.traceback; local xmlns_streams = "http://etherx.jabber.org/streams"; local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; @@ -32,9 +34,9 @@ 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 bosh_max_wait = module:get_option_number("bosh_max_wait", 120); 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" }; @@ -58,7 +60,7 @@ local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"}). local function get_ip_from_request(request) local ip = request.conn:ip(); - local forwarded_for = request.headers["x-forwarded-for"]; + local forwarded_for = request.headers.x_forwarded_for; if forwarded_for then forwarded_for = forwarded_for..", "..ip; for forwarded_ip in forwarded_for:gmatch("[^%s,]+") do @@ -73,8 +75,8 @@ end local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; local os_time = os.time; -local sessions = {}; -local inactive_sessions = {}; -- Sessions which have no open requests +-- All sessions, and sessions that have no requests open +local sessions, inactive_sessions = module:shared("sessions", "inactive_sessions"); -- Used to respond to idle sessions (those with waiting requests) local waiting_requests = {}; @@ -100,13 +102,6 @@ function on_destroy_request(request) end end -local function handle_GET(request) - return [[ -

It works! Now point your BOSH client to this URL to connect to Prosody.

-

For more information see Prosody: Setting up BOSH.

-]]; -end - function handle_OPTIONS(request) local headers = {}; for k,v in pairs(default_headers) do headers[k] = v; end @@ -181,7 +176,7 @@ function handle_POST(event) session:close(); return nil; else - return true; -- Inform httpserver we shall reply later + return true; -- Inform http server we shall reply later end end end @@ -252,69 +247,64 @@ function stream_callbacks.streamopened(context, attr) -- New session sid = new_uuid(); local session = { - type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid), host = attr.to, - bosh_version = attr.ver, bosh_wait = attr.wait, streamid = sid, + type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid)-1, host = attr.to, + bosh_version = attr.ver, bosh_wait = math_min(attr.wait, bosh_max_wait), streamid = sid, bosh_hold = BOSH_DEFAULT_HOLD, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY, requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream, - close = bosh_close_stream, dispatch_stanza = core_process_stanza, + close = bosh_close_stream, dispatch_stanza = core_process_stanza, notopen = true, log = logger.init("bosh"..sid), secure = consider_bosh_secure or request.secure, ip = get_ip_from_request(request); }; sessions[sid] = session; + local filter = initialize_filters(session); + session.log("debug", "BOSH session created for request from %s", session.ip); log("info", "New BOSH session, assigned it sid '%s'", sid); - local r, send_buffer = session.requests, session.send_buffer; + + -- Send creation response + local creating_session = true; + + local r = session.requests; function session.send(s) -- We need to ensure that outgoing stanzas have the jabber:client xmlns if s.attr and not s.attr.xmlns then s = st.clone(s); s.attr.xmlns = "jabber:client"; end + s = filter("stanzas/out", s); --log("debug", "Sending BOSH data: %s", tostring(s)); + t_insert(session.send_buffer, tostring(s)); + local oldest_request = r[1]; - if oldest_request and (not(auto_cork) or waiting_requests[oldest_request]) then + if oldest_request and not session.bosh_processing then log("debug", "We have an open request, so sending on that"); oldest_request.headers = default_headers; - oldest_request:send(t_concat({ - "", - tostring(s), - "" - })); - elseif s ~= "" then - log("debug", "Saved to send buffer because there are %d open requests", #r); - -- Hmm, no requests are open :( - t_insert(session.send_buffer, tostring(s)); - log("debug", "There are now %d things in the send_buffer", #session.send_buffer); + local body_attr = { xmlns = "http://jabber.org/protocol/httpbind", + ["xmlns:stream"] = "http://etherx.jabber.org/streams"; + type = session.bosh_terminate and "terminate" or nil; + sid = sid; + }; + if creating_session then + creating_session = nil; + body_attr.inactivity = tostring(BOSH_DEFAULT_INACTIVITY); + body_attr.polling = tostring(BOSH_DEFAULT_POLLING); + body_attr.requests = tostring(BOSH_DEFAULT_REQUESTS); + body_attr.wait = tostring(session.bosh_wait); + body_attr.hold = tostring(session.bosh_hold); + body_attr.authid = sid; + body_attr.secure = "true"; + body_attr.ver = '1.6'; + body_attr.from = session.host; + body_attr["xmlns:xmpp"] = "urn:xmpp:xbosh"; + body_attr["xmpp:version"] = "1.0"; + end + oldest_request:send(st.stanza("body", body_attr):top_tag()..t_concat(session.send_buffer)..""); + session.send_buffer = {}; end return true; end - - -- Send creation response - - local features = st.stanza("stream:features"); - hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); - fire_event("stream-features", session, features); - --xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh' - local body = st.stanza("body", { xmlns = xmlns_bosh, - wait = attr.wait, - inactivity = tostring(BOSH_DEFAULT_INACTIVITY), - polling = tostring(BOSH_DEFAULT_POLLING), - requests = tostring(BOSH_DEFAULT_REQUESTS), - hold = tostring(session.bosh_hold), - sid = sid, authid = sid, - ver = '1.6', from = session.host, - secure = 'true', ["xmpp:version"] = "1.0", - ["xmlns:xmpp"] = "urn:xmpp:xbosh", - ["xmlns:stream"] = "http://etherx.jabber.org/streams" - }):add_child(features); - response.headers = default_headers; - response:send(tostring(body)); - request.sid = sid; - return; end local session = sessions[sid]; @@ -334,7 +324,7 @@ function stream_callbacks.streamopened(context, attr) session.log("warn", "rid too large (means a request was lost). Last rid: %d New rid: %s", session.rid, attr.rid); elseif diff <= 0 then -- Repeated, ignore - session.log("debug", "rid repeated (on request %s), ignoring: %s (diff %d)", request.id, session.rid, diff); + session.log("debug", "rid repeated, ignoring: %s (diff %d)", session.rid, diff); context.notopen = nil; context.ignore = true; context.sid = sid; @@ -353,16 +343,18 @@ function stream_callbacks.streamopened(context, attr) context.notopen = nil; -- Signals that we accept this opening tag t_insert(session.requests, response); context.sid = sid; + session.bosh_processing = true; -- Used to suppress replies until processing of this request is done if session.notopen then local features = st.stanza("stream:features"); hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); fire_event("stream-features", session, features); - session.send(features); + session.send(tostring(features)); session.notopen = nil; end end +local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(tostring(err), 2)); end function stream_callbacks.handlestanza(context, stanza) if context.ignore then return; end log("debug", "BOSH stanza received: %s\n", stanza:top_tag()); @@ -371,7 +363,20 @@ function stream_callbacks.handlestanza(context, stanza) if stanza.attr.xmlns == xmlns_bosh then stanza.attr.xmlns = nil; end - core_process_stanza(session, stanza); + stanza = session.filter("stanzas/in", stanza); + if stanza then + return xpcall(function () return core_process_stanza(session, stanza) end, handleerr); + end + end +end + +function stream_callbacks.streamclosed(request) + local session = sessions[request.sid]; + if session then + session.bosh_processing = false; + if #session.send_buffer > 0 then + session.send(""); + end end end @@ -381,7 +386,7 @@ function stream_callbacks.error(context, error) local response = context.response; response.headers = default_headers; response.status_code = 400; - request:send(); + response:send(); return; end @@ -430,13 +435,27 @@ function on_timer() end module:add_timer(1, on_timer); + +local GET_response = { + headers = { + content_type = "text/html"; + }; + body = [[ +

It works! Now point your BOSH client to this URL to connect to Prosody.

+

For more information see Prosody: Setting up BOSH.

+ ]]; +}; + function module.add_host(module) module:depends("http"); module:provides("http", { default_path = "/http-bind"; route = { - ["GET /"] = handle_GET; + ["GET"] = GET_response; + ["GET /"] = GET_response; + ["OPTIONS"] = handle_OPTIONS; ["OPTIONS /"] = handle_OPTIONS; + ["POST"] = handle_POST; ["POST /"] = handle_POST; }; });