Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

890 righe
33 KiB

13 anni fa
11 anni fa
9 anni fa
9 anni fa
9 anni fa
9 anni fa
9 anni fa
9 anni fa
9 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
14 anni fa
13 anni fa
13 anni fa
13 anni fa
9 anni fa
9 anni fa
11 anni fa
11 anni fa
11 anni fa
  1. %%% File : ibrowse_test.erl
  2. %%% Author : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
  3. %%% Description : Test ibrowse
  4. %%% Created : 14 Oct 2003 by Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
  5. -module(ibrowse_test).
  6. -export([
  7. load_test_/3,
  8. send_reqs_1/3,
  9. do_send_req/2,
  10. local_unit_tests/0,
  11. unit_tests/0,
  12. unit_tests/2,
  13. unit_tests_1/3,
  14. verify_chunked_streaming/0,
  15. verify_chunked_streaming/1,
  16. test_chunked_streaming_once/0,
  17. i_do_async_req_list/4,
  18. test_stream_once/3,
  19. test_stream_once/4,
  20. test_20122010/0,
  21. test_20122010/1,
  22. test_pipeline_head_timeout/0,
  23. test_pipeline_head_timeout/1,
  24. do_test_pipeline_head_timeout/4,
  25. test_head_transfer_encoding/0,
  26. test_head_transfer_encoding/1,
  27. test_head_response_with_body/0,
  28. test_head_response_with_body/1,
  29. test_303_response_with_no_body/0,
  30. test_303_response_with_no_body/1,
  31. test_303_response_with_a_body/0,
  32. test_303_response_with_a_body/1,
  33. test_preserve_status_line/0,
  34. test_binary_headers/0,
  35. test_binary_headers/1,
  36. test_generate_body_0/0,
  37. test_retry_of_requests/0,
  38. test_retry_of_requests/1,
  39. test_save_to_file_no_content_length/0,
  40. socks5_noauth/0,
  41. socks5_auth_succ/0,
  42. socks5_auth_fail/0
  43. ]).
  44. -include_lib("ibrowse/include/ibrowse.hrl").
  45. %%------------------------------------------------------------------------------
  46. %% Unit Tests
  47. %%------------------------------------------------------------------------------
  48. -define(LOCAL_TESTS, [
  49. {local_test_fun, socks5_noauth, []},
  50. {local_test_fun, socks5_auth_succ, []},
  51. {local_test_fun, socks5_auth_fail, []},
  52. {local_test_fun, test_preserve_status_line, []},
  53. {local_test_fun, test_save_to_file_no_content_length, []},
  54. {local_test_fun, test_20122010, []},
  55. {local_test_fun, test_pipeline_head_timeout, []},
  56. {local_test_fun, test_head_transfer_encoding, []},
  57. {local_test_fun, test_head_response_with_body, []},
  58. {local_test_fun, test_303_response_with_a_body, []},
  59. {local_test_fun, test_303_response_with_no_body, []},
  60. {local_test_fun, test_binary_headers, []},
  61. {local_test_fun, test_retry_of_requests, []},
  62. {local_test_fun, verify_chunked_streaming, []},
  63. {local_test_fun, test_chunked_streaming_once, []},
  64. {local_test_fun, test_generate_body_0, []}
  65. ]).
  66. -define(TEST_LIST, [{"http://intranet/messenger", get},
  67. {"http://www.google.co.uk", get},
  68. {"http://www.google.com", get},
  69. {"http://www.google.com", options},
  70. {"https://mail.google.com", get},
  71. {"http://www.sun.com", get},
  72. {"http://www.oracle.com", get},
  73. {"http://www.bbc.co.uk", get},
  74. {"http://www.bbc.co.uk", trace},
  75. {"http://www.bbc.co.uk", options},
  76. {"http://yaws.hyber.org", get},
  77. {"http://jigsaw.w3.org/HTTP/ChunkedScript", get},
  78. {"http://jigsaw.w3.org/HTTP/TE/foo.txt", get},
  79. {"http://jigsaw.w3.org/HTTP/TE/bar.txt", get},
  80. {"http://jigsaw.w3.org/HTTP/connection.html", get},
  81. {"http://jigsaw.w3.org/HTTP/cc.html", get},
  82. {"http://jigsaw.w3.org/HTTP/cc-private.html", get},
  83. {"http://jigsaw.w3.org/HTTP/cc-proxy-revalidate.html", get},
  84. {"http://jigsaw.w3.org/HTTP/cc-nocache.html", get},
  85. {"http://jigsaw.w3.org/HTTP/h-content-md5.html", get},
  86. {"http://jigsaw.w3.org/HTTP/h-retry-after.html", get},
  87. {"http://jigsaw.w3.org/HTTP/h-retry-after-date.html", get},
  88. {"http://jigsaw.w3.org/HTTP/neg", get},
  89. {"http://jigsaw.w3.org/HTTP/negbad", get},
  90. {"http://jigsaw.w3.org/HTTP/400/toolong/", get},
  91. {"http://jigsaw.w3.org/HTTP/300/", get},
  92. {"http://jigsaw.w3.org/HTTP/Basic/", get, [{basic_auth, {"guest", "guest"}}]},
  93. {"http://jigsaw.w3.org/HTTP/CL/", get},
  94. {"http://www.httpwatch.com/httpgallery/chunked/", get},
  95. {"https://github.com", get, [{ssl_options, [{depth, 2}]}]}
  96. ]).
  97. socks5_noauth() ->
  98. case ibrowse:send_req("http://localhost:8181/success", [], get, [],
  99. [{socks5_host, "localhost"}, {socks5_port, 8282}], 2000) of
  100. {ok, "200", _, _} ->
  101. success;
  102. Err ->
  103. Err
  104. end.
  105. socks5_auth_succ() ->
  106. case ibrowse:send_req("http://localhost:8181/success", [], get, [],
  107. [{socks5_host, "localhost"}, {socks5_port, 8383},
  108. {socks5_user, <<"user">>}, {socks5_password, <<"password">>}], 2000) of
  109. {ok, "200", _, _} ->
  110. success;
  111. Err ->
  112. Err
  113. end.
  114. socks5_auth_fail() ->
  115. case ibrowse:send_req("http://localhost:8181/success", [], get, [],
  116. [{socks5_host, "localhost"}, {socks5_port, 8282},
  117. {socks5_user, <<"user">>}, {socks5_password, <<"wrong_password">>}], 2000) of
  118. {error,{conn_failed,{error,unacceptable}}} ->
  119. success;
  120. Err ->
  121. Err
  122. end.
  123. test_stream_once(Url, Method, Options) ->
  124. test_stream_once(Url, Method, Options, 5000).
  125. test_stream_once(Url, Method, Options, Timeout) ->
  126. case ibrowse:send_req(Url, [], Method, [], [{stream_to, {self(), once}} | Options], Timeout) of
  127. {ibrowse_req_id, Req_id} ->
  128. case ibrowse:stream_next(Req_id) of
  129. ok ->
  130. test_stream_once(Req_id);
  131. Err ->
  132. Err
  133. end;
  134. Err ->
  135. Err
  136. end.
  137. test_stream_once(Req_id) ->
  138. receive
  139. {ibrowse_async_headers, Req_id, StatCode, Headers} ->
  140. io:format("Recvd headers~n~p~n", [{ibrowse_async_headers, Req_id, StatCode, Headers}]),
  141. case ibrowse:stream_next(Req_id) of
  142. ok ->
  143. test_stream_once(Req_id);
  144. Err ->
  145. Err
  146. end;
  147. {ibrowse_async_response, Req_id, {error, Err}} ->
  148. io:format("Recvd error: ~p~n", [Err]);
  149. {ibrowse_async_response, Req_id, Body_1} ->
  150. io:format("Recvd body part: ~n~p~n", [{ibrowse_async_response, Req_id, Body_1}]),
  151. case ibrowse:stream_next(Req_id) of
  152. ok ->
  153. test_stream_once(Req_id);
  154. Err ->
  155. Err
  156. end;
  157. {ibrowse_async_response_end, Req_id} ->
  158. ok
  159. end.
  160. %% Use ibrowse:set_max_sessions/3 and ibrowse:set_max_pipeline_size/3 to
  161. %% tweak settings before running the load test. The defaults are 10 and 10.
  162. load_test_(Url, NumWorkers, NumReqsPerWorker) when is_list(Url),
  163. is_integer(NumWorkers),
  164. is_integer(NumReqsPerWorker),
  165. NumWorkers > 0,
  166. NumReqsPerWorker > 0 ->
  167. proc_lib:spawn(?MODULE, send_reqs_1, [Url, NumWorkers, NumReqsPerWorker]).
  168. send_reqs_1(Url, NumWorkers, NumReqsPerWorker) ->
  169. Start_time = os:timestamp(),
  170. ets:new(pid_table, [named_table, public]),
  171. ets:new(ibrowse_test_results, [named_table, public]),
  172. ets:new(ibrowse_errors, [named_table, public, ordered_set]),
  173. ets:new(ibrowse_counter, [named_table, public, ordered_set]),
  174. ets:insert(ibrowse_counter, {req_id, 1}),
  175. init_results(),
  176. process_flag(trap_exit, true),
  177. log_msg("Starting spawning of workers...~n", []),
  178. spawn_workers(Url, NumWorkers, NumReqsPerWorker),
  179. log_msg("Finished spawning workers...~n", []),
  180. do_wait(Url),
  181. End_time = os:timestamp(),
  182. log_msg("All workers are done...~n", []),
  183. log_msg("ibrowse_test_results table: ~n~p~n", [ets:tab2list(ibrowse_test_results)]),
  184. log_msg("Start time: ~1000.p~n", [calendar:now_to_local_time(Start_time)]),
  185. log_msg("End time : ~1000.p~n", [calendar:now_to_local_time(End_time)]),
  186. Elapsed_time_secs = trunc(timer:now_diff(End_time, Start_time) / 1000000),
  187. log_msg("Elapsed : ~p~n", [Elapsed_time_secs]),
  188. log_msg("Reqs/sec : ~p~n", [round(trunc((NumWorkers*NumReqsPerWorker) / Elapsed_time_secs))]),
  189. dump_errors().
  190. init_results() ->
  191. ets:insert(ibrowse_test_results, {crash, 0}),
  192. ets:insert(ibrowse_test_results, {send_failed, 0}),
  193. ets:insert(ibrowse_test_results, {other_error, 0}),
  194. ets:insert(ibrowse_test_results, {success, 0}),
  195. ets:insert(ibrowse_test_results, {retry_later, 0}),
  196. ets:insert(ibrowse_test_results, {trid_mismatch, 0}),
  197. ets:insert(ibrowse_test_results, {success_no_trid, 0}),
  198. ets:insert(ibrowse_test_results, {failed, 0}),
  199. ets:insert(ibrowse_test_results, {timeout, 0}),
  200. ets:insert(ibrowse_test_results, {req_id, 0}).
  201. spawn_workers(_Url, 0, _) ->
  202. ok;
  203. spawn_workers(Url, NumWorkers, NumReqsPerWorker) ->
  204. Pid = proc_lib:spawn_link(?MODULE, do_send_req, [Url, NumReqsPerWorker]),
  205. ets:insert(pid_table, {Pid, []}),
  206. spawn_workers(Url, NumWorkers - 1, NumReqsPerWorker).
  207. do_wait(Url) ->
  208. receive
  209. {'EXIT', _, normal} ->
  210. catch ibrowse:show_dest_status(Url),
  211. catch ibrowse:show_dest_status(),
  212. do_wait(Url);
  213. {'EXIT', Pid, Reason} ->
  214. ets:delete(pid_table, Pid),
  215. ets:insert(ibrowse_errors, {Pid, Reason}),
  216. ets:update_counter(ibrowse_test_results, crash, 1),
  217. do_wait(Url);
  218. Msg ->
  219. io:format("Recvd unknown message...~p~n", [Msg]),
  220. do_wait(Url)
  221. after 1000 ->
  222. case ets:info(pid_table, size) of
  223. 0 ->
  224. done;
  225. _ ->
  226. catch ibrowse:show_dest_status(Url),
  227. catch ibrowse:show_dest_status(),
  228. do_wait(Url)
  229. end
  230. end.
  231. do_send_req(Url, NumReqs) ->
  232. do_send_req_1(Url, NumReqs).
  233. do_send_req_1(_Url, 0) ->
  234. ets:delete(pid_table, self());
  235. do_send_req_1(Url, NumReqs) ->
  236. Counter = integer_to_list(ets:update_counter(ibrowse_test_results, req_id, 1)),
  237. case ibrowse:send_req(Url, [{"ib_req_id", Counter}], get, [], [], 10000) of
  238. {ok, _Status, Headers, _Body} ->
  239. case lists:keysearch("ib_req_id", 1, Headers) of
  240. {value, {_, Counter}} ->
  241. ets:update_counter(ibrowse_test_results, success, 1);
  242. {value, _} ->
  243. ets:update_counter(ibrowse_test_results, trid_mismatch, 1);
  244. false ->
  245. ets:update_counter(ibrowse_test_results, success_no_trid, 1)
  246. end;
  247. {error, req_timedout} ->
  248. ets:update_counter(ibrowse_test_results, timeout, 1);
  249. {error, send_failed} ->
  250. ets:update_counter(ibrowse_test_results, send_failed, 1);
  251. {error, retry_later} ->
  252. ets:update_counter(ibrowse_test_results, retry_later, 1);
  253. Err ->
  254. ets:insert(ibrowse_errors, {os:timestamp(), Err}),
  255. ets:update_counter(ibrowse_test_results, other_error, 1),
  256. ok
  257. end,
  258. do_send_req_1(Url, NumReqs-1).
  259. dump_errors() ->
  260. case ets:info(ibrowse_errors, size) of
  261. 0 ->
  262. ok;
  263. _ ->
  264. {A, B, C} = os:timestamp(),
  265. Filename = lists:flatten(
  266. io_lib:format("ibrowse_errors_~p_~p_~p.txt" , [A, B, C])),
  267. case file:open(Filename, [write, delayed_write, raw]) of
  268. {ok, Iod} ->
  269. dump_errors(ets:first(ibrowse_errors), Iod);
  270. Err ->
  271. io:format("failed to create file ~s. Reason: ~p~n", [Filename, Err]),
  272. ok
  273. end
  274. end.
  275. dump_errors('$end_of_table', Iod) ->
  276. file:close(Iod);
  277. dump_errors(Key, Iod) ->
  278. [{_, Term}] = ets:lookup(ibrowse_errors, Key),
  279. file:write(Iod, io_lib:format("~p~n", [Term])),
  280. dump_errors(ets:next(ibrowse_errors, Key), Iod).
  281. local_unit_tests() ->
  282. unit_tests([], ?LOCAL_TESTS).
  283. unit_tests() ->
  284. unit_tests([], ?TEST_LIST).
  285. unit_tests(Options, Test_list) ->
  286. error_logger:tty(false),
  287. application:start(crypto),
  288. application:start(asn1),
  289. application:start(public_key),
  290. application:start(ssl),
  291. (catch ibrowse_test_server:start_server(8181, tcp)),
  292. application:start(ibrowse),
  293. Options_1 = Options ++ [{connect_timeout, 5000}],
  294. Test_timeout = proplists:get_value(test_timeout, Options, 60000),
  295. {Pid, Ref} = erlang:spawn_monitor(?MODULE, unit_tests_1, [self(), Options_1, Test_list]),
  296. receive
  297. {done, Pid} ->
  298. ok;
  299. {'DOWN', Ref, _, _, Info} ->
  300. io:format("Test process crashed: ~p~n", [Info])
  301. after Test_timeout ->
  302. exit(Pid, kill),
  303. io:format("Timed out waiting for tests to complete~n", [])
  304. end,
  305. catch ibrowse_test_server:stop_server(8181),
  306. error_logger:tty(true),
  307. ok.
  308. unit_tests_1(Parent, Options, Test_list) ->
  309. lists:foreach(fun({local_test_fun, Fun_name, Args}) ->
  310. execute_req(local_test_fun, Fun_name, Args);
  311. ({Url, Method}) ->
  312. execute_req(Url, Method, Options);
  313. ({Url, Method, X_Opts}) ->
  314. execute_req(Url, Method, X_Opts ++ Options)
  315. end, Test_list),
  316. Parent ! {done, self()}.
  317. verify_chunked_streaming() ->
  318. verify_chunked_streaming([]).
  319. verify_chunked_streaming(Options) ->
  320. io:format("~nVerifying that chunked streaming is working...~n", []),
  321. Url = "http://www.httpwatch.com/httpgallery/chunked/",
  322. io:format(" URL: ~s~n", [Url]),
  323. io:format(" Fetching data without streaming...~n", []),
  324. Result_without_streaming = ibrowse:send_req(
  325. Url, [], get, [],
  326. [{response_format, binary} | Options]),
  327. io:format(" Fetching data with streaming as list...~n", []),
  328. Async_response_list = do_async_req_list(
  329. Url, get, [{response_format, list} | Options]),
  330. io:format(" Fetching data with streaming as binary...~n", []),
  331. Async_response_bin = do_async_req_list(
  332. Url, get, [{response_format, binary} | Options]),
  333. io:format(" Fetching data with streaming as binary, {active, once}...~n", []),
  334. Async_response_bin_once = do_async_req_list(
  335. Url, get, [once, {response_format, binary} | Options]),
  336. Res1 = compare_responses(Result_without_streaming, Async_response_list, Async_response_bin),
  337. Res2 = compare_responses(Result_without_streaming, Async_response_list, Async_response_bin_once),
  338. case {Res1, Res2} of
  339. {success, success} ->
  340. io:format(" Chunked streaming working~n", []),
  341. success;
  342. _ ->
  343. ok
  344. end.
  345. test_chunked_streaming_once() ->
  346. test_chunked_streaming_once([]).
  347. test_chunked_streaming_once(Options) ->
  348. io:format("~nTesting chunked streaming with the {stream_to, {Pid, once}} option...~n", []),
  349. Url = "http://www.httpwatch.com/httpgallery/chunked/",
  350. io:format(" URL: ~s~n", [Url]),
  351. io:format(" Fetching data with streaming as binary, {active, once}...~n", []),
  352. case do_async_req_list(Url, get, [once, {response_format, binary} | Options]) of
  353. {ok, _, _, _} ->
  354. success;
  355. Err ->
  356. io:format(" Fail: ~p~n", [Err])
  357. end.
  358. compare_responses({ok, St_code, _, Body}, {ok, St_code, _, Body}, {ok, St_code, _, Body}) ->
  359. success;
  360. compare_responses({ok, St_code, _, Body_1}, {ok, St_code, _, Body_2}, {ok, St_code, _, Body_3}) ->
  361. case Body_1 of
  362. Body_2 ->
  363. io:format("Body_1 and Body_2 match~n", []);
  364. Body_3 ->
  365. io:format("Body_1 and Body_3 match~n", []);
  366. _ when Body_2 == Body_3 ->
  367. io:format("Body_2 and Body_3 match~n", []);
  368. _ ->
  369. io:format("All three bodies are different!~n", [])
  370. end,
  371. io:format("Body_1 -> ~p~n", [Body_1]),
  372. io:format("Body_2 -> ~p~n", [Body_2]),
  373. io:format("Body_3 -> ~p~n", [Body_3]),
  374. fail_bodies_mismatch;
  375. compare_responses(R1, R2, R3) ->
  376. io:format("R1 -> ~p~n", [R1]),
  377. io:format("R2 -> ~p~n", [R2]),
  378. io:format("R3 -> ~p~n", [R3]),
  379. fail.
  380. %% do_async_req_list(Url) ->
  381. %% do_async_req_list(Url, get).
  382. %% do_async_req_list(Url, Method) ->
  383. %% do_async_req_list(Url, Method, [{stream_to, self()},
  384. %% {stream_chunk_size, 1000}]).
  385. do_async_req_list(Url, Method, Options) ->
  386. {Pid,_} = erlang:spawn_monitor(?MODULE, i_do_async_req_list,
  387. [self(), Url, Method,
  388. Options ++ [{stream_chunk_size, 1000}]]),
  389. %% io:format("Spawned process ~p~n", [Pid]),
  390. wait_for_resp(Pid).
  391. wait_for_resp(Pid) ->
  392. receive
  393. {async_result, Pid, Res} ->
  394. Res;
  395. {async_result, Other_pid, _} ->
  396. io:format("~p: Waiting for result from ~p: got from ~p~n", [self(), Pid, Other_pid]),
  397. wait_for_resp(Pid);
  398. {'DOWN', _, _, Pid, Reason} ->
  399. {'EXIT', Reason};
  400. {'DOWN', _, _, _, _} ->
  401. wait_for_resp(Pid);
  402. {'EXIT', _, normal} ->
  403. wait_for_resp(Pid);
  404. Msg ->
  405. io:format("Recvd unknown message: ~p~n", [Msg]),
  406. wait_for_resp(Pid)
  407. after 100000 ->
  408. {error, timeout}
  409. end.
  410. i_do_async_req_list(Parent, Url, Method, Options) ->
  411. Options_1 = case lists:member(once, Options) of
  412. true ->
  413. [{stream_to, {self(), once}} | (Options -- [once])];
  414. false ->
  415. [{stream_to, self()} | Options]
  416. end,
  417. Res = ibrowse:send_req(Url, [], Method, [], Options_1),
  418. case Res of
  419. {ibrowse_req_id, Req_id} ->
  420. Result = wait_for_async_resp(Req_id, Options, undefined, undefined, []),
  421. Parent ! {async_result, self(), Result};
  422. Err ->
  423. Parent ! {async_result, self(), Err}
  424. end.
  425. wait_for_async_resp(Req_id, Options, Acc_Stat_code, Acc_Headers, Body) ->
  426. receive
  427. {ibrowse_async_headers, Req_id, StatCode, Headers} ->
  428. %% io:format("Recvd headers...~n", []),
  429. maybe_stream_next(Req_id, Options),
  430. wait_for_async_resp(Req_id, Options, StatCode, Headers, Body);
  431. {ibrowse_async_response_end, Req_id} ->
  432. %% io:format("Recvd end of response.~n", []),
  433. Body_1 = list_to_binary(lists:reverse(Body)),
  434. {ok, Acc_Stat_code, Acc_Headers, Body_1};
  435. {ibrowse_async_response, Req_id, Data} ->
  436. maybe_stream_next(Req_id, Options),
  437. %% io:format("Recvd data...~n", []),
  438. wait_for_async_resp(Req_id, Options, Acc_Stat_code, Acc_Headers, [Data | Body]);
  439. {ibrowse_async_response, Req_id, {error, _} = Err} ->
  440. {ok, Acc_Stat_code, Acc_Headers, Err};
  441. Err ->
  442. {ok, Acc_Stat_code, Acc_Headers, Err}
  443. after 10000 ->
  444. {timeout, Acc_Stat_code, Acc_Headers, Body}
  445. end.
  446. maybe_stream_next(Req_id, Options) ->
  447. case lists:member(once, Options) of
  448. true ->
  449. ibrowse:stream_next(Req_id);
  450. false ->
  451. ok
  452. end.
  453. execute_req(local_test_fun, Method, Args) ->
  454. reset_ibrowse(),
  455. Result = (catch apply(?MODULE, Method, Args)),
  456. io:format(" ~-54.54w: ", [Method]),
  457. io:format("~p~n", [Result]);
  458. execute_req(Url, Method, Options) ->
  459. io:format("~7.7w, ~50.50s: ", [Method, Url]),
  460. Result = (catch ibrowse:send_req(Url, [], Method, [], Options)),
  461. case Result of
  462. {ok, SCode, _H, _B} ->
  463. io:format("Status code: ~p~n", [SCode]);
  464. Err ->
  465. io:format("~p~n", [Err])
  466. end.
  467. log_msg(Fmt, Args) ->
  468. io:format("~s -- " ++ Fmt,
  469. [ibrowse_lib:printable_date() | Args]).
  470. %%------------------------------------------------------------------------------
  471. %% Test what happens when the response to a HEAD request is a
  472. %% Chunked-Encoding response with a non-empty body. Issue #67 on
  473. %% Github
  474. %% ------------------------------------------------------------------------------
  475. test_head_transfer_encoding() ->
  476. clear_msg_q(),
  477. test_head_transfer_encoding("http://localhost:8181/ibrowse_head_test").
  478. test_head_transfer_encoding(Url) ->
  479. case ibrowse:send_req(Url, [], head) of
  480. {ok, "200", _, _} ->
  481. success;
  482. Res ->
  483. {test_failed, Res}
  484. end.
  485. %%------------------------------------------------------------------------------
  486. %% Test what happens when the response to a HEAD request is a
  487. %% Chunked-Encoding response with a non-empty body. Issue #67 on
  488. %% Github
  489. %% ------------------------------------------------------------------------------
  490. test_binary_headers() ->
  491. clear_msg_q(),
  492. test_binary_headers("http://localhost:8181/ibrowse_echo_header").
  493. test_binary_headers(Url) ->
  494. case ibrowse:send_req(Url, [{<<"x-binary">>, <<"x-header">>}], get) of
  495. {ok, "200", Headers, _} ->
  496. case proplists:get_value("x-binary", Headers) of
  497. "x-header" ->
  498. success;
  499. V ->
  500. {fail, V}
  501. end;
  502. Res ->
  503. {test_failed, Res}
  504. end.
  505. %%------------------------------------------------------------------------------
  506. %% Test what happens when the response to a HEAD request is a
  507. %% Chunked-Encoding response with a non-empty body. Issue #67 on
  508. %% Github
  509. %% ------------------------------------------------------------------------------
  510. test_head_response_with_body() ->
  511. clear_msg_q(),
  512. test_head_response_with_body("http://localhost:8181/ibrowse_head_transfer_enc").
  513. test_head_response_with_body(Url) ->
  514. case ibrowse:send_req(Url, [], head, [], [{workaround, head_response_with_body}]) of
  515. {ok, "400", _, _} ->
  516. success;
  517. Res ->
  518. {test_failed, Res}
  519. end.
  520. %%------------------------------------------------------------------------------
  521. %% Test what happens when a 303 response has no body
  522. %% Github issue #97
  523. %% ------------------------------------------------------------------------------
  524. test_303_response_with_no_body() ->
  525. clear_msg_q(),
  526. test_303_response_with_no_body("http://localhost:8181/ibrowse_303_no_body_test").
  527. test_303_response_with_no_body(Url) ->
  528. ibrowse:add_config([{allow_303_with_no_body, true}]),
  529. case ibrowse:send_req(Url, [], post) of
  530. {ok, "303", _, _} ->
  531. success;
  532. Res ->
  533. {test_failed, Res}
  534. end.
  535. %% Make sure we don't break requests that do have a body.
  536. test_303_response_with_a_body() ->
  537. clear_msg_q(),
  538. test_303_response_with_no_body("http://localhost:8181/ibrowse_303_with_body_test").
  539. test_303_response_with_a_body(Url) ->
  540. ibrowse:add_config([{allow_303_with_no_body, true}]),
  541. case ibrowse:send_req(Url, [], post) of
  542. {ok, "303", _, "abcde"} ->
  543. success;
  544. Res ->
  545. {test_failed, Res}
  546. end.
  547. %% Test that the 'preserve_status_line' option works as expected
  548. test_preserve_status_line() ->
  549. case ibrowse:send_req("http://localhost:8181/ibrowse_preserve_status_line", [], get, [],
  550. [{preserve_status_line, true}]) of
  551. {ok, "200", [{ibrowse_status_line,<<"HTTP/1.1 200 OKBlah">>} | _], _} ->
  552. success;
  553. Res ->
  554. {test_failed, Res}
  555. end.
  556. %%------------------------------------------------------------------------------
  557. %% Test that when the save_response_to_file option is used with a server which
  558. %% does not send the Content-Length header, the response is saved correctly to
  559. %% a file
  560. %%------------------------------------------------------------------------------
  561. test_save_to_file_no_content_length() ->
  562. clear_msg_q(),
  563. {{Y, M, D}, {H, Mi, S}} = calendar:local_time(),
  564. Test_file = filename:join
  565. ([".",
  566. lists:flatten(
  567. io_lib:format("test_save_to_file_no_content_length_~p~p~p_~p~p~p.txt", [Y, M, D, H, Mi, S]))]),
  568. try
  569. case ibrowse:send_req("http://localhost:8181/ibrowse_send_file_conn_close", [], get, [],
  570. [{save_response_to_file, Test_file}]) of
  571. {ok, "200", _, {file, Test_file}} ->
  572. success;
  573. Res ->
  574. {test_failed, Res}
  575. end
  576. after
  577. file:delete(Test_file)
  578. end.
  579. %%------------------------------------------------------------------------------
  580. %% Test that retry of requests happens correctly, and that ibrowse doesn't retry
  581. %% if there is not enough time left
  582. %%------------------------------------------------------------------------------
  583. test_retry_of_requests() ->
  584. clear_msg_q(),
  585. test_retry_of_requests("http://localhost:8181/ibrowse_handle_one_request_only_with_delay").
  586. test_retry_of_requests(Url) ->
  587. reset_ibrowse(),
  588. Timeout_1 = 2050,
  589. Res_1 = test_retry_of_requests(Url, Timeout_1),
  590. case lists:filter(fun({_Pid, {ok, "200", _, _}}) ->
  591. true;
  592. (_) -> false
  593. end, Res_1) of
  594. [_|_] = X ->
  595. Res_1_1 = Res_1 -- X,
  596. case lists:all(
  597. fun({_Pid, {error, retry_later}}) ->
  598. true;
  599. (_) ->
  600. false
  601. end, Res_1_1) of
  602. true ->
  603. ok;
  604. false ->
  605. exit({failed, Timeout_1, Res_1})
  606. end;
  607. _ ->
  608. exit({failed, Timeout_1, Res_1})
  609. end,
  610. Timeout_2 = 2200,
  611. Res_2 = test_retry_of_requests(Url, Timeout_2),
  612. case lists:filter(fun({_Pid, {ok, "200", _, _}}) ->
  613. true;
  614. (_) -> false
  615. end, Res_2) of
  616. [_|_] = Res_2_X ->
  617. Res_2_1 = Res_2 -- Res_2_X,
  618. case lists:all(
  619. fun({_Pid, {error, X_err_2}}) ->
  620. (X_err_2 == retry_later) orelse (X_err_2 == req_timedout);
  621. (_) ->
  622. false
  623. end, Res_2_1) of
  624. true ->
  625. ok;
  626. false ->
  627. exit({failed, {?MODULE, ?LINE}, Timeout_2, Res_2})
  628. end;
  629. _ ->
  630. exit({failed, {?MODULE, ?LINE}, Timeout_2, Res_2})
  631. end,
  632. success.
  633. test_retry_of_requests(Url, Timeout) ->
  634. #url{host = Host, port = Port} = ibrowse_lib:parse_url(Url),
  635. ibrowse:set_max_sessions(Host, Port, 1),
  636. Parent = self(),
  637. Pids = lists:map(fun(_) ->
  638. spawn(fun() ->
  639. Res = (catch ibrowse:send_req(Url, [], get, [], [], Timeout)),
  640. Parent ! {self(), Res}
  641. end)
  642. end, lists:seq(1,10)),
  643. accumulate_worker_resp(Pids).
  644. %%------------------------------------------------------------------------------
  645. %% Test what happens when the request at the head of a pipeline times out
  646. %%------------------------------------------------------------------------------
  647. test_pipeline_head_timeout() ->
  648. clear_msg_q(),
  649. test_pipeline_head_timeout("http://localhost:8181/ibrowse_inac_timeout_test").
  650. test_pipeline_head_timeout(Url) ->
  651. {ok, Pid} = ibrowse:spawn_worker_process(Url),
  652. Fixed_timeout = 2000,
  653. Test_parent = self(),
  654. Fun = fun({fixed, Timeout}) ->
  655. X_pid = spawn(fun() ->
  656. do_test_pipeline_head_timeout(Url, Pid, Test_parent, Timeout)
  657. end),
  658. %% io:format("Pid ~p with a fixed timeout~n", [X_pid]),
  659. X_pid;
  660. (Timeout_mult) ->
  661. Timeout = Fixed_timeout + Timeout_mult*1000,
  662. X_pid = spawn(fun() ->
  663. do_test_pipeline_head_timeout(Url, Pid, Test_parent, Timeout)
  664. end),
  665. %% io:format("Pid ~p with a timeout of ~p~n", [X_pid, Timeout]),
  666. X_pid
  667. end,
  668. Pids = [Fun(X) || X <- [{fixed, Fixed_timeout} | lists:seq(1,10)]],
  669. Result = accumulate_worker_resp(Pids),
  670. case lists:all(fun({_, X_res}) ->
  671. (X_res == {error,req_timedout}) orelse (X_res == {error, connection_closed})
  672. end, Result) of
  673. true ->
  674. success;
  675. false ->
  676. {test_failed, Result}
  677. end.
  678. do_test_pipeline_head_timeout(Url, Pid, Test_parent, Req_timeout) ->
  679. Resp = ibrowse:send_req_direct(
  680. Pid,
  681. Url,
  682. [], get, [],
  683. [{socket_options,[{keepalive,true}]},
  684. {inactivity_timeout,180000},
  685. {connect_timeout,180000}], Req_timeout),
  686. Test_parent ! {self(), Resp}.
  687. accumulate_worker_resp(Pids) ->
  688. accumulate_worker_resp(Pids, []).
  689. accumulate_worker_resp([_ | _] = Pids, Acc) ->
  690. receive
  691. {Pid, Res} when is_pid(Pid) ->
  692. accumulate_worker_resp(Pids -- [Pid], [{Pid, Res} | Acc]);
  693. Err ->
  694. io:format("Received unexpected: ~p~n", [Err])
  695. end;
  696. accumulate_worker_resp([], Acc) ->
  697. lists:reverse(Acc).
  698. clear_msg_q() ->
  699. receive
  700. _ ->
  701. clear_msg_q()
  702. after 0 ->
  703. ok
  704. end.
  705. %%------------------------------------------------------------------------------
  706. %%
  707. %%------------------------------------------------------------------------------
  708. test_20122010() ->
  709. test_20122010("http://localhost:8181").
  710. test_20122010(Url) ->
  711. {ok, Pid} = ibrowse:spawn_worker_process(Url),
  712. Expected_resp = <<"1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34-35-36-37-38-39-40-41-42-43-44-45-46-47-48-49-50-51-52-53-54-55-56-57-58-59-60-61-62-63-64-65-66-67-68-69-70-71-72-73-74-75-76-77-78-79-80-81-82-83-84-85-86-87-88-89-90-91-92-93-94-95-96-97-98-99-100">>,
  713. Test_parent = self(),
  714. Fun = fun() ->
  715. do_test_20122010(Url, Pid, Expected_resp, Test_parent)
  716. end,
  717. Pids = [erlang:spawn_monitor(Fun) || _ <- lists:seq(1,10)],
  718. wait_for_workers(Pids).
  719. wait_for_workers([{Pid, _Ref} | Pids]) ->
  720. receive
  721. {Pid, success} ->
  722. wait_for_workers(Pids)
  723. after 60000 ->
  724. test_failed
  725. end;
  726. wait_for_workers([]) ->
  727. success.
  728. do_test_20122010(Url, Pid, Expected_resp, Test_parent) ->
  729. do_test_20122010(10, Url, Pid, Expected_resp, Test_parent).
  730. do_test_20122010(0, _Url, _Pid, _Expected_resp, Test_parent) ->
  731. Test_parent ! {self(), success};
  732. do_test_20122010(Rem_count, Url, Pid, Expected_resp, Test_parent) ->
  733. {ibrowse_req_id, Req_id} = ibrowse:send_req_direct(
  734. Pid,
  735. Url ++ "/ibrowse_stream_once_chunk_pipeline_test",
  736. [], get, [],
  737. [{stream_to, {self(), once}},
  738. {inactivity_timeout, 10000},
  739. {include_ibrowse_req_id, true}]),
  740. do_trace("~p -- sent request ~1000.p~n", [self(), Req_id]),
  741. Req_id_str = lists:flatten(io_lib:format("~1000.p",[Req_id])),
  742. receive
  743. {ibrowse_async_headers, Req_id, "200", Headers} ->
  744. case lists:keysearch("x-ibrowse-request-id", 1, Headers) of
  745. {value, {_, Req_id_str}} ->
  746. ok;
  747. {value, {_, Req_id_1}} ->
  748. do_trace("~p -- Sent req-id: ~1000.p. Recvd: ~1000.p~n",
  749. [self(), Req_id, Req_id_1]),
  750. exit(req_id_mismatch)
  751. end
  752. after 5000 ->
  753. do_trace("~p -- response headers not received~n", [self()]),
  754. exit({timeout, test_failed})
  755. end,
  756. do_trace("~p -- response headers received~n", [self()]),
  757. ok = ibrowse:stream_next(Req_id),
  758. case do_test_20122010_1(Expected_resp, Req_id, []) of
  759. true ->
  760. do_test_20122010(Rem_count - 1, Url, Pid, Expected_resp, Test_parent);
  761. false ->
  762. Test_parent ! {self(), failed}
  763. end.
  764. do_test_20122010_1(Expected_resp, Req_id, Acc) ->
  765. receive
  766. {ibrowse_async_response, Req_id, Body_part} ->
  767. ok = ibrowse:stream_next(Req_id),
  768. do_test_20122010_1(Expected_resp, Req_id, [Body_part | Acc]);
  769. {ibrowse_async_response_end, Req_id} ->
  770. Acc_1 = list_to_binary(lists:reverse(Acc)),
  771. Result = Acc_1 == Expected_resp,
  772. do_trace("~p -- End of response. Result: ~p~n", [self(), Result]),
  773. Result
  774. after 1000 ->
  775. exit({timeout, test_failed})
  776. end.
  777. %%------------------------------------------------------------------------------
  778. %% Test requests where body is generated using a Fun
  779. %%------------------------------------------------------------------------------
  780. test_generate_body_0() ->
  781. Tid = ets:new(ibrowse_test_state, [public]),
  782. try
  783. Body_1 = <<"Part 1 of the body">>,
  784. Body_2 = <<"Part 2 of the body\r\n">>,
  785. Size = size(Body_1) + size(Body_2),
  786. Body = list_to_binary([Body_1, Body_2]),
  787. Fun = fun() ->
  788. case ets:lookup(Tid, body_gen_state) of
  789. [] ->
  790. ets:insert(Tid, {body_gen_state, 1}),
  791. {ok, Body_1};
  792. [{_, 1}]->
  793. ets:insert(Tid, {body_gen_state, 2}),
  794. {ok, Body_2};
  795. [{_, 2}] ->
  796. eof
  797. end
  798. end,
  799. case ibrowse:send_req("http://localhost:8181/echo_body",
  800. [{"Content-Length", Size}],
  801. post,
  802. Fun,
  803. [{response_format, binary},
  804. {http_vsn, {1,1}}]) of
  805. {ok, "200", _, Body} ->
  806. success;
  807. Err ->
  808. io:format("Test failed : ~p~n", [Err]),
  809. {test_failed, Err}
  810. end
  811. after
  812. ets:delete(Tid)
  813. end.
  814. do_trace(Fmt, Args) ->
  815. do_trace(get(my_trace_flag), Fmt, Args).
  816. do_trace(true, Fmt, Args) ->
  817. io:format("~s -- " ++ Fmt, [ibrowse_lib:printable_date() | Args]);
  818. do_trace(_, _, _) ->
  819. ok.
  820. reset_ibrowse() ->
  821. application:stop(ibrowse),
  822. application:start(ibrowse).