Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

909 řádky
34 KiB

před 13 roky
před 11 roky
před 9 roky
před 9 roky
před 9 roky
před 9 roky
před 9 roky
před 9 roky
před 9 roky
před 9 roky
před 9 roky
před 9 roky
před 9 roky
před 14 roky
před 14 roky
před 9 roky
před 14 roky
před 14 roky
před 14 roky
před 14 roky
před 14 roky
před 14 roky
před 9 roky
před 14 roky
před 14 roky
před 14 roky
před 9 roky
před 14 roky
před 14 roky
před 14 roky
před 9 roky
před 14 roky
před 13 roky
před 13 roky
před 13 roky
před 9 roky
před 9 roky
před 9 roky
před 9 roky
před 11 roky
před 9 roky
před 11 roky
před 9 roky
před 11 roky
  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).