Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

824 рядки
30 KiB

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