/deps/mochiweb/src/mochiweb_http.erl

https://code.google.com/p/zotonic/ · Erlang · 293 lines · 232 code · 35 blank · 26 comment · 10 complexity · 0466baae7175de73ed27b514f07ece73 MD5 · raw file

  1. %% @author Bob Ippolito <bob@mochimedia.com>
  2. %% @copyright 2007 Mochi Media, Inc.
  3. %% @doc HTTP server.
  4. -module(mochiweb_http).
  5. -author('bob@mochimedia.com').
  6. -export([start/0, start/1, stop/0, stop/1]).
  7. -export([loop/2, default_body/1]).
  8. -export([after_response/2, reentry/1]).
  9. -export([parse_range_request/1, range_skip_length/2]).
  10. -define(REQUEST_RECV_TIMEOUT, 300000). % timeout waiting for request line
  11. -define(HEADERS_RECV_TIMEOUT, 30000). % timeout waiting for headers
  12. -define(MAX_HEADERS, 1000).
  13. -define(DEFAULTS, [{name, ?MODULE},
  14. {port, 8888}]).
  15. parse_options(Options) ->
  16. {loop, HttpLoop} = proplists:lookup(loop, Options),
  17. Loop = fun (S) ->
  18. ?MODULE:loop(S, HttpLoop)
  19. end,
  20. Options1 = [{loop, Loop} | proplists:delete(loop, Options)],
  21. mochilists:set_defaults(?DEFAULTS, Options1).
  22. stop() ->
  23. mochiweb_socket_server:stop(?MODULE).
  24. stop(Name) ->
  25. mochiweb_socket_server:stop(Name).
  26. start() ->
  27. start([{ip, "127.0.0.1"},
  28. {loop, {?MODULE, default_body}}]).
  29. %% @spec start(Options) -> ServerRet
  30. %% Options = [option()]
  31. %% Option = {name, atom()} | {ip, string() | tuple()} | {backlog, integer()}
  32. %% | {nodelay, boolean()} | {acceptor_pool_size, integer()}
  33. %% | {ssl, boolean()} | {profile_fun, undefined | (Props) -> ok}
  34. %% @doc Start a mochiweb server.
  35. %% profile_fun is used to profile accept timing.
  36. %% After each accept, if defined, profile_fun is called with a proplist of a subset of the mochiweb_socket_server state and timing information.
  37. %% The proplist is as follows: [{name, Name}, {port, Port}, {active_sockets, ActiveSockets}, {timing, Timing}].
  38. %% @end
  39. start(Options) ->
  40. mochiweb_socket_server:start(parse_options(Options)).
  41. frm(Body) ->
  42. ["<html><head></head><body>"
  43. "<form method=\"POST\">"
  44. "<input type=\"hidden\" value=\"message\" name=\"hidden\"/>"
  45. "<input type=\"submit\" value=\"regular POST\">"
  46. "</form>"
  47. "<br />"
  48. "<form method=\"POST\" enctype=\"multipart/form-data\""
  49. " action=\"/multipart\">"
  50. "<input type=\"hidden\" value=\"multipart message\" name=\"hidden\"/>"
  51. "<input type=\"file\" name=\"file\"/>"
  52. "<input type=\"submit\" value=\"multipart POST\" />"
  53. "</form>"
  54. "<pre>", Body, "</pre>"
  55. "</body></html>"].
  56. default_body(Req, M, "/chunked") when M =:= 'GET'; M =:= 'HEAD' ->
  57. Res = Req:ok({"text/plain", [], chunked}),
  58. Res:write_chunk("First chunk\r\n"),
  59. timer:sleep(5000),
  60. Res:write_chunk("Last chunk\r\n"),
  61. Res:write_chunk("");
  62. default_body(Req, M, _Path) when M =:= 'GET'; M =:= 'HEAD' ->
  63. Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()},
  64. {parse_cookie, Req:parse_cookie()},
  65. Req:dump()]]),
  66. Req:ok({"text/html",
  67. [mochiweb_cookies:cookie("mochiweb_http", "test_cookie")],
  68. frm(Body)});
  69. default_body(Req, 'POST', "/multipart") ->
  70. Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()},
  71. {parse_cookie, Req:parse_cookie()},
  72. {body, Req:recv_body()},
  73. Req:dump()]]),
  74. Req:ok({"text/html", [], frm(Body)});
  75. default_body(Req, 'POST', _Path) ->
  76. Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()},
  77. {parse_cookie, Req:parse_cookie()},
  78. {parse_post, Req:parse_post()},
  79. Req:dump()]]),
  80. Req:ok({"text/html", [], frm(Body)});
  81. default_body(Req, _Method, _Path) ->
  82. Req:respond({501, [], []}).
  83. default_body(Req) ->
  84. default_body(Req, Req:get(method), Req:get(path)).
  85. loop(Socket, Body) ->
  86. mochiweb_socket:setopts(Socket, [{packet, http}]),
  87. request(Socket, Body).
  88. request(Socket, Body) ->
  89. mochiweb_socket:setopts(Socket, [{active, once}]),
  90. receive
  91. {Protocol, _, {http_request, Method, Path, Version}} when Protocol == http orelse Protocol == ssl ->
  92. mochiweb_socket:setopts(Socket, [{packet, httph}]),
  93. headers(Socket, {Method, Path, Version}, [], Body, 0);
  94. {Protocol, _, {http_error, "\r\n"}} when Protocol == http orelse Protocol == ssl ->
  95. request(Socket, Body);
  96. {Protocol, _, {http_error, "\n"}} when Protocol == http orelse Protocol == ssl ->
  97. request(Socket, Body);
  98. {tcp_closed, _} ->
  99. mochiweb_socket:close(Socket),
  100. exit(normal);
  101. _Other ->
  102. handle_invalid_request(Socket)
  103. after ?REQUEST_RECV_TIMEOUT ->
  104. mochiweb_socket:close(Socket),
  105. exit(normal)
  106. end.
  107. reentry(Body) ->
  108. fun (Req) ->
  109. ?MODULE:after_response(Body, Req)
  110. end.
  111. headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) ->
  112. %% Too many headers sent, bad request.
  113. mochiweb_socket:setopts(Socket, [{packet, raw}]),
  114. handle_invalid_request(Socket, Request, Headers);
  115. headers(Socket, Request, Headers, Body, HeaderCount) ->
  116. mochiweb_socket:setopts(Socket, [{active, once}]),
  117. receive
  118. {Protocol, _, http_eoh} when Protocol == http orelse Protocol == ssl ->
  119. Req = new_request(Socket, Request, Headers),
  120. call_body(Body, Req),
  121. ?MODULE:after_response(Body, Req);
  122. {Protocol, _, {http_header, _, Name, _, Value}} when Protocol == http orelse Protocol == ssl ->
  123. headers(Socket, Request, [{Name, Value} | Headers], Body,
  124. 1 + HeaderCount);
  125. {tcp_closed, _} ->
  126. mochiweb_socket:close(Socket),
  127. exit(normal);
  128. _Other ->
  129. handle_invalid_request(Socket, Request, Headers)
  130. after ?HEADERS_RECV_TIMEOUT ->
  131. mochiweb_socket:close(Socket),
  132. exit(normal)
  133. end.
  134. call_body({M, F, A}, Req) ->
  135. erlang:apply(M, F, [Req | A]);
  136. call_body({M, F}, Req) ->
  137. M:F(Req);
  138. call_body(Body, Req) ->
  139. Body(Req).
  140. handle_invalid_request(Socket) ->
  141. handle_invalid_request(Socket, {'GET', {abs_path, "/"}, {0,9}}, []),
  142. exit(normal).
  143. handle_invalid_request(Socket, Request, RevHeaders) ->
  144. Req = new_request(Socket, Request, RevHeaders),
  145. Req:respond({400, [], []}),
  146. mochiweb_socket:close(Socket),
  147. exit(normal).
  148. new_request(Socket, Request, RevHeaders) ->
  149. mochiweb_socket:setopts(Socket, [{packet, raw}]),
  150. mochiweb:new_request({Socket, Request, lists:reverse(RevHeaders)}).
  151. after_response(Body, Req) ->
  152. Socket = Req:get(socket),
  153. case Req:should_close() of
  154. true ->
  155. mochiweb_socket:close(Socket),
  156. exit(normal);
  157. false ->
  158. Req:cleanup(),
  159. ?MODULE:loop(Socket, Body)
  160. end.
  161. parse_range_request("bytes=0-") ->
  162. undefined;
  163. parse_range_request(RawRange) when is_list(RawRange) ->
  164. try
  165. "bytes=" ++ RangeString = RawRange,
  166. Ranges = string:tokens(RangeString, ","),
  167. lists:map(fun ("-" ++ V) ->
  168. {none, list_to_integer(V)};
  169. (R) ->
  170. case string:tokens(R, "-") of
  171. [S1, S2] ->
  172. {list_to_integer(S1), list_to_integer(S2)};
  173. [S] ->
  174. {list_to_integer(S), none}
  175. end
  176. end,
  177. Ranges)
  178. catch
  179. _:_ ->
  180. fail
  181. end.
  182. range_skip_length(Spec, Size) ->
  183. case Spec of
  184. {none, R} when R =< Size, R >= 0 ->
  185. {Size - R, R};
  186. {none, _OutOfRange} ->
  187. {0, Size};
  188. {R, none} when R >= 0, R < Size ->
  189. {R, Size - R};
  190. {_OutOfRange, none} ->
  191. invalid_range;
  192. {Start, End} when 0 =< Start, Start =< End, End < Size ->
  193. {Start, End - Start + 1};
  194. {_OutOfRange, _End} ->
  195. invalid_range
  196. end.
  197. %%
  198. %% Tests
  199. %%
  200. -ifdef(TEST).
  201. -include_lib("eunit/include/eunit.hrl").
  202. range_test() ->
  203. %% valid, single ranges
  204. ?assertEqual([{20, 30}], parse_range_request("bytes=20-30")),
  205. ?assertEqual([{20, none}], parse_range_request("bytes=20-")),
  206. ?assertEqual([{none, 20}], parse_range_request("bytes=-20")),
  207. %% trivial single range
  208. ?assertEqual(undefined, parse_range_request("bytes=0-")),
  209. %% invalid, single ranges
  210. ?assertEqual(fail, parse_range_request("")),
  211. ?assertEqual(fail, parse_range_request("garbage")),
  212. ?assertEqual(fail, parse_range_request("bytes=-20-30")),
  213. %% valid, multiple range
  214. ?assertEqual(
  215. [{20, 30}, {50, 100}, {110, 200}],
  216. parse_range_request("bytes=20-30,50-100,110-200")),
  217. ?assertEqual(
  218. [{20, none}, {50, 100}, {none, 200}],
  219. parse_range_request("bytes=20-,50-100,-200")),
  220. %% no ranges
  221. ?assertEqual([], parse_range_request("bytes=")),
  222. ok.
  223. range_skip_length_test() ->
  224. Body = <<"012345678901234567890123456789012345678901234567890123456789">>,
  225. BodySize = byte_size(Body), %% 60
  226. BodySize = 60,
  227. %% these values assume BodySize =:= 60
  228. ?assertEqual({1,9}, range_skip_length({1,9}, BodySize)), %% 1-9
  229. ?assertEqual({10,10}, range_skip_length({10,19}, BodySize)), %% 10-19
  230. ?assertEqual({40, 20}, range_skip_length({none, 20}, BodySize)), %% -20
  231. ?assertEqual({30, 30}, range_skip_length({30, none}, BodySize)), %% 30-
  232. %% valid edge cases for range_skip_length
  233. ?assertEqual({BodySize, 0}, range_skip_length({none, 0}, BodySize)),
  234. ?assertEqual({0, BodySize}, range_skip_length({none, BodySize}, BodySize)),
  235. ?assertEqual({0, BodySize}, range_skip_length({0, none}, BodySize)),
  236. BodySizeLess1 = BodySize - 1,
  237. ?assertEqual({BodySizeLess1, 1},
  238. range_skip_length({BodySize - 1, none}, BodySize)),
  239. %% out of range, return whole thing
  240. ?assertEqual({0, BodySize},
  241. range_skip_length({none, BodySize + 1}, BodySize)),
  242. ?assertEqual({0, BodySize},
  243. range_skip_length({none, -1}, BodySize)),
  244. %% invalid ranges
  245. ?assertEqual(invalid_range,
  246. range_skip_length({-1, 30}, BodySize)),
  247. ?assertEqual(invalid_range,
  248. range_skip_length({0, BodySize + 1}, BodySize)),
  249. ?assertEqual(invalid_range,
  250. range_skip_length({-1, BodySize + 1}, BodySize)),
  251. ?assertEqual(invalid_range,
  252. range_skip_length({BodySize, 40}, BodySize)),
  253. ?assertEqual(invalid_range,
  254. range_skip_length({-1, none}, BodySize)),
  255. ?assertEqual(invalid_range,
  256. range_skip_length({BodySize, none}, BodySize)),
  257. ok.
  258. -endif.