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