No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

852 líneas
31 KiB

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