req_SUITE.erl 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. %% Copyright (c) 2016, Loïc Hoguin <essen@ninenines.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(req_SUITE).
  15. -compile(export_all).
  16. -import(ct_helper, [config/2]).
  17. -import(ct_helper, [doc/1]).
  18. -import(cowboy_test, [gun_open/1]).
  19. %% ct.
  20. all() ->
  21. cowboy_test:common_all().
  22. groups() ->
  23. AllTests = ct_helper:all(?MODULE),
  24. [
  25. {http, [parallel], AllTests},
  26. {https, [parallel], AllTests},
  27. {h2, [parallel], AllTests},
  28. {h2c, [parallel], AllTests}
  29. %% @todo With compression enabled.
  30. ].
  31. init_per_group(Name, Config) ->
  32. cowboy_test:init_common_groups(Name, Config, ?MODULE).
  33. end_per_group(Name, _) ->
  34. cowboy:stop_listener(Name).
  35. %% Routes.
  36. init_dispatch(_) ->
  37. cowboy_router:compile([{"[...]", [
  38. {"/no/:key", echo_h, []},
  39. {"/args/:key/:arg[/:default]", echo_h, []},
  40. {"/:key/[...]", echo_h, []}
  41. ]}]).
  42. %% Internal.
  43. do_body(Method, Path, Config) ->
  44. do_body(Method, Path, [], Config).
  45. do_body(Method, Path, Headers, Config) ->
  46. ConnPid = gun_open(Config),
  47. Ref = gun:request(ConnPid, Method, Path, Headers),
  48. {response, IsFin, 200, _} = gun:await(ConnPid, Ref),
  49. {ok, Body} = case IsFin of
  50. nofin -> gun:await_body(ConnPid, Ref);
  51. fin -> {ok, <<>>}
  52. end,
  53. gun:close(ConnPid),
  54. Body.
  55. do_get_body(Path, Config) ->
  56. do_get_body(Path, [], Config).
  57. do_get_body(Path, Headers, Config) ->
  58. do_body("GET", Path, Headers, Config).
  59. %% Tests.
  60. binding(Config) ->
  61. doc("Value bound from request URI path with/without default."),
  62. <<"binding">> = do_get_body("/args/binding/key", Config),
  63. <<"binding">> = do_get_body("/args/binding/key/default", Config),
  64. <<"default">> = do_get_body("/args/binding/undefined/default", Config),
  65. ok.
  66. %% @todo Do we really want a key/value list here instead of a map?
  67. bindings(Config) ->
  68. doc("Values bound from request URI path."),
  69. <<"[{key,<<\"bindings\">>}]">> = do_get_body("/bindings", Config),
  70. ok.
  71. header(Config) ->
  72. doc("Request header with/without default."),
  73. <<"value">> = do_get_body("/args/header/defined", [{<<"defined">>, "value"}], Config),
  74. <<"value">> = do_get_body("/args/header/defined/default", [{<<"defined">>, "value"}], Config),
  75. <<"default">> = do_get_body("/args/header/undefined/default", [{<<"defined">>, "value"}], Config),
  76. ok.
  77. headers(Config) ->
  78. doc("Request headers."),
  79. << "#{<<\"header\">> => <<\"value\">>", _/bits >>
  80. = do_get_body("/headers", [{<<"header">>, "value"}], Config),
  81. ok.
  82. host(Config) ->
  83. doc("Request URI host."),
  84. <<"localhost">> = do_get_body("/host", Config),
  85. ok.
  86. host_info(Config) ->
  87. doc("Request host_info."),
  88. <<"[<<\"localhost\">>]">> = do_get_body("/host_info", Config),
  89. ok.
  90. method(Config) ->
  91. doc("Request method."),
  92. <<"GET">> = do_body("GET", "/method", Config),
  93. <<"HEAD">> = do_body("HEAD", "/method", Config),
  94. <<"OPTIONS">> = do_body("OPTIONS", "/method", Config),
  95. <<"PATCH">> = do_body("PATCH", "/method", Config),
  96. <<"POST">> = do_body("POST", "/method", Config),
  97. <<"PUT">> = do_body("PUT", "/method", Config),
  98. <<"ZZZZZZZZ">> = do_body("ZZZZZZZZ", "/method", Config),
  99. ok.
  100. %% @todo Do we really want a key/value list here instead of a map?
  101. parse_cookies(Config) ->
  102. doc("Request cookies."),
  103. <<"[]">> = do_get_body("/parse_cookies", Config),
  104. <<"[{<<\"cake\">>,<<\"strawberry\">>}]">>
  105. = do_get_body("/parse_cookies", [{<<"cookie">>, "cake=strawberry"}], Config),
  106. <<"[{<<\"cake\">>,<<\"strawberry\">>},{<<\"color\">>,<<\"blue\">>}]">>
  107. = do_get_body("/parse_cookies", [{<<"cookie">>, "cake=strawberry; color=blue"}], Config),
  108. ok.
  109. parse_header(Config) ->
  110. doc("Parsed request header with/without default."),
  111. <<"[{{<<\"text\">>,<<\"html\">>,[]},1000,[]}]">>
  112. = do_get_body("/args/parse_header/accept", [{<<"accept">>, "text/html"}], Config),
  113. <<"[{{<<\"text\">>,<<\"html\">>,[]},1000,[]}]">>
  114. = do_get_body("/args/parse_header/accept/default", [{<<"accept">>, "text/html"}], Config),
  115. %% Header not in request but with default defined by Cowboy.
  116. <<"0">> = do_get_body("/args/parse_header/content-length", Config),
  117. %% Header not in request and no default from Cowboy.
  118. <<"undefined">> = do_get_body("/args/parse_header/upgrade", Config),
  119. %% Header in request and with default provided.
  120. <<"100-continue">> = do_get_body("/args/parse_header/expect/100-continue", Config),
  121. ok.
  122. %% @todo Do we really want a key/value list here instead of a map?
  123. parse_qs(Config) ->
  124. doc("Parsed request URI query string."),
  125. <<"[]">> = do_get_body("/parse_qs", Config),
  126. <<"[{<<\"abc\">>,true}]">> = do_get_body("/parse_qs?abc", Config),
  127. <<"[{<<\"a\">>,<<\"b\">>},{<<\"c\">>,<<\"d e\">>}]">> = do_get_body("/parse_qs?a=b&c=d+e", Config),
  128. ok.
  129. path(Config) ->
  130. doc("Request URI path."),
  131. <<"/path/to/the/resource">> = do_get_body("/path/to/the/resource", Config),
  132. <<"/path/to/the/resource">> = do_get_body("/path/to/the/resource?query", Config),
  133. <<"/path/to/the/resource">> = do_get_body("/path/to/the/resource?query#fragment", Config),
  134. <<"/path/to/the/resource">> = do_get_body("/path/to/the/resource#fragment", Config),
  135. ok.
  136. path_info(Config) ->
  137. doc("Request path_info."),
  138. <<"undefined">> = do_get_body("/no/path_info", Config),
  139. <<"[]">> = do_get_body("/path_info", Config),
  140. <<"[]">> = do_get_body("/path_info/", Config),
  141. <<"[<<\"to\">>,<<\"the\">>,<<\"resource\">>]">> = do_get_body("/path_info/to/the/resource", Config),
  142. <<"[<<\"to\">>,<<\"the\">>,<<\"resource\">>]">> = do_get_body("/path_info/to/the/resource?query", Config),
  143. <<"[<<\"to\">>,<<\"the\">>,<<\"resource\">>]">> = do_get_body("/path_info/to/the/resource?query#fragment", Config),
  144. <<"[<<\"to\">>,<<\"the\">>,<<\"resource\">>]">> = do_get_body("/path_info/to/the/resource#fragment", Config),
  145. ok.
  146. peer(Config) ->
  147. doc("Request peer."),
  148. <<"{{127,0,0,1},", _/bits >> = do_get_body("/peer", Config),
  149. ok.
  150. port(Config) ->
  151. doc("Request URI port."),
  152. Port = integer_to_binary(config(port, Config)),
  153. Port = do_get_body("/port", Config),
  154. ok.
  155. qs(Config) ->
  156. doc("Request URI query string."),
  157. <<>> = do_get_body("/qs", Config),
  158. <<"abc">> = do_get_body("/qs?abc", Config),
  159. <<"a=b&c=d+e">> = do_get_body("/qs?a=b&c=d+e", Config),
  160. ok.
  161. scheme(Config) ->
  162. doc("Request URI scheme."),
  163. Transport = config(type, Config),
  164. case do_get_body("/scheme", Config) of
  165. <<"http">> when Transport =:= tcp -> ok;
  166. <<"https">> when Transport =:= ssl -> ok
  167. end.
  168. uri(Config) ->
  169. doc("Request URI building/modification."),
  170. Scheme = case config(type, Config) of
  171. tcp -> <<"http">>;
  172. ssl -> <<"https">>
  173. end,
  174. SLen = byte_size(Scheme),
  175. Port = integer_to_binary(config(port, Config)),
  176. PLen = byte_size(Port),
  177. %% Absolute form.
  178. << Scheme:SLen/binary, "://localhost:", Port:PLen/binary, "/uri?qs" >>
  179. = do_get_body("/uri?qs", Config),
  180. %% Origin form.
  181. << "/uri/origin?qs" >> = do_get_body("/uri/origin?qs", Config),
  182. %% Protocol relative.
  183. << "//localhost:", Port:PLen/binary, "/uri/protocol-relative?qs" >>
  184. = do_get_body("/uri/protocol-relative?qs", Config),
  185. %% No query string.
  186. << Scheme:SLen/binary, "://localhost:", Port:PLen/binary, "/uri/no-qs" >>
  187. = do_get_body("/uri/no-qs?qs", Config),
  188. %% No path or query string.
  189. << Scheme:SLen/binary, "://localhost:", Port:PLen/binary >>
  190. = do_get_body("/uri/no-path?qs", Config),
  191. %% Changed port.
  192. << Scheme:SLen/binary, "://localhost:123/uri/set-port?qs" >>
  193. = do_get_body("/uri/set-port?qs", Config),
  194. %% This function is tested more extensively through unit tests.
  195. ok.
  196. version(Config) ->
  197. doc("Request HTTP version."),
  198. Protocol = config(protocol, Config),
  199. case do_get_body("/version", Config) of
  200. <<"HTTP/1.1">> when Protocol =:= http -> ok;
  201. <<"HTTP/2">> when Protocol =:= http2 -> ok
  202. end.