Mercurial > prosody-hg
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 |
