Mercurial > prosody-hg
comparison plugins/mod_bosh.lua @ 5720:449399a7e136
Merge
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Sat, 29 Jun 2013 14:45:47 +0100 |
| parents | ebdbf4cf0b2f |
| children | 8de1f9290588 |
comparison
equal
deleted
inserted
replaced
| 5719:84025249fc04 | 5720:449399a7e136 |
|---|---|
| 1 -- Prosody IM | 1 -- Prosody IM |
| 2 -- Copyright (C) 2008-2010 Matthew Wild | 2 -- Copyright (C) 2008-2010 Matthew Wild |
| 3 -- Copyright (C) 2008-2010 Waqas Hussain | 3 -- Copyright (C) 2008-2010 Waqas Hussain |
| 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:set_global(); -- Global module | 9 module:set_global(); -- Global module |
| 33 local BOSH_DEFAULT_POLLING = module:get_option_number("bosh_max_polling", 5); | 33 local BOSH_DEFAULT_POLLING = module:get_option_number("bosh_max_polling", 5); |
| 34 local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2); | 34 local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2); |
| 35 local bosh_max_wait = module:get_option_number("bosh_max_wait", 120); | 35 local bosh_max_wait = module:get_option_number("bosh_max_wait", 120); |
| 36 | 36 |
| 37 local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure"); | 37 local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure"); |
| 38 | |
| 39 local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" }; | |
| 40 | |
| 41 local cross_domain = module:get_option("cross_domain_bosh", false); | 38 local cross_domain = module:get_option("cross_domain_bosh", false); |
| 42 if cross_domain then | 39 |
| 43 default_headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"; | 40 if cross_domain == true then cross_domain = "*"; end |
| 44 default_headers["Access-Control-Allow-Headers"] = "Content-Type"; | 41 if type(cross_domain) == "table" then cross_domain = table.concat(cross_domain, ", "); end |
| 45 default_headers["Access-Control-Max-Age"] = "7200"; | |
| 46 | |
| 47 if cross_domain == true then | |
| 48 default_headers["Access-Control-Allow-Origin"] = "*"; | |
| 49 elseif type(cross_domain) == "table" then | |
| 50 cross_domain = table.concat(cross_domain, ", "); | |
| 51 end | |
| 52 if type(cross_domain) == "string" then | |
| 53 default_headers["Access-Control-Allow-Origin"] = cross_domain; | |
| 54 end | |
| 55 end | |
| 56 | 42 |
| 57 local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items; | 43 local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items; |
| 58 | 44 |
| 59 local function get_ip_from_request(request) | 45 local function get_ip_from_request(request) |
| 60 local ip = request.conn:ip(); | 46 local ip = request.conn:ip(); |
| 75 | 61 |
| 76 -- All sessions, and sessions that have no requests open | 62 -- All sessions, and sessions that have no requests open |
| 77 local sessions, inactive_sessions = module:shared("sessions", "inactive_sessions"); | 63 local sessions, inactive_sessions = module:shared("sessions", "inactive_sessions"); |
| 78 | 64 |
| 79 -- Used to respond to idle sessions (those with waiting requests) | 65 -- Used to respond to idle sessions (those with waiting requests) |
| 80 local waiting_requests = {}; | 66 local waiting_requests = module:shared("waiting_requests"); |
| 81 function on_destroy_request(request) | 67 function on_destroy_request(request) |
| 82 log("debug", "Request destroyed: %s", tostring(request)); | 68 log("debug", "Request destroyed: %s", tostring(request)); |
| 83 waiting_requests[request] = nil; | 69 waiting_requests[request] = nil; |
| 84 local session = sessions[request.context.sid]; | 70 local session = sessions[request.context.sid]; |
| 85 if session then | 71 if session then |
| 98 (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive); | 84 (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive); |
| 99 end | 85 end |
| 100 end | 86 end |
| 101 end | 87 end |
| 102 | 88 |
| 103 function handle_OPTIONS(request) | 89 local function set_cross_domain_headers(response) |
| 104 local headers = {}; | 90 local headers = response.headers; |
| 105 for k,v in pairs(default_headers) do headers[k] = v; end | 91 headers.access_control_allow_methods = "GET, POST, OPTIONS"; |
| 106 headers["Content-Type"] = nil; | 92 headers.access_control_allow_headers = "Content-Type"; |
| 107 return { headers = headers, body = "" }; | 93 headers.access_control_max_age = "7200"; |
| 94 headers.access_control_allow_origin = cross_domain; | |
| 95 return response; | |
| 96 end | |
| 97 | |
| 98 function handle_OPTIONS(event) | |
| 99 if cross_domain and event.request.headers.origin then | |
| 100 set_cross_domain_headers(event.response); | |
| 101 end | |
| 102 return ""; | |
| 108 end | 103 end |
| 109 | 104 |
| 110 function handle_POST(event) | 105 function handle_POST(event) |
| 111 log("debug", "Handling new request %s: %s\n----------", tostring(event.request), tostring(event.request.body)); | 106 log("debug", "Handling new request %s: %s\n----------", tostring(event.request), tostring(event.request.body)); |
| 112 | 107 |
| 115 local body = request.body; | 110 local body = request.body; |
| 116 | 111 |
| 117 local context = { request = request, response = response, notopen = true }; | 112 local context = { request = request, response = response, notopen = true }; |
| 118 local stream = new_xmpp_stream(context, stream_callbacks); | 113 local stream = new_xmpp_stream(context, stream_callbacks); |
| 119 response.context = context; | 114 response.context = context; |
| 115 | |
| 116 local headers = response.headers; | |
| 117 headers.content_type = "text/xml; charset=utf-8"; | |
| 118 | |
| 119 if cross_domain and event.request.headers.origin then | |
| 120 set_cross_domain_headers(response); | |
| 121 end | |
| 120 | 122 |
| 121 -- stream:feed() calls the stream_callbacks, so all stanzas in | 123 -- stream:feed() calls the stream_callbacks, so all stanzas in |
| 122 -- the body are processed in this next line before it returns. | 124 -- the body are processed in this next line before it returns. |
| 123 -- In particular, the streamopened() stream callback is where | 125 -- In particular, the streamopened() stream callback is where |
| 124 -- much of the session logic happens, because it's where we first | 126 -- much of the session logic happens, because it's where we first |
| 125 -- get to see the 'sid' of this request. | 127 -- get to see the 'sid' of this request. |
| 126 stream:feed(body); | 128 if not stream:feed(body) then |
| 129 module:log("warn", "Error parsing BOSH payload") | |
| 130 return 400; | |
| 131 end | |
| 127 | 132 |
| 128 -- Stanzas (if any) in the request have now been processed, and | 133 -- Stanzas (if any) in the request have now been processed, and |
| 129 -- we take care of the high-level BOSH logic here, including | 134 -- we take care of the high-level BOSH logic here, including |
| 130 -- giving a response or putting the request "on hold". | 135 -- giving a response or putting the request "on hold". |
| 131 local session = sessions[context.sid]; | 136 local session = sessions[context.sid]; |
| 137 end | 142 end |
| 138 | 143 |
| 139 local r = session.requests; | 144 local r = session.requests; |
| 140 log("debug", "Session %s has %d out of %d requests open", context.sid, #r, session.bosh_hold); | 145 log("debug", "Session %s has %d out of %d requests open", context.sid, #r, session.bosh_hold); |
| 141 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); |
| 142 for i, thing in ipairs(session.send_buffer) do | |
| 143 log("debug", " %s", tostring(thing)); | |
| 144 end | |
| 145 if #r > session.bosh_hold then | 147 if #r > session.bosh_hold then |
| 146 -- We are holding too many requests, send what's in the buffer, | 148 -- We are holding too many requests, send what's in the buffer, |
| 147 log("debug", "We are holding too many requests, so..."); | 149 log("debug", "We are holding too many requests, so..."); |
| 148 if #session.send_buffer > 0 then | 150 if #session.send_buffer > 0 then |
| 149 log("debug", "...sending what is in the buffer") | 151 log("debug", "...sending what is in the buffer") |
| 175 return nil; | 177 return nil; |
| 176 else | 178 else |
| 177 return true; -- Inform http server we shall reply later | 179 return true; -- Inform http server we shall reply later |
| 178 end | 180 end |
| 179 end | 181 end |
| 182 module:log("warn", "Unable to associate request with a session (incomplete request?)"); | |
| 183 return 400; | |
| 180 end | 184 end |
| 181 | 185 |
| 182 | 186 |
| 183 local function bosh_reset_stream(session) session.notopen = true; end | 187 local function bosh_reset_stream(session) session.notopen = true; end |
| 184 | 188 |
| 213 log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply)); | 217 log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply)); |
| 214 end | 218 end |
| 215 | 219 |
| 216 local response_body = tostring(close_reply); | 220 local response_body = tostring(close_reply); |
| 217 for _, held_request in ipairs(session.requests) do | 221 for _, held_request in ipairs(session.requests) do |
| 218 held_request.headers = default_headers; | |
| 219 held_request:send(response_body); | 222 held_request:send(response_body); |
| 220 end | 223 end |
| 221 sessions[session.sid] = nil; | 224 sessions[session.sid] = nil; |
| 222 inactive_sessions[session] = nil; | 225 inactive_sessions[session] = nil; |
| 223 sm_destroy_session(session); | 226 sm_destroy_session(session); |
| 224 end | 227 end |
| 225 | 228 |
| 226 -- Handle the <body> tag in the request payload. | 229 -- Handle the <body> tag in the request payload. |
| 275 t_insert(session.send_buffer, tostring(s)); | 278 t_insert(session.send_buffer, tostring(s)); |
| 276 | 279 |
| 277 local oldest_request = r[1]; | 280 local oldest_request = r[1]; |
| 278 if oldest_request and not session.bosh_processing then | 281 if oldest_request and not session.bosh_processing then |
| 279 log("debug", "We have an open request, so sending on that"); | 282 log("debug", "We have an open request, so sending on that"); |
| 280 oldest_request.headers = default_headers; | |
| 281 local body_attr = { xmlns = "http://jabber.org/protocol/httpbind", | 283 local body_attr = { xmlns = "http://jabber.org/protocol/httpbind", |
| 282 ["xmlns:stream"] = "http://etherx.jabber.org/streams"; | 284 ["xmlns:stream"] = "http://etherx.jabber.org/streams"; |
| 283 type = session.bosh_terminate and "terminate" or nil; | 285 type = session.bosh_terminate and "terminate" or nil; |
| 284 sid = sid; | 286 sid = sid; |
| 285 }; | 287 }; |
| 307 | 309 |
| 308 local session = sessions[sid]; | 310 local session = sessions[sid]; |
| 309 if not session then | 311 if not session then |
| 310 -- Unknown sid | 312 -- Unknown sid |
| 311 log("info", "Client tried to use sid '%s' which we don't know about", sid); | 313 log("info", "Client tried to use sid '%s' which we don't know about", sid); |
| 312 response.headers = default_headers; | |
| 313 response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" }))); | 314 response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" }))); |
| 314 context.notopen = nil; | 315 context.notopen = nil; |
| 315 return; | 316 return; |
| 316 end | 317 end |
| 317 | 318 |
| 345 | 346 |
| 346 if session.notopen then | 347 if session.notopen then |
| 347 local features = st.stanza("stream:features"); | 348 local features = st.stanza("stream:features"); |
| 348 hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); | 349 hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); |
| 349 fire_event("stream-features", session, features); | 350 fire_event("stream-features", session, features); |
| 350 session.send(tostring(features)); | 351 session.send(features); |
| 351 session.notopen = nil; | 352 session.notopen = nil; |
| 352 end | 353 end |
| 353 end | 354 end |
| 354 | 355 |
| 355 function stream_callbacks.handlestanza(context, stanza) | 356 function stream_callbacks.handlestanza(context, stanza) |
| 363 stanza = session.filter("stanzas/in", stanza); | 364 stanza = session.filter("stanzas/in", stanza); |
| 364 core_process_stanza(session, stanza); | 365 core_process_stanza(session, stanza); |
| 365 end | 366 end |
| 366 end | 367 end |
| 367 | 368 |
| 368 function stream_callbacks.streamclosed(request) | 369 function stream_callbacks.streamclosed(context) |
| 369 local session = sessions[request.sid]; | 370 local session = sessions[context.sid]; |
| 370 if session then | 371 if session then |
| 371 session.bosh_processing = false; | 372 session.bosh_processing = false; |
| 372 if #session.send_buffer > 0 then | 373 if #session.send_buffer > 0 then |
| 373 session.send(""); | 374 session.send(""); |
| 374 end | 375 end |
| 377 | 378 |
| 378 function stream_callbacks.error(context, error) | 379 function stream_callbacks.error(context, error) |
| 379 log("debug", "Error parsing BOSH request payload; %s", error); | 380 log("debug", "Error parsing BOSH request payload; %s", error); |
| 380 if not context.sid then | 381 if not context.sid then |
| 381 local response = context.response; | 382 local response = context.response; |
| 382 response.headers = default_headers; | |
| 383 response.status_code = 400; | 383 response.status_code = 400; |
| 384 response:send(); | 384 response:send(); |
| 385 return; | 385 return; |
| 386 end | 386 end |
| 387 | 387 |
| 391 else | 391 else |
| 392 session:close({ condition = "bad-format", text = "Error processing stream" }); | 392 session:close({ condition = "bad-format", text = "Error processing stream" }); |
| 393 end | 393 end |
| 394 end | 394 end |
| 395 | 395 |
| 396 local dead_sessions = {}; | 396 local dead_sessions = module:shared("dead_sessions"); |
| 397 function on_timer() | 397 function on_timer() |
| 398 -- log("debug", "Checking for requests soon to timeout..."); | 398 -- log("debug", "Checking for requests soon to timeout..."); |
| 399 -- Identify requests timing out within the next few seconds | 399 -- Identify requests timing out within the next few seconds |
| 400 local now = os_time() + 3; | 400 local now = os_time() + 3; |
| 401 for request, reply_before in pairs(waiting_requests) do | 401 for request, reply_before in pairs(waiting_requests) do |
