./ct_report/coverage/mod_cowboy.COVER.html

1 %%%===================================================================
2 %%% @copyright (C) 2014, Erlang Solutions Ltd.
3 %%% @doc HTTP routing layer for MongooseIM's Cowboy listener
4 %%%
5 %%% Allows to set more than one handler for an endpoint:
6 %%% - one handler for http
7 %%% - one handler for each WS protocol
8 %%%
9 %%% It's a proxy between cowboy and actual handlers.
10 %%%
11 %%% So, this module handles calls from cowboy, and forwards the calls to the
12 %%% handler modules.
13 %%%
14 %%% I.e. it both implements and uses the cowboy_handler and cowboy_websocket
15 %%% behaviours.
16 %%% @end
17 %%%===================================================================
18 -module(mod_cowboy).
19
20 -behaviour(cowboy_handler).
21 -behaviour(cowboy_websocket).
22
23 %% common callbacks
24 -export([init/2,
25 terminate/3]).
26
27 %% cowboy_websocket callbacks
28 -export([websocket_init/1,
29 websocket_handle/2,
30 websocket_info/2]).
31
32 -record(state, {handler,
33 handler_state,
34 handler_opts,
35 protocol :: ws | http,
36 ws_protocol}).
37
38 -type option() :: {http, module()} | {ws, atom(), module()}.
39 -type state() :: #state{}.
40
41 -include("mongoose_logger.hrl").
42
43 %%--------------------------------------------------------------------
44 %% common callbacks
45 %%--------------------------------------------------------------------
46
47 -spec init(cowboy_req:req(), [option()])
48 -> {ok, cowboy_req:req(), state() | no_state} |
49 % upgrade protocol:
50 {cowboy_websocket, cowboy_req:req(), state()}.
51 init(Req, Opts) ->
52
:-(
try
53
:-(
init_unsafe(Req, Opts)
54 catch
55 Class:Reason:StackTrace ->
56 %% Because cowboy ignores stacktraces
57 %% and we can get a cryptic error like {crash,error,undef}
58
:-(
?LOG_ERROR(#{what => init_failed, class => Class,
59
:-(
reason => Reason, stacktrace => StackTrace}),
60
:-(
erlang:raise(Class, Reason, StackTrace)
61 end.
62
63 init_unsafe(Req, Opts) ->
64
:-(
case protocol(Req) of
65 ws ->
66
:-(
handle_ws_init(Req, Opts);
67 http ->
68
:-(
handle_http_init(Req, Opts);
69 _ ->
70
:-(
Req = cowboy_req:reply(404, Req),
71
:-(
{ok, Req, no_state}
72 end.
73
74 -spec terminate(any(), cowboy_req:req(), state() | no_state) -> ok.
75 terminate(_Reason, _Req, #state{handler=undefined}) ->
76
:-(
ok;
77 terminate(_Reason, _Req, no_state) -> %% failed to init
78
:-(
ok;
79 terminate(Reason, Req, #state{handler = Handler, handler_state = HandlerState, protocol = ws}) ->
80
:-(
Handler:websocket_terminate(Reason, Req, HandlerState);
81 terminate(Reason, Req, #state{handler = Handler, handler_state = HandlerState, protocol = http}) ->
82
:-(
Handler:terminate(Reason, Req, HandlerState).
83
84 %%--------------------------------------------------------------------
85 %% cowboy_websocket_handler callbacks
86 %%--------------------------------------------------------------------
87 websocket_init(State) ->
88
:-(
handle_websocket_init(State).
89
90 websocket_handle(InFrame, State) ->
91
:-(
try
92
:-(
websocket_handle_unsafe(InFrame, State)
93 catch
94 Class:Reason:StackTrace ->
95 %% Because cowboy ignores stacktraces
96 %% and we can get a cryptic error like {crash,error,undef}
97
:-(
?LOG_ERROR(#{what => ws_handle_failed, class => Class,
98
:-(
reason => Reason, stacktrace => StackTrace}),
99
:-(
erlang:raise(Class, Reason, StackTrace)
100 end.
101
102 websocket_handle_unsafe(InFrame,
103 #state{handler=Handler, handler_state=HandlerState}=State) ->
104
:-(
case Handler:websocket_handle(InFrame, HandlerState) of
105 {ok, HandlerState1} ->
106
:-(
{ok, update_handler_state(State, HandlerState1)};
107 {ok, HandlerState1, hibernate} ->
108
:-(
{ok, update_handler_state(State, HandlerState1), hibernate};
109 {reply, OutFrame, HandlerState1} ->
110
:-(
{reply, OutFrame, update_handler_state(State, HandlerState1)};
111 {reply, OutFrame, HandlerState1, hibernate} ->
112
:-(
{reply, OutFrame, update_handler_state(State, HandlerState1),
113 hibernate};
114 {stop, HandlerState1} ->
115
:-(
{stop, update_handler_state(State, HandlerState1)}
116 end.
117
118 websocket_info(Info, State) ->
119
:-(
try
120
:-(
websocket_info_unsafe(Info, State)
121 catch
122 Class:Reason:StackTrace ->
123 %% Because cowboy ignores stacktraces
124 %% and we can get a cryptic error like {crash,error,undef}
125
:-(
?LOG_ERROR(#{what => ws_info_failed, class => Class,
126
:-(
reason => Reason, stacktrace => StackTrace}),
127
:-(
erlang:raise(Class, Reason, StackTrace)
128 end.
129
130 websocket_info_unsafe(Info,
131 #state{handler=Handler, handler_state=HandlerState}=State) ->
132
:-(
case Handler:websocket_info(Info, HandlerState) of
133 {ok, HandlerState1} ->
134
:-(
{ok, update_handler_state(State, HandlerState1)};
135 {ok, HandlerState1, hibernate} ->
136
:-(
{ok, update_handler_state(State, HandlerState1), hibernate};
137 {reply, OutFrame, HandlerState1} ->
138
:-(
{reply, OutFrame, update_handler_state(State, HandlerState1)};
139 {reply, OutFrame, HandlerState1, hibernate} ->
140
:-(
{reply, OutFrame, update_handler_state(State, HandlerState1),
141 hibernate};
142 {stop, HandlerState1} ->
143
:-(
{stop, update_handler_state(State, HandlerState1)}
144 end.
145
146 %%--------------------------------------------------------------------
147 %% Internal functions
148 %%--------------------------------------------------------------------
149 handle_http_init(Req, Opts) ->
150
:-(
case http_handler(Opts) of
151 {Handler, HandlerOpts} ->
152
:-(
init_http_handler(Handler, Req, HandlerOpts);
153 _ ->
154
:-(
Req1 = cowboy_req:reply(404, Req),
155
:-(
{stop, Req1, no_state}
156 end.
157
158 handle_ws_init(Req, Opts) ->
159 %% Make the same check as in websocket_init
160 %% We should return 404, if ws subprotocol is not supported
161
:-(
Protocol = ws_protocol(Req),
162
:-(
case ws_handler(Protocol, Opts) of
163 {Handler, HandlerOpts} ->
164
:-(
init_ws_handler(Handler, Req, HandlerOpts, Protocol);
165 _ ->
166
:-(
Req2 = cowboy_req:reply(404, Req),
167
:-(
{stop, Req2, no_state}
168 end.
169
170 init_http_handler(Handler, Req, Opts) ->
171
:-(
case Handler:init(Req, Opts) of
172 {ok, Req1, HandlerState} ->
173
:-(
{ok, Req1, init_state(Handler, Opts, HandlerState, http)};
174 {stop, Req1, HandlerState} ->
175
:-(
{stop, Req1, init_state(Handler, Opts, HandlerState, http)}
176 end.
177
178 init_ws_handler(Handler, Req, Opts, Protocol) ->
179
:-(
case Handler:init(Req, Opts) of
180 {cowboy_websocket, Req1, HandlerState} ->
181
:-(
State = init_state(Handler, Opts, HandlerState, ws, Protocol),
182 %% Ask cowboy to call websocket_init/1 next
183
:-(
{cowboy_websocket, Req1, State}
184 end.
185
186 handle_websocket_init(State=#state{handler=Handler, handler_state=HandlerState}) ->
187
:-(
case Handler:websocket_init(HandlerState) of
188 {ok, HandlerState1} ->
189
:-(
{ok, update_handler_state(State, HandlerState1)};
190 {ok, HandlerState1, hibernate} ->
191
:-(
{ok, update_handler_state(State, HandlerState1), hibernate};
192 {stop, HandlerState1} ->
193
:-(
{stop, HandlerState1}
194 end.
195
196 http_handler(Handlers) ->
197
:-(
case lists:keyfind(http, 1, Handlers) of
198 {http, Handler, Opts} ->
199
:-(
{Handler, Opts};
200 {http, Handler} ->
201
:-(
{Handler, []};
202 _ ->
203
:-(
undefined
204 end.
205
206 ws_handler(undefined, _) ->
207
:-(
undefined;
208 ws_handler(_Protocol, []) ->
209
:-(
undefined;
210 ws_handler(Protocol, [{ws, ProtocolAtom, Handler}|Tail]) ->
211
:-(
case atom_to_binary(ProtocolAtom, utf8) of
212
:-(
Protocol -> {Handler, []};
213
:-(
_ -> ws_handler(Protocol, Tail)
214 end;
215 ws_handler(Protocol, [{ws, ProtocolAtom, Handler, Opts}|Tail]) ->
216
:-(
case atom_to_binary(ProtocolAtom, utf8) of
217
:-(
Protocol -> {Handler, Opts};
218
:-(
_ -> ws_handler(Protocol, Tail)
219 end;
220 ws_handler(Protocol, [_|Tail]) ->
221
:-(
ws_handler(Protocol, Tail).
222
223 protocol(Req) ->
224
:-(
case cowboy_req:header(<<"upgrade">>, Req) of
225 <<"websocket">> ->
226
:-(
ws;
227 undefined ->
228
:-(
http;
229 _ ->
230
:-(
undefined
231 end.
232
233 ws_protocol(Req) ->
234
:-(
cowboy_req:header(<<"sec-websocket-protocol">>, Req).
235
236 init_state(Handler, Opts, State, Protocol) ->
237
:-(
init_state(Handler, Opts, State, Protocol, undefined).
238
239 init_state(Handler, Opts, State, Protocol, WsProto) ->
240
:-(
#state{handler = Handler,
241 handler_opts = Opts,
242 handler_state = State,
243 protocol = Protocol,
244 ws_protocol = WsProto}.
245
246 update_handler_state(State, HandlerState) ->
247
:-(
State#state{handler_state = HandlerState}.
Line Hits Source