Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

909 строки
34 KiB

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