ibrowse: update to 4.1.0 - for COUCHDB-2041 - ibrowse tagged 4.1.0 sha#7871e2e
Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/b63f393b Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/b63f393b Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/b63f393b Branch: refs/heads/2041-update-ibrowse Commit: b63f393b8fad85bd4fca022a86227e4540c48a6b Parents: 510070c Author: Dave Cottlehuber <[email protected]> Authored: Wed Jan 29 11:05:23 2014 +0100 Committer: Dave Cottlehuber <[email protected]> Committed: Sun Mar 16 17:52:41 2014 +0100 ---------------------------------------------------------------------- NOTICE | 2 +- src/ibrowse/ibrowse.app.in | 2 +- src/ibrowse/ibrowse.erl | 67 ++++-- src/ibrowse/ibrowse_http_client.erl | 358 +++++++++++++++++++------------ src/ibrowse/ibrowse_lb.erl | 28 ++- src/ibrowse/ibrowse_lib.erl | 7 +- src/ibrowse/ibrowse_socks5.erl | 143 ++++-------- 7 files changed, 331 insertions(+), 276 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/NOTICE ---------------------------------------------------------------------- diff --git a/NOTICE b/NOTICE index fda3caf..aa39311 100644 --- a/NOTICE +++ b/NOTICE @@ -36,7 +36,7 @@ This product also includes the following third-party components: * ibrowse (http://github.com/cmullaparthi/ibrowse/tree/master) - Copyright 2005-2012, Chandrashekhar Mullaparthi + Copyright 2005-2014, Chandrashekhar Mullaparthi * Erlang OAuth (http://github.com/tim/erlang-oauth) http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse.app.in ---------------------------------------------------------------------- diff --git a/src/ibrowse/ibrowse.app.in b/src/ibrowse/ibrowse.app.in index 1d88084..f318aef 100644 --- a/src/ibrowse/ibrowse.app.in +++ b/src/ibrowse/ibrowse.app.in @@ -1,6 +1,6 @@ {application, ibrowse, [{description, "Erlang HTTP client application"}, - {vsn, "4.0.1"}, + {vsn, "4.1.0"}, {registered, [ibrowse_sup, ibrowse]}, {applications, [kernel,stdlib]}, {env, []}, http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse.erl ---------------------------------------------------------------------- diff --git a/src/ibrowse/ibrowse.erl b/src/ibrowse/ibrowse.erl index 80a4282..fc90dc6 100644 --- a/src/ibrowse/ibrowse.erl +++ b/src/ibrowse/ibrowse.erl @@ -6,7 +6,7 @@ %%% Created : 11 Oct 2003 by Chandrashekhar Mullaparthi <[email protected]> %%%------------------------------------------------------------------- %% @author Chandrashekhar Mullaparthi <chandrashekhar dot mullaparthi at gmail dot com> -%% @copyright 2005-2012 Chandrashekhar Mullaparthi +%% @copyright 2005-2014 Chandrashekhar Mullaparthi %% @doc The ibrowse application implements an HTTP 1.1 client in erlang. This %% module implements the API of the HTTP client. There is one named %% process called 'ibrowse' which assists in load balancing and maintaining configuration. There is one load balancing process per unique webserver. There is @@ -158,7 +158,7 @@ stop() -> %% respHeader() = {headerName(), headerValue()} %% headerName() = string() %% headerValue() = string() -%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason} +%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason} %% req_id() = term() %% ResponseBody = string() | {file, Filename} %% Reason = term() @@ -175,9 +175,11 @@ send_req(Url, Headers, Method) -> send_req(Url, Headers, Method, Body) -> send_req(Url, Headers, Method, Body, []). -%% @doc Same as send_req/4. -%% For a description of SSL Options, look in the <a href="http://www.erlang.org/doc/apps/ssl/index.html">ssl</a> manpage. If the -%% HTTP Version to use is not specified, the default is 1.1. +%% @doc Same as send_req/4. + +%% For a description of SSL Options, look in the <a href="http://www.erlang.org/doc/apps/ssl/index.html">ssl</a> manpage. +%% For a description of Process Options, look in the <a href="http://www.erlang.org/doc/man/gen_server.html">gen_server</a> manpage. +%% If the HTTP Version to use is not specified, the default is 1.1. %% <br/> %% <ul> %% <li>The <code>host_header</code> option is useful in the case where ibrowse is @@ -254,6 +256,8 @@ send_req(Url, Headers, Method, Body) -> %% to receive the raw data stream when the Transfer-Encoding of the server %% response is Chunked. %% </li> +%% <li> The <code>return_raw_request</code> option enables the caller to get the exact request which was sent by ibrowse to the server, along with the response. When this option is used, the response for synchronous requests is a 5-tuple instead of the usual 4-tuple. For asynchronous requests, the calling process gets a message <code>{ibrowse_async_raw_req, Raw_req}</code>. +%% </li> %% </ul> %% %% @spec send_req(Url::string(), Headers::headerList(), Method::method(), Body::body(), Options::optionList()) -> response() @@ -286,7 +290,9 @@ send_req(Url, Headers, Method, Body) -> %% {headers_as_is, boolean()} | %% {give_raw_headers, boolean()} | %% {preserve_chunked_encoding,boolean()} | -%% {workaround, head_response_with_body} +%% {workaround, head_response_with_body} | +%% {worker_process_options, list()} | +%% {return_raw_request, true} %% %% stream_to() = process() | {process(), once} %% process() = pid() | atom() @@ -340,10 +346,12 @@ try_routing_request(Lb_pid, Parsed_url, Max_pipeline_size, {SSLOptions, IsSSL}, Headers, Method, Body, Options_1, Timeout, Try_count) when Try_count < 3 -> + ProcessOptions = get_value(worker_process_options, Options_1, []), case ibrowse_lb:spawn_connection(Lb_pid, Parsed_url, Max_sessions, Max_pipeline_size, - {SSLOptions, IsSSL}) of + {SSLOptions, IsSSL}, + ProcessOptions) of {ok, Conn_Pid} -> case do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, Options_1, Timeout) of @@ -437,7 +445,9 @@ do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout) -> {'EXIT', {noproc, {gen_server, call, [Conn_Pid, _, _]}}} -> {error, sel_conn_closed}; {'EXIT', {normal, _}} -> - {error, req_timedout}; + {error, sel_conn_closed}; + {'EXIT', {connection_closed, _}} -> + {error, sel_conn_closed}; {error, connection_closed} -> {error, sel_conn_closed}; {'EXIT', Reason} -> @@ -449,6 +459,13 @@ do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout) -> binary -> Ret end; + {ok, St_code, Headers, Body, Req} = Ret when is_binary(Body) -> + case get_value(response_format, Options, list) of + list -> + {ok, St_code, Headers, binary_to_list(Body), Req}; + binary -> + Ret + end; Ret -> Ret end. @@ -470,28 +487,32 @@ ensure_bin({Fun, _} = Body) when is_function(Fun) -> Body. %% request is sent via any of the send_req_direct/4,5,6,7 functions.<br/> %% <b>Note:</b> It is the responsibility of the calling process to control %% pipeline size on such connections. -%% -%% @spec spawn_worker_process(Url::string()) -> {ok, pid()} -spawn_worker_process(Url) -> - ibrowse_http_client:start(Url). -%% @doc Same as spawn_worker_process/1 but takes as input a Host and Port -%% instead of a URL. +%% @spec spawn_worker_process(Url::string() | {Host::string(), Port::integer()}) -> {ok, pid()} +spawn_worker_process(Args) -> + spawn_worker_process(Args, []). + +%% @doc Same as spawn_worker_process/1 except with Erlang process options. %% @spec spawn_worker_process(Host::string(), Port::integer()) -> {ok, pid()} -spawn_worker_process(Host, Port) -> - ibrowse_http_client:start({Host, Port}). +spawn_worker_process(Host, Port) when is_list(Host), is_integer(Port) -> + %% Convert old API calls to new API format. + spawn_worker_process({Host, Port}, []); +spawn_worker_process(Args, Options) -> + ibrowse_http_client:start(Args, Options). %% @doc Same as spawn_worker_process/1 except the the calling process %% is linked to the worker process which is spawned. -%% @spec spawn_link_worker_process(Url::string()) -> {ok, pid()} -spawn_link_worker_process(Url) -> - ibrowse_http_client:start_link(Url). +%% @spec spawn_link_worker_process(Url::string() | {Host::string(), Port::integer()}) -> {ok, pid()} +spawn_link_worker_process(Args) -> + spawn_link_worker_process(Args, []). -%% @doc Same as spawn_worker_process/2 except the the calling process -%% is linked to the worker process which is spawned. +%% @doc Same as spawn_link_worker_process/1 except with Erlang process options. %% @spec spawn_link_worker_process(Host::string(), Port::integer()) -> {ok, pid()} -spawn_link_worker_process(Host, Port) -> - ibrowse_http_client:start_link({Host, Port}). +spawn_link_worker_process(Host, Port) when is_list(Host), is_integer(Port) -> + %% Convert old API calls to new API format. + spawn_link_worker_process({Host, Port}, []); +spawn_link_worker_process(Args, Options) -> + ibrowse_http_client:start_link(Args, Options). %% @doc Terminate a worker process spawned using %% spawn_worker_process/2 or spawn_link_worker_process/2. Requests in http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse_http_client.erl ---------------------------------------------------------------------- diff --git a/src/ibrowse/ibrowse_http_client.erl b/src/ibrowse/ibrowse_http_client.erl index a1cf6eb..64fc443 100644 --- a/src/ibrowse/ibrowse_http_client.erl +++ b/src/ibrowse/ibrowse_http_client.erl @@ -15,7 +15,9 @@ %% External exports -export([ start_link/1, + start_link/2, start/1, + start/2, stop/1, send_req/7 ]). @@ -39,8 +41,7 @@ -record(state, {host, port, connect_timeout, inactivity_timer_ref, - use_http_proxy = false, http_proxy_auth_digest, - socks5_host, socks5_port, socks5_user, socks5_password, + use_proxy = false, proxy_auth_digest, ssl_options = [], is_ssl = false, socket, proxy_tunnel_setup = false, tunnel_setup_queue = [], @@ -62,7 +63,7 @@ stream_chunk_size, save_response_to_file = false, tmp_file_name, tmp_file_fd, preserve_chunked_encoding, - response_format, timer_ref}). + response_format, timer_ref, raw_req}). -import(ibrowse_lib, [ get_value/2, @@ -80,10 +81,16 @@ %% Description: Starts the server %%-------------------------------------------------------------------- start(Args) -> - gen_server:start(?MODULE, Args, []). + start(Args, []). + +start(Args, Options) -> + gen_server:start(?MODULE, Args, Options). start_link(Args) -> - gen_server:start_link(?MODULE, Args, []). + start_link(Args, []). + +start_link(Args, Options) -> + gen_server:start_link(?MODULE, Args, Options). stop(Conn_pid) -> case catch gen_server:call(Conn_pid, stop) of @@ -187,7 +194,7 @@ handle_info({ssl, _Sock, Data}, State) -> handle_info({stream_next, Req_id}, #state{socket = Socket, cur_req = #request{req_id = Req_id}} = State) -> - do_setopts(Socket, [{active, once}], State), + _ = do_setopts(Socket, [{active, once}], State), {noreply, set_inac_timer(State)}; handle_info({stream_next, _Req_id}, State) -> @@ -226,12 +233,13 @@ handle_info({ssl_error, _Sock, Reason}, State) -> {stop, normal, State}; handle_info({req_timedout, From}, State) -> - case lists:keymember(From, #request.from, queue:to_list(State#state.reqs)) of + case lists:keysearch(From, #request.from, queue:to_list(State#state.reqs)) of false -> {noreply, State}; - true -> + {value, #request{stream_to = StreamTo, req_id = ReqId}} -> + catch StreamTo ! {ibrowse_async_response_timeout, ReqId}, shutting_down(State), -%% do_error_reply(State, req_timedout), + do_error_reply(State, req_timedout), {stop, normal, State} end; @@ -288,12 +296,12 @@ handle_sock_data(Data, #state{status = get_header}=State) -> shutting_down(State), {stop, normal, State}; #state{socket = Socket, status = Status, cur_req = CurReq} = State_1 -> - case {Status, CurReq} of - {get_header, #request{caller_controls_socket = true}} -> - do_setopts(Socket, [{active, once}], State_1); - _ -> - active_once(State_1) - end, + _ = case {Status, CurReq} of + {get_header, #request{caller_controls_socket = true}} -> + do_setopts(Socket, [{active, once}], State_1); + _ -> + active_once(State_1) + end, {noreply, set_inac_timer(State_1)} end; @@ -312,7 +320,7 @@ handle_sock_data(Data, #state{status = get_body, {error, {Reason, {stat_code, StatCode}, Headers}}), {stop, normal, State}; State_1 -> - active_once(State_1), + _ = active_once(State_1), State_2 = set_inac_timer(State_1), {noreply, State_2} end; @@ -325,14 +333,14 @@ handle_sock_data(Data, #state{status = get_body, {stop, normal, State}; #state{cur_req = #request{caller_controls_socket = Ccs}, interim_reply_sent = Irs} = State_1 -> - case Irs of - true -> - active_once(State_1); - false when Ccs == true -> - do_setopts(Socket, [{active, once}], State); - false -> - active_once(State_1) - end, + _ = case Irs of + true -> + active_once(State_1); + false when Ccs == true -> + do_setopts(Socket, [{active, once}], State); + false -> + active_once(State_1) + end, State_2 = State_1#state{interim_reply_sent = false}, case Ccs of true -> @@ -342,7 +350,7 @@ handle_sock_data(Data, #state{status = get_body, {noreply, set_inac_timer(State_2)} end; State_1 -> - active_once(State_1), + _ = active_once(State_1), State_2 = set_inac_timer(State_1), {noreply, State_2} end @@ -464,7 +472,9 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = Reqs, http_status_code = SC }=State) -> #request{from=From, stream_to=StreamTo, req_id=ReqId, response_format = Resp_format, - options = Options} = CurReq, + options = Options, + raw_req = Raw_req + } = CurReq, case IsClosing of true -> {_, Reqs_1} = queue:out(Reqs), @@ -475,11 +485,16 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = Reqs, http_status_code = SC ok = file:close(Fd), {file, TmpFilename} end, + Give_raw_req = get_value(return_raw_request, Options, false), Reply = case get_value(give_raw_headers, Options, false) of - true -> + true when Give_raw_req == false-> {ok, Status_line, Raw_headers, Body}; + true -> + {ok, Status_line, Raw_headers, Body, Raw_req}; + false when Give_raw_req == false -> + {ok, SC, Headers, Buf}; false -> - {ok, SC, Headers, Buf} + {ok, SC, Headers, Buf, Raw_req} end, State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, Reply), ok = do_error_reply(State_1#state{reqs = Reqs_1}, connection_closed), @@ -489,25 +504,19 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = Reqs, http_status_code = SC State end. -do_connect(Host, Port, Options, #state{socks5_host = SocksHost}=State, Timeout) - when SocksHost /= undefined -> - ProxyOptions = [ - {user, State#state.socks5_user}, - {password, State#state.socks5_password}, - {host, SocksHost}, - {port, State#state.socks5_port}, - {is_ssl, State#state.is_ssl}, - {ssl_opts, State#state.ssl_options}], - ibrowse_socks5:connect(Host, Port, ProxyOptions, - get_sock_options(SocksHost, Options, []), - Timeout); -do_connect(Host, Port, Options, #state{is_ssl = true, - use_http_proxy = false, - ssl_options = SSLOptions}, +do_connect(Host, Port, Options, #state{is_ssl = true, + use_proxy = false, + ssl_options = SSLOptions}, Timeout) -> ssl:connect(Host, Port, get_sock_options(Host, Options, SSLOptions), Timeout); do_connect(Host, Port, Options, _State, Timeout) -> - gen_tcp:connect(Host, Port, get_sock_options(Host, Options, []), Timeout). + Socks5Host = get_value(socks5_host, Options, undefined), + case Socks5Host of + undefined -> + gen_tcp:connect(Host, Port, get_sock_options(Host, Options, []), Timeout); + _ -> + catch ibrowse_socks5:connect(Host, Port, Options) + end. get_sock_options(Host, Options, SSLOptions) -> Caller_socket_options = get_value(socket_options, Options, []), @@ -554,42 +563,68 @@ filter_sock_options(Opts) -> do_send(Req, #state{socket = Sock, is_ssl = true, - use_http_proxy = true, + use_proxy = true, proxy_tunnel_setup = Pts}) when Pts /= done -> gen_tcp:send(Sock, Req); do_send(Req, #state{socket = Sock, is_ssl = true}) -> ssl:send(Sock, Req); do_send(Req, #state{socket = Sock, is_ssl = false}) -> gen_tcp:send(Sock, Req). -%% @spec do_send_body(Sock::socket_descriptor(), Source::source_descriptor(), IsSSL::boolean()) -> ok | error() -%% source_descriptor() = fun_arity_0 | -%% {fun_arity_0} | -%% {fun_arity_1, term()} -%% error() = term() do_send_body(Source, State, TE) when is_function(Source) -> do_send_body({Source}, State, TE); do_send_body({Source}, State, TE) when is_function(Source) -> - do_send_body1(Source, Source(), State, TE); + do_send_body_1(generate_body(Source), + State, TE, []); do_send_body({Source, Source_state}, State, TE) when is_function(Source) -> - do_send_body1(Source, Source(Source_state), State, TE); + do_send_body_1(generate_body({Source, Source_state}), + State, TE, []); do_send_body(Body, State, _TE) -> - do_send(Body, State). + case do_send(Body, State) of + ok -> + {ok, Body}; + Ret -> + Ret + end. -do_send_body1(Source, Resp, State, TE) -> +generate_body({Source, Source_state} = In) when is_function(Source) -> + case Source(Source_state) of + {ok, Data, Source_state_1} -> + {{ok, Data}, {Source, Source_state_1}}; + Ret -> + {Ret, In} + end; +generate_body(Source) when is_function(Source) -> + {Source(), Source}. + +do_send_body_1({Resp, Source}, State, TE, Acc) when is_function(Source) -> case Resp of - {ok, Data} when Data == []; Data == <<>> -> - do_send_body({Source}, State, TE); + {ok, Data} when Data == []; Data == <<>> -> + do_send_body_1(generate_body(Source), State, TE, Acc); {ok, Data} -> - do_send(maybe_chunked_encode(Data, TE), State), - do_send_body({Source}, State, TE); - {ok, Data, New_source_state} when Data == []; Data == <<>> -> - do_send_body({Source, New_source_state}, State, TE); + Acc_1 = case TE of + true -> + ok = do_send(maybe_chunked_encode(Data, TE), State), + Acc; + false -> + [Data | Acc] + end, + do_send_body_1(generate_body(Source), State, TE, Acc_1); + {ok, Data, New_source_state} when Data == []; Data == <<>> -> + do_send_body_1(generate_body({Source, New_source_state}), State, TE, Acc); {ok, Data, New_source_state} -> - do_send(maybe_chunked_encode(Data, TE), State), - do_send_body({Source, New_source_state}, State, TE); + Acc_1 = case TE of + true -> + ok = do_send(maybe_chunked_encode(Data, TE), State), + Acc; + false -> + [Data | Acc] + end, + do_send_body_1(generate_body({Source, New_source_state}), State, TE, Acc_1); eof when TE == true -> - do_send(<<"0\r\n\r\n">>, State), - ok; + ok = do_send(<<"0\r\n\r\n">>, State), + {ok, []}; eof -> - ok; + Body = list_to_binary(lists:reverse(Acc)), + ok = do_send(Body, State), + {ok, Body}; Err -> Err end. @@ -602,7 +637,7 @@ maybe_chunked_encode(Data, true) -> do_close(#state{socket = undefined}) -> ok; do_close(#state{socket = Sock, is_ssl = true, - use_http_proxy = 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); @@ -611,11 +646,11 @@ do_close(#state{socket = Sock, is_ssl = false}) -> catch gen_tcp:close(Sock). active_once(#state{cur_req = #request{caller_controls_socket = true}}) -> ok; active_once(#state{socket = Socket} = State) -> - do_setopts(Socket, [{active, once}], State). + _ = do_setopts(Socket, [{active, once}], State). do_setopts(_Sock, [], _) -> ok; do_setopts(Sock, Opts, #state{is_ssl = true, - use_http_proxy = true, + use_proxy = true, proxy_tunnel_setup = Pts} ) when Pts /= done -> inet:setopts(Sock, Opts); do_setopts(Sock, Opts, #state{is_ssl = true}) -> ssl:setopts(Sock, Opts); @@ -634,28 +669,17 @@ send_req_1(From, port = Port} = Url, Headers, Method, Body, Options, Timeout, #state{socket = undefined} = State) -> - ProxyHost = get_value(proxy_host, Options, false), - ProxyProtocol = get_value(proxy_protocol, Options, http), {Host_1, Port_1, State_1} = - case {ProxyHost, ProxyProtocol} of - {false, _} -> + case get_value(proxy_host, Options, false) of + false -> {Host, Port, State}; - {_, http} -> + PHost -> ProxyUser = get_value(proxy_user, Options, []), ProxyPassword = get_value(proxy_password, Options, []), Digest = http_auth_digest(ProxyUser, ProxyPassword), - {ProxyHost, get_value(proxy_port, Options, 80), - State#state{use_http_proxy = true, - http_proxy_auth_digest = Digest}}; - {_, socks5} -> - ProxyUser = list_to_binary(get_value(proxy_user, Options, [])), - ProxyPassword = list_to_binary(get_value(proxy_password, Options, [])), - ProxyPort = get_value(proxy_port, Options, 1080), - {Host, Port, - State#state{socks5_host = ProxyHost, - socks5_port = ProxyPort, - socks5_user = ProxyUser, - socks5_password = ProxyPassword}} + {PHost, get_value(proxy_port, Options, 80), + State#state{use_proxy = true, + proxy_auth_digest = Digest}} end, State_2 = check_ssl_options(Options, State_1), do_trace("Connecting...~n", []), @@ -686,7 +710,7 @@ send_req_1(From, Headers, Method, Body, Options, Timeout, #state{ proxy_tunnel_setup = false, - use_http_proxy = true, + use_proxy = true, is_ssl = true} = State) -> Ref = case Timeout of infinity -> @@ -711,9 +735,9 @@ send_req_1(From, case do_send(Req, State) of ok -> case do_send_body(Body_1, State_1, TE) of - ok -> + {ok, _Sent_body} -> trace_request_body(Body_1), - active_once(State_1), + _ = active_once(State_1), State_1_1 = inc_pipeline_counter(State_1), State_2 = State_1_1#state{status = get_header, cur_req = NewReq, @@ -775,6 +799,11 @@ send_req_1(From, _ -> erlang:send_after(Timeout, self(), {req_timedout, From}) end, + Headers_1 = maybe_modify_headers(Url, Method, Options, Headers, State), + {Req, Body_1} = make_request(Method, + Headers_1, + AbsPath, RelPath, Body, Options, State, + ReqId), NewReq = #request{url = Url, method = Method, stream_to = StreamTo, @@ -789,26 +818,24 @@ send_req_1(From, preserve_chunked_encoding = get_value(preserve_chunked_encoding, Options, false), timer_ref = Ref }, - State_1 = State#state{reqs=queue:in(NewReq, State#state.reqs)}, - Headers_1 = maybe_modify_headers(Url, Method, Options, Headers, State_1), - {Req, Body_1} = make_request(Method, - Headers_1, - AbsPath, RelPath, Body, Options, State_1, - ReqId), trace_request(Req), - do_setopts(Socket, Caller_socket_options, State_1), + ok = do_setopts(Socket, Caller_socket_options, State), TE = is_chunked_encoding_specified(Options), - case do_send(Req, State_1) of + case do_send(Req, State) of ok -> - case do_send_body(Body_1, State_1, TE) of - ok -> - trace_request_body(Body_1), + case do_send_body(Body_1, State, TE) of + {ok, Sent_body} -> + trace_request_body(Sent_body), + Raw_req = list_to_binary([Req, Sent_body]), + NewReq_1 = NewReq#request{raw_req = Raw_req}, + State_1 = State#state{reqs=queue:in(NewReq_1, State#state.reqs)}, State_2 = inc_pipeline_counter(State_1), - active_once(State_2), + _ = active_once(State_2), State_3 = case Status of idle -> - State_2#state{status = get_header, - cur_req = NewReq}; + State_2#state{ + status = get_header, + cur_req = NewReq_1}; _ -> State_2 end, @@ -816,21 +843,27 @@ send_req_1(From, undefined -> ok; _ -> - gen_server:reply(From, {ibrowse_req_id, ReqId}) + gen_server:reply(From, {ibrowse_req_id, ReqId}), + case get_value(return_raw_request, Options, false) of + false -> + ok; + true -> + catch StreamTo ! {ibrowse_async_raw_req, Raw_req} + end end, State_4 = set_inac_timer(State_3), {noreply, State_4}; Err -> - shutting_down(State_1), + shutting_down(State), do_trace("Send failed... Reason: ~p~n", [Err]), gen_server:reply(From, {error, {send_failed, Err}}), - {stop, normal, State_1} + {stop, normal, State} end; Err -> - shutting_down(State_1), + shutting_down(State), do_trace("Send failed... Reason: ~p~n", [Err]), gen_server:reply(From, {error, {send_failed, Err}}), - {stop, normal, State_1} + {stop, normal, State} end. maybe_modify_headers(#url{}, connect, _, Headers, State) -> @@ -874,11 +907,11 @@ add_auth_headers(#url{username = User, end, add_proxy_auth_headers(State, Headers_1). -add_proxy_auth_headers(#state{use_http_proxy = false}, Headers) -> +add_proxy_auth_headers(#state{use_proxy = false}, Headers) -> Headers; -add_proxy_auth_headers(#state{http_proxy_auth_digest = []}, Headers) -> +add_proxy_auth_headers(#state{proxy_auth_digest = []}, Headers) -> Headers; -add_proxy_auth_headers(#state{http_proxy_auth_digest = Auth_digest}, Headers) -> +add_proxy_auth_headers(#state{proxy_auth_digest = Auth_digest}, Headers) -> [{"Proxy-Authorization", ["Basic ", Auth_digest]} | Headers]. http_auth_digest([], []) -> @@ -887,7 +920,7 @@ http_auth_digest(Username, Password) -> ibrowse_lib:encode_base64(Username ++ [$: | Password]). make_request(Method, Headers, AbsPath, RelPath, Body, Options, - #state{use_http_proxy = UseHttpProxy, is_ssl = Is_ssl}, ReqId) -> + #state{use_proxy = UseProxy, is_ssl = Is_ssl}, ReqId) -> HttpVsn = http_vsn_string(get_value(http_vsn, Options, {1,1})), Fun1 = fun({X, Y}) when is_atom(X) -> {to_lower(atom_to_list(X)), X, Y}; @@ -930,7 +963,7 @@ make_request(Method, Headers, AbsPath, RelPath, Body, Options, Headers_2 end, Headers_4 = cons_headers(Headers_3), - Uri = case get_value(use_absolute_uri, Options, false) or UseHttpProxy of + Uri = case get_value(use_absolute_uri, Options, false) or UseProxy of true -> case Is_ssl of true -> @@ -1028,7 +1061,8 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = Reqs, cur_req = CurReq} = State) -> #request{from=From, stream_to=StreamTo, req_id=ReqId, method=Method, response_format = Resp_format, - options = Options, timer_ref = T_ref + options = Options, timer_ref = T_ref, + raw_req = Raw_req } = CurReq, MaxHeaderSize = ibrowse:get_config_value(max_headers_size, infinity), case scan_header(Acc, Data) of @@ -1048,6 +1082,7 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = Reqs, State end, Give_raw_headers = get_value(give_raw_headers, Options, false), + Give_raw_req = get_value(return_raw_request, Options, false), State_1 = case Give_raw_headers of true -> State_0#state{recvd_headers=Headers_1, status=get_body, @@ -1061,7 +1096,9 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = Reqs, http_status_code=StatCode} end, put(conn_close, ConnClose), - TransferEncoding = to_lower(get_value("transfer-encoding", LCHeaders, "false")), + TransferEncodings = to_lower(get_value("transfer-encoding", LCHeaders, "false")), + IsChunked = lists:any(fun(Enc) -> string:strip(Enc) =:= "chunked" end, + string:tokens(TransferEncodings, ",")), Head_response_with_body = lists:member({workaround, head_response_with_body}, Options), case get_value("content-length", LCHeaders, undefined) of _ when Method == connect, @@ -1086,8 +1123,13 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = Reqs, %% there was still a body. Issue #67 on Github {_, Reqs_1} = queue:out(Reqs), send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1), - State_1_1 = do_reply(State_1, From, StreamTo, ReqId, Resp_format, - {ok, StatCode, Headers_1, []}), + Reply = case Give_raw_req of + false -> + {ok, StatCode, Headers_1, []}; + true -> + {ok, StatCode, Headers_1, [], Raw_req} + end, + State_1_1 = do_reply(State_1, From, StreamTo, ReqId, Resp_format, Reply), cancel_timer(T_ref, {eat_message, {req_timedout, From}}), State_2 = reset_state(State_1_1), State_3 = set_cur_request(State_2#state{reqs = Reqs_1}), @@ -1106,13 +1148,18 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = Reqs, %% RFC2616 - Sec 4.4 {_, Reqs_1} = queue:out(Reqs), send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1), - State_1_1 = do_reply(State_1, From, StreamTo, ReqId, Resp_format, - {ok, StatCode, Headers_1, []}), + Reply = case Give_raw_req of + true -> + {ok, StatCode, Headers_1, []}; + false -> + {ok, StatCode, Headers_1, [], Raw_req} + end, + State_1_1 = do_reply(State_1, From, StreamTo, ReqId, Resp_format, Reply), cancel_timer(T_ref, {eat_message, {req_timedout, From}}), State_2 = reset_state(State_1_1), State_3 = set_cur_request(State_2#state{reqs = Reqs_1}), parse_response(Data_1, State_3); - _ when TransferEncoding =:= "chunked" -> + _ when IsChunked -> do_trace("Chunked encoding detected...~n",[]), send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1), case parse_11_response(Data_1, State_1#state{transfer_encoding=chunked, @@ -1130,6 +1177,25 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = Reqs, ConnClose =:= "close" -> send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1), State_1#state{reply_buffer = Data_1}; + undefined when StatCode =:= "303" -> + %% Some servers send 303 requests without a body. + %% RFC2616 says that they SHOULD, but they dont. + case ibrowse:get_config_value(allow_303_with_no_body, false) of + false -> + fail_pipelined_requests(State_1, + {error, {content_length_undefined, + {stat_code, StatCode}, Headers}}), + {error, content_length_undefined}; + true -> + {_, Reqs_1} = queue:out(Reqs), + send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1), + State_1_1 = do_reply(State_1, From, StreamTo, ReqId, Resp_format, + {ok, StatCode, Headers_1, []}), + cancel_timer(T_ref, {eat_message, {req_timedout, From}}), + State_2 = reset_state(State_1_1), + State_3 = set_cur_request(State_2#state{reqs = Reqs_1}), + parse_response(Data_1, State_3) + end; undefined -> fail_pipelined_requests(State_1, {error, {content_length_undefined, @@ -1335,7 +1401,8 @@ handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId, tmp_file_name = TmpFilename, tmp_file_fd = Fd, options = Options, - timer_ref = ReqTimer + timer_ref = ReqTimer, + raw_req = Raw_req }, #state{http_status_code = SCode, status_line = Status_line, @@ -1356,18 +1423,25 @@ handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId, {file, TmpFilename} end, {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(RespHeaders, Raw_headers, Options), + Give_raw_req = get_value(return_raw_request, Options, false), Reply = case get_value(give_raw_headers, Options, false) of - true -> + true when Give_raw_req == false -> {ok, Status_line, Raw_headers_1, ResponseBody}; + true -> + {ok, Status_line, Raw_headers_1, ResponseBody, Raw_req}; + false when Give_raw_req == false -> + {ok, SCode, Resp_headers_1, ResponseBody}; false -> - {ok, SCode, Resp_headers_1, ResponseBody} + {ok, SCode, Resp_headers_1, ResponseBody, Raw_req} end, State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, Reply), cancel_timer(ReqTimer, {eat_message, {req_timedout, From}}), set_cur_request(State_1); handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId, response_format = Resp_format, - options = Options, timer_ref = ReqTimer}, + options = Options, timer_ref = ReqTimer, + raw_req = Raw_req + }, #state{http_status_code = SCode, status_line = Status_line, raw_headers = Raw_headers, @@ -1376,11 +1450,16 @@ handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId, } = State) -> Body = RepBuf, {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(Resp_headers, Raw_headers, Options), + Give_raw_req = get_value(return_raw_request, Options, false), Reply = case get_value(give_raw_headers, Options, false) of - true -> + true when Give_raw_req == false -> {ok, Status_line, Raw_headers_1, Body}; + true -> + {ok, Status_line, Raw_headers_1, Body, Raw_req}; + false when Give_raw_req == false -> + {ok, SCode, Resp_headers_1, Body}; false -> - {ok, SCode, Resp_headers_1, Body} + {ok, SCode, Resp_headers_1, Body, Raw_req} end, State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, Reply), cancel_timer(ReqTimer, {eat_message, {req_timedout, From}}), @@ -1407,12 +1486,8 @@ set_cur_request(#state{reqs = Reqs, socket = Socket} = State) -> empty -> State#state{cur_req = undefined}; {value, #request{caller_controls_socket = Ccs} = NextReq} -> - case Ccs of - true -> - do_setopts(Socket, [{active, once}], State); - _ -> - ok - end, + _ = Ccs =:= true + andalso do_setopts(Socket, [{active, once}], State), State#state{cur_req = NextReq} end. @@ -1586,6 +1661,7 @@ get_crlf_pos(<<>>, _) -> no. fmt_val(L) when is_list(L) -> L; fmt_val(I) when is_integer(I) -> integer_to_list(I); fmt_val(A) when is_atom(A) -> atom_to_list(A); +fmt_val(B) when is_binary(B) -> B; fmt_val(Term) -> io_lib:format("~p", [Term]). crnl() -> "\r\n". @@ -1863,13 +1939,13 @@ dec_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> State; dec_pipeline_counter(#state{cur_pipeline_size = Pipe_sz, lb_ets_tid = Tid} = State) -> - try - update_counter(Tid, self(), {2,-1,0,0}), - update_counter(Tid, self(), {3,-1,0,0}) - catch - _:_ -> - ok - end, + _ = try + update_counter(Tid, self(), {2,-1,0,0}), + update_counter(Tid, self(), {3,-1,0,0}) + catch + _:_ -> + ok + end, State#state{cur_pipeline_size = Pipe_sz - 1}. flatten([H | _] = L) when is_integer(H) -> @@ -1941,5 +2017,7 @@ trace_request_body(Body) -> ok end. -to_binary(X) when is_list(X) -> list_to_binary(X); -to_binary(X) when is_binary(X) -> X. +to_binary({X, _}) when is_function(X) -> to_binary(X); +to_binary(X) when is_function(X) -> <<"body generated by function">>; +to_binary(X) when is_list(X) -> list_to_binary(X); +to_binary(X) when is_binary(X) -> X. http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse_lb.erl ---------------------------------------------------------------------- diff --git a/src/ibrowse/ibrowse_lb.erl b/src/ibrowse/ibrowse_lb.erl index d98cf32..f5a9aef 100644 --- a/src/ibrowse/ibrowse_lb.erl +++ b/src/ibrowse/ibrowse_lb.erl @@ -16,7 +16,7 @@ %% External exports -export([ start_link/1, - spawn_connection/5, + spawn_connection/6, stop/1 ]). @@ -81,13 +81,14 @@ init([Host, Port]) -> spawn_connection(Lb_pid, Url, Max_sessions, Max_pipeline_size, - SSL_options) + SSL_options, + Process_options) when is_pid(Lb_pid), is_record(Url, url), is_integer(Max_pipeline_size), is_integer(Max_sessions) -> gen_server:call(Lb_pid, - {spawn_connection, Url, Max_sessions, Max_pipeline_size, SSL_options}). + {spawn_connection, Url, Max_sessions, Max_pipeline_size, SSL_options, Process_options}). stop(Lb_pid) -> case catch gen_server:call(Lb_pid, stop) of @@ -123,19 +124,19 @@ handle_call(_, _From, #state{proc_state = shutting_down} = State) -> {reply, {error, shutting_down}, State}; %% Update max_sessions in #state with supplied value -handle_call({spawn_connection, _Url, Max_sess, Max_pipe, _}, _From, - #state{num_cur_sessions = Num} = State) +handle_call({spawn_connection, _Url, Max_sess, Max_pipe, _, _}, _From, + #state{num_cur_sessions = Num} = State) when Num >= Max_sess -> State_1 = maybe_create_ets(State), Reply = find_best_connection(State_1#state.ets_tid, Max_pipe), {reply, Reply, State_1#state{max_sessions = Max_sess, max_pipeline_size = Max_pipe}}; -handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options}, _From, +handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, Process_options}, _From, #state{num_cur_sessions = Cur} = State) -> State_1 = maybe_create_ets(State), Tid = State_1#state.ets_tid, - {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}), + {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, Process_options), ets:insert(Tid, {Pid, 0, 0}), {reply, {ok, Pid}, State_1#state{num_cur_sessions = Cur + 1, max_sessions = Max_sess, @@ -230,7 +231,9 @@ code_change(_OldVsn, State, _Extra) -> %%% Internal functions %%-------------------------------------------------------------------- find_best_connection(Tid, Max_pipe) -> + ets:safe_fixtable(Tid, true), Res = find_best_connection(ets:first(Tid), Tid, Max_pipe), + ets:safe_fixtable(Tid, false), Res. find_best_connection('$end_of_table', _, _) -> @@ -239,9 +242,14 @@ find_best_connection(Pid, Tid, Max_pipe) -> case ets:lookup(Tid, Pid) of [{Pid, Cur_sz, Speculative_sz}] when Cur_sz < Max_pipe, Speculative_sz < Max_pipe -> - ets:update_counter(Tid, Pid, {3, 1, 9999999, 9999999}), - {ok, Pid}; - _ -> + case catch ets:update_counter(Tid, Pid, {3, 1, 9999999, 9999999}) of + {'EXIT', _} -> + %% The selected process has shutdown + find_best_connection(ets:next(Tid, Pid), Tid, Max_pipe); + _ -> + {ok, Pid} + end; + _ -> find_best_connection(ets:next(Tid, Pid), Tid, Max_pipe) end. http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse_lib.erl ---------------------------------------------------------------------- diff --git a/src/ibrowse/ibrowse_lib.erl b/src/ibrowse/ibrowse_lib.erl index 7b12cb3..1ce6bd4 100644 --- a/src/ibrowse/ibrowse_lib.erl +++ b/src/ibrowse/ibrowse_lib.erl @@ -362,10 +362,9 @@ parse_url([], get_password, Url, TmpAcc) -> parse_url([], State, Url, TmpAcc) -> {invalid_uri_2, State, Url, TmpAcc}. -default_port(socks5) -> 1080; -default_port(http) -> 80; -default_port(https) -> 443; -default_port(ftp) -> 21. +default_port(http) -> 80; +default_port(https) -> 443; +default_port(ftp) -> 21. printable_date() -> {{Y,Mo,D},{H, M, S}} = calendar:local_time(), http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse_socks5.erl ---------------------------------------------------------------------- diff --git a/src/ibrowse/ibrowse_socks5.erl b/src/ibrowse/ibrowse_socks5.erl index d00df44..e6d8913 100644 --- a/src/ibrowse/ibrowse_socks5.erl +++ b/src/ibrowse/ibrowse_socks5.erl @@ -1,109 +1,58 @@ -% Licensed under the Apache License, Version 2.0 (the "License"); you may not -% use this file except in compliance with the License. You may obtain a copy of -% the License at -% -% http://www.apache.org/licenses/LICENSE-2.0 -% -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -% License for the specific language governing permissions and limitations under -% the License. - -module(ibrowse_socks5). --define(VERSION, 5). --define(CONNECT, 1). +-include_lib("kernel/src/inet_dns.hrl"). --define(NO_AUTH, 0). --define(USERPASS, 2). --define(UNACCEPTABLE, 16#FF). --define(RESERVED, 0). +-export([connect/3]). --define(ATYP_IPV4, 1). --define(ATYP_DOMAINNAME, 3). --define(ATYP_IPV6, 4). +-define(TIMEOUT, 2000). --define(SUCCEEDED, 0). +-define(SOCKS5, 5). +-define(AUTH_METHOD_NO, 0). +-define(AUTH_METHOD_USERPASS, 2). +-define(ADDRESS_TYPE_IP4, 1). +-define(COMMAND_TYPE_TCPIP_STREAM, 1). +-define(RESERVER, 0). +-define(STATUS_GRANTED, 0). --export([connect/5]). +-define(DNS_IP, {8,8,8,8}). --import(ibrowse_lib, [get_value/2, get_value/3]). +connect(Host, Port, Options) -> + Socks5Host = proplists:get_value(socks5_host, Options), + Socks5Port = proplists:get_value(socks5_port, Options), -connect(TargetHost, TargetPort, ProxyOptions, Options, Timeout) -> - case gen_tcp:connect(get_value(host, ProxyOptions), - get_value(port, ProxyOptions), - Options, Timeout) of - {ok, Socket} -> - case handshake(Socket, Options) of - ok -> - case connect(TargetHost, TargetPort, Socket) of - ok -> - maybe_ssl(Socket, ProxyOptions, Timeout); - Else -> - gen_tcp:close(Socket), - Else - end; - Else -> - gen_tcp:close(Socket), - Else - end; - Else -> - Else - end. + {ok, Socket} = gen_tcp:connect(Socks5Host, Socks5Port, [binary, {packet, 0}, {keepalive, true}, {active, false}]), -handshake(Socket, ProxyOptions) when is_port(Socket) -> - {Handshake, Success} = case get_value(user, ProxyOptions, <<>>) of - <<>> -> - {<<?VERSION, 1, ?NO_AUTH>>, ?NO_AUTH}; - User -> - Password = get_value(password, ProxyOptions, <<>>), - {<<?VERSION, 1, ?USERPASS, (byte_size(User)), User, - (byte_size(Password)), Password>>, ?USERPASS} - end, - ok = gen_tcp:send(Socket, Handshake), - case gen_tcp:recv(Socket, 0) of - {ok, <<?VERSION, Success>>} -> - ok; - {ok, <<?VERSION, ?UNACCEPTABLE>>} -> - {error, unacceptable}; - {error, Reason} -> - {error, Reason} - end. + {ok, _Bin} = + case proplists:get_value(socks5_user, Options, undefined) of + undefined -> + ok = gen_tcp:send(Socket, <<?SOCKS5, 1, ?AUTH_METHOD_NO>>), + {ok, <<?SOCKS5, ?AUTH_METHOD_NO>>} = gen_tcp:recv(Socket, 2, ?TIMEOUT); + _Else -> + Socks5User = list_to_binary(proplists:get_value(socks5_user, Options)), + Socks5Pass = list_to_binary(proplists:get_value(socks5_pass, Options)), + + ok = gen_tcp:send(Socket, <<?SOCKS5, 1, ?AUTH_METHOD_USERPASS>>), + {ok, <<?SOCKS5, ?AUTH_METHOD_USERPASS>>} = gen_tcp:recv(Socket, 2, ?TIMEOUT), -connect(Host, Port, Via) when is_list(Host) -> - connect(list_to_binary(Host), Port, Via); -connect(Host, Port, Via) when is_binary(Host), is_integer(Port), - is_port(Via) -> - ok = gen_tcp:send(Via, - <<?VERSION, ?CONNECT, ?RESERVED, ?ATYP_DOMAINNAME, - (byte_size(Host)), Host/binary, - (Port):16>>), - case gen_tcp:recv(Via, 0) of - {ok, <<?VERSION, ?SUCCEEDED, ?RESERVED, _/binary>>} -> - ok; - {ok, <<?VERSION, Rep, ?RESERVED, _/binary>>} -> - {error, rep(Rep)}; - {error, Reason} -> - {error, Reason} - end. + UserLength = byte_size(Socks5User), -maybe_ssl(Socket, ProxyOptions, Timeout) -> - IsSsl = get_value(is_ssl, ProxyOptions, false), - SslOpts = get_value(ssl_opts, ProxyOptions, []), - case IsSsl of - false -> - {ok, Socket}; - true -> - ssl:connect(Socket, SslOpts, Timeout) - end. + ok = gen_tcp:send(Socket, << 1, UserLength >>), + ok = gen_tcp:send(Socket, Socks5User), + PassLength = byte_size(Socks5Pass), + ok = gen_tcp:send(Socket, << PassLength >>), + ok = gen_tcp:send(Socket, Socks5Pass), + {ok, <<1, 0>>} = gen_tcp:recv(Socket, 2, ?TIMEOUT) + end, + + {IP1,IP2,IP3,IP4} = case inet_parse:address(Host) of + {ok, IP} -> + IP; + _Other -> + {ok, NsData} = inet_res:nslookup(Host, in, a, [{?DNS_IP, 53}]), + [Addr | _NewAnList] = [D || #dns_rr{data=D, type=a} <- NsData#dns_rec.anlist], + Addr + end, -rep(0) -> succeeded; -rep(1) -> server_fail; -rep(2) -> disallowed_by_ruleset; -rep(3) -> network_unreachable; -rep(4) -> host_unreachable; -rep(5) -> connection_refused; -rep(6) -> ttl_expired; -rep(7) -> command_not_supported; -rep(8) -> address_type_not_supported. + ok = gen_tcp:send(Socket, <<?SOCKS5, ?COMMAND_TYPE_TCPIP_STREAM, ?RESERVER, ?ADDRESS_TYPE_IP4, IP1, IP2, IP3, IP4, Port:16>>), + {ok, << ?SOCKS5, ?STATUS_GRANTED, ?RESERVER, ?ADDRESS_TYPE_IP4, IP1, IP2, IP3, IP4, Port:16 >>} = gen_tcp:recv(Socket, 10, ?TIMEOUT), + {ok, Socket}.
