http_SUITE.erl 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209
  1. %% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.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(http_SUITE).
  16. -compile(export_all).
  17. -include_lib("common_test/include/ct.hrl").
  18. %% ct.
  19. all() ->
  20. [
  21. {group, http},
  22. {group, https},
  23. {group, http_compress},
  24. {group, https_compress},
  25. {group, onrequest},
  26. {group, onresponse},
  27. {group, onresponse_capitalize},
  28. {group, parse_host},
  29. {group, set_env}
  30. ].
  31. groups() ->
  32. Tests = [
  33. check_raw_status,
  34. check_status,
  35. chunked_response,
  36. echo_body,
  37. echo_body_max_length,
  38. echo_body_qs,
  39. echo_body_qs_max_length,
  40. error_chain_handle_after_reply,
  41. error_chain_handle_before_reply,
  42. error_handle_after_reply,
  43. error_init_after_reply,
  44. error_init_reply_handle_error,
  45. headers_dupe,
  46. http10_chunkless,
  47. http10_hostless,
  48. keepalive_max,
  49. keepalive_nl,
  50. keepalive_stream_loop,
  51. multipart,
  52. multipart_large,
  53. nc_rand,
  54. nc_zero,
  55. pipeline,
  56. pipeline_long_polling,
  57. rest_bad_accept,
  58. rest_bad_content_type,
  59. rest_expires,
  60. rest_keepalive,
  61. rest_keepalive_post,
  62. rest_missing_get_callbacks,
  63. rest_missing_put_callbacks,
  64. rest_nodelete,
  65. rest_options_default,
  66. rest_param_all,
  67. rest_patch,
  68. rest_post_charset,
  69. rest_postonly,
  70. rest_resource_etags,
  71. rest_resource_etags_if_none_match,
  72. set_resp_body,
  73. set_resp_header,
  74. set_resp_overwrite,
  75. slowloris,
  76. slowloris2,
  77. static_attribute_etag,
  78. static_function_etag,
  79. static_mimetypes_function,
  80. static_specify_file,
  81. static_specify_file_catchall,
  82. static_test_file,
  83. static_test_file_css,
  84. stream_body_set_resp,
  85. stream_body_set_resp_close,
  86. stream_body_set_resp_chunked,
  87. stream_body_set_resp_chunked10,
  88. streamed_response,
  89. te_chunked,
  90. te_chunked_chopped,
  91. te_chunked_delayed,
  92. te_chunked_split_body,
  93. te_chunked_split_crlf,
  94. te_identity
  95. ],
  96. [
  97. {http, [parallel], Tests},
  98. {https, [parallel], Tests},
  99. {http_compress, [parallel], Tests},
  100. {https_compress, [parallel], Tests},
  101. {onrequest, [parallel], [
  102. onrequest,
  103. onrequest_reply
  104. ]},
  105. {onresponse, [parallel], [
  106. onresponse_crash,
  107. onresponse_reply
  108. ]},
  109. {onresponse_capitalize, [parallel], [
  110. onresponse_capitalize
  111. ]},
  112. {parse_host, [], [
  113. parse_host
  114. ]},
  115. {set_env, [], [
  116. set_env_dispatch
  117. ]}
  118. ].
  119. init_per_suite(Config) ->
  120. application:start(crypto),
  121. application:start(asn1),
  122. application:start(public_key),
  123. application:start(ssl),
  124. application:start(ranch),
  125. application:start(gun),
  126. application:start(cowlib),
  127. application:start(cowboy),
  128. Dir = ?config(priv_dir, Config) ++ "/static",
  129. ct_helper:create_static_dir(Dir),
  130. [{static_dir, Dir}|Config].
  131. end_per_suite(Config) ->
  132. Dir = ?config(static_dir, Config),
  133. ct_helper:delete_static_dir(Dir),
  134. application:stop(cowboy),
  135. application:stop(cowlib),
  136. application:stop(gun),
  137. application:stop(ranch),
  138. application:stop(ssl),
  139. application:stop(public_key),
  140. application:stop(asn1),
  141. application:stop(crypto),
  142. ok.
  143. init_tcp_group(Ref, ProtoOpts, Config) ->
  144. Transport = ranch_tcp,
  145. {ok, _} = cowboy:start_http(Ref, 100, [{port, 0}], [
  146. {env, [{dispatch, init_dispatch(Config)}]},
  147. {max_keepalive, 50},
  148. {timeout, 500}
  149. |ProtoOpts]),
  150. Port = ranch:get_port(Ref),
  151. [{type, tcp}, {port, Port}, {opts, []}, {transport, Transport}|Config].
  152. init_ssl_group(Ref, ProtoOpts, Config) ->
  153. Transport = ranch_ssl,
  154. {_, Cert, Key} = ct_helper:make_certs(),
  155. Opts = [{cert, Cert}, {key, Key}],
  156. {ok, _} = cowboy:start_https(Ref, 100, Opts ++ [{port, 0}], [
  157. {env, [{dispatch, init_dispatch(Config)}]},
  158. {max_keepalive, 50},
  159. {timeout, 500}
  160. |ProtoOpts]),
  161. Port = ranch:get_port(Ref),
  162. [{type, ssl}, {port, Port}, {opts, Opts}, {transport, Transport}|Config].
  163. init_per_group(http, Config) ->
  164. init_tcp_group(http, [], Config);
  165. init_per_group(https, Config) ->
  166. init_ssl_group(https, [], Config);
  167. init_per_group(http_compress, Config) ->
  168. init_tcp_group(http_compress, [{compress, true}], Config);
  169. init_per_group(https_compress, Config) ->
  170. init_ssl_group(https_compress, [{compress, true}], Config);
  171. %% Most, if not all of these, should be in separate test suites.
  172. init_per_group(onrequest, Config) ->
  173. Transport = ranch_tcp,
  174. {ok, _} = cowboy:start_http(onrequest, 100, [{port, 0}], [
  175. {env, [{dispatch, init_dispatch(Config)}]},
  176. {max_keepalive, 50},
  177. {onrequest, fun onrequest_hook/1},
  178. {timeout, 500}
  179. ]),
  180. Port = ranch:get_port(onrequest),
  181. [{scheme, <<"http">>}, {type, tcp}, {port, Port}, {opts, []},
  182. {transport, Transport}|Config];
  183. init_per_group(onresponse, Config) ->
  184. Transport = ranch_tcp,
  185. {ok, _} = cowboy:start_http(onresponse, 100, [{port, 0}], [
  186. {env, [{dispatch, init_dispatch(Config)}]},
  187. {max_keepalive, 50},
  188. {onresponse, fun onresponse_hook/4},
  189. {timeout, 500}
  190. ]),
  191. Port = ranch:get_port(onresponse),
  192. [{scheme, <<"http">>}, {type, tcp}, {port, Port}, {opts, []},
  193. {transport, Transport}|Config];
  194. init_per_group(onresponse_capitalize, Config) ->
  195. Transport = ranch_tcp,
  196. {ok, _} = cowboy:start_http(onresponse_capitalize, 100, [{port, 0}], [
  197. {env, [{dispatch, init_dispatch(Config)}]},
  198. {max_keepalive, 50},
  199. {onresponse, fun onresponse_capitalize_hook/4},
  200. {timeout, 500}
  201. ]),
  202. Port = ranch:get_port(onresponse_capitalize),
  203. [{scheme, <<"http">>}, {type, tcp}, {port, Port}, {opts, []},
  204. {transport, Transport}|Config];
  205. init_per_group(parse_host, Config) ->
  206. Transport = ranch_tcp,
  207. Dispatch = cowboy_router:compile([
  208. {'_', [
  209. {"/req_attr", http_req_attr, []}
  210. ]}
  211. ]),
  212. {ok, _} = cowboy:start_http(http, 100, [{port, 0}], [
  213. {env, [{dispatch, Dispatch}]},
  214. {max_keepalive, 50},
  215. {timeout, 500}
  216. ]),
  217. Port = ranch:get_port(http),
  218. [{scheme, <<"http">>}, {type, tcp}, {port, Port}, {opts, []},
  219. {transport, Transport}|Config];
  220. init_per_group(set_env, Config) ->
  221. Transport = ranch_tcp,
  222. {ok, _} = cowboy:start_http(set_env, 100, [{port, 0}], [
  223. {env, [{dispatch, []}]},
  224. {max_keepalive, 50},
  225. {timeout, 500}
  226. ]),
  227. Port = ranch:get_port(set_env),
  228. [{scheme, <<"http">>}, {type, tcp}, {port, Port}, {opts, []},
  229. {transport, Transport}|Config].
  230. end_per_group(Name, _) ->
  231. cowboy:stop_listener(Name),
  232. ok.
  233. %% Dispatch configuration.
  234. init_dispatch(Config) ->
  235. cowboy_router:compile([
  236. {"localhost", [
  237. {"/chunked_response", http_chunked, []},
  238. {"/streamed_response", http_streamed, []},
  239. {"/init_shutdown", http_init_shutdown, []},
  240. {"/long_polling", http_long_polling, []},
  241. {"/headers/dupe", http_handler,
  242. [{headers, [{<<"connection">>, <<"close">>}]}]},
  243. {"/set_resp/header", http_set_resp,
  244. [{headers, [{<<"vary">>, <<"Accept">>}]}]},
  245. {"/set_resp/overwrite", http_set_resp,
  246. [{headers, [{<<"server">>, <<"DesireDrive/1.0">>}]}]},
  247. {"/set_resp/body", http_set_resp,
  248. [{body, <<"A flameless dance does not equal a cycle">>}]},
  249. {"/stream_body/set_resp", http_stream_body,
  250. [{reply, set_resp}, {body, <<"stream_body_set_resp">>}]},
  251. {"/stream_body/set_resp_close",
  252. http_stream_body, [
  253. {reply, set_resp_close},
  254. {body, <<"stream_body_set_resp_close">>}]},
  255. {"/stream_body/set_resp_chunked",
  256. http_stream_body, [
  257. {reply, set_resp_chunked},
  258. {body, [<<"stream_body">>, <<"_set_resp_chunked">>]}]},
  259. {"/static/[...]", cowboy_static,
  260. {dir, ?config(static_dir, Config)}},
  261. {"/static_mimetypes_function/[...]", cowboy_static,
  262. {dir, ?config(static_dir, Config),
  263. [{mimetypes, ?MODULE, mimetypes_text_html}]}},
  264. {"/handler_errors", http_errors, []},
  265. {"/static_attribute_etag/[...]", cowboy_static,
  266. {dir, ?config(static_dir, Config)}},
  267. {"/static_function_etag/[...]", cowboy_static,
  268. {dir, ?config(static_dir, Config),
  269. [{etag, ?MODULE, etag_gen}]}},
  270. {"/static_specify_file/[...]", cowboy_static,
  271. {file, ?config(static_dir, Config) ++ "/style.css"}},
  272. {"/multipart", http_multipart, []},
  273. {"/multipart/large", http_multipart_stream, []},
  274. {"/echo/body", http_echo_body, []},
  275. {"/echo/body_qs", http_body_qs, []},
  276. {"/param_all", rest_param_all, []},
  277. {"/bad_accept", rest_simple_resource, []},
  278. {"/bad_content_type", rest_patch_resource, []},
  279. {"/simple", rest_simple_resource, []},
  280. {"/forbidden_post", rest_forbidden_resource, [true]},
  281. {"/simple_post", rest_forbidden_resource, [false]},
  282. {"/missing_get_callbacks", rest_missing_callbacks, []},
  283. {"/missing_put_callbacks", rest_missing_callbacks, []},
  284. {"/nodelete", rest_nodelete_resource, []},
  285. {"/post_charset", rest_post_charset_resource, []},
  286. {"/postonly", rest_postonly_resource, []},
  287. {"/patch", rest_patch_resource, []},
  288. {"/resetags", rest_resource_etags, []},
  289. {"/rest_expires", rest_expires, []},
  290. {"/rest_empty_resource", rest_empty_resource, []},
  291. {"/loop_recv", http_loop_recv, []},
  292. {"/loop_stream_recv", http_loop_stream_recv, []},
  293. {"/loop_timeout", http_loop_timeout, []},
  294. {"/", http_handler, []}
  295. ]}
  296. ]).
  297. etag_gen(_, _, _) ->
  298. {strong, <<"etag">>}.
  299. mimetypes_text_html(_) ->
  300. <<"text/html">>.
  301. %% Support functions for testing using Gun.
  302. gun_open(Config) ->
  303. gun_open(Config, []).
  304. gun_open(Config, Opts) ->
  305. {_, Port} = lists:keyfind(port, 1, Config),
  306. {_, Type} = lists:keyfind(type, 1, Config),
  307. {ok, ConnPid} = gun:open("localhost", Port, [{retry, 0}, {type, Type}|Opts]),
  308. ConnPid.
  309. gun_monitor_open(Config) ->
  310. gun_monitor_open(Config, []).
  311. gun_monitor_open(Config, Opts) ->
  312. ConnPid = gun_open(Config, Opts),
  313. {ConnPid, monitor(process, ConnPid)}.
  314. gun_is_gone(ConnPid) ->
  315. gun_is_gone(ConnPid, monitor(process, ConnPid)).
  316. gun_is_gone(ConnPid, MRef) ->
  317. receive {'DOWN', MRef, process, ConnPid, gone} -> ok
  318. after 500 -> error(timeout) end.
  319. %% Support functions for testing using a raw socket.
  320. raw_open(Config) ->
  321. {_, Port} = lists:keyfind(port, 1, Config),
  322. {_, Type} = lists:keyfind(type, 1, Config),
  323. Transport = case Type of
  324. tcp -> gen_tcp;
  325. ssl -> ssl
  326. end,
  327. {_, Opts} = lists:keyfind(opts, 1, Config),
  328. {ok, Socket} = Transport:connect("localhost", Port,
  329. [binary, {active, false}, {packet, raw},
  330. {reuseaddr, true}, {nodelay, true}|Opts]),
  331. {raw_client, Socket, Transport}.
  332. raw_send({raw_client, Socket, Transport}, Data) ->
  333. Transport:send(Socket, Data).
  334. raw_recv_head({raw_client, Socket, Transport}) ->
  335. {ok, Data} = Transport:recv(Socket, 0, 5000),
  336. raw_recv_head(Socket, Transport, Data).
  337. raw_recv_head(Socket, Transport, Buffer) ->
  338. case binary:match(Buffer, <<"\r\n\r\n">>) of
  339. nomatch ->
  340. {ok, Data} = Transport:recv(Socket, 0, 5000),
  341. raw_recv_head(Socket, Transport, << Buffer/binary, Data/binary >>);
  342. {_, _} ->
  343. Buffer
  344. end.
  345. raw_expect_recv({raw_client, Socket, Transport}, Expect) ->
  346. {ok, Expect} = Transport:recv(Socket, iolist_size(Expect), 5000),
  347. ok.
  348. %% Convenience functions.
  349. quick_raw(Data, Config) ->
  350. Client = raw_open(Config),
  351. ok = raw_send(Client, Data),
  352. case catch raw_recv_head(Client) of
  353. {'EXIT', _} -> closed;
  354. Resp -> element(2, cow_http:parse_status_line(Resp))
  355. end.
  356. quick_get(Path, Config) ->
  357. ConnPid = gun_open(Config),
  358. Ref = gun:get(ConnPid, Path),
  359. {response, _, Status, _} = gun:await(ConnPid, Ref),
  360. gun:close(ConnPid),
  361. Status.
  362. %% Tests.
  363. check_raw_status(Config) ->
  364. Huge = [$0 || _ <- lists:seq(1, 5000)],
  365. HugeCookie = lists:flatten(["whatever_man_biiiiiiiiiiiig_cookie_me_want_77="
  366. "Wed Apr 06 2011 10:38:52 GMT-0500 (CDT)" || _ <- lists:seq(1, 40)]),
  367. ResponsePacket =
  368. "HTTP/1.0 302 Found\r
  369. Location: http://www.google.co.il/\r
  370. Cache-Control: private\r
  371. Content-Type: text/html; charset=UTF-8\r
  372. Set-Cookie: PREF=ID=568f67013d4a7afa:FF=0:TM=1323014101:LM=1323014101:S=XqctDWC65MzKT0zC; expires=Tue, 03-Dec-2013 15:55:01 GMT; path=/; domain=.google.com\r
  373. Date: Sun, 04 Dec 2011 15:55:01 GMT\r
  374. Server: gws\r
  375. Content-Length: 221\r
  376. X-XSS-Protection: 1; mode=block\r
  377. X-Frame-Options: SAMEORIGIN\r
  378. \r
  379. <HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">
  380. <TITLE>302 Moved</TITLE></HEAD><BODY>
  381. <H1>302 Moved</H1>
  382. The document has moved
  383. <A HREF=\"http://www.google.co.il/\">here</A>.
  384. </BODY></HTML>",
  385. Tests = [
  386. {102, <<"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n"
  387. "Content-Length: 5000\r\n\r\n", 0:5000/unit:8 >>},
  388. {200, ["GET / HTTP/1.0\r\nHost: localhost\r\n"
  389. "Set-Cookie: ", HugeCookie, "\r\n\r\n"]},
  390. {200, "\r\n\r\n\r\n\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n"},
  391. {200, "GET http://proxy/ HTTP/1.1\r\nHost: localhost\r\n\r\n"},
  392. {200, <<"POST /loop_recv HTTP/1.1\r\nHost: localhost\r\n"
  393. "Content-Length: 100000\r\n\r\n", 0:100000/unit:8 >>},
  394. {400, "\n"},
  395. {400, "Garbage\r\n\r\n"},
  396. {400, "\r\n\r\n\r\n\r\n\r\n\r\n"},
  397. {400, "GET / HTTP/1.1\r\nHost: ninenines.eu\r\n\r\n"},
  398. {400, "GET http://proxy/ HTTP/1.1\r\n\r\n"},
  399. {400, "GET / HTTP/1.1\r\nHost: localhost:bad_port\r\n\r\n"},
  400. {505, ResponsePacket},
  401. {408, "GET / HTTP/1.1\r\n"},
  402. {408, "GET / HTTP/1.1\r\nHost: localhost"},
  403. {408, "GET / HTTP/1.1\r\nHost: localhost\r\n"},
  404. {408, "GET / HTTP/1.1\r\nHost: localhost\r\n\r"},
  405. {414, Huge},
  406. {400, "GET / HTTP/1.1\r\n" ++ Huge},
  407. {500, <<"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n"
  408. "Content-Length: 100000\r\n\r\n", 0:100000/unit:8 >>},
  409. {505, "GET / HTTP/1.2\r\nHost: localhost\r\n\r\n"},
  410. {closed, ""},
  411. {closed, "\r\n"},
  412. {closed, "\r\n\r\n"},
  413. {closed, "GET / HTTP/1.1"}
  414. ],
  415. _ = [{Status, Packet} = begin
  416. Ret = quick_raw(Packet, Config),
  417. {Ret, Packet}
  418. end || {Status, Packet} <- Tests],
  419. ok.
  420. check_status(Config) ->
  421. Tests = [
  422. {102, "/long_polling"},
  423. {200, "/"},
  424. {200, "/simple"},
  425. {204, "/loop_timeout"},
  426. {400, "/static/%2f"},
  427. {400, "/static/%2e"},
  428. {400, "/static/%2e%2e"},
  429. {403, "/static/directory"},
  430. {403, "/static/directory/"},
  431. {403, "/static/unreadable"},
  432. {404, "/not/found"},
  433. {404, "/static/not_found"},
  434. {500, "/handler_errors?case=handle_before_reply"},
  435. {500, "/handler_errors?case=init_before_reply"},
  436. {666, "/init_shutdown"}
  437. ],
  438. _ = [{Status, URL} = begin
  439. Ret = quick_get(URL, Config),
  440. {Ret, URL}
  441. end || {Status, URL} <- Tests].
  442. chunked_response(Config) ->
  443. ConnPid = gun_open(Config),
  444. Ref = gun:get(ConnPid, "/chunked_response"),
  445. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  446. true = lists:keymember(<<"transfer-encoding">>, 1, Headers),
  447. {ok, <<"chunked_handler\r\nworks fine!">>} = gun:await_body(ConnPid, Ref),
  448. ok.
  449. %% Check if sending requests whose size is around the MTU breaks something.
  450. echo_body(Config) ->
  451. MTU = ct_helper:get_loopback_mtu(),
  452. _ = [begin
  453. Body = list_to_binary(lists:duplicate(Size, $a)),
  454. ConnPid = gun_open(Config),
  455. Ref = gun:post(ConnPid, "/echo/body", [], Body),
  456. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  457. {ok, Body} = gun:await_body(ConnPid, Ref)
  458. end || Size <- lists:seq(MTU - 500, MTU)],
  459. ok.
  460. %% Check if sending request whose size is bigger than 1000000 bytes causes 413
  461. echo_body_max_length(Config) ->
  462. ConnPid = gun_open(Config),
  463. Ref = gun:post(ConnPid, "/echo/body", [], << 0:8000008 >>),
  464. {response, nofin, 413, _} = gun:await(ConnPid, Ref),
  465. ok.
  466. % check if body_qs echo's back results
  467. echo_body_qs(Config) ->
  468. ConnPid = gun_open(Config),
  469. Ref = gun:post(ConnPid, "/echo/body_qs", [], <<"echo=67890">>),
  470. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  471. {ok, <<"67890">>} = gun:await_body(ConnPid, Ref),
  472. ok.
  473. echo_body_qs_max_length(Config) ->
  474. ConnPid = gun_open(Config),
  475. Ref = gun:post(ConnPid, "/echo/body_qs", [], << "echo=", 0:15996/unit:8 >>),
  476. {response, nofin, 413, _} = gun:await(ConnPid, Ref),
  477. ok.
  478. error_chain_handle_after_reply(Config) ->
  479. {ConnPid, MRef} = gun_monitor_open(Config),
  480. Ref1 = gun:get(ConnPid, "/"),
  481. Ref2 = gun:get(ConnPid, "/handler_errors?case=handle_after_reply"),
  482. {response, nofin, 200, _} = gun:await(ConnPid, Ref1, MRef),
  483. {response, nofin, 200, _} = gun:await(ConnPid, Ref2, MRef),
  484. gun_is_gone(ConnPid, MRef).
  485. error_chain_handle_before_reply(Config) ->
  486. {ConnPid, MRef} = gun_monitor_open(Config),
  487. Ref1 = gun:get(ConnPid, "/"),
  488. Ref2 = gun:get(ConnPid, "/handler_errors?case=handle_before_reply"),
  489. {response, nofin, 200, _} = gun:await(ConnPid, Ref1, MRef),
  490. {response, fin, 500, _} = gun:await(ConnPid, Ref2, MRef),
  491. gun_is_gone(ConnPid, MRef).
  492. error_handle_after_reply(Config) ->
  493. {ConnPid, MRef} = gun_monitor_open(Config),
  494. Ref = gun:get(ConnPid, "/handler_errors?case=handle_after_reply"),
  495. {response, nofin, 200, _} = gun:await(ConnPid, Ref, MRef),
  496. gun_is_gone(ConnPid, MRef).
  497. error_init_after_reply(Config) ->
  498. {ConnPid, MRef} = gun_monitor_open(Config),
  499. Ref = gun:get(ConnPid, "/handler_errors?case=init_after_reply"),
  500. {response, nofin, 200, _} = gun:await(ConnPid, Ref, MRef),
  501. gun_is_gone(ConnPid, MRef).
  502. error_init_reply_handle_error(Config) ->
  503. {ConnPid, MRef} = gun_monitor_open(Config),
  504. Ref = gun:get(ConnPid, "/handler_errors?case=init_reply_handle_error"),
  505. {response, nofin, 200, _} = gun:await(ConnPid, Ref, MRef),
  506. gun_is_gone(ConnPid, MRef).
  507. headers_dupe(Config) ->
  508. {ConnPid, MRef} = gun_monitor_open(Config),
  509. Ref = gun:get(ConnPid, "/headers/dupe"),
  510. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref, MRef),
  511. %% Ensure that only one connection header was received.
  512. [<<"close">>] = [V || {Name, V} <- Headers, Name =:= <<"connection">>],
  513. gun_is_gone(ConnPid, MRef).
  514. http10_chunkless(Config) ->
  515. {ConnPid, MRef} = gun_monitor_open(Config, [{http, [{version, 'HTTP/1.0'}]}]),
  516. Ref = gun:get(ConnPid, "/chunked_response"),
  517. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref, MRef),
  518. false = lists:keyfind(<<"transfer-encoding">>, 1, Headers),
  519. {ok, <<"chunked_handler\r\nworks fine!">>} = gun:await_body(ConnPid, Ref, MRef),
  520. gun_is_gone(ConnPid, MRef).
  521. http10_hostless(Config) ->
  522. Port10 = ?config(port, Config) + 10,
  523. Name = list_to_atom("http10_hostless_" ++ integer_to_list(Port10)),
  524. ranch:start_listener(Name, 5,
  525. ?config(transport, Config), ?config(opts, Config) ++ [{port, Port10}],
  526. cowboy_protocol, [
  527. {env, [{dispatch, cowboy_router:compile([
  528. {'_', [{"/http1.0/hostless", http_handler, []}]}])}]},
  529. {max_keepalive, 50},
  530. {timeout, 500}]
  531. ),
  532. 200 = quick_raw("GET /http1.0/hostless HTTP/1.0\r\n\r\n",
  533. [{port, Port10}|Config]),
  534. cowboy:stop_listener(http10).
  535. keepalive_max(Config) ->
  536. {ConnPid, MRef} = gun_monitor_open(Config),
  537. Refs = [gun:get(ConnPid, "/", [{<<"connection">>, <<"keep-alive">>}])
  538. || _ <- lists:seq(1, 49)],
  539. CloseRef = gun:get(ConnPid, "/", [{<<"connection">>, <<"keep-alive">>}]),
  540. _ = [begin
  541. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref, MRef),
  542. {_, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, Headers)
  543. end || Ref <- Refs],
  544. {response, nofin, 200, Headers} = gun:await(ConnPid, CloseRef, MRef),
  545. {_, <<"close">>} = lists:keyfind(<<"connection">>, 1, Headers),
  546. gun_is_gone(ConnPid, MRef).
  547. keepalive_nl(Config) ->
  548. ConnPid = gun_open(Config),
  549. Refs = [begin
  550. Ref = gun:get(ConnPid, "/", [{<<"connection">>, <<"keep-alive">>}]),
  551. gun:dbg_send_raw(ConnPid, <<"\r\n">>),
  552. Ref
  553. end || _ <- lists:seq(1, 10)],
  554. _ = [begin
  555. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  556. {_, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, Headers)
  557. end || Ref <- Refs],
  558. ok.
  559. keepalive_stream_loop(Config) ->
  560. ConnPid = gun_open(Config),
  561. Refs = [begin
  562. Ref = gun:post(ConnPid, "/loop_stream_recv",
  563. [{<<"transfer-encoding">>, <<"chunked">>}]),
  564. _ = [gun:data(ConnPid, Ref, nofin, << ID:32 >>)
  565. || ID <- lists:seq(1, 250)],
  566. gun:data(ConnPid, Ref, fin, <<>>),
  567. Ref
  568. end || _ <- lists:seq(1, 10)],
  569. _ = [begin
  570. {response, fin, 200, _} = gun:await(ConnPid, Ref)
  571. end || Ref <- Refs],
  572. ok.
  573. multipart(Config) ->
  574. ConnPid = gun_open(Config),
  575. Body = <<
  576. "This is a preamble."
  577. "\r\n--OHai\r\nX-Name:answer\r\n\r\n42"
  578. "\r\n--OHai\r\nServer:Cowboy\r\n\r\nIt rocks!\r\n"
  579. "\r\n--OHai--\r\n"
  580. "This is an epilogue."
  581. >>,
  582. Ref = gun:post(ConnPid, "/multipart",
  583. [{<<"content-type">>, <<"multipart/x-makes-no-sense; boundary=OHai">>}],
  584. Body),
  585. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  586. {ok, RespBody} = gun:await_body(ConnPid, Ref),
  587. Parts = binary_to_term(RespBody),
  588. Parts = [
  589. {[{<<"x-name">>, <<"answer">>}], <<"42">>},
  590. {[{<<"server">>, <<"Cowboy">>}], <<"It rocks!\r\n">>}
  591. ],
  592. ok.
  593. multipart_large(Config) ->
  594. ConnPid = gun_open(Config),
  595. Boundary = "----------",
  596. Big = << 0:9000000/unit:8 >>,
  597. Bigger = << 0:9999999/unit:8 >>,
  598. Body = ["--", Boundary, "\r\ncontent-length: 9000000\r\n\r\n", Big, "\r\n",
  599. "--", Boundary, "\r\ncontent-length: 9999999\r\n\r\n", Bigger, "\r\n",
  600. "--", Boundary, "--\r\n"],
  601. Ref = gun:post(ConnPid, "/multipart/large",
  602. [{<<"content-type">>, ["multipart/x-large; boundary=", Boundary]}],
  603. Body),
  604. {response, fin, 200, _} = gun:await(ConnPid, Ref),
  605. ok.
  606. nc_reqs(Config, Input) ->
  607. Cat = os:find_executable("cat"),
  608. Nc = os:find_executable("nc"),
  609. case {Cat, Nc} of
  610. {false, _} ->
  611. {skip, {notfound, cat}};
  612. {_, false} ->
  613. {skip, {notfound, nc}};
  614. _Good ->
  615. %% Throw garbage at the server then check if it's still up.
  616. {port, Port} = lists:keyfind(port, 1, Config),
  617. StrPort = integer_to_list(Port),
  618. [os:cmd("cat " ++ Input ++ " | nc localhost " ++ StrPort)
  619. || _ <- lists:seq(1, 100)],
  620. 200 = quick_get("/", Config)
  621. end.
  622. nc_rand(Config) ->
  623. nc_reqs(Config, "/dev/urandom").
  624. nc_zero(Config) ->
  625. nc_reqs(Config, "/dev/zero").
  626. onrequest(Config) ->
  627. ConnPid = gun_open(Config),
  628. Ref = gun:get(ConnPid, "/"),
  629. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  630. {<<"server">>, <<"Serenity">>} = lists:keyfind(<<"server">>, 1, Headers),
  631. {ok, <<"http_handler">>} = gun:await_body(ConnPid, Ref),
  632. ok.
  633. onrequest_reply(Config) ->
  634. ConnPid = gun_open(Config),
  635. Ref = gun:get(ConnPid, "/?reply=1"),
  636. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  637. {<<"server">>, <<"Cowboy">>} = lists:keyfind(<<"server">>, 1, Headers),
  638. {ok, <<"replied!">>} = gun:await_body(ConnPid, Ref),
  639. ok.
  640. %% Hook for the above onrequest tests.
  641. onrequest_hook(Req) ->
  642. case cowboy_req:qs_val(<<"reply">>, Req) of
  643. {undefined, Req2} ->
  644. cowboy_req:set_resp_header(<<"server">>, <<"Serenity">>, Req2);
  645. {_, Req2} ->
  646. {ok, Req3} = cowboy_req:reply(
  647. 200, [], <<"replied!">>, Req2),
  648. Req3
  649. end.
  650. onresponse_capitalize(Config) ->
  651. Client = raw_open(Config),
  652. ok = raw_send(Client, "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"),
  653. Data = raw_recv_head(Client),
  654. false = nomatch =:= binary:match(Data, <<"Content-Length">>),
  655. ok.
  656. %% Hook for the above onresponse_capitalize test.
  657. onresponse_capitalize_hook(Status, Headers, Body, Req) ->
  658. Headers2 = [{cowboy_bstr:capitalize_token(N), V}
  659. || {N, V} <- Headers],
  660. {ok, Req2} = cowboy_req:reply(Status, Headers2, Body, Req),
  661. Req2.
  662. onresponse_crash(Config) ->
  663. ConnPid = gun_open(Config),
  664. Ref = gun:get(ConnPid, "/handler_errors?case=init_before_reply"),
  665. {response, fin, 777, Headers} = gun:await(ConnPid, Ref),
  666. {<<"x-hook">>, <<"onresponse">>} = lists:keyfind(<<"x-hook">>, 1, Headers).
  667. onresponse_reply(Config) ->
  668. ConnPid = gun_open(Config),
  669. Ref = gun:get(ConnPid, "/"),
  670. {response, nofin, 777, Headers} = gun:await(ConnPid, Ref),
  671. {<<"x-hook">>, <<"onresponse">>} = lists:keyfind(<<"x-hook">>, 1, Headers),
  672. ok.
  673. %% Hook for the above onresponse tests.
  674. onresponse_hook(_, Headers, _, Req) ->
  675. {ok, Req2} = cowboy_req:reply(
  676. <<"777 Lucky">>, [{<<"x-hook">>, <<"onresponse">>}|Headers], Req),
  677. Req2.
  678. parse_host(Config) ->
  679. ConnPid = gun_open(Config),
  680. Tests = [
  681. {<<"example.org:8080">>, <<"example.org\n8080">>},
  682. {<<"example.org">>, <<"example.org\n80">>},
  683. {<<"192.0.2.1:8080">>, <<"192.0.2.1\n8080">>},
  684. {<<"192.0.2.1">>, <<"192.0.2.1\n80">>},
  685. {<<"[2001:db8::1]:8080">>, <<"[2001:db8::1]\n8080">>},
  686. {<<"[2001:db8::1]">>, <<"[2001:db8::1]\n80">>},
  687. {<<"[::ffff:192.0.2.1]:8080">>, <<"[::ffff:192.0.2.1]\n8080">>},
  688. {<<"[::ffff:192.0.2.1]">>, <<"[::ffff:192.0.2.1]\n80">>}
  689. ],
  690. [begin
  691. Ref = gun:get(ConnPid, "/req_attr?attr=host_and_port",
  692. [{<<"host">>, Host}]),
  693. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  694. {ok, Body} = gun:await_body(ConnPid, Ref)
  695. end || {Host, Body} <- Tests],
  696. ok.
  697. pipeline(Config) ->
  698. ConnPid = gun_open(Config),
  699. Refs = [gun:get(ConnPid, "/") || _ <- lists:seq(1, 5)],
  700. _ = [{response, nofin, 200, _} = gun:await(ConnPid, Ref) || Ref <- Refs],
  701. ok.
  702. pipeline_long_polling(Config) ->
  703. ConnPid = gun_open(Config),
  704. Refs = [gun:get(ConnPid, "/long_polling") || _ <- lists:seq(1, 2)],
  705. _ = [{response, fin, 102, _} = gun:await(ConnPid, Ref) || Ref <- Refs],
  706. ok.
  707. rest_param_all(Config) ->
  708. ConnPid = gun_open(Config),
  709. %% Accept without param.
  710. Ref1 = gun:get(ConnPid, "/param_all",
  711. [{<<"accept">>, <<"text/plain">>}]),
  712. {response, nofin, 200, _} = gun:await(ConnPid, Ref1),
  713. {ok, <<"[]">>} = gun:await_body(ConnPid, Ref1),
  714. %% Accept with param.
  715. Ref2 = gun:get(ConnPid, "/param_all",
  716. [{<<"accept">>, <<"text/plain;level=1">>}]),
  717. {response, nofin, 200, _} = gun:await(ConnPid, Ref2),
  718. {ok, <<"level=1">>} = gun:await_body(ConnPid, Ref2),
  719. %% Accept with param and quality.
  720. Ref3 = gun:get(ConnPid, "/param_all",
  721. [{<<"accept">>, <<"text/plain;level=1;q=0.8, text/plain;level=2;q=0.5">>}]),
  722. {response, nofin, 200, _} = gun:await(ConnPid, Ref3),
  723. {ok, <<"level=1">>} = gun:await_body(ConnPid, Ref3),
  724. Ref4 = gun:get(ConnPid, "/param_all",
  725. [{<<"accept">>, <<"text/plain;level=1;q=0.5, text/plain;level=2;q=0.8">>}]),
  726. {response, nofin, 200, _} = gun:await(ConnPid, Ref4),
  727. {ok, <<"level=2">>} = gun:await_body(ConnPid, Ref4),
  728. %% Without Accept.
  729. Ref5 = gun:get(ConnPid, "/param_all"),
  730. {response, nofin, 200, _} = gun:await(ConnPid, Ref5),
  731. {ok, <<"'*'">>} = gun:await_body(ConnPid, Ref5),
  732. %% Content-Type without param.
  733. Ref6 = gun:put(ConnPid, "/param_all",
  734. [{<<"content-type">>, <<"text/plain">>}]),
  735. {response, fin, 204, _} = gun:await(ConnPid, Ref6),
  736. %% Content-Type with param.
  737. Ref7 = gun:put(ConnPid, "/param_all",
  738. [{<<"content-type">>, <<"text/plain; charset=utf-8">>}]),
  739. {response, fin, 204, _} = gun:await(ConnPid, Ref7),
  740. ok.
  741. rest_bad_accept(Config) ->
  742. ConnPid = gun_open(Config),
  743. Ref = gun:get(ConnPid, "/bad_accept",
  744. [{<<"accept">>, <<"1">>}]),
  745. {response, fin, 400, _} = gun:await(ConnPid, Ref),
  746. ok.
  747. rest_bad_content_type(Config) ->
  748. ConnPid = gun_open(Config),
  749. Ref = gun:patch(ConnPid, "/bad_content_type",
  750. [{<<"content-type">>, <<"text/plain, text/html">>}], <<"Whatever">>),
  751. {response, fin, 415, _} = gun:await(ConnPid, Ref),
  752. ok.
  753. rest_expires(Config) ->
  754. ConnPid = gun_open(Config),
  755. Ref = gun:get(ConnPid, "/rest_expires"),
  756. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  757. {_, Expires} = lists:keyfind(<<"expires">>, 1, Headers),
  758. {_, LastModified} = lists:keyfind(<<"last-modified">>, 1, Headers),
  759. Expires = LastModified = <<"Fri, 21 Sep 2012 22:36:14 GMT">>,
  760. ok.
  761. rest_keepalive(Config) ->
  762. ConnPid = gun_open(Config),
  763. Refs = [gun:get(ConnPid, "/simple") || _ <- lists:seq(1, 10)],
  764. _ = [begin
  765. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  766. {_, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, Headers)
  767. end || Ref <- Refs],
  768. ok.
  769. rest_keepalive_post(Config) ->
  770. ConnPid = gun_open(Config),
  771. Refs = [{
  772. gun:post(ConnPid, "/forbidden_post",
  773. [{<<"content-type">>, <<"text/plain">>}]),
  774. gun:post(ConnPid, "/simple_post",
  775. [{<<"content-type">>, <<"text/plain">>}])
  776. } || _ <- lists:seq(1, 5)],
  777. _ = [begin
  778. {response, fin, 403, Headers1} = gun:await(ConnPid, Ref1),
  779. {_, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, Headers1),
  780. {response, fin, 303, Headers2} = gun:await(ConnPid, Ref2),
  781. {_, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, Headers2)
  782. end || {Ref1, Ref2} <- Refs],
  783. ok.
  784. rest_missing_get_callbacks(Config) ->
  785. ConnPid = gun_open(Config),
  786. Ref = gun:get(ConnPid, "/missing_get_callbacks"),
  787. {response, fin, 500, _} = gun:await(ConnPid, Ref),
  788. ok.
  789. rest_missing_put_callbacks(Config) ->
  790. ConnPid = gun_open(Config),
  791. Ref = gun:put(ConnPid, "/missing_put_callbacks",
  792. [{<<"content-type">>, <<"application/json">>}], <<"{}">>),
  793. {response, fin, 500, _} = gun:await(ConnPid, Ref),
  794. ok.
  795. rest_nodelete(Config) ->
  796. ConnPid = gun_open(Config),
  797. Ref = gun:delete(ConnPid, "/nodelete"),
  798. {response, fin, 500, _} = gun:await(ConnPid, Ref),
  799. ok.
  800. rest_options_default(Config) ->
  801. ConnPid = gun_open(Config),
  802. Ref = gun:options(ConnPid, "/rest_empty_resource"),
  803. {response, fin, 200, Headers} = gun:await(ConnPid, Ref),
  804. {_, <<"HEAD, GET, OPTIONS">>} = lists:keyfind(<<"allow">>, 1, Headers),
  805. ok.
  806. rest_patch(Config) ->
  807. Tests = [
  808. {204, [{<<"content-type">>, <<"text/plain">>}], <<"whatever">>},
  809. {422, [{<<"content-type">>, <<"text/plain">>}], <<"false">>},
  810. {400, [{<<"content-type">>, <<"text/plain">>}], <<"halt">>},
  811. {415, [{<<"content-type">>, <<"application/json">>}], <<"bad_content_type">>}
  812. ],
  813. ConnPid = gun_open(Config),
  814. _ = [begin
  815. Ref = gun:patch(ConnPid, "/patch", Headers, Body),
  816. {response, fin, Status, _} = gun:await(ConnPid, Ref)
  817. end || {Status, Headers, Body} <- Tests],
  818. ok.
  819. rest_post_charset(Config) ->
  820. ConnPid = gun_open(Config),
  821. Ref = gun:post(ConnPid, "/post_charset",
  822. [{<<"content-type">>, <<"text/plain;charset=UTF-8">>}], "12345"),
  823. {response, fin, 204, _} = gun:await(ConnPid, Ref),
  824. ok.
  825. rest_postonly(Config) ->
  826. ConnPid = gun_open(Config),
  827. Ref = gun:post(ConnPid, "/postonly",
  828. [{<<"content-type">>, <<"text/plain">>}], "12345"),
  829. {response, fin, 204, _} = gun:await(ConnPid, Ref),
  830. ok.
  831. rest_resource_get_etag(Config, Type) ->
  832. rest_resource_get_etag(Config, Type, []).
  833. rest_resource_get_etag(Config, Type, Headers) ->
  834. ConnPid = gun_open(Config),
  835. Ref = gun:get(ConnPid, "/resetags?type=" ++ Type, Headers),
  836. {response, _, Status, RespHeaders} = gun:await(ConnPid, Ref),
  837. case lists:keyfind(<<"etag">>, 1, RespHeaders) of
  838. false -> {Status, false};
  839. {<<"etag">>, ETag} -> {Status, ETag}
  840. end.
  841. rest_resource_etags(Config) ->
  842. Tests = [
  843. {200, <<"W/\"etag-header-value\"">>, "tuple-weak"},
  844. {200, <<"\"etag-header-value\"">>, "tuple-strong"},
  845. {200, <<"W/\"etag-header-value\"">>, "binary-weak-quoted"},
  846. {200, <<"\"etag-header-value\"">>, "binary-strong-quoted"},
  847. {500, false, "binary-strong-unquoted"},
  848. {500, false, "binary-weak-unquoted"}
  849. ],
  850. _ = [{Status, ETag, Type} = begin
  851. {Ret, RespETag} = rest_resource_get_etag(Config, Type),
  852. {Ret, RespETag, Type}
  853. end || {Status, ETag, Type} <- Tests].
  854. rest_resource_etags_if_none_match(Config) ->
  855. Tests = [
  856. {304, <<"W/\"etag-header-value\"">>, "tuple-weak"},
  857. {304, <<"\"etag-header-value\"">>, "tuple-strong"},
  858. {304, <<"W/\"etag-header-value\"">>, "binary-weak-quoted"},
  859. {304, <<"\"etag-header-value\"">>, "binary-strong-quoted"}
  860. ],
  861. _ = [{Status, Type} = begin
  862. {Ret, _} = rest_resource_get_etag(Config, Type,
  863. [{<<"if-none-match">>, ETag}]),
  864. {Ret, Type}
  865. end || {Status, ETag, Type} <- Tests].
  866. set_env_dispatch(Config) ->
  867. ConnPid1 = gun_open(Config),
  868. Ref1 = gun:get(ConnPid1, "/"),
  869. {response, fin, 400, _} = gun:await(ConnPid1, Ref1),
  870. ok = cowboy:set_env(set_env, dispatch,
  871. cowboy_router:compile([{'_', [{"/", http_handler, []}]}])),
  872. ConnPid2 = gun_open(Config),
  873. Ref2 = gun:get(ConnPid2, "/"),
  874. {response, nofin, 200, _} = gun:await(ConnPid2, Ref2),
  875. ok.
  876. set_resp_body(Config) ->
  877. ConnPid = gun_open(Config),
  878. Ref = gun:get(ConnPid, "/set_resp/body"),
  879. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  880. {ok, <<"A flameless dance does not equal a cycle">>}
  881. = gun:await_body(ConnPid, Ref),
  882. ok.
  883. set_resp_header(Config) ->
  884. ConnPid = gun_open(Config),
  885. Ref = gun:get(ConnPid, "/set_resp/header"),
  886. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  887. {_, <<"Accept">>} = lists:keyfind(<<"vary">>, 1, Headers),
  888. {_, _} = lists:keyfind(<<"set-cookie">>, 1, Headers),
  889. ok.
  890. set_resp_overwrite(Config) ->
  891. ConnPid = gun_open(Config),
  892. Ref = gun:get(ConnPid, "/set_resp/overwrite"),
  893. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  894. {_, <<"DesireDrive/1.0">>} = lists:keyfind(<<"server">>, 1, Headers),
  895. ok.
  896. slowloris(Config) ->
  897. Client = raw_open(Config),
  898. try
  899. [begin
  900. ok = raw_send(Client, [C]),
  901. receive after 25 -> ok end
  902. end || C <- "GET / HTTP/1.1\r\nHost: localhost\r\n"
  903. "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US)\r\n"
  904. "Cookie: name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n\r\n"],
  905. error(failure)
  906. catch error:{badmatch, _} ->
  907. ok
  908. end.
  909. slowloris2(Config) ->
  910. Client = raw_open(Config),
  911. ok = raw_send(Client, "GET / HTTP/1.1\r\n"),
  912. receive after 300 -> ok end,
  913. ok = raw_send(Client, "Host: localhost\r\n"),
  914. receive after 300 -> ok end,
  915. Data = raw_recv_head(Client),
  916. {_, 408, _, _} = cow_http:parse_status_line(Data),
  917. ok.
  918. static_attribute_etag(Config) ->
  919. ConnPid = gun_open(Config),
  920. Ref1 = gun:get(ConnPid, "/static_attribute_etag/index.html"),
  921. Ref2 = gun:get(ConnPid, "/static_attribute_etag/index.html"),
  922. {response, nofin, 200, Headers1} = gun:await(ConnPid, Ref1),
  923. {response, nofin, 200, Headers2} = gun:await(ConnPid, Ref2),
  924. {_, ETag} = lists:keyfind(<<"etag">>, 1, Headers1),
  925. {_, ETag} = lists:keyfind(<<"etag">>, 1, Headers2),
  926. true = ETag =/= undefined,
  927. ok.
  928. static_function_etag(Config) ->
  929. ConnPid = gun_open(Config),
  930. Ref1 = gun:get(ConnPid, "/static_function_etag/index.html"),
  931. Ref2 = gun:get(ConnPid, "/static_function_etag/index.html"),
  932. {response, nofin, 200, Headers1} = gun:await(ConnPid, Ref1),
  933. {response, nofin, 200, Headers2} = gun:await(ConnPid, Ref2),
  934. {_, ETag} = lists:keyfind(<<"etag">>, 1, Headers1),
  935. {_, ETag} = lists:keyfind(<<"etag">>, 1, Headers2),
  936. true = ETag =/= undefined,
  937. ok.
  938. static_mimetypes_function(Config) ->
  939. ConnPid = gun_open(Config),
  940. Ref = gun:get(ConnPid, "/static_mimetypes_function/index.html"),
  941. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  942. {_, <<"text/html">>} = lists:keyfind(<<"content-type">>, 1, Headers),
  943. ok.
  944. static_specify_file(Config) ->
  945. ConnPid = gun_open(Config),
  946. Ref = gun:get(ConnPid, "/static_specify_file"),
  947. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  948. {_, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, Headers),
  949. {ok, <<"body{color:red}\n">>} = gun:await_body(ConnPid, Ref),
  950. ok.
  951. static_specify_file_catchall(Config) ->
  952. ConnPid = gun_open(Config),
  953. Ref = gun:get(ConnPid, "/static_specify_file/none"),
  954. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  955. {_, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, Headers),
  956. {ok, <<"body{color:red}\n">>} = gun:await_body(ConnPid, Ref),
  957. ok.
  958. static_test_file(Config) ->
  959. ConnPid = gun_open(Config),
  960. Ref = gun:get(ConnPid, "/static/unknown"),
  961. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  962. {_, <<"application/octet-stream">>} = lists:keyfind(<<"content-type">>, 1, Headers),
  963. ok.
  964. static_test_file_css(Config) ->
  965. ConnPid = gun_open(Config),
  966. Ref = gun:get(ConnPid, "/static/style.css"),
  967. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  968. {_, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, Headers),
  969. ok.
  970. stream_body_set_resp(Config) ->
  971. ConnPid = gun_open(Config),
  972. Ref = gun:get(ConnPid, "/stream_body/set_resp"),
  973. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  974. {ok, <<"stream_body_set_resp">>} = gun:await_body(ConnPid, Ref),
  975. ok.
  976. stream_body_set_resp_close(Config) ->
  977. {ConnPid, MRef} = gun_monitor_open(Config),
  978. Ref = gun:get(ConnPid, "/stream_body/set_resp_close"),
  979. {response, nofin, 200, _} = gun:await(ConnPid, Ref, MRef),
  980. {ok, <<"stream_body_set_resp_close">>} = gun:await_body(ConnPid, Ref, MRef),
  981. gun_is_gone(ConnPid, MRef).
  982. stream_body_set_resp_chunked(Config) ->
  983. ConnPid = gun_open(Config),
  984. Ref = gun:get(ConnPid, "/stream_body/set_resp_chunked"),
  985. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  986. {_, <<"chunked">>} = lists:keyfind(<<"transfer-encoding">>, 1, Headers),
  987. {ok, <<"stream_body_set_resp_chunked">>} = gun:await_body(ConnPid, Ref),
  988. ok.
  989. stream_body_set_resp_chunked10(Config) ->
  990. {ConnPid, MRef} = gun_monitor_open(Config, [{http, [{version, 'HTTP/1.0'}]}]),
  991. Ref = gun:get(ConnPid, "/stream_body/set_resp_chunked"),
  992. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref, MRef),
  993. false = lists:keyfind(<<"transfer-encoding">>, 1, Headers),
  994. {ok, <<"stream_body_set_resp_chunked">>} = gun:await_body(ConnPid, Ref, MRef),
  995. gun_is_gone(ConnPid, MRef).
  996. %% Undocumented hack: force chunked response to be streamed as HTTP/1.1.
  997. streamed_response(Config) ->
  998. Client = raw_open(Config),
  999. ok = raw_send(Client, "GET /streamed_response HTTP/1.1\r\nHost: localhost\r\n\r\n"),
  1000. Data = raw_recv_head(Client),
  1001. {'HTTP/1.1', 200, _, Rest} = cow_http:parse_status_line(Data),
  1002. {Headers, Rest2} = cow_http:parse_headers(Rest),
  1003. false = lists:keymember(<<"transfer-encoding">>, 1, Headers),
  1004. Rest2Size = byte_size(Rest2),
  1005. ok = case <<"streamed_handler\r\nworks fine!">> of
  1006. Rest2 -> ok;
  1007. << Rest2:Rest2Size/binary, Expect/bits >> -> raw_expect_recv(Client, Expect)
  1008. end.
  1009. te_chunked(Config) ->
  1010. Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
  1011. ConnPid = gun_open(Config),
  1012. Ref = gun:post(ConnPid, "/echo/body",
  1013. [{<<"transfer-encoding">>, <<"chunked">>}], Body),
  1014. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  1015. {ok, Body} = gun:await_body(ConnPid, Ref),
  1016. ok.
  1017. body_to_chunks(_, <<>>, Acc) ->
  1018. lists:reverse([<<"0\r\n\r\n">>|Acc]);
  1019. body_to_chunks(ChunkSize, Body, Acc) ->
  1020. BodySize = byte_size(Body),
  1021. ChunkSize2 = case BodySize < ChunkSize of
  1022. true -> BodySize;
  1023. false -> ChunkSize
  1024. end,
  1025. << Chunk:ChunkSize2/binary, Rest/binary >> = Body,
  1026. ChunkSizeBin = list_to_binary(integer_to_list(ChunkSize2, 16)),
  1027. body_to_chunks(ChunkSize, Rest,
  1028. [<< ChunkSizeBin/binary, "\r\n", Chunk/binary, "\r\n" >>|Acc]).
  1029. te_chunked_chopped(Config) ->
  1030. Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
  1031. Body2 = iolist_to_binary(body_to_chunks(50, Body, [])),
  1032. ConnPid = gun_open(Config),
  1033. Ref = gun:post(ConnPid, "/echo/body",
  1034. [{<<"transfer-encoding">>, <<"chunked">>}]),
  1035. _ = [begin
  1036. ok = gun:dbg_send_raw(ConnPid, << C >>),
  1037. receive after 10 -> ok end
  1038. end || << C >> <= Body2],
  1039. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  1040. {ok, Body} = gun:await_body(ConnPid, Ref),
  1041. ok.
  1042. te_chunked_delayed(Config) ->
  1043. Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
  1044. Chunks = body_to_chunks(50, Body, []),
  1045. ConnPid = gun_open(Config),
  1046. Ref = gun:post(ConnPid, "/echo/body",
  1047. [{<<"transfer-encoding">>, <<"chunked">>}]),
  1048. _ = [begin
  1049. ok = gun:dbg_send_raw(ConnPid, Chunk),
  1050. receive after 10 -> ok end
  1051. end || Chunk <- Chunks],
  1052. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  1053. {ok, Body} = gun:await_body(ConnPid, Ref),
  1054. ok.
  1055. te_chunked_split_body(Config) ->
  1056. Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
  1057. Chunks = body_to_chunks(50, Body, []),
  1058. ConnPid = gun_open(Config),
  1059. Ref = gun:post(ConnPid, "/echo/body",
  1060. [{<<"transfer-encoding">>, <<"chunked">>}]),
  1061. _ = [begin
  1062. case Chunk of
  1063. <<"0\r\n\r\n">> ->
  1064. ok = gun:dbg_send_raw(ConnPid, Chunk);
  1065. _ ->
  1066. [Size, ChunkBody, <<>>] =
  1067. binary:split(Chunk, [<<"\r\n">>], [global]),
  1068. PartASize = random:uniform(byte_size(ChunkBody)),
  1069. <<PartA:PartASize/binary, PartB/binary>> = ChunkBody,
  1070. ok = gun:dbg_send_raw(ConnPid, [Size, <<"\r\n">>, PartA]),
  1071. receive after 10 -> ok end,
  1072. ok = gun:dbg_send_raw(ConnPid, [PartB, <<"\r\n">>])
  1073. end
  1074. end || Chunk <- Chunks],
  1075. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  1076. {ok, Body} = gun:await_body(ConnPid, Ref),
  1077. ok.
  1078. te_chunked_split_crlf(Config) ->
  1079. Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
  1080. Chunks = body_to_chunks(50, Body, []),
  1081. ConnPid = gun_open(Config),
  1082. Ref = gun:post(ConnPid, "/echo/body",
  1083. [{<<"transfer-encoding">>, <<"chunked">>}]),
  1084. _ = [begin
  1085. %% Split in the newline just before the end of the chunk.
  1086. Len = byte_size(Chunk) - (random:uniform(2) - 1),
  1087. << Chunk2:Len/binary, End/binary >> = Chunk,
  1088. ok = gun:dbg_send_raw(ConnPid, Chunk2),
  1089. receive after 10 -> ok end,
  1090. ok = gun:dbg_send_raw(ConnPid, End)
  1091. end || Chunk <- Chunks],
  1092. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  1093. {ok, Body} = gun:await_body(ConnPid, Ref),
  1094. ok.
  1095. te_identity(Config) ->
  1096. Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
  1097. ConnPid = gun_open(Config),
  1098. Ref = gun:post(ConnPid, "/echo/body", [], Body),
  1099. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  1100. {ok, Body} = gun:await_body(ConnPid, Ref),
  1101. ok.