cowboy_http_req.erl 9.2 KB

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