Mercurial > prosody-hg
comparison plugins/mod_bosh.lua @ 11200:bf8f2da84007
Merge 0.11->trunk
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Thu, 05 Nov 2020 22:31:25 +0100 |
| parents | cc6b1dab01a2 |
| children | 8eff5c744395 |
comparison
equal
deleted
inserted
replaced
| 11199:6c7c50a4de32 | 11200:bf8f2da84007 |
|---|---|
| 42 -- The maximum amount of time that the server will hold onto a request before replying | 42 -- The maximum amount of time that the server will hold onto a request before replying |
| 43 -- (the client can set this to a lower value when it connects, if it chooses) | 43 -- (the client can set this to a lower value when it connects, if it chooses) |
| 44 local bosh_max_wait = module:get_option_number("bosh_max_wait", 120); | 44 local bosh_max_wait = module:get_option_number("bosh_max_wait", 120); |
| 45 | 45 |
| 46 local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure"); | 46 local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure"); |
| 47 local cross_domain = module:get_option("cross_domain_bosh", false); | 47 local cross_domain = module:get_option("cross_domain_bosh"); |
| 48 | 48 |
| 49 if cross_domain == true then cross_domain = "*"; end | 49 if cross_domain ~= nil then |
| 50 if type(cross_domain) == "table" then cross_domain = table.concat(cross_domain, ", "); end | 50 module:log("info", "The 'cross_domain_bosh' option has been deprecated"); |
| 51 end | |
| 51 | 52 |
| 52 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; | 53 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; |
| 53 | 54 |
| 54 -- All sessions, and sessions that have no requests open | 55 -- All sessions, and sessions that have no requests open |
| 55 local sessions = module:shared("sessions"); | 56 local sessions = module:shared("sessions"); |
| 56 | 57 |
| 58 local measure_active = module:measure("active_sessions", "amount"); | |
| 59 local measure_inactive = module:measure("inactive_sessions", "amount"); | |
| 60 local report_bad_host = module:measure("bad_host", "rate"); | |
| 61 local report_bad_sid = module:measure("bad_sid", "rate"); | |
| 62 local report_new_sid = module:measure("new_sid", "rate"); | |
| 63 local report_timeout = module:measure("timeout", "rate"); | |
| 64 | |
| 65 module:hook("stats-update", function () | |
| 66 local active = 0; | |
| 67 local inactive = 0; | |
| 68 for _, session in pairs(sessions) do | |
| 69 if #session.requests > 0 then | |
| 70 active = active + 1; | |
| 71 else | |
| 72 inactive = inactive + 1; | |
| 73 end | |
| 74 end | |
| 75 measure_active(active); | |
| 76 measure_inactive(inactive); | |
| 77 end); | |
| 78 | |
| 57 -- Used to respond to idle sessions (those with waiting requests) | 79 -- Used to respond to idle sessions (those with waiting requests) |
| 58 function on_destroy_request(request) | 80 function on_destroy_request(request) |
| 59 log("debug", "Request destroyed: %s", tostring(request)); | 81 log("debug", "Request destroyed: %s", request); |
| 60 local session = sessions[request.context.sid]; | 82 local session = sessions[request.context.sid]; |
| 61 if session then | 83 if session then |
| 62 local requests = session.requests; | 84 local requests = session.requests; |
| 63 for i, r in ipairs(requests) do | 85 for i, r in ipairs(requests) do |
| 64 if r == request then | 86 if r == request then |
| 71 local max_inactive = session.bosh_max_inactive; | 93 local max_inactive = session.bosh_max_inactive; |
| 72 if max_inactive and #requests == 0 then | 94 if max_inactive and #requests == 0 then |
| 73 if session.inactive_timer then | 95 if session.inactive_timer then |
| 74 session.inactive_timer:stop(); | 96 session.inactive_timer:stop(); |
| 75 end | 97 end |
| 76 session.inactive_timer = module:add_timer(max_inactive, check_inactive, session, request.context, | 98 session.inactive_timer = module:add_timer(max_inactive, session_timeout, session, request.context, |
| 77 "BOSH client silent for over "..max_inactive.." seconds"); | 99 "BOSH client silent for over "..max_inactive.." seconds"); |
| 78 (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive); | 100 (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive); |
| 79 end | 101 end |
| 80 if session.bosh_wait_timer then | 102 if session.bosh_wait_timer then |
| 81 session.bosh_wait_timer:stop(); | 103 session.bosh_wait_timer:stop(); |
| 82 session.bosh_wait_timer = nil; | 104 session.bosh_wait_timer = nil; |
| 83 end | 105 end |
| 84 end | 106 end |
| 85 end | 107 end |
| 86 | 108 |
| 87 function check_inactive(now, session, context, reason) -- luacheck: ignore 212/now | 109 function session_timeout(now, session, context, reason) -- luacheck: ignore 212/now |
| 88 if not session.destroyed then | 110 if not session.destroyed then |
| 111 report_timeout(); | |
| 89 sessions[context.sid] = nil; | 112 sessions[context.sid] = nil; |
| 90 sm_destroy_session(session, reason); | 113 sm_destroy_session(session, reason); |
| 91 end | 114 end |
| 92 end | 115 end |
| 93 | 116 |
| 94 local function set_cross_domain_headers(response) | |
| 95 local headers = response.headers; | |
| 96 headers.access_control_allow_methods = "GET, POST, OPTIONS"; | |
| 97 headers.access_control_allow_headers = "Content-Type"; | |
| 98 headers.access_control_max_age = "7200"; | |
| 99 headers.access_control_allow_origin = cross_domain; | |
| 100 return response; | |
| 101 end | |
| 102 | |
| 103 function handle_OPTIONS(event) | |
| 104 if cross_domain and event.request.headers.origin then | |
| 105 set_cross_domain_headers(event.response); | |
| 106 end | |
| 107 return ""; | |
| 108 end | |
| 109 | |
| 110 function handle_POST(event) | 117 function handle_POST(event) |
| 111 log("debug", "Handling new request %s: %s\n----------", tostring(event.request), tostring(event.request.body)); | 118 log("debug", "Handling new request %s: %s\n----------", event.request, event.request.body); |
| 112 | 119 |
| 113 local request, response = event.request, event.response; | 120 local request, response = event.request, event.response; |
| 114 response.on_destroy = on_destroy_request; | 121 response.on_destroy = on_destroy_request; |
| 115 local body = request.body; | 122 local body = request.body; |
| 116 | 123 |
| 118 local stream = new_xmpp_stream(context, stream_callbacks); | 125 local stream = new_xmpp_stream(context, stream_callbacks); |
| 119 response.context = context; | 126 response.context = context; |
| 120 | 127 |
| 121 local headers = response.headers; | 128 local headers = response.headers; |
| 122 headers.content_type = "text/xml; charset=utf-8"; | 129 headers.content_type = "text/xml; charset=utf-8"; |
| 123 | |
| 124 if cross_domain and request.headers.origin then | |
| 125 set_cross_domain_headers(response); | |
| 126 end | |
| 127 | 130 |
| 128 -- stream:feed() calls the stream_callbacks, so all stanzas in | 131 -- stream:feed() calls the stream_callbacks, so all stanzas in |
| 129 -- the body are processed in this next line before it returns. | 132 -- the body are processed in this next line before it returns. |
| 130 -- In particular, the streamopened() stream callback is where | 133 -- In particular, the streamopened() stream callback is where |
| 131 -- much of the session logic happens, because it's where we first | 134 -- much of the session logic happens, because it's where we first |
| 203 -- A response has been sent already, or we're ignoring this request | 206 -- A response has been sent already, or we're ignoring this request |
| 204 -- (e.g. so a different instance of the module can handle it) | 207 -- (e.g. so a different instance of the module can handle it) |
| 205 return; | 208 return; |
| 206 end | 209 end |
| 207 module:log("warn", "Unable to associate request with a session (incomplete request?)"); | 210 module:log("warn", "Unable to associate request with a session (incomplete request?)"); |
| 211 report_bad_sid(); | |
| 208 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 212 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
| 209 ["xmlns:stream"] = xmlns_streams, condition = "item-not-found" }); | 213 ["xmlns:stream"] = xmlns_streams, condition = "item-not-found" }); |
| 210 return tostring(close_reply) .. "\n"; | 214 return tostring(close_reply) .. "\n"; |
| 211 end | 215 end |
| 212 | 216 |
| 218 | 222 |
| 219 local function bosh_reset_stream(session) session.notopen = true; end | 223 local function bosh_reset_stream(session) session.notopen = true; end |
| 220 | 224 |
| 221 local stream_xmlns_attr = { xmlns = "urn:ietf:params:xml:ns:xmpp-streams" }; | 225 local stream_xmlns_attr = { xmlns = "urn:ietf:params:xml:ns:xmpp-streams" }; |
| 222 local function bosh_close_stream(session, reason) | 226 local function bosh_close_stream(session, reason) |
| 223 (session.log or log)("info", "BOSH client disconnected: %s", tostring((reason and reason.condition or reason) or "session close")); | 227 (session.log or log)("info", "BOSH client disconnected: %s", (reason and reason.condition or reason) or "session close"); |
| 224 | 228 |
| 225 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 229 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
| 226 ["xmlns:stream"] = xmlns_streams }); | 230 ["xmlns:stream"] = xmlns_streams }); |
| 227 | 231 |
| 228 | 232 |
| 243 end | 247 end |
| 244 elseif reason.name then -- a stanza | 248 elseif reason.name then -- a stanza |
| 245 close_reply = reason; | 249 close_reply = reason; |
| 246 end | 250 end |
| 247 end | 251 end |
| 248 log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply)); | 252 log("info", "Disconnecting client, <stream:error> is: %s", close_reply); |
| 249 end | 253 end |
| 250 | 254 |
| 251 local response_body = tostring(close_reply); | 255 local response_body = tostring(close_reply); |
| 252 for _, held_request in ipairs(session.requests) do | 256 for _, held_request in ipairs(session.requests) do |
| 253 held_request:send(response_body); | 257 held_request:send(response_body); |
| 266 context.rid = rid; | 270 context.rid = rid; |
| 267 if not sid then | 271 if not sid then |
| 268 -- New session request | 272 -- New session request |
| 269 context.notopen = nil; -- Signals that we accept this opening tag | 273 context.notopen = nil; -- Signals that we accept this opening tag |
| 270 | 274 |
| 271 local to_host = nameprep(attr.to); | 275 if not attr.to then |
| 272 if not to_host then | 276 log("debug", "BOSH client tried to connect without specifying a host"); |
| 273 log("debug", "BOSH client tried to connect to invalid host: %s", tostring(attr.to)); | 277 report_bad_host(); |
| 274 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 278 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
| 275 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); | 279 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); |
| 276 response:send(tostring(close_reply)); | 280 response:send(tostring(close_reply)); |
| 277 return; | 281 return; |
| 278 end | 282 end |
| 279 | 283 |
| 280 if not prosody.hosts[to_host] then | 284 local to_host = nameprep(attr.to); |
| 281 log("debug", "BOSH client tried to connect to non-existant host: %s", attr.to); | 285 if not to_host then |
| 286 log("debug", "BOSH client tried to connect to invalid host: %s", attr.to); | |
| 287 report_bad_host(); | |
| 282 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 288 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
| 283 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); | 289 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); |
| 284 response:send(tostring(close_reply)); | 290 response:send(tostring(close_reply)); |
| 285 return; | 291 return; |
| 286 end | 292 end |
| 287 | 293 |
| 288 if prosody.hosts[to_host].type ~= "local" then | 294 if not prosody.hosts[to_host] then |
| 289 log("debug", "BOSH client tried to connect to %s host: %s", prosody.hosts[to_host].type, attr.to); | 295 log("debug", "BOSH client tried to connect to non-existant host: %s", attr.to); |
| 296 report_bad_host(); | |
| 290 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 297 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
| 291 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); | 298 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); |
| 292 response:send(tostring(close_reply)); | 299 response:send(tostring(close_reply)); |
| 293 return; | 300 return; |
| 294 end | 301 end |
| 295 | 302 |
| 303 if prosody.hosts[to_host].type ~= "local" then | |
| 304 log("debug", "BOSH client tried to connect to %s host: %s", prosody.hosts[to_host].type, attr.to); | |
| 305 report_bad_host(); | |
| 306 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | |
| 307 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); | |
| 308 response:send(tostring(close_reply)); | |
| 309 return; | |
| 310 end | |
| 311 | |
| 296 local wait = tonumber(attr.wait); | 312 local wait = tonumber(attr.wait); |
| 297 if not rid or (not attr.wait or not wait or wait < 0 or wait % 1 ~= 0) then | 313 if not rid or (not attr.wait or not wait or wait < 0 or wait % 1 ~= 0) then |
| 298 log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", tostring(attr.rid), tostring(attr.wait)); | 314 log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", attr.rid, attr.wait); |
| 299 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 315 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
| 300 ["xmlns:stream"] = xmlns_streams, condition = "bad-request" }); | 316 ["xmlns:stream"] = xmlns_streams, condition = "bad-request" }); |
| 301 response:send(tostring(close_reply)); | 317 response:send(tostring(close_reply)); |
| 302 return; | 318 return; |
| 303 end | 319 end |
| 324 | 340 |
| 325 local filter = initialize_filters(session); | 341 local filter = initialize_filters(session); |
| 326 | 342 |
| 327 session.log("debug", "BOSH session created for request from %s", session.ip); | 343 session.log("debug", "BOSH session created for request from %s", session.ip); |
| 328 log("info", "New BOSH session, assigned it sid '%s'", sid); | 344 log("info", "New BOSH session, assigned it sid '%s'", sid); |
| 345 report_new_sid(); | |
| 329 | 346 |
| 330 module:fire_event("bosh-session", { session = session, request = request }); | 347 module:fire_event("bosh-session", { session = session, request = request }); |
| 331 | 348 |
| 332 -- Send creation response | 349 -- Send creation response |
| 333 local creating_session = true; | 350 local creating_session = true; |
| 338 if s.attr and not s.attr.xmlns then | 355 if s.attr and not s.attr.xmlns then |
| 339 s = st.clone(s); | 356 s = st.clone(s); |
| 340 s.attr.xmlns = "jabber:client"; | 357 s.attr.xmlns = "jabber:client"; |
| 341 end | 358 end |
| 342 s = filter("stanzas/out", s); | 359 s = filter("stanzas/out", s); |
| 343 --log("debug", "Sending BOSH data: %s", tostring(s)); | 360 --log("debug", "Sending BOSH data: %s", s); |
| 344 if not s then return true end | 361 if not s then return true end |
| 345 t_insert(session.send_buffer, tostring(s)); | 362 t_insert(session.send_buffer, tostring(s)); |
| 346 | 363 |
| 347 local oldest_request = r[1]; | 364 local oldest_request = r[1]; |
| 348 if oldest_request and not session.bosh_processing then | 365 if oldest_request and not session.bosh_processing then |
| 378 | 395 |
| 379 local session = sessions[sid]; | 396 local session = sessions[sid]; |
| 380 if not session then | 397 if not session then |
| 381 -- Unknown sid | 398 -- Unknown sid |
| 382 log("info", "Client tried to use sid '%s' which we don't know about", sid); | 399 log("info", "Client tried to use sid '%s' which we don't know about", sid); |
| 400 report_bad_sid(); | |
| 383 response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" }))); | 401 response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" }))); |
| 384 context.notopen = nil; | 402 context.notopen = nil; |
| 385 return; | 403 return; |
| 386 end | 404 end |
| 387 | 405 |
| 440 session.send(features); | 458 session.send(features); |
| 441 session.notopen = nil; | 459 session.notopen = nil; |
| 442 end | 460 end |
| 443 end | 461 end |
| 444 | 462 |
| 445 local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(tostring(err), 2)); end | 463 local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(err, 2)); end |
| 446 | 464 |
| 447 function runner_callbacks:error(err) -- luacheck: ignore 212/self | 465 function runner_callbacks:error(err) -- luacheck: ignore 212/self |
| 448 return handleerr(err); | 466 return handleerr(err); |
| 449 end | 467 end |
| 450 | 468 |
| 510 else | 528 else |
| 511 session:close({ condition = "bad-format", text = "Error processing stream" }); | 529 session:close({ condition = "bad-format", text = "Error processing stream" }); |
| 512 end | 530 end |
| 513 end | 531 end |
| 514 | 532 |
| 533 local GET_response_body = [[<html><body> | |
| 534 <p>It works! Now point your BOSH client to this URL to connect to Prosody.</p> | |
| 535 <p>For more information see <a href="https://prosody.im/doc/setting_up_bosh">Prosody: Setting up BOSH</a>.</p> | |
| 536 </body></html>]]; | |
| 537 | |
| 515 local GET_response = { | 538 local GET_response = { |
| 516 headers = { | 539 headers = { |
| 517 content_type = "text/html"; | 540 content_type = "text/html"; |
| 518 }; | 541 }; |
| 519 body = [[<html><body> | 542 body = module:get_option_string("bosh_get_response_body", GET_response_body); |
| 520 <p>It works! Now point your BOSH client to this URL to connect to Prosody.</p> | |
| 521 <p>For more information see <a href="https://prosody.im/doc/setting_up_bosh">Prosody: Setting up BOSH</a>.</p> | |
| 522 </body></html>]]; | |
| 523 }; | 543 }; |
| 524 | 544 |
| 525 module:depends("http"); | 545 module:depends("http"); |
| 526 module:provides("http", { | 546 module:provides("http", { |
| 527 default_path = "/http-bind"; | 547 default_path = "/http-bind"; |
| 528 route = { | 548 route = { |
| 529 ["GET"] = GET_response; | 549 ["GET"] = GET_response; |
| 530 ["GET /"] = GET_response; | 550 ["GET /"] = GET_response; |
| 531 ["OPTIONS"] = handle_OPTIONS; | |
| 532 ["OPTIONS /"] = handle_OPTIONS; | |
| 533 ["POST"] = handle_POST; | 551 ["POST"] = handle_POST; |
| 534 ["POST /"] = handle_POST; | 552 ["POST /"] = handle_POST; |
| 535 }; | 553 }; |
| 536 }); | 554 }); |
