req_SUITE.erl 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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. match_cookies(Config) ->
  91. doc("Matched request cookies."),
  92. <<"#{}">> = do_get_body("/match/cookies", [{<<"cookie">>, "a=b; c=d"}], Config),
  93. <<"#{a => <<\"b\">>}">> = do_get_body("/match/cookies/a", [{<<"cookie">>, "a=b; c=d"}], Config),
  94. <<"#{c => <<\"d\">>}">> = do_get_body("/match/cookies/c", [{<<"cookie">>, "a=b; c=d"}], Config),
  95. <<"#{a => <<\"b\">>,c => <<\"d\">>}">> = do_get_body("/match/cookies/a/c",
  96. [{<<"cookie">>, "a=b; c=d"}], Config),
  97. %% This function is tested more extensively through unit tests.
  98. ok.
  99. match_qs(Config) ->
  100. doc("Matched request URI query string."),
  101. <<"#{}">> = do_get_body("/match/qs?a=b&c=d", Config),
  102. <<"#{a => <<\"b\">>}">> = do_get_body("/match/qs/a?a=b&c=d", Config),
  103. <<"#{c => <<\"d\">>}">> = do_get_body("/match/qs/c?a=b&c=d", Config),
  104. <<"#{a => <<\"b\">>,c => <<\"d\">>}">> = do_get_body("/match/qs/a/c?a=b&c=d", Config),
  105. %% This function is tested more extensively through unit tests.
  106. ok.
  107. method(Config) ->
  108. doc("Request method."),
  109. <<"GET">> = do_body("GET", "/method", Config),
  110. <<"HEAD">> = do_body("HEAD", "/method", Config),
  111. <<"OPTIONS">> = do_body("OPTIONS", "/method", Config),
  112. <<"PATCH">> = do_body("PATCH", "/method", Config),
  113. <<"POST">> = do_body("POST", "/method", Config),
  114. <<"PUT">> = do_body("PUT", "/method", Config),
  115. <<"ZZZZZZZZ">> = do_body("ZZZZZZZZ", "/method", Config),
  116. ok.
  117. %% @todo Do we really want a key/value list here instead of a map?
  118. parse_cookies(Config) ->
  119. doc("Request cookies."),
  120. <<"[]">> = do_get_body("/parse_cookies", Config),
  121. <<"[{<<\"cake\">>,<<\"strawberry\">>}]">>
  122. = do_get_body("/parse_cookies", [{<<"cookie">>, "cake=strawberry"}], Config),
  123. <<"[{<<\"cake\">>,<<\"strawberry\">>},{<<\"color\">>,<<\"blue\">>}]">>
  124. = do_get_body("/parse_cookies", [{<<"cookie">>, "cake=strawberry; color=blue"}], Config),
  125. ok.
  126. parse_header(Config) ->
  127. doc("Parsed request header with/without default."),
  128. <<"[{{<<\"text\">>,<<\"html\">>,[]},1000,[]}]">>
  129. = do_get_body("/args/parse_header/accept", [{<<"accept">>, "text/html"}], Config),
  130. <<"[{{<<\"text\">>,<<\"html\">>,[]},1000,[]}]">>
  131. = do_get_body("/args/parse_header/accept/default", [{<<"accept">>, "text/html"}], Config),
  132. %% Header not in request but with default defined by Cowboy.
  133. <<"0">> = do_get_body("/args/parse_header/content-length", Config),
  134. %% Header not in request and no default from Cowboy.
  135. <<"undefined">> = do_get_body("/args/parse_header/upgrade", Config),
  136. %% Header in request and with default provided.
  137. <<"100-continue">> = do_get_body("/args/parse_header/expect/100-continue", Config),
  138. ok.
  139. %% @todo Do we really want a key/value list here instead of a map?
  140. parse_qs(Config) ->
  141. doc("Parsed request URI query string."),
  142. <<"[]">> = do_get_body("/parse_qs", Config),
  143. <<"[{<<\"abc\">>,true}]">> = do_get_body("/parse_qs?abc", Config),
  144. <<"[{<<\"a\">>,<<\"b\">>},{<<\"c\">>,<<\"d e\">>}]">> = do_get_body("/parse_qs?a=b&c=d+e", Config),
  145. ok.
  146. path(Config) ->
  147. doc("Request URI path."),
  148. <<"/path/to/the/resource">> = do_get_body("/path/to/the/resource", Config),
  149. <<"/path/to/the/resource">> = do_get_body("/path/to/the/resource?query", Config),
  150. <<"/path/to/the/resource">> = do_get_body("/path/to/the/resource?query#fragment", Config),
  151. <<"/path/to/the/resource">> = do_get_body("/path/to/the/resource#fragment", Config),
  152. ok.
  153. path_info(Config) ->
  154. doc("Request path_info."),
  155. <<"undefined">> = do_get_body("/no/path_info", Config),
  156. <<"[]">> = do_get_body("/path_info", Config),
  157. <<"[]">> = do_get_body("/path_info/", Config),
  158. <<"[<<\"to\">>,<<\"the\">>,<<\"resource\">>]">> = do_get_body("/path_info/to/the/resource", Config),
  159. <<"[<<\"to\">>,<<\"the\">>,<<\"resource\">>]">> = do_get_body("/path_info/to/the/resource?query", Config),
  160. <<"[<<\"to\">>,<<\"the\">>,<<\"resource\">>]">> = do_get_body("/path_info/to/the/resource?query#fragment", Config),
  161. <<"[<<\"to\">>,<<\"the\">>,<<\"resource\">>]">> = do_get_body("/path_info/to/the/resource#fragment", Config),
  162. ok.
  163. peer(Config) ->
  164. doc("Request peer."),
  165. <<"{{127,0,0,1},", _/bits >> = do_get_body("/peer", Config),
  166. ok.
  167. port(Config) ->
  168. doc("Request URI port."),
  169. Port = integer_to_binary(config(port, Config)),
  170. Port = do_get_body("/port", Config),
  171. ok.
  172. qs(Config) ->
  173. doc("Request URI query string."),
  174. <<>> = do_get_body("/qs", Config),
  175. <<"abc">> = do_get_body("/qs?abc", Config),
  176. <<"a=b&c=d+e">> = do_get_body("/qs?a=b&c=d+e", Config),
  177. ok.
  178. scheme(Config) ->
  179. doc("Request URI scheme."),
  180. Transport = config(type, Config),
  181. case do_get_body("/scheme", Config) of
  182. <<"http">> when Transport =:= tcp -> ok;
  183. <<"https">> when Transport =:= ssl -> ok
  184. end.
  185. uri(Config) ->
  186. doc("Request URI building/modification."),
  187. Scheme = case config(type, Config) of
  188. tcp -> <<"http">>;
  189. ssl -> <<"https">>
  190. end,
  191. SLen = byte_size(Scheme),
  192. Port = integer_to_binary(config(port, Config)),
  193. PLen = byte_size(Port),
  194. %% Absolute form.
  195. << Scheme:SLen/binary, "://localhost:", Port:PLen/binary, "/uri?qs" >>
  196. = do_get_body("/uri?qs", Config),
  197. %% Origin form.
  198. << "/uri/origin?qs" >> = do_get_body("/uri/origin?qs", Config),
  199. %% Protocol relative.
  200. << "//localhost:", Port:PLen/binary, "/uri/protocol-relative?qs" >>
  201. = do_get_body("/uri/protocol-relative?qs", Config),
  202. %% No query string.
  203. << Scheme:SLen/binary, "://localhost:", Port:PLen/binary, "/uri/no-qs" >>
  204. = do_get_body("/uri/no-qs?qs", Config),
  205. %% No path or query string.
  206. << Scheme:SLen/binary, "://localhost:", Port:PLen/binary >>
  207. = do_get_body("/uri/no-path?qs", Config),
  208. %% Changed port.
  209. << Scheme:SLen/binary, "://localhost:123/uri/set-port?qs" >>
  210. = do_get_body("/uri/set-port?qs", Config),
  211. %% This function is tested more extensively through unit tests.
  212. ok.
  213. version(Config) ->
  214. doc("Request HTTP version."),
  215. Protocol = config(protocol, Config),
  216. case do_get_body("/version", Config) of
  217. <<"HTTP/1.1">> when Protocol =:= http -> ok;
  218. <<"HTTP/2">> when Protocol =:= http2 -> ok
  219. end.