cowboy_http_req.erl 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. reply/4
  26. ]). %% Response API.
  27. -include("include/types.hrl").
  28. -include("include/http.hrl").
  29. -include_lib("eunit/include/eunit.hrl").
  30. %% Request API.
  31. -spec method(Req::#http_req{}) -> {Method::http_method(), Req::#http_req{}}.
  32. method(Req) ->
  33. {Req#http_req.method, Req}.
  34. -spec version(Req::#http_req{}) -> {Version::http_version(), Req::#http_req{}}.
  35. version(Req) ->
  36. {Req#http_req.version, Req}.
  37. -spec peer(Req::#http_req{})
  38. -> {{Address::ip_address(), Port::port_number()}, Req::#http_req{}}.
  39. peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) ->
  40. {ok, Peer} = Transport:peername(Socket),
  41. {Peer, Req#http_req{peer=Peer}};
  42. peer(Req) ->
  43. {Req#http_req.peer, Req}.
  44. -spec host(Req::#http_req{}) -> {Host::path_tokens(), Req::#http_req{}}.
  45. host(Req) ->
  46. {Req#http_req.host, Req}.
  47. -spec raw_host(Req::#http_req{}) -> {RawHost::string(), Req::#http_req{}}.
  48. raw_host(Req) ->
  49. {Req#http_req.raw_host, Req}.
  50. -spec path(Req::#http_req{}) -> {Path::path_tokens(), Req::#http_req{}}.
  51. path(Req) ->
  52. {Req#http_req.path, Req}.
  53. -spec raw_path(Req::#http_req{}) -> {RawPath::string(), Req::#http_req{}}.
  54. raw_path(Req) ->
  55. {Req#http_req.raw_path, Req}.
  56. -spec qs_val(Name::atom(), Req::#http_req{})
  57. -> {Value::string(), Req::#http_req{}}.
  58. qs_val(Name, Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
  59. QsVals = parse_qs(RawQs),
  60. qs_val(Name, Req#http_req{qs_vals=QsVals});
  61. qs_val(Name, Req) ->
  62. {Name, Value} = lists:keyfind(Name, 1, Req#http_req.qs_vals),
  63. {Value, Req}.
  64. -spec qs_val(Name::atom(), Default::term(), Req::#http_req{})
  65. -> {Value::string() | term(), Req::#http_req{}}.
  66. qs_val(Name, Default, Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
  67. QsVals = parse_qs(RawQs),
  68. qs_val(Name, Default, Req#http_req{qs_vals=QsVals});
  69. qs_val(Name, Default, Req) ->
  70. Value = proplists:get_value(Name, Req#http_req.qs_vals, Default),
  71. {Value, Req}.
  72. -spec qs_vals(Req::#http_req{}) -> list({Name::atom(), Value::string()}).
  73. qs_vals(Req=#http_req{raw_qs=RawQs, qs_vals=undefined}) ->
  74. QsVals = parse_qs(RawQs),
  75. qs_vals(Req#http_req{qs_vals=QsVals});
  76. qs_vals(Req=#http_req{qs_vals=QsVals}) ->
  77. {QsVals, Req}.
  78. -spec raw_qs(Req::#http_req{}) -> {RawQs::string(), Req::#http_req{}}.
  79. raw_qs(Req) ->
  80. {Req#http_req.raw_qs, Req}.
  81. -spec binding(Name::atom(), Req::#http_req{})
  82. -> {Value::string(), Req::#http_req{}}.
  83. binding(Name, Req) ->
  84. {Name, Value} = lists:keyfind(Name, 1, Req#http_req.bindings),
  85. {Value, Req}.
  86. -spec binding(Name::atom(), Default::term(), Req::#http_req{})
  87. -> {Value::string() | term(), Req::#http_req{}}.
  88. binding(Name, Default, Req) ->
  89. Value = proplists:get_value(Name, Req#http_req.bindings, Default),
  90. {Value, Req}.
  91. -spec bindings(Req::#http_req{})
  92. -> {list({Name::atom(), Value::string()}), Req::#http_req{}}.
  93. bindings(Req) ->
  94. {Req#http_req.bindings, Req}.
  95. -spec header(Name::atom() | string(), Req::#http_req{})
  96. -> {Value::string(), Req::#http_req{}}.
  97. header(Name, Req) ->
  98. {Name, Value} = lists:keyfind(Name, 1, Req#http_req.headers),
  99. {Value, Req}.
  100. -spec header(Name::atom() | string(), Default::term(), Req::#http_req{})
  101. -> {Value::string() | term(), Req::#http_req{}}.
  102. header(Name, Default, Req) ->
  103. Value = proplists:get_value(Name, Req#http_req.headers, Default),
  104. {Value, Req}.
  105. -spec headers(Req::#http_req{})
  106. -> {list({Name::atom() | string(), Value::string()}), Req::#http_req{}}.
  107. headers(Req) ->
  108. {Req#http_req.headers, Req}.
  109. %% Response API.
  110. -spec reply(Code::http_status(), Headers::http_headers(),
  111. Body::iolist(), Req::#http_req{}) -> ok.
  112. %% @todo Don't be naive about the headers!
  113. reply(Code, Headers, Body, Req=#http_req{socket=Socket,
  114. transport=Transport, connection=Connection,
  115. resp_state=waiting}) ->
  116. StatusLine = ["HTTP/1.1 ", status(Code), "\r\n"],
  117. BaseHeaders = ["Connection: ", atom_to_connection(Connection),
  118. "\r\nContent-Length: ", integer_to_list(iolist_size(Body)), "\r\n"],
  119. Transport:send(Socket,
  120. [StatusLine, BaseHeaders, Headers, "\r\n", Body]),
  121. {ok, Req#http_req{resp_state=done}}.
  122. %% Internal.
  123. -spec parse_qs(Qs::string()) -> list({Name::string(), Value::string()}).
  124. parse_qs(Qs) ->
  125. Tokens = string:tokens(Qs, "&"),
  126. [case string:chr(Token, $=) of
  127. 0 ->
  128. {Token, true};
  129. N ->
  130. {Name, [$=|Value]} = lists:split(N - 1, Token),
  131. {Name, Value}
  132. end || Token <- Tokens].
  133. -spec atom_to_connection(Atom::keepalive | close) -> string().
  134. atom_to_connection(keepalive) ->
  135. "keep-alive";
  136. atom_to_connection(close) ->
  137. "close".
  138. -spec status(Code::http_status()) -> string().
  139. status(100) -> "100 Continue";
  140. status(101) -> "101 Switching Protocols";
  141. status(102) -> "102 Processing";
  142. status(200) -> "200 OK";
  143. status(201) -> "201 Created";
  144. status(202) -> "202 Accepted";
  145. status(203) -> "203 Non-Authoritative Information";
  146. status(204) -> "204 No Content";
  147. status(205) -> "205 Reset Content";
  148. status(206) -> "206 Partial Content";
  149. status(207) -> "207 Multi-Status";
  150. status(226) -> "226 IM Used";
  151. status(300) -> "300 Multiple Choices";
  152. status(301) -> "301 Moved Permanently";
  153. status(302) -> "302 Found";
  154. status(303) -> "303 See Other";
  155. status(304) -> "304 Not Modified";
  156. status(305) -> "305 Use Proxy";
  157. status(306) -> "306 Switch Proxy";
  158. status(307) -> "307 Temporary Redirect";
  159. status(400) -> "400 Bad Request";
  160. status(401) -> "401 Unauthorized";
  161. status(402) -> "402 Payment Required";
  162. status(403) -> "403 Forbidden";
  163. status(404) -> "404 Not Found";
  164. status(405) -> "405 Method Not Allowed";
  165. status(406) -> "406 Not Acceptable";
  166. status(407) -> "407 Proxy Authentication Required";
  167. status(408) -> "408 Request Timeout";
  168. status(409) -> "409 Conflict";
  169. status(410) -> "410 Gone";
  170. status(411) -> "411 Length Required";
  171. status(412) -> "412 Precondition Failed";
  172. status(413) -> "413 Request Entity Too Large";
  173. status(414) -> "414 Request-URI Too Long";
  174. status(415) -> "415 Unsupported Media Type";
  175. status(416) -> "416 Requested Range Not Satisfiable";
  176. status(417) -> "417 Expectation Failed";
  177. status(418) -> "418 I'm a teapot";
  178. status(422) -> "422 Unprocessable Entity";
  179. status(423) -> "423 Locked";
  180. status(424) -> "424 Failed Dependency";
  181. status(425) -> "425 Unordered Collection";
  182. status(426) -> "426 Upgrade Required";
  183. status(500) -> "500 Internal Server Error";
  184. status(501) -> "501 Not Implemented";
  185. status(502) -> "502 Bad Gateway";
  186. status(503) -> "503 Service Unavailable";
  187. status(504) -> "504 Gateway Timeout";
  188. status(505) -> "505 HTTP Version Not Supported";
  189. status(506) -> "506 Variant Also Negotiates";
  190. status(507) -> "507 Insufficient Storage";
  191. status(510) -> "510 Not Extended";
  192. status(L) when is_list(L) -> L.
  193. %% Tests.
  194. -ifdef(TEST).
  195. parse_qs_test_() ->
  196. %% {Qs, Result}
  197. Tests = [
  198. {"", []},
  199. {"a=b", [{"a", "b"}]},
  200. {"aaa=bbb", [{"aaa", "bbb"}]},
  201. {"a&b", [{"a", true}, {"b", true}]},
  202. {"a=b&c&d=e", [{"a", "b"}, {"c", true}, {"d", "e"}]},
  203. {"a=b=c=d=e&f=g", [{"a", "b=c=d=e"}, {"f", "g"}]}
  204. ],
  205. [{Qs, fun() -> R = parse_qs(Qs) end} || {Qs, R} <- Tests].
  206. -endif.