1 |
|
%%%=================================================================== |
2 |
|
%%% @copyright (C) 2013, Erlang Solutions Ltd. |
3 |
|
%%% @doc Cowboy based BOSH support for MongooseIM |
4 |
|
%%% |
5 |
|
%%% @end |
6 |
|
%%%=================================================================== |
7 |
|
-module(mod_bosh). |
8 |
|
-behaviour(gen_mod). |
9 |
|
-behaviour(mongoose_module_metrics). |
10 |
|
%% cowboy_loop is a long polling handler |
11 |
|
-behaviour(cowboy_loop). |
12 |
|
|
13 |
|
-xep([{xep, 206}, {version, "1.4"}]). |
14 |
|
-xep([{xep, 124}, {version, "1.11.2"}]). |
15 |
|
|
16 |
|
%% gen_mod callbacks |
17 |
|
-export([start/2, |
18 |
|
stop/1, |
19 |
|
config_spec/0, |
20 |
|
supported_features/0]). |
21 |
|
|
22 |
|
%% cowboy_loop_handler callbacks |
23 |
|
-export([init/2, |
24 |
|
info/3, |
25 |
|
terminate/3]). |
26 |
|
|
27 |
|
%% Hooks callbacks |
28 |
|
-export([node_cleanup/3]). |
29 |
|
|
30 |
|
%% For testing and debugging |
31 |
|
-export([get_session_socket/1, store_session/2, instrumentation/0]). |
32 |
|
|
33 |
|
-export([config_metrics/1]). |
34 |
|
|
35 |
|
-ignore_xref([get_session_socket/1, store_session/2, instrumentation/0]). |
36 |
|
|
37 |
|
-include("mongoose.hrl"). |
38 |
|
-include("jlib.hrl"). |
39 |
|
-include_lib("exml/include/exml_stream.hrl"). |
40 |
|
-include("mod_bosh.hrl"). |
41 |
|
-include("mongoose_config_spec.hrl"). |
42 |
|
|
43 |
|
-define(DEFAULT_MAX_AGE, 1728000). %% 20 days in seconds |
44 |
|
-define(DEFAULT_ALLOW_ORIGIN, <<"*">>). |
45 |
|
|
46 |
|
-export_type([session/0, |
47 |
|
sid/0, |
48 |
|
event_type/0, |
49 |
|
socket/0 |
50 |
|
]). |
51 |
|
|
52 |
|
-type socket() :: #bosh_socket{}. |
53 |
|
-type session() :: #bosh_session{ |
54 |
|
sid :: mod_bosh:sid(), |
55 |
|
socket :: pid() |
56 |
|
}. |
57 |
|
-type sid() :: binary(). |
58 |
|
-type event_type() :: streamstart |
59 |
|
| restart |
60 |
|
| normal |
61 |
|
| pause |
62 |
|
| streamend. |
63 |
|
|
64 |
|
-type headers_list() :: [{binary(), binary()}]. |
65 |
|
|
66 |
|
%% Request State |
67 |
|
-record(rstate, {req_sid, opts}). |
68 |
|
-type rstate() :: #rstate{}. |
69 |
|
-type req() :: cowboy_req:req(). |
70 |
|
|
71 |
|
-type info() :: accept_options |
72 |
|
| accept_get |
73 |
|
| item_not_found |
74 |
|
| no_body |
75 |
|
| policy_violation |
76 |
|
| {bosh_reply, exml:element()} |
77 |
|
| {close, _} |
78 |
|
| {wrong_method, _}. |
79 |
|
|
80 |
|
%%-------------------------------------------------------------------- |
81 |
|
%% gen_mod callbacks |
82 |
|
%%-------------------------------------------------------------------- |
83 |
|
|
84 |
|
-spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok. |
85 |
|
start(_HostType, Opts) -> |
86 |
377 |
case mod_bosh_socket:is_supervisor_started() of |
87 |
|
true -> |
88 |
279 |
ok; % There is only one backend implementation (mnesia), so it is started globally |
89 |
|
false -> |
90 |
98 |
mod_bosh_backend:start(Opts), |
91 |
98 |
{ok, _Pid} = mod_bosh_socket:start_supervisor(), |
92 |
98 |
gen_hook:add_handlers(hooks()), |
93 |
|
% Because mod_bosh acts more like a service, than a module, instrumentation has to be |
94 |
|
% set up manually, only once. |
95 |
98 |
mongoose_instrument:set_up(instrumentation()) |
96 |
|
end. |
97 |
|
|
98 |
|
-spec stop(mongooseim:host_type()) -> ok. |
99 |
|
stop(_HostType) -> |
100 |
377 |
mod_bosh_socket:stop_supervisor(), |
101 |
377 |
mongoose_instrument:tear_down(instrumentation()), |
102 |
377 |
gen_hook:delete_handlers(hooks()), |
103 |
377 |
ok. |
104 |
|
|
105 |
|
-spec hooks() -> gen_hook:hook_list(). |
106 |
|
hooks() -> |
107 |
475 |
[{node_cleanup, global, fun ?MODULE:node_cleanup/3, #{}, 50}]. |
108 |
|
|
109 |
|
-spec instrumentation() -> [mongoose_instrument:spec()]. |
110 |
|
instrumentation() -> |
111 |
478 |
[{mod_bosh_data_sent, #{}, |
112 |
|
#{metrics => #{byte_size => spiral}}}, |
113 |
|
{mod_bosh_data_received, #{}, |
114 |
|
#{metrics => #{byte_size => spiral}}}]. |
115 |
|
|
116 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
117 |
|
config_spec() -> |
118 |
186 |
#section{items = #{<<"backend">> => #option{type = atom, |
119 |
|
validate = {module, mod_bosh}}, |
120 |
|
<<"inactivity">> => #option{type = int_or_infinity, |
121 |
|
validate = positive}, |
122 |
|
<<"max_wait">> => #option{type = int_or_infinity, |
123 |
|
validate = positive}, |
124 |
|
<<"server_acks">> => #option{type = boolean}, |
125 |
|
<<"max_pause">> => #option{type = integer, |
126 |
|
validate = positive} |
127 |
|
}, |
128 |
|
defaults = #{<<"backend">> => mnesia, |
129 |
|
<<"inactivity">> => 30, % seconds |
130 |
|
<<"max_wait">> => infinity, % seconds |
131 |
|
<<"server_acks">> => false, |
132 |
|
<<"max_pause">> => 120} % seconds |
133 |
|
}. |
134 |
|
|
135 |
|
-spec supported_features() -> [atom()]. |
136 |
|
supported_features() -> |
137 |
193 |
[dynamic_domains]. |
138 |
|
|
139 |
|
%%-------------------------------------------------------------------- |
140 |
|
%% Hooks handlers |
141 |
|
%%-------------------------------------------------------------------- |
142 |
|
|
143 |
|
-spec node_cleanup(Acc, Params, Extra) -> {ok, Acc} when |
144 |
|
Acc :: map(), |
145 |
|
Params :: #{node := node()}, |
146 |
|
Extra :: gen_hook:extra(). |
147 |
|
node_cleanup(Acc, #{node := Node}, _) -> |
148 |
9 |
Res = mod_bosh_backend:node_cleanup(Node), |
149 |
9 |
{ok, maps:put(?MODULE, Res, Acc)}. |
150 |
|
|
151 |
|
%%-------------------------------------------------------------------- |
152 |
|
%% cowboy_loop_handler callbacks |
153 |
|
%%-------------------------------------------------------------------- |
154 |
|
|
155 |
|
-spec init(req(), mongoose_http_handler:options()) -> {cowboy_loop, req(), rstate()}. |
156 |
|
init(Req, Opts) -> |
157 |
2029 |
?LOG_DEBUG(#{what => bosh_init, req => Req}), |
158 |
2029 |
Msg = init_msg(Req), |
159 |
2029 |
self() ! Msg, |
160 |
|
%% Upgrade to cowboy_loop behaviour to enable long polling |
161 |
2029 |
{cowboy_loop, Req, #rstate{opts = Opts}}. |
162 |
|
|
163 |
|
|
164 |
|
%% ok return keep the handler looping. |
165 |
|
%% stop handler is used to reply to the client. |
166 |
|
-spec info(info(), req(), rstate()) -> {ok, req(), _} | {stop, req(), _}. |
167 |
|
info(accept_options, Req, State) -> |
168 |
2 |
Origin = cowboy_req:header(<<"origin">>, Req), |
169 |
2 |
Headers = ac_all(Origin), |
170 |
2 |
?LOG_DEBUG(#{what => bosh_accept_options, headers => Headers, |
171 |
2 |
text => <<"Handle OPTIONS request in Bosh">>}), |
172 |
2 |
Req1 = cowboy_reply(200, Headers, <<>>, Req), |
173 |
2 |
{stop, Req1, State}; |
174 |
|
info(accept_get, Req, State) -> |
175 |
2 |
Headers = [content_type(), |
176 |
|
ac_allow_methods(), |
177 |
|
ac_allow_headers(), |
178 |
|
ac_max_age()], |
179 |
2 |
Body = <<"MongooseIM bosh endpoint">>, |
180 |
2 |
Req1 = cowboy_reply(200, Headers, Body, Req), |
181 |
2 |
{stop, Req1, State}; |
182 |
|
info(forward_body, Req, S) -> |
183 |
2021 |
{ok, Body, Req1} = cowboy_req:read_body(Req), |
184 |
|
%% TODO: the parser should be stored per session, |
185 |
|
%% but the session is identified inside the to-be-parsed element |
186 |
2021 |
{ok, BodyElem} = exml:parse(Body), |
187 |
2021 |
Sid = exml_query:attr(BodyElem, <<"sid">>, <<"missing">>), |
188 |
2021 |
?LOG_DEBUG(#{what => bosh_receive, sid => Sid, request_body => Body}), |
189 |
|
%% Remember req_sid, so it can be used to print a debug message in bosh_reply |
190 |
2021 |
forward_body(Req1, BodyElem, S#rstate{req_sid = Sid}); |
191 |
|
info({bosh_reply, El}, Req, S) -> |
192 |
2010 |
BEl = exml:to_binary(El), |
193 |
|
%% 'mod_bosh_data_sent' metric includes 'body' wrapping elements and resending attempts |
194 |
2010 |
mongoose_instrument:execute(mod_bosh_data_sent, #{}, #{byte_size => byte_size(BEl)}), |
195 |
2010 |
?LOG_DEBUG(#{what => bosh_send, req_sid => S#rstate.req_sid, reply_body => BEl, |
196 |
2010 |
sid => exml_query:attr(El, <<"sid">>, <<"missing">>)}), |
197 |
2010 |
Headers = bosh_reply_headers(), |
198 |
2010 |
Req1 = cowboy_reply(200, Headers, BEl, Req), |
199 |
2010 |
{stop, Req1, S}; |
200 |
|
|
201 |
|
info({close, Sid}, Req, S) -> |
202 |
:-( |
?LOG_DEBUG(#{what => bosh_close, sid => Sid}), |
203 |
:-( |
Req1 = cowboy_reply(200, [], <<>>, Req), |
204 |
:-( |
{stop, Req1, S}; |
205 |
|
info(no_body, Req, State) -> |
206 |
2 |
?LOG_DEBUG(#{what => bosh_stop, reason => missing_request_body, req => Req}), |
207 |
2 |
Req1 = no_body_error(Req), |
208 |
2 |
{stop, Req1, State}; |
209 |
|
info({wrong_method, Method}, Req, State) -> |
210 |
2 |
?LOG_DEBUG(#{what => bosh_stop, reason => wrong_request_method, |
211 |
2 |
method => Method, req => Req}), |
212 |
2 |
Req1 = method_not_allowed_error(Req), |
213 |
2 |
{stop, Req1, State}; |
214 |
|
info(item_not_found, Req, S) -> |
215 |
4 |
Req1 = terminal_condition(<<"item-not-found">>, Req), |
216 |
4 |
{stop, Req1, S}; |
217 |
|
info(policy_violation, Req, S) -> |
218 |
2 |
Req1 = terminal_condition(<<"policy-violation">>, Req), |
219 |
2 |
{stop, Req1, S}. |
220 |
|
|
221 |
|
|
222 |
|
terminate(_Reason, _Req, _State) -> |
223 |
2028 |
?LOG_DEBUG(#{what => bosh_terminate}), |
224 |
2028 |
ok. |
225 |
|
|
226 |
|
%%-------------------------------------------------------------------- |
227 |
|
%% Callbacks implementation |
228 |
|
%%-------------------------------------------------------------------- |
229 |
|
|
230 |
|
init_msg(Req) -> |
231 |
2029 |
Method = cowboy_req:method(Req), |
232 |
2029 |
case Method of |
233 |
|
<<"OPTIONS">> -> |
234 |
2 |
accept_options; |
235 |
|
<<"POST">> -> |
236 |
2023 |
case cowboy_req:has_body(Req) of |
237 |
|
true -> |
238 |
2021 |
forward_body; |
239 |
|
false -> |
240 |
2 |
no_body |
241 |
|
end; |
242 |
|
<<"GET">> -> |
243 |
2 |
accept_get; |
244 |
|
_ -> |
245 |
2 |
{wrong_method, Method} |
246 |
|
end. |
247 |
|
|
248 |
|
-spec to_event_type(exml:element()) -> event_type(). |
249 |
|
to_event_type(Body) -> |
250 |
|
%% Order of checks is important: |
251 |
|
%% stream restart has got sid attribute, |
252 |
|
%% so check for it at the end. |
253 |
2021 |
check_event_type_streamend(Body). |
254 |
|
|
255 |
|
check_event_type_streamend(Body) -> |
256 |
2021 |
case exml_query:attr(Body, <<"type">>) of |
257 |
|
<<"terminate">> -> |
258 |
118 |
streamend; |
259 |
|
_ -> |
260 |
1903 |
check_event_type_restart(Body) |
261 |
|
end. |
262 |
|
|
263 |
|
check_event_type_restart(Body) -> |
264 |
1903 |
case exml_query:attr(Body, <<"xmpp:restart">>) of |
265 |
|
<<"true">> -> |
266 |
121 |
restart; |
267 |
|
_ -> |
268 |
1782 |
check_event_type_pause(Body) |
269 |
|
end. |
270 |
|
|
271 |
|
check_event_type_pause(Body) -> |
272 |
1782 |
case exml_query:attr(Body, <<"pause">>) of |
273 |
|
undefined -> |
274 |
1779 |
check_event_type_streamstrart(Body); |
275 |
|
_ -> |
276 |
3 |
pause |
277 |
|
end. |
278 |
|
|
279 |
|
check_event_type_streamstrart(Body) -> |
280 |
1779 |
case exml_query:attr(Body, <<"sid">>) of |
281 |
|
undefined -> |
282 |
128 |
streamstart; |
283 |
|
_ -> |
284 |
1651 |
normal |
285 |
|
end. |
286 |
|
|
287 |
|
-spec forward_body(req(), exml:element(), rstate()) |
288 |
|
-> {ok, req(), rstate()} | {stop, req(), rstate()}. |
289 |
|
forward_body(Req, #xmlel{} = Body, #rstate{opts = Opts} = S) -> |
290 |
2021 |
Type = to_event_type(Body), |
291 |
2021 |
case Type of |
292 |
|
streamstart -> |
293 |
128 |
{SessionStarted, Req1} = maybe_start_session(Req, Body, Opts), |
294 |
128 |
case SessionStarted of |
295 |
|
true -> |
296 |
125 |
{ok, Req1, S}; |
297 |
|
false -> |
298 |
3 |
{stop, Req1, S} |
299 |
|
end; |
300 |
|
_ -> |
301 |
1893 |
Sid = exml_query:attr(Body, <<"sid">>), |
302 |
1893 |
case get_session_socket(Sid) of |
303 |
|
{ok, Socket} -> |
304 |
|
%% Forward request from a client to c2s process |
305 |
1892 |
handle_request(Socket, Type, Body), |
306 |
1892 |
{ok, Req, S}; |
307 |
|
{error, item_not_found} -> |
308 |
1 |
?LOG_WARNING(#{what => bosh_stop, reason => session_not_found, |
309 |
:-( |
sid => Sid}), |
310 |
1 |
Req1 = terminal_condition(<<"item-not-found">>, Req), |
311 |
1 |
{stop, Req1, S} |
312 |
|
end |
313 |
|
end. |
314 |
|
|
315 |
|
|
316 |
|
-spec handle_request(pid(), event_type(), exml:element()) -> ok. |
317 |
|
handle_request(Socket, EventType, Body) -> |
318 |
|
%% 'mod_bosh_data_received' metric includes 'body' wrapping elements |
319 |
2017 |
mongoose_instrument:execute(mod_bosh_data_received, #{}, #{byte_size => exml:xml_size(Body)}), |
320 |
2017 |
mod_bosh_socket:handle_request(Socket, {EventType, self(), Body}). |
321 |
|
|
322 |
|
|
323 |
|
-spec get_session_socket(mod_bosh:sid()) -> {ok, pid()} | {error, item_not_found}. |
324 |
|
get_session_socket(Sid) -> |
325 |
1893 |
case mod_bosh_backend:get_session(Sid) of |
326 |
|
[BS] -> |
327 |
1892 |
{ok, BS#bosh_session.socket}; |
328 |
|
[] -> |
329 |
1 |
{error, item_not_found} |
330 |
|
end. |
331 |
|
|
332 |
|
|
333 |
|
-spec maybe_start_session(req(), exml:element(), map()) -> |
334 |
|
{SessionStarted :: boolean(), req()}. |
335 |
|
maybe_start_session(Req, Body, Opts) -> |
336 |
128 |
Domain = exml_query:attr(Body, <<"to">>), |
337 |
128 |
case mongoose_domain_api:get_domain_host_type(Domain) of |
338 |
|
{ok, HostType} -> |
339 |
128 |
case gen_mod:is_loaded(HostType, ?MODULE) of |
340 |
|
true -> |
341 |
127 |
maybe_start_session_on_known_host(HostType, Req, Body, Opts); |
342 |
|
false -> |
343 |
1 |
{false, terminal_condition(<<"host-unknown">>, Req)} |
344 |
|
end; |
345 |
|
{error, not_found} -> |
346 |
:-( |
{false, terminal_condition(<<"host-unknown">>, Req)} |
347 |
|
end. |
348 |
|
|
349 |
|
-spec maybe_start_session_on_known_host(mongooseim:host_type(), req(), exml:element(), map()) -> |
350 |
|
{SessionStarted :: boolean(), req()}. |
351 |
|
maybe_start_session_on_known_host(HostType, Req, Body, Opts) -> |
352 |
127 |
try |
353 |
127 |
maybe_start_session_on_known_host_unsafe(HostType, Req, Body, Opts) |
354 |
|
catch |
355 |
|
error:Reason:Stacktrace -> |
356 |
|
%% It's here because something catch-y was here before |
357 |
2 |
?LOG_ERROR(#{what => bosh_stop, issue => undefined_condition, |
358 |
:-( |
reason => Reason, stacktrace => Stacktrace}), |
359 |
2 |
Req1 = terminal_condition(<<"undefined-condition">>, [], Req), |
360 |
2 |
{false, Req1} |
361 |
|
end. |
362 |
|
|
363 |
|
-spec maybe_start_session_on_known_host_unsafe(mongooseim:host_type(), req(), exml:element(), map()) -> |
364 |
|
{SessionStarted :: boolean(), req()}. |
365 |
|
maybe_start_session_on_known_host_unsafe(HostType, Req, Body, Opts) -> |
366 |
|
%% Version isn't checked as it would be meaningless when supporting |
367 |
|
%% only a subset of the specification. |
368 |
127 |
{ok, NewBody} = set_max_hold(Body), |
369 |
125 |
Peer = cowboy_req:peer(Req), |
370 |
125 |
PeerCert = cowboy_req:cert(Req), |
371 |
125 |
start_session(HostType, Peer, PeerCert, NewBody, Opts), |
372 |
125 |
{true, Req}. |
373 |
|
|
374 |
|
-spec start_session(mongooseim:host_type(), mongoose_transport:peer(), |
375 |
|
binary() | undefined, exml:element(), map()) -> any(). |
376 |
|
start_session(HostType, Peer, PeerCert, Body, Opts) -> |
377 |
125 |
Sid = make_sid(), |
378 |
125 |
{ok, Socket} = mod_bosh_socket:start(HostType, Sid, Peer, PeerCert, Opts), |
379 |
125 |
store_session(Sid, Socket), |
380 |
125 |
handle_request(Socket, streamstart, Body), |
381 |
125 |
?LOG_DEBUG(#{what => bosh_start_session, sid => Sid}). |
382 |
|
|
383 |
|
-spec store_session(Sid :: sid(), Socket :: pid()) -> any(). |
384 |
|
store_session(Sid, Socket) -> |
385 |
125 |
mod_bosh_backend:create_session(#bosh_session{sid = Sid, socket = Socket}). |
386 |
|
|
387 |
|
%% MUST be unique and unpredictable |
388 |
|
%% https://xmpp.org/extensions/xep-0124.html#security-sidrid |
389 |
|
%% Also, CETS requires to use node as a part of the key |
390 |
|
%% (but if the key is always random CETS is happy with that too) |
391 |
|
-spec make_sid() -> binary(). |
392 |
|
make_sid() -> |
393 |
125 |
base16:encode(crypto:strong_rand_bytes(20)). |
394 |
|
|
395 |
|
%%-------------------------------------------------------------------- |
396 |
|
%% HTTP errors |
397 |
|
%%-------------------------------------------------------------------- |
398 |
|
|
399 |
|
-spec no_body_error(cowboy_req:req()) -> cowboy_req:req(). |
400 |
|
no_body_error(Req) -> |
401 |
2 |
cowboy_reply(400, ac_all(?DEFAULT_ALLOW_ORIGIN), |
402 |
|
<<"Missing request body">>, Req). |
403 |
|
|
404 |
|
|
405 |
|
-spec method_not_allowed_error(cowboy_req:req()) -> cowboy_req:req(). |
406 |
|
method_not_allowed_error(Req) -> |
407 |
2 |
cowboy_reply(405, ac_all(?DEFAULT_ALLOW_ORIGIN), |
408 |
|
<<"Use POST request method">>, Req). |
409 |
|
|
410 |
|
%%-------------------------------------------------------------------- |
411 |
|
%% BOSH Terminal Binding Error Conditions |
412 |
|
%%-------------------------------------------------------------------- |
413 |
|
|
414 |
|
-spec terminal_condition(binary(), cowboy_req:req()) -> cowboy_req:req(). |
415 |
|
terminal_condition(Condition, Req) -> |
416 |
8 |
terminal_condition(Condition, [], Req). |
417 |
|
|
418 |
|
|
419 |
|
-spec terminal_condition(binary(), [exml:element()], cowboy_req:req()) |
420 |
|
-> cowboy_req:req(). |
421 |
|
terminal_condition(Condition, Details, Req) -> |
422 |
10 |
Body = terminal_condition_body(Condition, Details), |
423 |
10 |
Headers = [content_type()] ++ ac_all(?DEFAULT_ALLOW_ORIGIN), |
424 |
10 |
cowboy_reply(200, Headers, Body, Req). |
425 |
|
|
426 |
|
|
427 |
|
-spec terminal_condition_body(binary(), [exml:element()]) -> binary(). |
428 |
|
terminal_condition_body(Condition, Children) -> |
429 |
10 |
exml:to_binary(#xmlel{name = <<"body">>, |
430 |
|
attrs = [{<<"type">>, <<"terminate">>}, |
431 |
|
{<<"condition">>, Condition}, |
432 |
|
{<<"xmlns">>, ?NS_HTTPBIND}], |
433 |
|
children = Children}). |
434 |
|
|
435 |
|
%%-------------------------------------------------------------------- |
436 |
|
%% Helpers |
437 |
|
%%-------------------------------------------------------------------- |
438 |
|
|
439 |
|
content_type() -> |
440 |
2022 |
{<<"content-type">>, <<"text/xml; charset=utf8">>}. |
441 |
|
|
442 |
|
ac_allow_origin(Origin) -> |
443 |
2026 |
{<<"access-control-allow-origin">>, Origin}. |
444 |
|
|
445 |
|
ac_allow_methods() -> |
446 |
2028 |
{<<"access-control-allow-methods">>, <<"POST, OPTIONS, GET">>}. |
447 |
|
|
448 |
|
ac_allow_headers() -> |
449 |
2028 |
{<<"access-control-allow-headers">>, <<"content-type">>}. |
450 |
|
|
451 |
|
ac_max_age() -> |
452 |
2028 |
{<<"access-control-max-age">>, integer_to_binary(?DEFAULT_MAX_AGE)}. |
453 |
|
|
454 |
|
|
455 |
|
-spec ac_all('undefined' | binary()) -> headers_list(). |
456 |
|
ac_all(Origin) -> |
457 |
16 |
[ac_allow_origin(Origin), |
458 |
|
ac_allow_methods(), |
459 |
|
ac_allow_headers(), |
460 |
|
ac_max_age()]. |
461 |
|
|
462 |
|
-spec bosh_reply_headers() -> headers_list(). |
463 |
|
bosh_reply_headers() -> |
464 |
2010 |
[content_type(), |
465 |
|
ac_allow_origin(?DEFAULT_ALLOW_ORIGIN), |
466 |
|
ac_allow_methods(), |
467 |
|
ac_allow_headers(), |
468 |
|
ac_max_age()]. |
469 |
|
|
470 |
|
set_max_hold(Body) -> |
471 |
127 |
HoldBin = exml_query:attr(Body, <<"hold">>), |
472 |
127 |
ClientHold = binary_to_integer(HoldBin), |
473 |
127 |
maybe_set_max_hold(ClientHold, Body). |
474 |
|
|
475 |
|
|
476 |
|
maybe_set_max_hold(1, Body) -> |
477 |
123 |
{ok, Body}; |
478 |
|
maybe_set_max_hold(ClientHold, #xmlel{attrs = Attrs} = Body) when ClientHold > 1 -> |
479 |
2 |
NewAttrs = lists:keyreplace(<<"hold">>, 1, Attrs, {<<"hold">>, <<"1">>}), |
480 |
2 |
{ok, Body#xmlel{attrs = NewAttrs}}; |
481 |
|
maybe_set_max_hold(_, _) -> |
482 |
2 |
{error, invalid_hold}. |
483 |
|
|
484 |
|
-spec cowboy_reply(non_neg_integer(), headers_list(), binary(), req()) -> req(). |
485 |
|
cowboy_reply(Code, Headers, Body, Req) when is_list(Headers) -> |
486 |
2028 |
cowboy_req:reply(Code, maps:from_list(Headers), Body, Req). |
487 |
|
|
488 |
|
-spec config_metrics(mongooseim:host_type()) -> [{gen_mod:opt_key(), gen_mod:opt_value()}]. |
489 |
|
config_metrics(HostType) -> |
490 |
252 |
mongoose_module_metrics:opts_for_module(HostType, ?MODULE, [backend]). |