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