PageRenderTime 94ms CodeModel.GetById 73ms RepoModel.GetById 1ms app.codeStats 0ms

/src/mochiweb_http.erl

http://github.com/basho/mochiweb
Erlang | 321 lines | 237 code | 36 blank | 48 comment | 12 complexity | cbd2f1bc3f8dcc2d7e736887936b5388 MD5 | raw file
Possible License(s): MIT
  1. %% @author Bob Ippolito <bob@mochimedia.com>
  2. %% @copyright 2007 Mochi Media, Inc.
  3. %%
  4. %% Permission is hereby granted, free of charge, to any person obtaining a
  5. %% copy of this software and associated documentation files (the "Software"),
  6. %% to deal in the Software without restriction, including without limitation
  7. %% the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. %% and/or sell copies of the Software, and to permit persons to whom the
  9. %% Software is furnished to do so, subject to the following conditions:
  10. %%
  11. %% The above copyright notice and this permission notice shall be included in
  12. %% all copies or substantial portions of the Software.
  13. %%
  14. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  17. %% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. %% DEALINGS IN THE SOFTWARE.
  21. %% @doc HTTP server.
  22. -module(mochiweb_http).
  23. -author('bob@mochimedia.com').
  24. -export([start/1, start_link/1, stop/0, stop/1]).
  25. -export([loop/3]).
  26. -export([after_response/2, reentry/1]).
  27. -export([parse_range_request/1, range_skip_length/2]).
  28. -define(REQUEST_RECV_TIMEOUT, 300000). %% timeout waiting for request line
  29. -define(HEADERS_RECV_TIMEOUT, 30000). %% timeout waiting for headers
  30. -define(MAX_HEADERS, 1000).
  31. -define(DEFAULTS, [{name, ?MODULE},
  32. {port, 8888}]).
  33. -ifdef(gen_tcp_r15b_workaround).
  34. r15b_workaround() -> true.
  35. -else.
  36. r15b_workaround() -> false.
  37. -endif.
  38. parse_options(Options) ->
  39. {loop, HttpLoop} = proplists:lookup(loop, Options),
  40. Loop = {?MODULE, loop, [HttpLoop]},
  41. Options1 = [{loop, Loop} | proplists:delete(loop, Options)],
  42. mochilists:set_defaults(?DEFAULTS, Options1).
  43. stop() ->
  44. mochiweb_socket_server:stop(?MODULE).
  45. stop(Name) ->
  46. mochiweb_socket_server:stop(Name).
  47. %% @spec start(Options) -> ServerRet
  48. %% Options = [option()]
  49. %% Option = {name, atom()} | {ip, string() | tuple()} | {backlog, integer()}
  50. %% | {nodelay, boolean()} | {acceptor_pool_size, integer()}
  51. %% | {ssl, boolean()} | {profile_fun, undefined | (Props) -> ok}
  52. %% | {link, false} | {recbuf, undefined | non_negative_integer()}
  53. %% @doc Start a mochiweb server.
  54. %% profile_fun is used to profile accept timing.
  55. %% After each accept, if defined, profile_fun is called with a proplist of a subset of the mochiweb_socket_server state and timing information.
  56. %% The proplist is as follows: [{name, Name}, {port, Port}, {active_sockets, ActiveSockets}, {timing, Timing}].
  57. %% @end
  58. start(Options) ->
  59. mochiweb_socket_server:start(parse_options(Options)).
  60. start_link(Options) ->
  61. mochiweb_socket_server:start_link(parse_options(Options)).
  62. loop(Socket, Opts, Body) ->
  63. ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, http}])),
  64. request(Socket, Opts, Body).
  65. request(Socket, Opts, Body) ->
  66. ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{active, once}])),
  67. receive
  68. {Protocol, _, {http_request, Method, Path, Version}} when Protocol == http orelse Protocol == ssl ->
  69. ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, httph}])),
  70. headers(Socket, Opts, {Method, Path, Version}, [], Body, 0);
  71. {Protocol, _, {http_error, "\r\n"}} when Protocol == http orelse Protocol == ssl ->
  72. request(Socket, Opts, Body);
  73. {Protocol, _, {http_error, "\n"}} when Protocol == http orelse Protocol == ssl ->
  74. request(Socket, Opts, Body);
  75. {tcp_closed, _} ->
  76. mochiweb_socket:close(Socket),
  77. exit(normal);
  78. {ssl_closed, _} ->
  79. mochiweb_socket:close(Socket),
  80. exit(normal);
  81. Msg = {ProtocolErr, _Socket, emsgsize} when
  82. ProtocolErr =:= tcp_error ; ProtocolErr =:= ssl_error ->
  83. handle_invalid_msg_request(Msg, Socket);
  84. {ProtocolErr, _Socket, _Reason} when
  85. ProtocolErr =:= tcp_error ; ProtocolErr =:= ssl_error ->
  86. mochiweb_socket:close(Socket),
  87. exit(normal);
  88. Other ->
  89. handle_invalid_msg_request(Other, Socket, Opts)
  90. after ?REQUEST_RECV_TIMEOUT ->
  91. mochiweb_socket:close(Socket),
  92. exit(normal)
  93. end.
  94. reentry(Body) ->
  95. fun (Req) ->
  96. ?MODULE:after_response(Body, Req)
  97. end.
  98. headers(Socket, Opts, Request, Headers, _Body, ?MAX_HEADERS) ->
  99. %% Too many headers sent, bad request.
  100. ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, raw}])),
  101. handle_invalid_request(Socket, Opts, Request, Headers);
  102. headers(Socket, Opts, Request, Headers, Body, HeaderCount) ->
  103. ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{active, once}])),
  104. receive
  105. {Protocol, _, http_eoh} when Protocol == http orelse Protocol == ssl ->
  106. Req = new_request(Socket, Opts, Request, Headers),
  107. call_body(Body, Req),
  108. ?MODULE:after_response(Body, Req);
  109. {Protocol, _, {http_header, _, Name, _, Value}} when Protocol == http orelse Protocol == ssl ->
  110. headers(Socket, Opts, Request, [{Name, Value} | Headers], Body,
  111. 1 + HeaderCount);
  112. {tcp_closed, _} ->
  113. mochiweb_socket:close(Socket),
  114. exit(normal);
  115. Msg = {ProtocolErr, _Socket, emsgsize} when
  116. ProtocolErr =:= tcp_error ; ProtocolErr =:= ssl_error ->
  117. handle_invalid_msg_request(Msg, Socket);
  118. Msg = {ProtocolErr, _Socket, _Reason} when
  119. ProtocolErr =:= tcp_error ; ProtocolErr =:= ssl_error ->
  120. error_logger:warning_msg("Got unexpected TCP error message: ~w (to pid=~w)~n",
  121. [Msg, self()]),
  122. mochiweb_socket:close(Socket),
  123. exit(normal);
  124. Other ->
  125. handle_invalid_msg_request(Other, Socket, Opts, Request, Headers)
  126. after ?HEADERS_RECV_TIMEOUT ->
  127. mochiweb_socket:close(Socket),
  128. exit(normal)
  129. end.
  130. call_body({M, F, A}, Req) ->
  131. erlang:apply(M, F, [Req | A]);
  132. call_body({M, F}, Req) ->
  133. M:F(Req);
  134. call_body(Body, Req) ->
  135. Body(Req).
  136. -spec handle_invalid_msg_request(term(), term(), term()) -> no_return().
  137. handle_invalid_msg_request(Msg, Socket, Opts) ->
  138. handle_invalid_msg_request(Msg, Socket, Opts, {'GET', {abs_path, "/"}, {0,9}}, []).
  139. -spec handle_invalid_msg_request(term(), term(), term(), term(), term()) -> no_return().
  140. handle_invalid_msg_request(Msg, Socket, Opts, Request, RevHeaders) ->
  141. case {Msg, r15b_workaround()} of
  142. {{tcp_error,_,emsgsize}, true} ->
  143. %% R15B02 returns this then closes the socket, so close and exit
  144. mochiweb_socket:close(Socket),
  145. exit(normal);
  146. _ ->
  147. handle_invalid_request(Socket, Opts, Request, RevHeaders)
  148. end.
  149. -spec handle_invalid_request(term(), term(), term(), term()) -> no_return().
  150. handle_invalid_request(Socket, Opts, Request, RevHeaders) ->
  151. Req = new_request(Socket, Opts, Request, RevHeaders),
  152. Req:respond({400, [], []}),
  153. mochiweb_socket:close(Socket),
  154. exit(normal).
  155. new_request(Socket, Opts, Request, RevHeaders) ->
  156. ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, raw}])),
  157. mochiweb:new_request({Socket, Opts, Request, lists:reverse(RevHeaders)}).
  158. after_response(Body, Req) ->
  159. Socket = Req:get(socket),
  160. case Req:should_close() of
  161. true ->
  162. mochiweb_socket:close(Socket),
  163. exit(normal);
  164. false ->
  165. Req:cleanup(),
  166. erlang:garbage_collect(),
  167. ?MODULE:loop(Socket, mochiweb_request:get(opts, Req), Body)
  168. end.
  169. parse_range_request(RawRange) when is_list(RawRange) ->
  170. try
  171. "bytes=" ++ RangeString = RawRange,
  172. RangeTokens = [string:strip(R) || R <- string:tokens(RangeString, ",")],
  173. Ranges = [R || R <- RangeTokens, string:len(R) > 0],
  174. lists:map(fun ("-" ++ V) ->
  175. {none, list_to_integer(V)};
  176. (R) ->
  177. case string:tokens(R, "-") of
  178. [S1, S2] ->
  179. {list_to_integer(S1), list_to_integer(S2)};
  180. [S] ->
  181. {list_to_integer(S), none}
  182. end
  183. end,
  184. Ranges)
  185. catch
  186. _:_ ->
  187. fail
  188. end.
  189. range_skip_length(Spec, Size) ->
  190. case Spec of
  191. {none, R} when R =< Size, R >= 0 ->
  192. {Size - R, R};
  193. {none, _OutOfRange} ->
  194. {0, Size};
  195. {R, none} when R >= 0, R < Size ->
  196. {R, Size - R};
  197. {_OutOfRange, none} ->
  198. invalid_range;
  199. {Start, End} when 0 =< Start, Start =< End, End < Size ->
  200. {Start, End - Start + 1};
  201. {Start, End} when 0 =< Start, Start < Size, Start =< End ->
  202. {Start, Size - Start};
  203. {_InvalidStart, _InvalidEnd} ->
  204. invalid_range
  205. end.
  206. %%
  207. %% Tests
  208. %%
  209. -ifdef(TEST).
  210. -include_lib("eunit/include/eunit.hrl").
  211. range_test() ->
  212. %% valid, single ranges
  213. ?assertEqual([{20, 30}], parse_range_request("bytes=20-30")),
  214. ?assertEqual([{20, none}], parse_range_request("bytes=20-")),
  215. ?assertEqual([{none, 20}], parse_range_request("bytes=-20")),
  216. %% trivial single range
  217. ?assertEqual([{0, none}], parse_range_request("bytes=0-")),
  218. %% invalid, single ranges
  219. ?assertEqual(fail, parse_range_request("")),
  220. ?assertEqual(fail, parse_range_request("garbage")),
  221. ?assertEqual(fail, parse_range_request("bytes=-20-30")),
  222. %% valid, multiple range
  223. ?assertEqual(
  224. [{20, 30}, {50, 100}, {110, 200}],
  225. parse_range_request("bytes=20-30,50-100,110-200")),
  226. ?assertEqual(
  227. [{20, none}, {50, 100}, {none, 200}],
  228. parse_range_request("bytes=20-,50-100,-200")),
  229. %% valid, multiple range with whitespace
  230. ?assertEqual(
  231. [{20, 30}, {50, 100}, {110, 200}],
  232. parse_range_request("bytes=20-30, 50-100 , 110-200")),
  233. %% valid, multiple range with extra commas
  234. ?assertEqual(
  235. [{20, 30}, {50, 100}, {110, 200}],
  236. parse_range_request("bytes=20-30,,50-100,110-200")),
  237. ?assertEqual(
  238. [{20, 30}, {50, 100}, {110, 200}],
  239. parse_range_request("bytes=20-30, ,50-100,,,110-200")),
  240. %% no ranges
  241. ?assertEqual([], parse_range_request("bytes=")),
  242. ok.
  243. range_skip_length_test() ->
  244. Body = <<"012345678901234567890123456789012345678901234567890123456789">>,
  245. BodySize = byte_size(Body), %% 60
  246. BodySize = 60,
  247. %% these values assume BodySize =:= 60
  248. ?assertEqual({1,9}, range_skip_length({1,9}, BodySize)), %% 1-9
  249. ?assertEqual({10,10}, range_skip_length({10,19}, BodySize)), %% 10-19
  250. ?assertEqual({40, 20}, range_skip_length({none, 20}, BodySize)), %% -20
  251. ?assertEqual({30, 30}, range_skip_length({30, none}, BodySize)), %% 30-
  252. %% valid edge cases for range_skip_length
  253. ?assertEqual({BodySize, 0}, range_skip_length({none, 0}, BodySize)),
  254. ?assertEqual({0, BodySize}, range_skip_length({none, BodySize}, BodySize)),
  255. ?assertEqual({0, BodySize}, range_skip_length({0, none}, BodySize)),
  256. ?assertEqual({0, BodySize}, range_skip_length({0, BodySize + 1}, BodySize)),
  257. BodySizeLess1 = BodySize - 1,
  258. ?assertEqual({BodySizeLess1, 1},
  259. range_skip_length({BodySize - 1, none}, BodySize)),
  260. ?assertEqual({BodySizeLess1, 1},
  261. range_skip_length({BodySize - 1, BodySize+5}, BodySize)),
  262. ?assertEqual({BodySizeLess1, 1},
  263. range_skip_length({BodySize - 1, BodySize}, BodySize)),
  264. %% out of range, return whole thing
  265. ?assertEqual({0, BodySize},
  266. range_skip_length({none, BodySize + 1}, BodySize)),
  267. ?assertEqual({0, BodySize},
  268. range_skip_length({none, -1}, BodySize)),
  269. ?assertEqual({0, BodySize},
  270. range_skip_length({0, BodySize + 1}, BodySize)),
  271. %% invalid ranges
  272. ?assertEqual(invalid_range,
  273. range_skip_length({-1, 30}, BodySize)),
  274. ?assertEqual(invalid_range,
  275. range_skip_length({-1, BodySize + 1}, BodySize)),
  276. ?assertEqual(invalid_range,
  277. range_skip_length({BodySize, 40}, BodySize)),
  278. ?assertEqual(invalid_range,
  279. range_skip_length({-1, none}, BodySize)),
  280. ?assertEqual(invalid_range,
  281. range_skip_length({BodySize, none}, BodySize)),
  282. ?assertEqual(invalid_range,
  283. range_skip_length({BodySize + 1, BodySize + 5}, BodySize)),
  284. ok.
  285. -endif.