cowboy_http_req.erl 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. %% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(cowboy_http_req).
  15. -export([
  16. method/1, version/1, peer/1,
  17. host/1, raw_host/1,
  18. path/1, raw_path/1,
  19. qs_val/2, qs_val/3, qs_vals/1, raw_qs/1,
  20. binding/2, binding/3, bindings/1,
  21. header/2, header/3, headers/1
  22. %% cookie/2, cookie/3, cookies/1 @todo
  23. ]). %% Request API.
  24. -export([
  25. body/1, body/2, body_qs/1
  26. ]). %% Request Body API.
  27. -export([
  28. reply/4
  29. ]). %% Response API.
  30. -include("include/types.hrl").
  31. -include("include/http.hrl").
  32. -include_lib("eunit/include/eunit.hrl").
  33. %% Request API.
  34. -spec method(Req::#http_req{}) -> {Method::http_method(), Req::#http_req{}}.
  35. method(Req) ->
  36. {Req#http_req.method, Req}.
  37. -spec version(Req::#http_req{}) -> {Version::http_version(), Req::#http_req{}}.
  38. version(Req) ->
  39. {Req#http_req.version, Req}.
  40. -spec peer(Req::#http_req{})
  41. -> {{Address::ip_address(), Port::port_number()}, Req::#http_req{}}.
  42. peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) ->
  43. {ok, Peer} = Transport:peername(Socket),
  44. {Peer, Req#http_req{peer=Peer}};
  45. peer(Req) ->
  46. {Req#http_req.peer, Req}.
  47. -spec host(Req::#http_req{}) -> {Host::path_tokens(), Req::#http_req{}}.
  48. host(Req) ->
  49. {Req#http_req.host, Req}.
  50. -spec raw_host(Req::#http_req{}) -> {RawHost::string(), Req::#http_req{}}.
  51. raw_host(Req) ->
  52. {Req#http_req.raw_host, Req}.
  53. -spec path(Req::#http_req{}) -> {Path::path_tokens(), Req::#http_req{}}.
  54. path(Req) ->
  55. {Req#http_req.path, Req}.
  56. -spec raw_path(Req::#http_req{}) -> {RawPath::string(), Req::#http_req{}}.
  57. raw_path(Req) ->
  58. {Req#http_req.raw_path, Req}.
  59. -spec qs_val(Name::atom(), Req::#http_req{})
  60. -> {Value::string(), Req::#http_req{}}.
  61. qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
  62. QsVals = parse_qs(RawQs),
  63. qs_val(Name, Req#http_req{qs_vals=QsVals});
  64. qs_val(Name, Req) ->
  65. {Name, Value} = lists:keyfind(Name, 1, Req#http_req.qs_vals),
  66. {Value, Req}.
  67. -spec qs_val(Name::atom(), Default::term(), Req::#http_req{})
  68. -> {Value::string() | term(), Req::#http_req{}}.
  69. qs_val(Name, Default, Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
  70. QsVals = parse_qs(RawQs),
  71. qs_val(Name, Default, Req#http_req{qs_vals=QsVals});
  72. qs_val(Name, Default, Req) ->
  73. Value = proplists:get_value(Name, Req#http_req.qs_vals, Default),
  74. {Value, Req}.
  75. -spec qs_vals(Req::#http_req{})
  76. -> {list({Name::atom(), Value::string()}), Req::#http_req{}}.
  77. qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
  78. QsVals = parse_qs(RawQs),
  79. qs_vals(Req#http_req{qs_vals=QsVals});
  80. qs_vals(Req=#http_req{qs_vals=QsVals}) ->
  81. {QsVals, Req}.
  82. -spec raw_qs(Req::#http_req{}) -> {RawQs::string(), Req::#http_req{}}.
  83. raw_qs(Req) ->
  84. {Req#http_req.raw_qs, Req}.
  85. -spec binding(Name::atom(), Req::#http_req{})
  86. -> {Value::string(), Req::#http_req{}}.
  87. binding(Name, Req) ->
  88. {Name, Value} = lists:keyfind(Name, 1, Req#http_req.bindings),
  89. {Value, Req}.
  90. -spec binding(Name::atom(), Default::term(), Req::#http_req{})
  91. -> {Value::string() | term(), Req::#http_req{}}.
  92. binding(Name, Default, Req) ->
  93. Value = proplists:get_value(Name, Req#http_req.bindings, Default),
  94. {Value, Req}.
  95. -spec bindings(Req::#http_req{})
  96. -> {list({Name::atom(), Value::string()}), Req::#http_req{}}.
  97. bindings(Req) ->
  98. {Req#http_req.bindings, Req}.
  99. -spec header(Name::atom() | string(), Req::#http_req{})
  100. -> {Value::string(), Req::#http_req{}}.
  101. header(Name, Req) ->
  102. case lists:keyfind(Name, 1, Req#http_req.headers) of
  103. {Name, Value} -> {Value, Req};
  104. false -> {"", Req}
  105. end.
  106. -spec header(Name::atom() | string(), Default::term(), Req::#http_req{})
  107. -> {Value::string() | term(), Req::#http_req{}}.
  108. header(Name, Default, Req) ->
  109. Value = proplists:get_value(Name, Req#http_req.headers, Default),
  110. {Value, Req}.
  111. -spec headers(Req::#http_req{})
  112. -> {list({Name::atom() | string(), Value::string()}), Req::#http_req{}}.
  113. headers(Req) ->
  114. {Req#http_req.headers, Req}.
  115. %% Request Body API.
  116. %% @todo We probably want to allow a max length.
  117. -spec body(Req::#http_req{})
  118. -> {ok, Body::binary(), Req::#http_req{}} | {error, Reason::posix()}.
  119. body(Req) ->
  120. {Length, Req2} = cowboy_http_req:header('Content-Length', Req),
  121. case Length of
  122. "" -> {error, badarg};
  123. _Any ->
  124. Length2 = list_to_integer(Length),
  125. body(Length2, Req2)
  126. end.
  127. %% @todo We probably want to configure the timeout.
  128. -spec body(Length::non_neg_integer(), Req::#http_req{})
  129. -> {ok, Body::binary(), Req::#http_req{}} | {error, Reason::posix()}.
  130. body(Length, Req=#http_req{socket=Socket, transport=Transport,
  131. body_state=waiting}) ->
  132. Transport:setopts(Socket, [{packet, raw}]),
  133. case Transport:recv(Socket, Length, 5000) of
  134. {ok, Body} -> {ok, Body, Req#http_req{body_state=done}};
  135. {error, Reason} -> {error, Reason}
  136. end.
  137. -spec body_qs(Req::#http_req{})
  138. -> {list({Name::string(), Value::string()}), Req::#http_req{}}.
  139. body_qs(Req) ->
  140. {ok, Body, Req2} = body(Req),
  141. {parse_qs(binary_to_list(Body)), Req2}.
  142. %% Response API.
  143. -spec reply(Code::http_status(), Headers::http_headers(),
  144. Body::iolist(), Req::#http_req{}) -> {ok, Req::#http_req{}}.
  145. %% @todo Don't be naive about the headers!
  146. reply(Code, Headers, Body, Req=#http_req{socket=Socket,
  147. transport=Transport, connection=Connection,
  148. resp_state=waiting}) ->
  149. StatusLine = ["HTTP/1.1 ", status(Code), "\r\n"],
  150. BaseHeaders = ["Connection: ", atom_to_connection(Connection),
  151. "\r\nContent-Length: ", integer_to_list(iolist_size(Body)), "\r\n"],
  152. Transport:send(Socket,
  153. [StatusLine, BaseHeaders, Headers, "\r\n", Body]),
  154. {ok, Req#http_req{resp_state=done}}.
  155. %% Internal.
  156. -spec parse_qs(Qs::string()) -> list({Name::string(), Value::string() | true}).
  157. parse_qs(Qs) ->
  158. Tokens = string:tokens(Qs, "&"),
  159. [case string:chr(Token, $=) of
  160. 0 ->
  161. {Token, true};
  162. N ->
  163. {Name, [$=|Value]} = lists:split(N - 1, Token),
  164. {Name, Value}
  165. end || Token <- Tokens].
  166. -spec atom_to_connection(Atom::keepalive | close) -> string().
  167. atom_to_connection(keepalive) ->
  168. "keep-alive";
  169. atom_to_connection(close) ->
  170. "close".
  171. -spec status(Code::http_status()) -> string().
  172. status(100) -> "100 Continue";
  173. status(101) -> "101 Switching Protocols";
  174. status(102) -> "102 Processing";
  175. status(200) -> "200 OK";
  176. status(201) -> "201 Created";
  177. status(202) -> "202 Accepted";
  178. status(203) -> "203 Non-Authoritative Information";
  179. status(204) -> "204 No Content";
  180. status(205) -> "205 Reset Content";
  181. status(206) -> "206 Partial Content";
  182. status(207) -> "207 Multi-Status";
  183. status(226) -> "226 IM Used";
  184. status(300) -> "300 Multiple Choices";
  185. status(301) -> "301 Moved Permanently";
  186. status(302) -> "302 Found";
  187. status(303) -> "303 See Other";
  188. status(304) -> "304 Not Modified";
  189. status(305) -> "305 Use Proxy";
  190. status(306) -> "306 Switch Proxy";
  191. status(307) -> "307 Temporary Redirect";
  192. status(400) -> "400 Bad Request";
  193. status(401) -> "401 Unauthorized";
  194. status(402) -> "402 Payment Required";
  195. status(403) -> "403 Forbidden";
  196. status(404) -> "404 Not Found";
  197. status(405) -> "405 Method Not Allowed";
  198. status(406) -> "406 Not Acceptable";
  199. status(407) -> "407 Proxy Authentication Required";
  200. status(408) -> "408 Request Timeout";
  201. status(409) -> "409 Conflict";
  202. status(410) -> "410 Gone";
  203. status(411) -> "411 Length Required";
  204. status(412) -> "412 Precondition Failed";
  205. status(413) -> "413 Request Entity Too Large";
  206. status(414) -> "414 Request-URI Too Long";
  207. status(415) -> "415 Unsupported Media Type";
  208. status(416) -> "416 Requested Range Not Satisfiable";
  209. status(417) -> "417 Expectation Failed";
  210. status(418) -> "418 I'm a teapot";
  211. status(422) -> "422 Unprocessable Entity";
  212. status(423) -> "423 Locked";
  213. status(424) -> "424 Failed Dependency";
  214. status(425) -> "425 Unordered Collection";
  215. status(426) -> "426 Upgrade Required";
  216. status(500) -> "500 Internal Server Error";
  217. status(501) -> "501 Not Implemented";
  218. status(502) -> "502 Bad Gateway";
  219. status(503) -> "503 Service Unavailable";
  220. status(504) -> "504 Gateway Timeout";
  221. status(505) -> "505 HTTP Version Not Supported";
  222. status(506) -> "506 Variant Also Negotiates";
  223. status(507) -> "507 Insufficient Storage";
  224. status(510) -> "510 Not Extended";
  225. status(L) when is_list(L) -> L.
  226. %% Tests.
  227. -ifdef(TEST).
  228. parse_qs_test_() ->
  229. %% {Qs, Result}
  230. Tests = [
  231. {"", []},
  232. {"a=b", [{"a", "b"}]},
  233. {"aaa=bbb", [{"aaa", "bbb"}]},
  234. {"a&b", [{"a", true}, {"b", true}]},
  235. {"a=b&c&d=e", [{"a", "b"}, {"c", true}, {"d", "e"}]},
  236. {"a=b=c=d=e&f=g", [{"a", "b=c=d=e"}, {"f", "g"}]}
  237. ],
  238. [{Qs, fun() -> R = parse_qs(Qs) end} || {Qs, R} <- Tests].
  239. -endif.