This is an automated email from the ASF dual-hosted git repository. jiahuili430 pushed a commit to branch replace-catch-to-try-catch in repository https://gitbox.apache.org/repos/asf/couchdb-ibrowse.git
commit e68b10284bceb9e93a9c2cef8d5d34171390d53e Author: Jiahui Li <[email protected]> AuthorDate: Mon May 18 10:15:59 2026 -0500 Replace `catch` with `try-catch` to fix the compilation error in OTP-29 ```bash $ make src/ibrowse_lib.erl:65:10: 'catch ...' is deprecated; please use 'try ... catch ... end' instead. Compile directive 'nowarn_deprecated_catch' can be used to suppress warnings in selected modules. % 65| case catch decode_rfc822_date_1(string:tokens(String, ", \t\r\n")) of % | ``` --- src/ibrowse.erl | 254 +++++++++++++++++++++++----------- src/ibrowse_http_client.erl | 321 ++++++++++++++++++++++++++++++------------- src/ibrowse_lb.erl | 26 +++- src/ibrowse_lib.erl | 26 ++-- test/ibrowse_load_test.erl | 46 +++---- test/ibrowse_test.erl | 176 ++++++++++++++++++++++-- test/ibrowse_test_server.erl | 65 +++++++-- 7 files changed, 670 insertions(+), 244 deletions(-) diff --git a/src/ibrowse.erl b/src/ibrowse.erl index 5adf047..9116ace 100644 --- a/src/ibrowse.erl +++ b/src/ibrowse.erl @@ -91,6 +91,7 @@ set_max_sessions/3, set_max_pipeline_size/3, set_max_attempts/3, + set_download_dir/1, set_dest/3, trace_on/0, trace_off/0, @@ -142,11 +143,11 @@ start() -> %% @doc Stop the ibrowse process. Useful when testing using the shell. stop() -> - case catch gen_server:call(ibrowse, stop) of - {'EXIT',{noproc,_}} -> - ok; - Res -> - Res + try + gen_server:call(ibrowse, stop) + catch + exit:{noproc, _} -> ok; + exit:Reason -> {'EXIT', Reason} end. %% @doc This is the basic function to send a HTTP request. @@ -338,28 +339,46 @@ send_req(Url, Headers, Method, Body, Options) -> %% @spec send_req(Url, Headers::headerList(), Method::method(), Body::body(), Options::optionList(), Timeout) -> response() %% Timeout = integer() | infinity send_req(Url, Headers, Method, Body, Options, Timeout) -> - case catch parse_url(Url) of - #url{host = Host, - port = Port, - protocol = Protocol} = Parsed_url -> + try parse_url(Url) of + #url{ + host = Host, + port = Port, + protocol = Protocol + } = Parsed_url -> Lb_pid = lb_pid(Host, Port, Parsed_url), Max_sessions = get_max_sessions(Host, Port, Options), Max_pipeline_size = get_max_pipeline_size(Host, Port, Options), Max_attempts = get_max_attempts(Host, Port, Options), Options_1 = merge_options(Host, Port, Options), {SSLOptions, IsSSL} = - case (Protocol == https) orelse - get_value(is_ssl, Options_1, false) of + case + (Protocol == https) orelse + get_value(is_ssl, Options_1, false) + of false -> {[], false}; true -> {get_value(ssl_options, Options_1, []), true} end, - try_routing_request(Lb_pid, Parsed_url, - Max_sessions, - Max_pipeline_size, - {SSLOptions, IsSSL}, - Headers, Method, Body, Options_1, Timeout, Timeout, os:timestamp(), Max_attempts, 0); + try_routing_request( + Lb_pid, + Parsed_url, + Max_sessions, + Max_pipeline_size, + {SSLOptions, IsSSL}, + Headers, + Method, + Body, + Options_1, + Timeout, + Timeout, + os:timestamp(), + Max_attempts, + 0 + ); Err -> {error, {url_parsing_failed, Err}} + catch + _:Reason -> + {error, {url_parsing_failed, Reason}} end. lb_pid(Host, Port, Url) -> @@ -422,7 +441,7 @@ merge_options(Host, Port, Options) -> get_config_value({options, global}, []), lists:foldl( fun({Key, Val}, Acc) -> - case lists:keysearch(Key, 1, Options) of + case lists:keyfind(Key, 1, Options) of false -> [{Key, Val} | Acc]; _ -> @@ -496,37 +515,30 @@ set_max_pipeline_size(Host, Port, Max) when is_integer(Max), Max > 0 -> set_max_attempts(Host, Port, Max) when is_integer(Max), Max > 0 -> gen_server:call(?MODULE, {set_config_value, {max_attempts, Host, Port}, Max}). +%% @doc set the download directory where the files are stored +%% @spec set_download_dir(Dir::string()) -> ok +set_download_dir(Dir) -> + gen_server:call(?MODULE, {set_config_value, download_dir, Dir}). + do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout) -> - case catch ibrowse_http_client:send_req(Conn_Pid, Parsed_url, - Headers, Method, ensure_bin(Body), - Options, Timeout) of - {'EXIT', {timeout, _}} -> - P_info = case catch erlang:process_info(Conn_Pid, [messages, message_queue_len, backtrace]) of - [_|_] = Conn_Pid_info_list -> - Conn_Pid_info_list; - _ -> - process_info_not_available - end, - log_msg("{ibrowse_http_client, send_req, ~1000.p} gen_server call timeout.~nProcess info: ~p~n", - [[Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout], P_info]), - {error, req_timedout}; - {'EXIT', {normal, _}} = Ex_rsn -> - log_msg("{ibrowse_http_client, send_req, ~1000.p} gen_server call got ~1000.p~n", - [[Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout], Ex_rsn]), - {error, req_timedout}; - {error, X} when X == connection_closed; - X == {send_failed, {error, enotconn}}; - X == {send_failed,{error,einval}}; - X == {send_failed,{error,closed}}; - X == connection_closing; - ((X == connection_closed_no_retry) andalso ((Method == get) orelse (Method == head))) -> + try + ibrowse_http_client:send_req( + Conn_Pid, Parsed_url, Headers, Method, ensure_bin(Body), Options, Timeout + ) + of + {error, X} when + X == connection_closed; + X == {send_failed, {error, enotconn}}; + X == {send_failed, {error, einval}}; + X == {send_failed, {error, closed}}; + X == connection_closing; + ((X == connection_closed_no_retry) andalso ((Method == get) orelse (Method == head))) + -> {error, sel_conn_closed}; {error, connection_closed_no_retry} -> {error, connection_closed}; {error, {'EXIT', {noproc, _}}} -> {error, sel_conn_closed}; - {'EXIT', Reason} -> - {error, {'EXIT', Reason}}; {ok, St_code, Headers, Body} = Ret when is_binary(Body) -> case get_value(response_format, Options, list) of list -> @@ -543,6 +555,35 @@ do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout) -> end; Ret -> Ret + catch + throw:Term -> + Term; + exit:{timeout, _} -> + P_info = + try erlang:process_info(Conn_Pid, [messages, message_queue_len, backtrace]) of + [_ | _] = Conn_Pid_info_list -> + Conn_Pid_info_list; + _ -> + process_info_not_available + catch + _:_ -> + process_info_not_available + end, + log_msg( + "{ibrowse_http_client, send_req, ~1000.p} gen_server call timeout.~nProcess info: ~p~n", + [[Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout], P_info] + ), + {error, req_timedout}; + exit:{normal, _} = Ex_rsn -> + log_msg( + "{ibrowse_http_client, send_req, ~1000.p} gen_server call got ~1000.p~n", + [[Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout], Ex_rsn] + ), + {error, req_timedout}; + exit:Reason -> + {'EXIT', Reason}; + error:Reason:Stacktrace -> + {'EXIT', {Reason, Stacktrace}} end. ensure_bin(L) when is_list(L) -> list_to_binary(L); @@ -614,9 +655,11 @@ send_req_direct(Conn_pid, Url, Headers, Method, Body, Options) -> %% @doc Same as send_req/6 except that the first argument is the PID %% returned by spawn_worker_process/2 or spawn_link_worker_process/2 send_req_direct(Conn_pid, Url, Headers, Method, Body, Options, Timeout) -> - case catch parse_url(Url) of - #url{host = Host, - port = Port} = Parsed_url -> + try parse_url(Url) of + #url{ + host = Host, + port = Port + } = Parsed_url -> Options_1 = merge_options(Host, Port, Options), case do_send_req(Conn_pid, Parsed_url, Headers, Method, Body, Options_1, Timeout) of {error, {'EXIT', {noproc, _}}} -> @@ -626,18 +669,27 @@ send_req_direct(Conn_pid, Url, Headers, Method, Body, Options, Timeout) -> end; Err -> {error, {url_parsing_failed, Err}} + catch + _:Reason -> + {error, {url_parsing_failed, Reason}} end. %% @doc Tell ibrowse to stream the next chunk of data to the %% caller. Should be used in conjunction with the %% <code>stream_to</code> option %% @spec stream_next(Req_id :: req_id()) -> ok | {error, unknown_req_id} -stream_next(Req_id) -> +stream_next(Req_id) -> case ets:lookup(ibrowse_stream, {req_id_pid, Req_id}) of [] -> {error, unknown_req_id}; [{_, Pid}] -> - catch Pid ! {stream_next, Req_id}, + try + Pid ! {stream_next, Req_id} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, ok end. @@ -647,12 +699,18 @@ stream_next(Req_id) -> %% the connection which is serving this Req_id will be aborted, and an %% error returned. %% @spec stream_close(Req_id :: req_id()) -> ok | {error, unknown_req_id} -stream_close(Req_id) -> +stream_close(Req_id) -> case ets:lookup(ibrowse_stream, {req_id_pid, Req_id}) of [] -> {error, unknown_req_id}; [{_, Pid}] -> - catch Pid ! {stream_close, Req_id}, + try + Pid ! {stream_close, Req_id} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, ok end. @@ -688,7 +746,6 @@ all_trace_off() -> %% @doc Shows some internal information about load balancing. Info %% about workers spawned using spawn_worker_process/2 or %% spawn_link_worker_process/2 is not included. --ifdef(ets_ref). show_dest_status() -> io:format("~-40.40s | ~-5.5s | ~-10.10s | ~s~n", ["Server:port", "ETS", "Num conns", "LB Pid"]), @@ -702,21 +759,6 @@ show_dest_status() -> integer_to_list(Size), Lb_pid]) end, Metrics). --else. -show_dest_status() -> - io:format("~-40.40s | ~-5.5s | ~-10.10s | ~s~n", - ["Server:port", "ETS", "Num conns", "LB Pid"]), - io:format("~80.80.=s~n", [""]), - Metrics = get_metrics(), - lists:foreach( - fun({Host, Port, {Lb_pid, _, Tid, Size, _}}) -> - io:format("~40.40s | ~-5.5s | ~-5.5s | ~p~n", - [Host ++ ":" ++ integer_to_list(Port), - integer_to_list(Tid), - integer_to_list(Size), - Lb_pid]) - end, Metrics). --endif. show_dest_status(Url) -> #url{host = Host, port = Port} = ibrowse_lib:parse_url(Url), @@ -767,12 +809,15 @@ get_metrics(Host, Port) -> [] -> no_active_processes; [#lb_pid{pid = Lb_pid, ets_tid = Tid}] -> - MsgQueueSize = case (catch process_info(Lb_pid, message_queue_len)) of - {message_queue_len, Msg_q_len} -> - Msg_q_len; - _ -> - -1 - end, + MsgQueueSize = + try process_info(Lb_pid, message_queue_len) of + {message_queue_len, Msg_q_len} -> + Msg_q_len; + _ -> + -1 + catch + _:_ -> -1 + end, case Tid of undefined -> {Lb_pid, MsgQueueSize, undefined, 0, {{0, 0}, {0, 0}}}; @@ -934,19 +979,47 @@ handle_call({set_config_value, Key, Val}, _From, State) -> {reply, ok, State}; handle_call(rescan_config, _From, State) -> - Ret = (catch import_config()), + Ret = + try + import_config() + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, {reply, Ret, State}; handle_call({rescan_config, File}, _From, State) -> - Ret = (catch import_config(File)), + Ret = + try + import_config(File) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, {reply, Ret, State}; handle_call({rescan_config_terms, Terms}, _From, State) -> - Ret = (catch apply_config(Terms)), + Ret = + try + apply_config(Terms) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, {reply, Ret, State}; handle_call({add_config_terms, Terms}, _From, State) -> - Ret = (catch insert_config(Terms)), + Ret = + try + insert_config(Terms) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, {reply, Ret, State}; handle_call(Request, _From, State) -> @@ -979,7 +1052,13 @@ handle_info(all_trace_off, State) -> false -> ok; true -> - catch Pid ! {trace, false} + try + Pid ! {trace, false} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end end; (_, Acc) -> Acc @@ -993,13 +1072,22 @@ handle_info({trace, Bool}, State) -> {noreply, State}; handle_info({trace, Bool, Host, Port}, State) -> - Fun = fun(#lb_pid{host_port = {H, P}, pid = Pid}, _) - when H == Host, - P == Port -> - catch Pid ! {trace, Bool}; - (_, Acc) -> - Acc - end, + Fun = + fun + (#lb_pid{host_port = {H, P}, pid = Pid}, _) when + H == Host, + P == Port + -> + try + Pid ! {trace, Bool} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end; + (_, Acc) -> + Acc + end, ets:foldl(Fun, undefined, ibrowse_lb), ets:insert(ibrowse_conf, #ibrowse_conf{key = {trace, Host, Port}, value = Bool}), diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index 443772e..2b9aa53 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -19,7 +19,8 @@ start/1, start/2, stop/1, - send_req/7 + send_req/7, + chunk_request_body/2 ]). -ifdef(debug). @@ -102,23 +103,29 @@ start_link(Args, Options) -> gen_server:start_link(?MODULE, Args, Options). stop(Conn_pid) -> - case catch gen_server:call(Conn_pid, stop) of - {'EXIT', {timeout, _}} -> + try gen_server:call(Conn_pid, stop) of + _ -> + ok + catch + exit:{timeout, _} -> exit(Conn_pid, kill), ok; - _ -> + _:_ -> ok end. send_req(Conn_Pid, Url, Headers, Method, Body, Options, Timeout) -> - case catch gen_server:call(Conn_Pid, - {send_req, {Url, Headers, Method, Body, Options, Timeout}}, Timeout) of - {'EXIT', {timeout, _}} -> + try + gen_server:call( + Conn_Pid, {send_req, {Url, Headers, Method, Body, Options, Timeout}}, Timeout + ) + catch + exit:{timeout, _} -> {error, req_timedout}; - {'EXIT', {noproc, _}} -> + exit:{noproc, _} -> {error, connection_closed}; - Res -> - Res + exit:Reason -> + {'EXIT', Reason} end. %%==================================================================== @@ -145,10 +152,13 @@ init({Lb_Tid, #url{host = Host, port = Port}, {SSLOptions, Is_ssl}}) -> {ok, set_inac_timer(State)}; init(Url) when is_list(Url) -> maybe_trap_exits(), - case catch ibrowse_lib:parse_url(Url) of + try ibrowse_lib:parse_url(Url) of #url{protocol = Protocol} = Url_rec -> init({undefined, Url_rec, {[], Protocol == https}}); - {'EXIT', _} -> + _ -> + {error, invalid_url} + catch + _:_ -> {error, invalid_url} end; init({Host, Port}) -> @@ -266,11 +276,17 @@ handle_info({ssl_error, _Sock, Reason}, State) -> handle_info({req_timedout, From}, #state{reqs = Reqs} = State) -> Reqs_list = queue:to_list(Reqs), - case lists:keysearch(From, #request.from, Reqs_list) of + case lists:keyfind(From, #request.from, Reqs_list) of false -> {noreply, State}; - {value, #request{stream_to = StreamTo, req_id = ReqId}} -> - catch StreamTo ! {ibrowse_async_response_timeout, ReqId}, + #request{stream_to = StreamTo, req_id = ReqId} -> + try + StreamTo ! {ibrowse_async_response_timeout, ReqId} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, State_1 = State#state{proc_state = ?dead_proc_walking}, shutting_down(State_1), Reqs_1 = lists:filter(fun(#request{from = X_from}) -> @@ -311,7 +327,13 @@ handle_info(Info, State) -> terminate(_Reason, #state{lb_ets_tid = Tid} = State) -> do_close(State), shutting_down(State), - (catch ets:select_delete(Tid, [{{{'_','_','$1'},'_'},[{'==','$1',{const,self()}}],[true]}])), + try + ets:select_delete(Tid, [{{{'_','_','$1'},'_'},[{'==','$1',{const,self()}}],[true]}]) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, ok. %%-------------------------------------------------------------------- @@ -322,11 +344,10 @@ terminate(_Reason, #state{lb_ets_tid = Tid} = State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. - %%-------------------------------------------------------------------- %% Function: format_status/1 %% Purpose: Clean process state before logging -%% Returns: key value maps +%% Returns: #{state => ScrubbedState} %%-------------------------------------------------------------------- format_status(Status) -> maps:map( @@ -539,13 +560,9 @@ accumulate_response(Data, #state{reply_buffer = RepBuf, end. generate_timestamp() -> - case catch erlang:unique_integer([positive]) of - {'EXIT', _} -> - erlang:apply(erlang, now, []); - Unique -> - {A,B,C} = os:timestamp(), - {A * 1000000 + B, C, Unique} - end. + {A, B, C} = os:timestamp(), + Unique = erlang:unique_integer([positive]), + {A * 1000000 + B, C, Unique}. make_tmp_filename(true) -> DownloadDir = ibrowse:get_config_value(download_dir, filename:absname("./")), @@ -634,13 +651,18 @@ do_connect(Host, Port, Options, #state{is_ssl = true, use_proxy = false, ssl_options = SSLOptions}, Timeout) -> + %% Check for connect_to override and remove from options + Host1 = get_value(connect_to, Options, Host), + Options1 = proplists:delete(connect_to, Options), + %% if a socks5 proxy is configured, open the socket separately %% before upgrading the socket to a TLS connection. - case get_value(socks5_host, Options, undefined) of + case get_value(socks5_host, Options1, undefined) of %% no socks5 proxy is configured, connect directly with TLS: undefined -> - Sock_options = get_sock_options(Host, Options, SSLOptions), - ssl:connect(Host, Port, Sock_options, Timeout); + Sock_options = get_sock_options(Host, Options1, SSLOptions), + Sock_options1 = ensure_sni(Sock_options, Host), + ssl:connect(Host1, Port, Sock_options1, Timeout); %% proxy configuration is present: first establish a socket %% and then upgrade: @@ -657,13 +679,23 @@ do_connect(Host, Port, Options, #state{is_ssl = true, end; do_connect(Host, Port, Options, _State, Timeout) -> - Socks5Host = get_value(socks5_host, Options, undefined), - Sock_options = get_sock_options(Host, Options, []), + %% Check for connect_to override and remove from options + Host1 = get_value(connect_to, Options, Host), + Options1 = proplists:delete(connect_to, Options), + + Socks5Host = get_value(socks5_host, Options1, undefined), + Sock_options = get_sock_options(Host, Options1, []), case Socks5Host of - undefined -> - gen_tcp:connect(Host, Port, Sock_options, Timeout); - _ -> - catch ibrowse_socks5:connect(Host, Port, Options, Sock_options, Timeout) + undefined -> + gen_tcp:connect(Host1, Port, Sock_options, Timeout); + _ -> + try + ibrowse_socks5:connect(Host1, Port, Options1, Sock_options, Timeout) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end end. get_sock_options(Host, Options, SSLOptions) -> @@ -681,10 +713,10 @@ get_sock_options(Host, Options, SSLOptions) -> [] end, Other_sock_options = filter_sock_options(SSLOptions ++ Caller_socket_options ++ Ipv6Options), - case lists:keysearch(nodelay, 1, Other_sock_options) of + case lists:keyfind(nodelay, 1, Other_sock_options) of false -> [{nodelay, true}, binary, {active, false} | Other_sock_options]; - {value, _} -> + _ -> [binary, {active, false} | Other_sock_options] end. @@ -711,10 +743,20 @@ filter_sock_options(Opts) -> false; (list) -> false; + (_) -> true end, Opts). +%% Ensure SNI is set for SSL connections when using connect_to override +ensure_sni(Opts, Host) -> + case lists:keyfind(server_name_indication, 1, Opts) of + false -> + [{server_name_indication, Host} | Opts]; + _ -> + Opts + end. + do_send(Req, #state{socket = Sock, is_ssl = true, use_proxy = true, @@ -807,9 +849,30 @@ do_close(#state{socket = Sock, is_ssl = true, use_proxy = true, proxy_tunnel_setup = Pts - }) when Pts /= done -> catch gen_tcp:close(Sock); -do_close(#state{socket = Sock, is_ssl = true}) -> catch ssl:close(Sock); -do_close(#state{socket = Sock, is_ssl = false}) -> catch gen_tcp:close(Sock). + }) when Pts /= done -> + try + gen_tcp:close(Sock) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end; +do_close(#state{socket = Sock, is_ssl = true}) -> + try + ssl:close(Sock) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end; +do_close(#state{socket = Sock, is_ssl = false}) -> + try + gen_tcp:close(Sock) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end. active_once(#state{cur_req = #request{caller_controls_socket = true}}) -> ok; @@ -1023,7 +1086,13 @@ send_req_1(From, false -> ok; true -> - catch StreamTo ! {ibrowse_async_raw_req, Raw_req} + try + StreamTo ! {ibrowse_async_raw_req, Raw_req} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end end end, State_4 = set_inac_timer(State_3), @@ -1053,14 +1122,14 @@ maybe_modify_headers(#url{host = Host, port = Port} = Url, case get_value(headers_as_is, Options, false) of false -> Headers_1 = add_auth_headers(Url, Options, Headers, State), - HostHeaderValue = case lists:keysearch(host_header, 1, Options) of + HostHeaderValue = case lists:keyfind(host_header, 1, Options) of false -> case Port of 80 -> Host; 443 -> Host; _ -> [Host, ":", integer_to_list(Port)] end; - {value, {_, Host_h_val}} -> + {_, Host_h_val} -> Host_h_val end, [{"Host", HostHeaderValue} | Headers_1]; @@ -1108,7 +1177,7 @@ make_request(Method, Headers, AbsPath, RelPath, Body, Options, end, Headers_0 = [Fun1(X) || X <- Headers], Headers_1 = - case lists:keysearch("content-length", 1, Headers_0) of + case lists:keyfind("content-length", 1, Headers_0) of false when (Body =:= [] orelse Body =:= <<>>) andalso (Method =:= post orelse Method =:= put) -> [{"content-length", "Content-Length", "0"} | Headers_0]; @@ -1202,33 +1271,36 @@ chunk_request_body(Body, _ChunkSize) when is_tuple(Body) orelse chunk_request_body(Body, ChunkSize) -> chunk_request_body(Body, ChunkSize, []). -chunk_request_body(Body, _ChunkSize, Acc) when Body == <<>>; Body == [] -> +chunk_request_body(Body, ChunkSize, Acc) when is_binary(Body) -> + chunk_binary_body(Body, ChunkSize, Acc); +chunk_request_body([], _ChunkSize, Acc) -> LastChunk = "0\r\n", lists:reverse(["\r\n", LastChunk | Acc]); -chunk_request_body(Body, ChunkSize, Acc) when is_binary(Body), - size(Body) >= ChunkSize -> - <<ChunkBody:ChunkSize/binary, Rest/binary>> = Body, - Chunk = [?dec2hex(ChunkSize),"\r\n", - ChunkBody, "\r\n"], - chunk_request_body(Rest, ChunkSize, [Chunk | Acc]); -chunk_request_body(Body, _ChunkSize, Acc) when is_binary(Body) -> - BodySize = size(Body), - Chunk = [?dec2hex(BodySize),"\r\n", - Body, "\r\n"], - LastChunk = "0\r\n", - lists:reverse(["\r\n", LastChunk, Chunk | Acc]); chunk_request_body(Body, ChunkSize, Acc) when length(Body) >= ChunkSize -> {ChunkBody, Rest} = split_list_at(Body, ChunkSize), - Chunk = [?dec2hex(ChunkSize),"\r\n", - ChunkBody, "\r\n"], + Chunk = [?dec2hex(ChunkSize),"\r\n", ChunkBody, "\r\n"], chunk_request_body(Rest, ChunkSize, [Chunk | Acc]); chunk_request_body(Body, _ChunkSize, Acc) when is_list(Body) -> BodySize = length(Body), - Chunk = [?dec2hex(BodySize),"\r\n", - Body, "\r\n"], + Chunk = [?dec2hex(BodySize),"\r\n", Body, "\r\n"], LastChunk = "0\r\n", lists:reverse(["\r\n", LastChunk, Chunk | Acc]). +chunk_binary_body(Body, ChunkSize, Acc) -> + case Body of + <<ChunkBody:ChunkSize/binary, Rest/binary>> -> + Chunk = [?dec2hex(ChunkSize),"\r\n", ChunkBody, "\r\n"], + chunk_binary_body(Rest, ChunkSize, [Chunk | Acc]); + <<>> -> + LastChunk = "0\r\n", + lists:reverse(["\r\n", LastChunk | Acc]); + _ -> + BodySize = byte_size(Body), + Chunk = [?dec2hex(BodySize),"\r\n", Body, "\r\n"], + LastChunk = "0\r\n", + lists:reverse(["\r\n", LastChunk, Chunk | Acc]) + end. + parse_response(<<>>, #state{cur_req = undefined}=State) -> State#state{status = idle}; @@ -1382,26 +1454,37 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = Reqs, {stat_code, StatCode}, Headers}}), {error, content_length_undefined}; V -> - case catch list_to_integer(V) of + try list_to_integer(V) of V_1 when is_integer(V_1), V_1 >= 0 -> send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1), do_trace("Recvd Content-Length of ~p~n", [V_1]), - State_2 = State_1#state{rep_buf_size=0, - reply_buffer = <<>>, - content_length=V_1}, + State_2 = State_1#state{ + rep_buf_size = 0, + reply_buffer = <<>>, + content_length = V_1 + }, case parse_11_response(Data_1, State_2) of {error, Reason} -> - fail_pipelined_requests(State_1, - {error, {Reason, - {stat_code, StatCode}, Headers_1}}), + fail_pipelined_requests( + State_1, + {error, {Reason, {stat_code, StatCode}, Headers_1}} + ), {error, Reason}; State_3 -> State_3 end; _ -> - fail_pipelined_requests(State_1, - {error, {content_length_undefined, - {stat_code, StatCode}, Headers}}), + fail_pipelined_requests( + State_1, + {error, {content_length_undefined, {stat_code, StatCode}, Headers}} + ), + {error, content_length_undefined} + catch + _:_ -> + fail_pipelined_requests( + State_1, + {error, {content_length_undefined, {stat_code, StatCode}, Headers}} + ), {error, content_length_undefined} end end; @@ -1425,7 +1508,7 @@ upgrade_to_ssl(#state{socket = Socket, proxy_tunnel_setup = done}, send_queued_requests(lists:reverse(Q), State_1); Err -> - do_trace("Upgrade to SSL socket failed. Reson: ~p~n", [Err]), + do_trace("Upgrade to SSL socket failed. Reason: ~p~n", [Err]), do_error_reply(State, {error, {send_failed, Err}}), {error, send_failed} end. @@ -1956,9 +2039,21 @@ send_async_headers(ReqId, StreamTo, Give_raw_headers, {Headers_1, Raw_headers_1} = maybe_add_custom_headers(Status_line, Headers, Raw_headers, Opts), case Give_raw_headers of false -> - catch StreamTo ! {ibrowse_async_headers, ReqId, StatCode, Headers_1}; + try + StreamTo ! {ibrowse_async_headers, ReqId, StatCode, Headers_1} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end; true -> - catch StreamTo ! {ibrowse_async_headers, ReqId, Status_line, Raw_headers_1} + try + StreamTo ! {ibrowse_async_headers, ReqId, Status_line, Raw_headers_1} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end end. maybe_add_custom_headers(Status_line, Headers, Raw_headers, Opts) -> @@ -2012,16 +2107,28 @@ do_reply(#state{prev_req_id = Prev_req_id} = State, ok; _ -> Body_1 = format_response_data(Resp_format, Body), - catch StreamTo ! {ibrowse_async_response, ReqId, Body_1} + try + StreamTo ! {ibrowse_async_response, ReqId, Body_1} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end + end, + try + StreamTo ! {ibrowse_async_response_end, ReqId} + catch + throw:_Term -> _Term; + exit:_Reason -> {'EXIT', _Reason}; + error:_Reason:_Stacktrace -> {'EXIT', {_Reason, _Stacktrace}} end, - catch StreamTo ! {ibrowse_async_response_end, ReqId}, - %% We don't want to delete the Req-id to Pid mapping straightaway + %% We don't want to delete the Req-id to Pid mapping straight away %% as the client may send a stream_next message just while we are %% sending back this ibrowse_async_response_end message. If we - %% deleted this mapping straightaway, the caller will see a + %% deleted this mapping straight away, the caller will see a %% {error, unknown_req_id} when it calls ibrowse:stream_next/1. To %% get around this, we store the req id, and clear it after the - %% next request. If there are wierd combinations of stream, + %% next request. If there are weird combinations of stream, %% stream_once and sync requests on the same connection, it will %% take a while for the req_id-pid mapping to get cleared, but it %% should do no harm. @@ -2030,14 +2137,26 @@ do_reply(#state{prev_req_id = Prev_req_id} = State, do_reply(State, _From, StreamTo, ReqId, Resp_format, Msg) -> State_1 = dec_pipeline_counter(State), Msg_1 = format_response_data(Resp_format, Msg), - catch StreamTo ! {ibrowse_async_response, ReqId, Msg_1}, + try + StreamTo ! {ibrowse_async_response, ReqId, Msg_1} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, State_1. do_interim_reply(undefined, _, _ReqId, _Msg) -> ok; do_interim_reply(StreamTo, Response_format, ReqId, Msg) -> Msg_1 = format_response_data(Response_format, Msg), - catch StreamTo ! {ibrowse_async_response, ReqId, Msg_1}. + try + StreamTo ! {ibrowse_async_response, ReqId, Msg_1} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end. do_error_reply(#state{reqs = Reqs, tunnel_setup_queue = Tun_q} = State, Err) -> ReqList = queue:to_list(Reqs), @@ -2130,7 +2249,13 @@ shutting_down(#state{lb_ets_tid = undefined}) -> ok; shutting_down(#state{lb_ets_tid = Tid, cur_pipeline_size = _Sz}) -> - (catch ets:select_delete(Tid, [{{{'_', '_', '$1'},'_'},[{'==','$1',{const,self()}}],[true]}])). + try + ets:select_delete(Tid, [{{{'_', '_', '$1'},'_'},[{'==','$1',{const,self()}}],[true]}]) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end. inc_pipeline_counter(#state{is_closing = true} = State) -> State; @@ -2144,12 +2269,24 @@ dec_pipeline_counter(#state{cur_pipeline_size = Pipe_sz, proc_state = Proc_state} = State) when Tid /= undefined, Proc_state /= ?dead_proc_walking -> Ts = os:timestamp(), - catch ets:insert(Tid, {{Pipe_sz - 1, os:timestamp(), self()}, []}), - (catch ets:select_delete(Tid, [{{{'_', '$2', '$1'},'_'}, - [{'==', '$1', {const,self()}}, - {'<', '$2', {const,Ts}} - ], - [true]}])), + try + ets:insert(Tid, {{Pipe_sz - 1, os:timestamp(), self()}, []}) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, + try + ets:select_delete(Tid, [{{{'_', '$2', '$1'},'_'}, + [{'==', '$1', {const,self()}}, + {'<', '$2', {const,Ts}} + ], + [true]}]) + catch + throw:_Term -> _Term; + exit:_Reason -> {'EXIT', _Reason}; + error:_Reason:_Stacktrace -> {'EXIT', {_Reason, _Stacktrace}} + end, State#state{cur_pipeline_size = Pipe_sz - 1}; dec_pipeline_counter(State) -> State. @@ -2166,8 +2303,8 @@ get_stream_chunk_size(Options) -> true -> infinity; _ -> - case lists:keysearch(stream_chunk_size, 1, Options) of - {value, {_, V}} when V > 0 -> + case lists:keyfind(stream_chunk_size, 1, Options) of + {_, V} when V > 0 -> V; _ -> ?DEFAULT_STREAM_CHUNK_SIZE @@ -2234,12 +2371,12 @@ to_binary(X) when is_list(X) -> list_to_binary(X); to_binary(X) when is_binary(X) -> X. get_header_value(Name, Headers, Default_val) -> - case lists:keysearch(Name, 1, Headers) of + case lists:keyfind(Name, 1, Headers) of false -> Default_val; - {value, {_, Val}} when is_binary(Val) -> + {_, Val} when is_binary(Val) -> binary_to_list(Val); - {value, {_, Val}} -> + {_, Val} -> Val end. diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 894d8ad..e7b19ce 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -90,11 +90,11 @@ spawn_connection(Lb_pid, Url, {spawn_connection, Url, Max_sessions, Max_pipeline_size, SSL_options, Process_options}). stop(Lb_pid) -> - case catch gen_server:call(Lb_pid, stop) of - {'EXIT', {timeout, _}} -> - exit(Lb_pid, kill); - ok -> - ok + try + gen_server:call(Lb_pid, stop) + catch + exit:{timeout, _} -> exit(Lb_pid, kill); + exit:Reason -> {'EXIT', Reason} end. %%-------------------------------------------------------------------- %% Function: handle_call/3 @@ -165,7 +165,13 @@ handle_info({trace, Bool}, #state{ets_tid = undefined} = State) -> handle_info({trace, Bool}, #state{ets_tid = Tid} = State) -> ets:foldl(fun({{_, Pid}, _}, Acc) when is_pid(Pid) -> - catch Pid ! {trace, Bool}, + try + Pid ! {trace, Bool} + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, Acc; (_, Acc) -> Acc @@ -194,7 +200,13 @@ handle_info(_Info, State) -> %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- terminate(_Reason, #state{host = Host, port = Port, ets_tid = Tid} = _State) -> - catch ets:delete(ibrowse_lb, {Host, Port}), + try + ets:delete(ibrowse_lb, {Host, Port}) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, stop_all_conn_procs(Tid), ok. diff --git a/src/ibrowse_lib.erl b/src/ibrowse_lib.erl index 6c1883d..4053721 100644 --- a/src/ibrowse_lib.erl +++ b/src/ibrowse_lib.erl @@ -62,11 +62,13 @@ d2h(N) when N<10 -> N+$0; d2h(N) -> N+$a-10. decode_rfc822_date(String) when is_list(String) -> - case catch decode_rfc822_date_1(string:tokens(String, ", \t\r\n")) of - {'EXIT', _} -> - {error, invalid_date}; - Res -> - Res + try + decode_rfc822_date_1(string:tokens(String, ", \t\r\n")) + catch + throw:Term -> + Term; + _:_ -> + {error, invalid_date} end. % TODO: Have to handle the Zone @@ -174,15 +176,15 @@ decode_base64(Bin) when is_binary(Bin) -> base64:decode(Bin). get_value(Tag, TVL, DefVal) -> - case lists:keysearch(Tag, 1, TVL) of + case lists:keyfind(Tag, 1, TVL) of false -> DefVal; - {value, {_, Val}} -> + {_, Val} -> Val end. get_value(Tag, TVL) -> - {value, {_, V}} = lists:keysearch(Tag,1,TVL), + {_, V} = lists:keyfind(Tag, 1, TVL), V. parse_url(Url) -> @@ -422,7 +424,13 @@ log_msg(Fmt, Args) -> end. log_msg(M, F, Fmt, Args) -> - catch apply(M, F, [Fmt, Args]). + try + apply(M, F, [Fmt, Args]) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end. -ifdef(EUNIT). diff --git a/test/ibrowse_load_test.erl b/test/ibrowse_load_test.erl index 076c46b..707b75b 100644 --- a/test/ibrowse_load_test.erl +++ b/test/ibrowse_load_test.erl @@ -1,7 +1,6 @@ -module(ibrowse_load_test). %%-compile(export_all). -export([ - random_seed/0, start/3, query_state/0, shutdown/0, @@ -15,20 +14,6 @@ update_unknown_counter/2 ]). --ifdef(new_rand). - --define(RAND, rand). -random_seed() -> - ok. - --else. - --define(RAND, random). -random_seed() -> - random:seed(os:timestamp()). - --endif. - -define(ibrowse_load_test_counters, ibrowse_load_test_counters). start(Num_workers, Num_requests, Max_sess) -> @@ -47,12 +32,16 @@ start_1(Num_workers, Num_requests, Max_sess) -> application:start(ibrowse), application:set_env(ibrowse, inactivity_timeout, 5000), Ulimit = os:cmd("ulimit -n"), - case catch list_to_integer(string:strip(Ulimit, right, $\n)) of + try list_to_integer(string:strip(Ulimit, right, $\n)) of X when is_integer(X), X > 3000 -> ok; X -> io:format("Load test not starting. {insufficient_value_for_ulimit, ~p}~n", [X]), exit({insufficient_value_for_ulimit, X}) + catch + _:X -> + io:format("Load test not starting. {insufficient_value_for_ulimit, ~p}~n", [X]), + exit({insufficient_value_for_ulimit, X}) end, ets:new(?ibrowse_load_test_counters, [named_table, public]), ets:new(ibrowse_load_timings, [named_table, public]), @@ -123,12 +112,11 @@ spawn_workers(0, _Num_requests, _Parent, Acc) -> lists:reverse(Acc); spawn_workers(Num_workers, Num_requests, Parent, Acc) -> Pid_ref = spawn_monitor(fun() -> - random_seed(), - case catch worker_loop(Parent, Num_requests) of - {'EXIT', Rsn} -> - io:format("Worker crashed with reason: ~p~n", [Rsn]); - _ -> - ok + try worker_loop(Parent, Num_requests) of + _ -> ok + catch + throw:_ -> ok; + _:Rsn -> io:format("Worker crashed with reason: ~p~n", [Rsn]) end end), spawn_workers(Num_workers - 1, Num_requests, Parent, [Pid_ref | Acc]). @@ -163,7 +151,7 @@ wait_for_workers([{Pid, Pid_ref} | T] = Pids) -> worker_loop(Parent, 0) -> Parent ! {done, self()}; worker_loop(Parent, N) -> - Delay = ?RAND:uniform(100), + Delay = rand:uniform(100), Url = case Delay rem 10 of %% Change 10 to some number between 0-9 depending on how %% much chaos you want to introduce into the server @@ -200,10 +188,12 @@ worker_loop(Parent, N) -> worker_loop(Parent, N - 1). update_unknown_counter(Counter, Inc_val) -> - case catch ets:update_counter(?ibrowse_load_test_counters, Counter, Inc_val) of - {'EXIT', _} -> + try ets:update_counter(?ibrowse_load_test_counters, Counter, Inc_val) of + _ -> ok + catch + throw:_ -> + ok; + _:_ -> ets:insert_new(?ibrowse_load_test_counters, {Counter, 0}), - update_unknown_counter(Counter, Inc_val); - _ -> - ok + update_unknown_counter(Counter, Inc_val) end. diff --git a/test/ibrowse_test.erl b/test/ibrowse_test.erl index 9a7b192..5fe5be2 100644 --- a/test/ibrowse_test.erl +++ b/test/ibrowse_test.erl @@ -34,14 +34,18 @@ test_preserve_status_line/0, test_binary_headers/0, test_binary_headers/1, + test_connect_to_overrides_target/0, + test_connect_to_unreachable_fails/0, + test_connect_to_preserves_host_header/0, + test_dead_lb_pid/0, + test_chunk_request_body/0, test_generate_body_0/0, test_retry_of_requests/0, test_retry_of_requests/1, test_save_to_file_no_content_length/0, socks5_noauth/0, socks5_auth_succ/0, - socks5_auth_fail/0, - test_dead_lb_pid/0 + socks5_auth_fail/0 ]). -include_lib("ibrowse/include/ibrowse.hrl"). @@ -62,11 +66,15 @@ {local_test_fun, test_303_response_with_a_body, []}, {local_test_fun, test_303_response_with_no_body, []}, {local_test_fun, test_binary_headers, []}, + {local_test_fun, test_connect_to_overrides_target, []}, + {local_test_fun, test_connect_to_unreachable_fails, []}, + {local_test_fun, test_connect_to_preserves_host_header, []}, + {local_test_fun, test_dead_lb_pid, []}, + {local_test_fun, test_chunk_request_body, []}, {local_test_fun, test_retry_of_requests, []}, {local_test_fun, verify_chunked_streaming, []}, {local_test_fun, test_chunked_streaming_once, []}, - {local_test_fun, test_generate_body_0, []}, - {local_test_fun, test_dead_lb_pid, []} + {local_test_fun, test_generate_body_0, []} ]). -define(TEST_LIST, [{"http://intranet/messenger", get}, @@ -224,8 +232,20 @@ spawn_workers(Url, NumWorkers, NumReqsPerWorker) -> do_wait(Url) -> receive {'EXIT', _, normal} -> - catch ibrowse:show_dest_status(Url), - catch ibrowse:show_dest_status(), + try + ibrowse:show_dest_status(Url) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, + try + ibrowse:show_dest_status() + catch + throw:_Term -> _Term; + exit:_Reason -> {'EXIT', _Reason}; + error:_Reason:_Stacktrace -> {'EXIT', {_Reason, _Stacktrace}} + end, do_wait(Url); {'EXIT', Pid, Reason} -> ets:delete(pid_table, Pid), @@ -240,8 +260,20 @@ do_wait(Url) -> 0 -> done; _ -> - catch ibrowse:show_dest_status(Url), - catch ibrowse:show_dest_status(), + try + ibrowse:show_dest_status(Url) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, + try + ibrowse:show_dest_status() + catch + throw:_Term -> _Term; + exit:_Reason -> {'EXIT', _Reason}; + error:_Reason:_Stacktrace -> {'EXIT', {_Reason, _Stacktrace}} + end, do_wait(Url) end end. @@ -312,7 +344,13 @@ unit_tests(Options, Test_list) -> application:start(asn1), application:start(public_key), application:start(ssl), - (catch ibrowse_test_server:start_server(8181, tcp)), + try + ibrowse_test_server:start_server(8181, tcp) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, application:start(ibrowse), Options_1 = Options ++ [{connect_timeout, 5000}], Test_timeout = proplists:get_value(test_timeout, Options, 60000), @@ -326,7 +364,13 @@ unit_tests(Options, Test_list) -> exit(Pid, kill), io:format("Timed out waiting for tests to complete~n", []) end, - catch ibrowse_test_server:stop_server(8181), + try + ibrowse_test_server:stop_server(8181) + catch + throw:_Term -> _Term; + exit:_Reason -> {'EXIT', _Reason}; + error:_Reason:_Stacktrace -> {'EXIT', {_Reason, _Stacktrace}} + end, error_logger:tty(true), ok. @@ -490,12 +534,26 @@ maybe_stream_next(Req_id, Options) -> execute_req(local_test_fun, Method, Args) -> reset_ibrowse(), - Result = (catch apply(?MODULE, Method, Args)), + Result = + try + apply(?MODULE, Method, Args) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, io:format(" ~-54.54w: ", [Method]), io:format("~p~n", [Result]); execute_req(Url, Method, Options) -> io:format("~7.7w, ~50.50s: ", [Method, Url]), - Result = (catch ibrowse:send_req(Url, [], Method, [], Options)), + Result = + try + ibrowse:send_req(Url, [], Method, [], Options) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, case Result of {ok, SCode, _H, _B} -> io:format("Status code: ~p~n", [SCode]); @@ -546,6 +604,55 @@ test_binary_headers(Url) -> {test_failed, Res} end. +%%------------------------------------------------------------------------------ +%% url host is bogus, request only succeeds if connect_to actually +%% redirects +%%------------------------------------------------------------------------------ +test_connect_to_overrides_target() -> + clear_msg_q(), + Url = "http://no.such.host.invalid:8181/", + case ibrowse:send_req(Url, [], get, [], [{connect_to, "localhost"}], 5000) of + {ok, "200", _, _} -> + success; + Res -> + {test_failed, Res} + end. + +%%------------------------------------------------------------------------------ +%% url host reachable, but connect_to points at an unroutable +%% address +%%------------------------------------------------------------------------------ +test_connect_to_unreachable_fails() -> + clear_msg_q(), + case ibrowse:send_req("http://localhost:8181/", [], get, [], + [{connect_to, "192.0.2.1"}, + {connect_timeout, 500}], + 5000) of + {error, _} -> + success; + Res -> + {test_failed, Res} + end. + +%%------------------------------------------------------------------------------ +%% connect_to shouldn't bleed into the Host header, server should see the +%% original url host +%%------------------------------------------------------------------------------ +test_connect_to_preserves_host_header() -> + clear_msg_q(), + Url = "http://example.invalid:8181/ibrowse_echo_host", + case ibrowse:send_req(Url, [], get, [], [{connect_to, "localhost"}], 5000) of + {ok, "200", Headers, _} -> + case proplists:get_value("x-host", Headers) of + "example.invalid:8181" -> + success; + V -> + {fail, V} + end; + Res -> + {test_failed, Res} + end. + %%------------------------------------------------------------------------------ %% Test what happens when the response to a HEAD request is a %% Chunked-Encoding response with a non-empty body. Issue #67 on @@ -690,7 +797,14 @@ test_retry_of_requests(Url, Timeout) -> Parent = self(), Pids = lists:map(fun(_) -> spawn(fun() -> - Res = (catch ibrowse:send_req(Url, [], get, [], [], Timeout)), + Res = + try + ibrowse:send_req(Url, [], get, [], [], Timeout) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, Parent ! {self(), Res} end) end, lists:seq(1,10)), @@ -879,6 +993,7 @@ test_generate_body_0() -> ets:delete(Tid) end. +%%------------------------------------------------------------------------------ %% Test that when an lb process dies, its entry is removed from the ibrowse_lb %% table by the next requestor and replaced with a new process %%------------------------------------------------------------------------------ @@ -895,6 +1010,41 @@ test_dead_lb_pid() -> true = is_process_alive(NewPid), success. +%%------------------------------------------------------------------------------ +%% Test chunk request body encoding. We exported the function from the module +%% to run it through these tests +%%------------------------------------------------------------------------------ + +% Helper +chunk_body(Body, N) -> + ibrowse_http_client:chunk_request_body(Body, N). + +test_chunk_request_body() -> + Zero = <<"0\r\n\r\n">>, + Cases = [ + {<<Zero/binary>>, <<>>, 1024}, + {<<Zero/binary>>, [], 1024}, + {<<"5\r\nhello\r\n", Zero/binary>>, <<"hello">>, 1024}, + {<<"5\r\nhello\r\n", Zero/binary>>, "hello", 1024}, + {<<"5\r\nhello\r\n", Zero/binary>>, <<"hello">>, 5}, + {<<"5\r\nhello\r\n", Zero/binary>>, "hello", 5}, + {<<"5\r\nhello\r\n5\r\n worl\r\n2\r\nd!\r\n", Zero/binary>>, <<"hello world!">>, 5}, + {<<"5\r\nhello\r\n5\r\n worl\r\n2\r\nd!\r\n", Zero/binary>>, "hello world!", 5} + ], + Failures = + [{Body, N, Expect, Got} + || {Expect, Body, N} <- Cases, + Got <- [iolist_to_binary(chunk_body(Body, N))], + Got =/= Expect + ], + % We expect fun tuples to pass through + Fun = fun(_) -> eof end, + PassthroughOk = chunk_body(Fun, 1024) =:= Fun andalso chunk_body({Fun, foo}, 1024) =:= {Fun, foo}, + case {Failures, PassthroughOk} of + {[], true} -> success; + {_, _} -> {failed, Failures, {passthrough_ok, PassthroughOk}} + end. + do_trace(Fmt, Args) -> do_trace(get(my_trace_flag), Fmt, Args). diff --git a/test/ibrowse_test_server.erl b/test/ibrowse_test_server.erl index 631e93a..aa6badf 100644 --- a/test/ibrowse_test_server.erl +++ b/test/ibrowse_test_server.erl @@ -11,12 +11,6 @@ get_conn_pipeline_depth/0 ]). --ifdef(new_rand). --define(RAND, rand). --else. --define(RAND, random). --endif. - -record(request, {method, uri, version, headers = [], body = [], state}). -define(dec2hex(X), erlang:integer_to_list(X, 16)). @@ -57,7 +51,13 @@ start_server(Port, Sock_type) -> ibrowse_socks_server:start(8383, 2). %% Username/Password auth stop_server(Port) -> - catch server_proc_name(Port) ! stop, + try + server_proc_name(Port) ! stop + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, ibrowse_socks_server:stop(8282), ibrowse_socks_server:stop(8383), timer:sleep(2000), % wait for server to receive msg and unregister @@ -110,12 +110,24 @@ accept_loop(Sock, Sock_type) -> end. connection(Conn, Sock_type) -> - catch ets:insert(?CONN_PIPELINE_DEPTH, {self(), 0}), + try + ets:insert(?CONN_PIPELINE_DEPTH, {self(), 0}) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, try inet:setopts(Conn, [{packet, http}, {active, true}]), server_loop(Conn, Sock_type, #request{}) after - catch ets:delete(?CONN_PIPELINE_DEPTH, self()) + try + ets:delete(?CONN_PIPELINE_DEPTH, self()) + catch + throw:_Term -> _Term; + exit:_Reason -> {'EXIT', _Reason}; + error:_Reason:_Stacktrace -> {'EXIT', {_Reason, _Stacktrace}} + end end. set_controlling_process(Sock, tcp, Pid) -> @@ -131,7 +143,13 @@ setopts(Sock, ssl, Opts) -> server_loop(Sock, Sock_type, #request{headers = Headers} = Req) -> receive {http, Sock, {http_request, HttpMethod, HttpUri, HttpVersion}} -> - catch ets:update_counter(?CONN_PIPELINE_DEPTH, self(), 1), + try + ets:update_counter(?CONN_PIPELINE_DEPTH, self(), 1) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end, server_loop(Sock, Sock_type, Req#request{method = HttpMethod, uri = HttpUri, version = HttpVersion}); @@ -146,7 +164,13 @@ server_loop(Sock, Sock_type, #request{headers = Headers} = Req) -> collect_body -> server_loop(Sock, Sock_type, Req#request{state = collect_body}); _ -> - catch ets:update_counter(?CONN_PIPELINE_DEPTH, self(), -1) + try + ets:update_counter(?CONN_PIPELINE_DEPTH, self(), -1) + catch + throw:Term -> Term; + exit:Reason -> {'EXIT', Reason}; + error:Reason:Stacktrace -> {'EXIT', {Reason, Stacktrace}} + end end, server_loop(Sock, Sock_type, #request{}); {http, Sock, {http_error, Packet}} when Req#request.state == collect_body -> @@ -235,6 +259,16 @@ process_request(Sock, Sock_type, false -> collect_body end; +process_request(Sock, Sock_type, + #request{method='GET', + headers = Headers, + uri = {abs_path, "/ibrowse_echo_host"}}) -> + Host = get_host_header(Headers), + Resp = [<<"HTTP/1.1 200 OK\r\n">>, + <<"Server: ibrowse_test\r\n">>, + "x-host: ", Host, "\r\n", + <<"Content-Length: 0\r\n\r\n">>], + do_send(Sock, Sock_type, Resp); process_request(Sock, Sock_type, #request{method='GET', headers = Headers, @@ -307,7 +341,7 @@ process_request(Sock, Sock_type, Req) -> do_trace("Recvd req: ~p~n", [Req]), Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>, do_send(Sock, Sock_type, Resp), - timer:sleep(?RAND:uniform(100)). + timer:sleep(rand:uniform(100)). do_send(Sock, tcp, Resp) -> gen_tcp:send(Sock, Resp); @@ -372,3 +406,10 @@ get_content_length([{http_header, _, _X, _, _Y} | T]) -> get_content_length(T); get_content_length([]) -> undefined. + +get_host_header([{http_header, _, 'Host', _, V} | _]) -> + V; +get_host_header([_ | T]) -> + get_host_header(T); +get_host_header([]) -> + "not_found".
