From fb133d0a8c99a26b0f26bde21bf88e71b6235cba Mon Sep 17 00:00:00 2001 From: Lastres Date: Mon, 21 Jan 2013 16:55:35 +0000 Subject: [PATCH 01/49] Totally restructure the lhttpc client. lhttpc_client is now a gen_server. Now it supports conections without using a pool. It is possible to define clients and use them to establish connections. All the code from this commit was done by me and Diana Corbacho. --- include/lhttpc_types.hrl | 2 +- src/lhttpc.erl | 139 ++++--- src/lhttpc_client.erl | 847 ++++++++++++++++++++++++--------------- src/lhttpc_manager.erl | 19 +- 4 files changed, 615 insertions(+), 392 deletions(-) diff --git a/include/lhttpc_types.hrl b/include/lhttpc_types.hrl index 64bc9323..3ae9e251 100644 --- a/include/lhttpc_types.hrl +++ b/include/lhttpc_types.hrl @@ -86,7 +86,7 @@ -type window_size() :: non_neg_integer() | 'infinity'. --type upload_state() :: {pid(), window_size()}. +-type upload_state() :: {partial_upload, pid()}. -type body() :: binary() | 'undefined' | % HEAD request. diff --git a/src/lhttpc.erl b/src/lhttpc.erl index 8b3dc308..564a01c2 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -26,7 +26,9 @@ %%------------------------------------------------------------------------------ %%% @author Oscar Hellström -%%% @doc Main interface to the lightweight http client. +%%% @author Diana Parra Corbacho +%%% @author Ramon Lastres Guerrero +%%% @doc Main interface to the lightweight http client %%% See {@link request/4}, {@link request/5} and {@link request/6} functions. %%% @end %%------------------------------------------------------------------------------ @@ -35,8 +37,11 @@ -export([start/0, stop/0, start/2, stop/1, request/4, request/5, request/6, request/9, + request_client/5, request_client/6, request_client/7, add_pool/1, add_pool/2, add_pool/3, delete_pool/1, + connect_client/2, + disconnect_client/1, send_body_part/2, send_body_part/3, send_trailers/2, send_trailers/3, get_body_part/1, get_body_part/2 @@ -161,6 +166,70 @@ delete_pool(PoolName) when is_atom(PoolName) -> {error, Reason} -> {error, Reason} end. +%%------------------------------------------------------------------------------ +%% @doc Starts a Client. +%% @end +%%------------------------------------------------------------------------------ +%-spec connect( ,options()) -> {ok,Pid} | ignore | {error,Error}. +% WHICH TIMEOUT TO USE? +connect_client(Destination, Options) -> + %Gs_Options = ?? + lhttpc_client:start({Destination, Options}, []). + +%%------------------------------------------------------------------------------ +%% @doc Stops a Client. +%% @end +%%------------------------------------------------------------------------------ +-spec disconnect_client(pid()) -> ok. +disconnect_client(Client) -> + lhttpc_client:stop(Client). + + +%REQUESTS USING THE CLIENT +-spec request_client(pid(), string(), method(), headers(), pos_timeout()) -> result(). +request_client(Client, PathOrUrl, Method, Hdrs, Timeout) -> + request_client(Client, PathOrUrl, Method, Hdrs, [], Timeout, []). + +-spec request_client(pid(), string(), method(), headers(), iodata(), pos_timeout()) -> result(). +request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout) -> + request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout, []). + + +-spec request_client(pid(), string(), method(), headers(), iodata(), + pos_timeout(), options()) -> result(). +request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout, Options) -> + {FinalPath, FinalHeaders} = + case lhttpc_lib:parse_url(PathOrUrl) of + #lhttpc_url{ %its an URL + host = _Host, + port = _Port, + path = Path, + is_ssl = _Ssl, + user = User, + password = Passwd + } -> % @TODO: check that the host is the right one. + Headers = case User of + "" -> + Hdrs; + _ -> + Auth = "Basic " ++ binary_to_list(base64:encode(User ++ ":" ++ Passwd)), + lists:keystore("Authorization", 1, Hdrs, {"Authorization", Auth}) + end, + {Path, Headers}; + %request_client(Client, Path, Method, Headers, Body, Timeout, Options); + _ -> % its a path + %request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout, Options) + {PathOrUrl} + end, + verify_options(Options), + try + Reply = lhttpc_client:request(Client, FinalPath, Method, FinalHeaders, Body, Options, Timeout), + Reply + catch + exit:{timeout, _} -> + {error, timeout} + end. + %%------------------------------------------------------------------------------ %% @spec (URL, Method, Hdrs, Timeout) -> Result %% URL = string() @@ -427,18 +496,15 @@ request(URL, Method, Hdrs, Body, Timeout, Options) -> headers(), iodata(), pos_timeout(), options()) -> result(). request(Host, Port, Ssl, Path, Method, Hdrs, Body, Timeout, Options) -> verify_options(Options), - Args = [self(), Host, Port, Ssl, Path, Method, Hdrs, Body, Options], - Pid = spawn_link(lhttpc_client, request, Args), - receive - {response, Pid, R} -> - R; - {'EXIT', Pid, Reason} -> - % This could happen if the process we're running in traps exits - % and the client process exits due to some exit signal being - % sent to it. Very unlikely though - {error, Reason} - after Timeout -> - kill_client(Pid) + {ok, Client} = connect_client({Host, Port, Ssl}, Options), + try + Reply = lhttpc_client:request(Client, Path, Method, Hdrs, Body, Options, Timeout), + disconnect_client(Client), + Reply + catch + exit:{timeout, _} -> + disconnect_client(Client), + {error, timeout} end. %%------------------------------------------------------------------------------ @@ -483,33 +549,8 @@ send_body_part({Pid, Window}, IoList) -> %% @end %%------------------------------------------------------------------------------ -spec send_body_part(upload_state(), bodypart(), timeout()) -> result(). -send_body_part({Pid, _Window}, http_eob, Timeout) when is_pid(Pid) -> - Pid ! {body_part, self(), http_eob}, - read_response(Pid, Timeout); -send_body_part({Pid, 0}, IoList, Timeout) when is_pid(Pid) -> - receive - {ack, Pid} -> - send_body_part({Pid, 1}, IoList, Timeout); - {response, Pid, R} -> - R; - {'EXIT', Pid, Reason} -> - {error, Reason} - after Timeout -> - kill_client(Pid) - end; -send_body_part({Pid, Window}, IoList, _Timeout) when Window > 0, is_pid(Pid) -> - % atom > 0 =:= true - Pid ! {body_part, self(), IoList}, - receive - {ack, Pid} -> - {ok, {Pid, Window}}; - {response, Pid, R} -> - R; - {'EXIT', Pid, Reason} -> - {error, Reason} - after 0 -> - {ok, {Pid, lhttpc_lib:dec(Window)}} - end. +send_body_part(Client, BodyPart, Timeout) -> + lhttpc_client:send_body_part(Client, BodyPart, Timeout). %%------------------------------------------------------------------------------ %% @spec (UploadState :: UploadState, Trailers) -> Result @@ -527,8 +568,8 @@ send_body_part({Pid, Window}, IoList, _Timeout) when Window > 0, is_pid(Pid) -> %% @end %%------------------------------------------------------------------------------ -spec send_trailers({pid(), window_size()}, headers()) -> result(). -send_trailers({Pid, Window}, Trailers) -> - send_trailers({Pid, Window}, Trailers, infinity). +send_trailers(Client, Trailers) -> + lhttpc_client:send_trailers(Client, Trailers, infinity). %%------------------------------------------------------------------------------ %% @spec (UploadState :: UploadState, Trailers, Timeout) -> Result @@ -596,18 +637,8 @@ get_body_part(Pid) -> %%------------------------------------------------------------------------------ -spec get_body_part(pid(), timeout()) -> {ok, binary()} | {ok, {http_eob, headers()}} | {error, term()}. -get_body_part(Pid, Timeout) -> - receive - {body_part, Pid, Bin} -> - Pid ! {ack, self()}, - {ok, Bin}; - {http_eob, Pid, Trailers} -> - {ok, {http_eob, Trailers}}; - {error, Pid, Reason} -> - {error, Reason} - after Timeout -> - kill_client(Pid) - end. +get_body_part(Client, Timeout) -> + lhttpc_client:get_body_part(Client, Timeout). %%============================================================================== %% Internal functions diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 3a159120..555377b3 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -27,13 +27,30 @@ %%------------------------------------------------------------------------------ %%% @private %%% @author Oscar Hellström +%%% @author Diana Parra Corbacho +%%% @author Ramon Lastres Guerrero %%% @doc This module implements the HTTP request handling. This should normally %%% not be called directly since it should be spawned by the lhttpc module. %%% @end %%------------------------------------------------------------------------------ -module(lhttpc_client). --export([request/9]). +-export([start_link/2, + start/2, + request/7, + send_body_part/3, + send_trailers/3, + stop/1]). + +%% gen_server callbacks +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 + ]). -include("lhttpc_types.hrl"). -include("lhttpc.hrl"). @@ -48,34 +65,49 @@ method :: string(), request :: iolist(), request_headers :: headers(), + requester, socket, connect_timeout = infinity :: timeout(), connect_options = [] :: [any()], attempts :: integer(), - requester :: pid(), partial_upload = false :: boolean(), chunked_upload = false :: boolean(), upload_window :: non_neg_integer() | infinity, partial_download = false :: boolean(), download_window = infinity :: timeout(), + download_proc :: pid(), part_size :: non_neg_integer() | infinity, %% in case of infinity we read whatever data we can get from %% the wire at that point or in case of chunked one chunk proxy :: undefined | #lhttpc_url{}, proxy_ssl_options = [] :: [any()], - proxy_setup = false :: boolean() + proxy_setup = false :: boolean(), + download_info :: {term(), term()}, + pool = undefined, + init_options }). %%============================================================================== %% Exported functions %%============================================================================== +start(Args, Options) -> + gen_server:start(?MODULE, Args, Options). + +start_link(Args, Options) -> + gen_server:start_link(?MODULE, Args, Options). + +send_body_part(Client, Part, Timeout) -> + gen_server:call(Client, {send_body_part, Part}, Timeout). + +send_trailers(Client, Trailers, Timeout) -> + gen_server:call(Client, {send_trailers, Trailers}, Timeout). + +stop(Client) -> + gen_server:cast(Client, stop). %%------------------------------------------------------------------------------ -%% @spec (From, Host, Port, Ssl, Path, Method, Hdrs, RequestBody, Options) -> ok +%% @spec (Client, Path, Method, Hdrs, RequestBody, Options, Timeout) -> ok %% From = pid() -%% Host = string() -%% Port = integer() -%% Ssl = boolean() %% Method = atom() | string() %% Hdrs = [Header] %% Header = {string() | atom(), string()} @@ -85,32 +117,47 @@ %% @doc %% @end %%------------------------------------------------------------------------------ --spec request(pid(), string(), port_num(), boolean(), string(), - method(), headers(), iolist(), options()) -> ok. -request(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) -> - Result = try - execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) - catch - Reason -> - {response, self(), {error, Reason}}; - error:closed -> - {response, self(), {error, connection_closed}}; - error:Reason -> - Stack = erlang:get_stacktrace(), - {response, self(), {error, {Reason, Stack}}} - end, - case Result of - {response, _, {ok, {no_return, _}}} -> ok; - _Else -> From ! Result - end, - % Don't send back {'EXIT', self(), normal} if the process - % calling us is trapping exits - unlink(From), - ok. +-spec request(pid(), string(), method(), headers(), iolist(), options(), integer()) -> ok. +request(Client, Path, Method, Hdrs, Body, Options, Timeout) -> + gen_server:call(Client, + {request, Path, Method, Hdrs, Body, Options}, Timeout). -%%============================================================================== -%% Internal functions -%%============================================================================== +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== +init({Destination, Options}) -> + %{Host, Port, Ssl, Options + %% TODO use pool if configured. Otherwise start socket alone + Pool = proplists:get_value(pool, Options), + State = case Destination of + {Host, Port, Ssl} -> + #client_state{host = Host, + port = Port, + ssl = Ssl, + pool = Pool, + init_options = Options}; + URL -> + #lhttpc_url{host = Host, + port = Port, + path = _Path, + is_ssl = Ssl, + user = _User, + password = _Passwd + } = lhttpc_lib:parse_url(URL), + #client_state{host = Host, + port = Port, + ssl = Ssl, + pool = Pool, + init_options = Options} + end, + %Pool = proplists:get_value(pool, Options), + %% Get a socket for the pool or exit + case connect_socket(State) of + {ok, NewState} -> + {ok, NewState}; + {error, Reason} -> + {stop, Reason} + end. %%------------------------------------------------------------------------------ %% @doc This function fills in the Client record used in the requests and obtains @@ -118,70 +165,156 @@ request(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) -> %% socket used is new, it also makes the pool gen_server its controlling process. %% @end %%------------------------------------------------------------------------------ -execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) -> +handle_call({request, Path, Method, Hdrs, Body, Options}, From, + State = #client_state{ssl = Ssl, host = Host, port = Port, + socket = Socket}) -> UploadWindowSize = proplists:get_value(partial_upload, Options), PartialUpload = proplists:is_defined(partial_upload, Options), PartialDownload = proplists:is_defined(partial_download, Options), PartialDownloadOptions = proplists:get_value(partial_download, Options, []), NormalizedMethod = lhttpc_lib:normalize_method(Method), Proxy = case proplists:get_value(proxy, Options) of - undefined -> - undefined; - ProxyUrl when is_list(ProxyUrl), not Ssl -> - % The point of HTTP CONNECT proxying is to use TLS tunneled in - % a plain HTTP/1.1 connection to the proxy (RFC2817). - throw(origin_server_not_https); - ProxyUrl when is_list(ProxyUrl) -> - lhttpc_lib:parse_url(ProxyUrl) - end, - {ChunkedUpload, Request} = lhttpc_lib:format_request(Path, NormalizedMethod, - Hdrs, Host, Port, Body, PartialUpload), - %SocketRequest = {socket, self(), Host, Port, Ssl}, - Pool = proplists:get_value(pool, Options, whereis(lhttpc_manager)), - %% Get a socket for the pool or exit - %Socket = lhttpc_manager:ensure_call(Pool, SocketRequest, Options), - Socket = lhttpc_manager:ensure_call(Pool, self(), Host, Port, Ssl, Options), - State = #client_state{ - host = Host, - port = Port, - ssl = Ssl, - method = NormalizedMethod, - request = Request, - requester = From, - request_headers = Hdrs, - socket = Socket, - connect_timeout = proplists:get_value(connect_timeout, Options, - infinity), - connect_options = proplists:get_value(connect_options, Options, []), - attempts = 1 + proplists:get_value(send_retry, Options, 1), - partial_upload = PartialUpload, - upload_window = UploadWindowSize, - chunked_upload = ChunkedUpload, - partial_download = PartialDownload, - download_window = proplists:get_value(window_size, - PartialDownloadOptions, infinity), - part_size = proplists:get_value(part_size, - PartialDownloadOptions, infinity), - proxy = Proxy, - proxy_setup = (Socket =/= undefined), - proxy_ssl_options = proplists:get_value(proxy_ssl_options, Options, []) - }, - Response = case send_request(State) of - {R, undefined} -> - {ok, R}; - {R, NewSocket} -> - % The socket we ended up doing the request over is returned - % here, it might be the same as Socket, but we don't know. - % I've noticed that we don't want to give send sockets that we - % can't change the controlling process for to the manager. This - % really shouldn't fail, but it could do if: - % * The socket was closed remotely already - % * Due to an error in this module (returning dead sockets for - % instance) - ok = lhttpc_manager:client_done(Pool, Host, Port, Ssl, NewSocket), - {ok, R} - end, - {response, self(), Response}. + undefined -> + undefined; + ProxyUrl when is_list(ProxyUrl), not Ssl -> + % The point of HTTP CONNECT proxying is to use TLS tunneled in + % a plain HTTP/1.1 connection to the proxy (RFC2817). + throw(origin_server_not_https); + ProxyUrl when is_list(ProxyUrl) -> + lhttpc_lib:parse_url(ProxyUrl) + end, + {ChunkedUpload, Request} = lhttpc_lib:format_request( + Path, NormalizedMethod, + Hdrs, Host, Port, Body, PartialUpload), + NewState = State#client_state{ + method = NormalizedMethod, + request = Request, + requester = From, + request_headers = Hdrs, + connect_timeout = proplists:get_value(connect_timeout, Options, + infinity), + connect_options = proplists:get_value(connect_options, Options, []), + attempts = 1 + proplists:get_value(send_retry, Options, 1), + partial_upload = PartialUpload, + upload_window = UploadWindowSize, + chunked_upload = ChunkedUpload, + partial_download = PartialDownload, + download_window = proplists:get_value(window_size, + PartialDownloadOptions, infinity), + download_proc = proplists:get_value(recv_proc, + PartialDownloadOptions, infinity), + part_size = proplists:get_value(part_size, + PartialDownloadOptions, infinity), + proxy = Proxy, + proxy_setup = (Socket =/= undefined), + proxy_ssl_options = proplists:get_value(proxy_ssl_options, Options, []) + }, + {Response, NewState2} = send_request(NewState), + {reply, Response, NewState2}; +handle_call({trailers, Trailers}, _From, State = #client_state{partial_upload = true}) -> + case send_trailers(State, Trailers) of + {ok, NewState} -> + read_response(NewState); + {Error, NewState} -> + {reply, Error, NewState} + end; +handle_call({send_body_part, http_eob}, _From, State = #client_state{partial_upload = true, socket = undefined}) -> + {reply, {error, closed}, State}; +handle_call({send_body_part, http_eob}, From, State = #client_state{partial_upload = true}) -> + case send_body_part(State, http_eob) of + {ok, NewState} -> + read_response(NewState#client_state{requester = From}); + {Error, NewState} -> + {reply, Error, NewState} + end; +handle_call({send_body_part, Data}, _From, #client_state{partial_upload = true, + upload_window = 0} + = State) -> + {Reply, NewState} = send_body_part(State, Data), + {reply, Reply, NewState}; +handle_call({send_body_part, Data}, From, State = #client_state{partial_upload = true}) -> + gen_server:reply(From, ok), + {_Reply, NewState} = send_body_part(State, Data), + {noreply, NewState}; +%We send the parts to the specified Pid. +handle_call({get_body_part, _Options}, From, + State=#client_state{partial_download = true, download_info = {_Vsn, Hdrs}}) -> + gen_server:reply(ok, From), + read_partial_body(State, body_type(Hdrs)), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling cast messages +%% +%% @spec handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_cast(stop, State) -> + {stop, normal, State}; +handle_cast(_Msg, State) -> +{noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling all non call/cast messages +%% +%% @spec handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_info(_Info, State) -> +{noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any +%% necessary cleaning up. When it returns, the gen_server terminates +%% with Reason. The return value is ignored. +%% +%% @spec terminate(Reason, State) -> void() +%% @end +%%-------------------------------------------------------------------- +terminate(_Reason, State = #client_state{pool = Pool, host = Host, ssl = Ssl, socket = Socket, port = Port}) -> + case Pool of + undefined -> + % if we dont have pool we just close the socket. + close_socket(State), + ok; + _ -> + %return the control of the socket to the pool. + lhttpc_manager:client_done(Pool, Host, Port, Ssl, Socket) + end. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Convert process state when code is changed +%% +%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} +%% @end +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> +{ok, State}. + +%%============================================================================== +%% Internal functions +%%============================================================================== +%%------------------------------------------------------------------------------ +%% @private +%%------------------------------------------------------------------------------ +send_body_part(State = #client_state{socket = Socket, ssl = Ssl, + upload_window = Window}, BodyPart) -> + Data = encode_body_part(State, BodyPart), + check_send_result(State#client_state{upload_window = lhttpc_lib:dec(Window)}, + lhttpc_sock:send(Socket, Data, Ssl)). %%------------------------------------------------------------------------------ %% @private @@ -189,105 +322,105 @@ execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) -> %% handles the proxy connection. %% @end %%------------------------------------------------------------------------------ -send_request(#client_state{attempts = 0}) -> +%send_request(#client_state{attempts = 0} = State) -> % Don't try again if the number of allowed attempts is 0. - throw(connection_closed); +% {{error, connection_closed}, State}; %we need a socket. -send_request(#client_state{socket = undefined} = State) -> - {Host, Port, Ssl} = request_first_destination(State), - Timeout = State#client_state.connect_timeout, - ConnectOptions0 = State#client_state.connect_options, - ConnectOptions = case (not lists:member(inet, ConnectOptions0)) andalso - (not lists:member(inet6, ConnectOptions0)) andalso - is_ipv6_host(Host) of - true -> - [inet6 | ConnectOptions0]; - false -> - ConnectOptions0 - end, - SocketOptions = [binary, {packet, http}, {active, false} | ConnectOptions], - try lhttpc_sock:connect(Host, Port, SocketOptions, Timeout, Ssl) of - {ok, Socket} -> - send_request(State#client_state{socket = Socket}); - {error, etimedout} -> - % TCP stack decided to give up - throw(connect_timeout); - {error, timeout} -> - throw(connect_timeout); - {error, 'record overflow'} -> - throw(ssl_error); - {error, Reason} -> - erlang:error(Reason) - catch - exit:{{{badmatch, {error, {asn1, _}}}, _}, _} -> - throw(ssl_decode_error); - Type:Error -> - error_logger:error_msg("Socket connection error: ~p ~p, ~p", - [Type, Error, erlang:get_stacktrace()]) - end; -send_request(#client_state{proxy = #lhttpc_url{}, proxy_setup = false} = State) -> -% use a proxy. +%send_request(#client_state{socket = undefined} = State) -> % MOVED TO CONNECT SOCKET!! +% {Host, Port, Ssl} = request_first_destination(State), +% Timeout = State#client_state.connect_timeout, +% ConnectOptions0 = State#client_state.connect_options, +% ConnectOptions = case (not lists:member(inet, ConnectOptions0)) andalso +% (not lists:member(inet6, ConnectOptions0)) andalso +% is_ipv6_host(Host) of +% true -> +% [inet6 | ConnectOptions0]; +% false -> +% ConnectOptions0 +% end, +% SocketOptions = [binary, {packet, http}, {active, false} | ConnectOptions], +% Reply = try lhttpc_sock:connect(Host, Port, SocketOptions, Timeout, Ssl) of +% {ok, Socket} -> +% send_request(State#client_state{socket = Socket}); +% {error, etimedout} -> +% % TCP stack decided to give up +% {error, connect_timeout}; +% {error, timeout} -> +% {error, connect_timeout}; +% {error, 'record overflow'} -> +% {error, ssl_error}; +% {error, _} = Error -> +% Error +% catch +% exit:{{{badmatch, {error, {asn1, _}}}, _}, _} -> +% {error, ssl_decode_error}; +% Type:Error -> +% error_logger:error_msg("Socket connection error: ~p ~p, ~p", +% [Type, Error, erlang:get_stacktrace()]), +% {error, connection_error} +% end, +% {Reply, State}; +send_request(#client_state{proxy = #lhttpc_url{}, proxy_setup = false, + host = DestHost, port = Port, socket = Socket} = State) -> + %% use a proxy. #lhttpc_url{ - user = User, - password = Passwd, - is_ssl = Ssl - } = State#client_state.proxy, - #client_state{ - host = DestHost, - port = Port, - socket = Socket - } = State, + user = User, + password = Passwd, + is_ssl = Ssl + } = State#client_state.proxy, Host = case inet_parse:address(DestHost) of - {ok, {_, _, _, _, _, _, _, _}} -> - % IPv6 address literals are enclosed by square brackets (RFC2732) - [$[, DestHost, $], $:, integer_to_list(Port)]; - _ -> - [DestHost, $:, integer_to_list(Port)] - end, + {ok, {_, _, _, _, _, _, _, _}} -> + %% IPv6 address literals are enclosed by square brackets (RFC2732) + [$[, DestHost, $], $:, integer_to_list(Port)]; + _ -> + [DestHost, $:, integer_to_list(Port)] + end, ConnectRequest = [ - "CONNECT ", Host, " HTTP/1.1\r\n", - "Host: ", Host, "\r\n", - case User of - "" -> - ""; - _ -> - ["Proxy-Authorization: Basic ", - base64:encode(User ++ ":" ++ Passwd), "\r\n"] - end, - "\r\n" - ], + "CONNECT ", Host, " HTTP/1.1\r\n", + "Host: ", Host, "\r\n", + case User of + "" -> + ""; + _ -> + ["Proxy-Authorization: Basic ", + base64:encode(User ++ ":" ++ Passwd), "\r\n"] + end, + "\r\n" + ], case lhttpc_sock:send(Socket, ConnectRequest, Ssl) of ok -> read_proxy_connect_response(State, nil, nil); {error, closed} -> - lhttpc_sock:close(Socket, Ssl), - throw(proxy_connection_closed); - {error, Reason} -> - lhttpc_sock:close(Socket, Ssl), - erlang:error(Reason) + close_socket(State), + {{error, proxy_connection_closed}, State#client_state{socket = undefined}}; + {error, _Reason} -> + close_socket(State), + {{error, proxy_connection_closed}, State#client_state{socket = undefined}} end; -send_request(State) -> -%already have socket - Socket = State#client_state.socket, - Ssl = State#client_state.ssl, - Request = State#client_state.request, +send_request(#client_state{socket = Socket, ssl = Ssl, request = Request} = State) -> + %% no proxy case lhttpc_sock:send(Socket, Request, Ssl) of ok -> if - % {partial_upload, WindowSize} is used. - State#client_state.partial_upload -> partial_upload(State); + % {partial_upload, WindowSize} is used. + State#client_state.partial_upload -> + {{ok, partial_upload}, State#client_state{attempts = 1}}; not State#client_state.partial_upload -> read_response(State) end; {error, closed} -> - lhttpc_sock:close(Socket, Ssl), + close_socket(State), NewState = State#client_state{ socket = undefined, - attempts = State#client_state.attempts - 1 - }, - send_request(NewState); - {error, Reason} -> - lhttpc_sock:close(Socket, Ssl), - erlang:error(Reason) + attempts = State#client_state.attempts - 1}, + case connect_socket(NewState) of + {error, connection_closed} -> + {{error, connection_closed}, NewState}; + NewState2 -> + send_request(NewState2) + end; + {error, _Reason} -> + close_socket(State), + {{error, connection_closed}, State#client_state{socket = undefined}} end. %%------------------------------------------------------------------------------ @@ -310,71 +443,35 @@ read_proxy_connect_response(State, StatusCode, StatusText) -> {ok, {http_header, _, _Name, _, _Value}} -> read_proxy_connect_response(State, StatusCode, StatusText); {ok, http_eoh} when StatusCode >= 100, StatusCode =< 199 -> - % RFC 2616, section 10.1: - % A client MUST be prepared to accept one or more - % 1xx status responses prior to a regular - % response, even if the client does not expect a - % 100 (Continue) status message. Unexpected 1xx - % status responses MAY be ignored by a user agent. + %% RFC 2616, section 10.1: + %% A client MUST be prepared to accept one or more + %% 1xx status responses prior to a regular + %% response, even if the client does not expect a + %% 100 (Continue) status message. Unexpected 1xx + %% status responses MAY be ignored by a user agent. read_proxy_connect_response(State, nil, nil); {ok, http_eoh} when StatusCode >= 200, StatusCode < 300 -> - % RFC2817, any 2xx code means success. + %% RFC2817, any 2xx code means success. ConnectOptions = State#client_state.connect_options, SslOptions = State#client_state.proxy_ssl_options, Timeout = State#client_state.connect_timeout, State2 = case ssl:connect(Socket, SslOptions ++ ConnectOptions, Timeout) of - {ok, SslSocket} -> - State#client_state{socket = SslSocket, proxy_setup = true}; - {error, Reason} -> - lhttpc_sock:close(Socket, ProxyIsSsl), - erlang:error({proxy_connection_failed, Reason}) - end, + {ok, SslSocket} -> + State#client_state{socket = SslSocket, proxy_setup = true}; + {error, Reason} -> + close_socket(State), + {{error, {proxy_connection_failed, Reason}}, State} + end, send_request(State2); {ok, http_eoh} -> - throw({proxy_connection_refused, StatusCode, StatusText}); + {{error, {proxy_connection_refused, StatusCode, StatusText}}, State}; {error, closed} -> - lhttpc_sock:close(Socket, ProxyIsSsl), - throw(proxy_connection_closed); + close_socket(State), + {{error, proxy_connection_closed}, State#client_state{socket = undefined}}; {error, Reason} -> - erlang:error({proxy_connection_failed, Reason}) - end. - - -%%------------------------------------------------------------------------------ -%% @private -%% @doc Called when {partial_upload, WindowSize} is used. The user can send -%% messages using functions in lhttpc module -%% @end -%%------------------------------------------------------------------------------ -partial_upload(State) -> - Response = {ok, {self(), State#client_state.upload_window}}, - State#client_state.requester ! {response, self(), Response}, - partial_upload_loop(State#client_state{attempts = 1, request = undefined}). - -%%------------------------------------------------------------------------------ -%% @private -%%------------------------------------------------------------------------------ -partial_upload_loop(State = #client_state{requester = Pid}) -> - receive - {trailers, Pid, Trailers} -> - send_trailers(State, Trailers), - read_response(State); - {body_part, Pid, http_eob} -> - send_body_part(State, http_eob), - read_response(State); - {body_part, Pid, Data} -> - send_body_part(State, Data), - Pid ! {ack, self()}, - partial_upload_loop(State) + {{error, {proxy_connection_failed, Reason}}, State} end. -%%------------------------------------------------------------------------------ -%% @private -%%------------------------------------------------------------------------------ -send_body_part(State = #client_state{socket = Socket, ssl = Ssl}, BodyPart) -> - Data = encode_body_part(State, BodyPart), - check_send_result(State, lhttpc_sock:send(Socket, Data, Ssl)). - %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ @@ -383,8 +480,8 @@ send_trailers(State = #client_state{chunked_upload = true}, Trailers) -> Ssl = State#client_state.ssl, Data = [<<"0\r\n">>, lhttpc_lib:format_hdrs(Trailers)], check_send_result(State, lhttpc_sock:send(Socket, Data, Ssl)); -send_trailers(#client_state{chunked_upload = false}, _Trailers) -> - erlang:error(trailers_not_allowed). +send_trailers(#client_state{chunked_upload = false} = State, _Trailers) -> + {{error, trailers_not_allowed}, State}. %%------------------------------------------------------------------------------ %% @private @@ -402,11 +499,11 @@ encode_body_part(#client_state{chunked_upload = false}, Data) -> %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -check_send_result(_State, ok) -> - ok; -check_send_result(#client_state{socket = Sock, ssl = Ssl}, {error, Reason}) -> - lhttpc_sock:close(Sock, Ssl), - throw(Reason). +check_send_result(State, ok) -> + {ok, State}; +check_send_result(State, Error) -> + close_socket(State), + {Error, State#client_state{socket = undefined}}. %%------------------------------------------------------------------------------ %% @private @@ -443,27 +540,35 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> read_response(State, nil, {nil, nil}, []); {ok, http_eoh} -> lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl), - Response = handle_response_body(State, Vsn, Status, Hdrs), - NewHdrs = element(2, Response), + {Reply, NewState} = handle_response_body(State, Vsn, Status, Hdrs), + NewHdrs = element(2, Reply), ReqHdrs = State#client_state.request_headers, - NewSocket = maybe_close_socket(Socket, Ssl, Vsn, ReqHdrs, NewHdrs), - {Response, NewSocket}; + NewSocket = maybe_close_socket(State, Vsn, ReqHdrs, NewHdrs), + case Reply of + noreply -> + {noreply, NewState#client_state{socket = NewSocket}}; + _ -> + %{reply, Reply, NewState#client_state{socket = NewSocket}} + {Reply, NewState#client_state{socket = NewSocket}} + end; {error, closed} -> + %% TODO does it work for partial uploads? I think should return an error + % Either we only noticed that the socket was closed after we % sent the request, the server closed it just after we put % the request on the wire or the server has some issues and is % closing connections without sending responses. % If this the first attempt to send the request, we will try again. - lhttpc_sock:close(Socket, Ssl), + close_socket(State), NewState = State#client_state{ socket = undefined, attempts = State#client_state.attempts - 1 }, send_request(NewState); {ok, {http_error, _} = Reason} -> - erlang:error(Reason); + {reply, {error, Reason}, State}; {error, Reason} -> - erlang:error(Reason) + {reply, {error, Reason}, State} end. %%------------------------------------------------------------------------------ @@ -484,21 +589,20 @@ handle_response_body(#client_state{partial_download = false} = State, Vsn, true -> read_body(Vsn, Hdrs, Ssl, Socket, body_type(Hdrs)); false -> {<<>>, Hdrs} end, - {Status, NewHdrs, Body}; + {{Status, NewHdrs, Body}, State}; handle_response_body(#client_state{partial_download = true} = State, Vsn, Status, Hdrs) -> %when {partial_download, PartialDownloadOptions} option is used. Method = State#client_state.method, case has_body(Method, element(1, Status), Hdrs) of true -> - Response = {ok, {Status, Hdrs, self()}}, - State#client_state.requester ! {response, self(), Response}, - MonRef = erlang:monitor(process, State#client_state.requester), - Res = read_partial_body(State, Vsn, Hdrs, body_type(Hdrs)), - erlang:demonitor(MonRef, [flush]), - Res; + Response = {Status, Hdrs, partial_download}, + gen_server:reply(State#client_state.requester, Response), + NewState = State#client_state{download_info = {Vsn, Hdrs}}, + read_partial_body(NewState, body_type(Hdrs)), + {noreply, NewState}; false -> - {Status, Hdrs, undefined} + {{Status, Hdrs, undefined}, State} end. %%------------------------------------------------------------------------------ @@ -549,22 +653,6 @@ body_type(Hdrs) -> ContentLength -> {fixed_length, list_to_integer(ContentLength)} end. - -%%------------------------------------------------------------------------------ -%%% @private -%%% @doc Called when {partial_download, PartialDownloadOptions} option is used. -%%% @end -%%------------------------------------------------------------------------------ -read_partial_body(State, _Vsn, Hdrs, chunked) -> - Window = State#client_state.download_window, - read_partial_chunked_body(State, Hdrs, Window, 0, [], 0); -read_partial_body(State, Vsn, Hdrs, infinite) -> - check_infinite_response(Vsn, Hdrs), - read_partial_infinite_body(State, Hdrs, State#client_state.download_window); -read_partial_body(State, _Vsn, Hdrs, {fixed_length, ContentLength}) -> - read_partial_finite_body(State, Hdrs, ContentLength, - State#client_state.download_window). - %%------------------------------------------------------------------------------ %%% @private %%% @doc Called when {partial_download, PartialDownloadOptions} option is NOT used. @@ -580,37 +668,39 @@ read_body(_Vsn, Hdrs, Ssl, Socket, {fixed_length, ContentLength}) -> %%------------------------------------------------------------------------------ %%% @private +%%% @doc Called when {partial_download, PartialDownloadOptions} option is used. +%%% @end %%------------------------------------------------------------------------------ -read_partial_finite_body(State = #client_state{}, Hdrs, 0, _Window) -> - reply_end_of_body(State, [], Hdrs); -read_partial_finite_body(State = #client_state{requester = To}, Hdrs, - ContentLength, 0) -> - receive - {ack, To} -> - read_partial_finite_body(State, Hdrs, ContentLength, 1); - {'DOWN', _, process, To, _} -> - exit(normal) - end; +read_partial_body(State=#client_state{download_info = {_Vsn, Hdrs}}, chunked) -> + Window = State#client_state.download_window, + read_partial_chunked_body(State, Hdrs, Window, 0, [], 0); +read_partial_body(State=#client_state{download_info = {Vsn, Hdrs}}, infinite) -> + check_infinite_response(Vsn, Hdrs), + read_partial_infinite_body(State, Hdrs, State#client_state.download_window); +read_partial_body(State=#client_state{download_info = {_Vsn, Hdrs}}, {fixed_length, ContentLength}) -> + read_partial_finite_body(State, Hdrs, ContentLength, State#client_state.download_window). + +%%------------------------------------------------------------------------------ +%%% @private +%%------------------------------------------------------------------------------ +read_partial_finite_body(State , _Hdrs, 0, _Window) -> + reply_end_of_body(State, []); +read_partial_finite_body(#client_state{download_proc = To}, _Hdrs, _ContentLength, 0) -> + %finished the window, reply to ask for another call to get_body_part + To ! {body_part, window_finished}; read_partial_finite_body(State, Hdrs, ContentLength, Window) when Window >= 0-> case read_body_part(State, ContentLength) of {ok, Bin} -> - State#client_state.requester ! {body_part, self(), Bin}, - To = State#client_state.requester, - receive - {ack, To} -> + State#client_state.download_proc ! {body_part, Bin}, Length = ContentLength - iolist_size(Bin), - read_partial_finite_body(State, Hdrs, Length, Window); - {'DOWN', _, process, To, _} -> - exit(normal) - after 0 -> - Length = ContentLength - iolist_size(Bin), - read_partial_finite_body(State, Hdrs, Length, lhttpc_lib:dec(Window)) - end; + read_partial_finite_body(State, Hdrs, Length, lhttpc_lib:dec(Window)); + %TODO do we need to close the socket here? {error, Reason} -> - State#client_state.requester ! {error, self(), Reason}, + State#client_state.download_proc ! {error, Reason}, exit(normal) end. + %%------------------------------------------------------------------------------ %%% @private %%------------------------------------------------------------------------------ @@ -642,15 +732,18 @@ read_length(Hdrs, Ssl, Socket, Length) -> %%------------------------------------------------------------------------------ %%% @private %%------------------------------------------------------------------------------ +read_partial_chunked_body(State, _Hdrs, 0, 0, _Buffer, 0) -> + %we ask for another call to get_body_part + State#client_state.download_proc ! {body_part, http_eob}; read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, 0) -> Socket = State#client_state.socket, Ssl = State#client_state.ssl, PartSize = State#client_state.part_size, - case read_chunk_size(Socket, Ssl) of + case read_chunk_size(Socket, Ssl) of 0 -> reply_chunked_part(State, Buffer, Window), - {Trailers, NewHdrs} = read_trailers(Socket, Ssl, [], Hdrs), - reply_end_of_body(State, Trailers, NewHdrs); + {Trailers, _NewHdrs} = read_trailers(Socket, Ssl, [], Hdrs), + reply_end_of_body(State, Trailers); ChunkSize when PartSize =:= infinity -> Chunk = read_chunk(Socket, Ssl, ChunkSize), NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), @@ -699,21 +792,9 @@ read_chunk_size(Socket, Ssl) -> %%------------------------------------------------------------------------------ reply_chunked_part(_State, [], Window) -> Window; -reply_chunked_part(State = #client_state{requester = Pid}, Buff, 0) -> - receive - {ack, Pid} -> - reply_chunked_part(State, Buff, 1); - {'DOWN', _, process, Pid, _} -> - exit(normal) - end; -reply_chunked_part(#client_state{requester = Pid}, Buffer, Window) -> - Pid ! {body_part, self(), list_to_binary(lists:reverse(Buffer))}, - receive - {ack, Pid} -> Window; - {'DOWN', _, process, Pid, _} -> exit(normal) - after 0 -> - lhttpc_lib:dec(Window) - end. +reply_chunked_part(#client_state{download_proc = Pid}, Buffer, Window) -> + Pid ! {body_part, list_to_binary(lists:reverse(Buffer))}, + lhttpc_lib:dec(Window). %%------------------------------------------------------------------------------ %%% @private @@ -759,7 +840,7 @@ read_partial_chunk(Socket, Ssl, Size, ChunkSize) -> {ok, Chunk} -> {Chunk, ChunkSize - Size}; {error, Reason} -> - erlang:error(Reason) + {error, Reason} end. %%------------------------------------------------------------------------------ @@ -771,9 +852,9 @@ read_chunk(Socket, Ssl, Size) -> {ok, <>} -> Chunk; {ok, Data} -> - erlang:error({invalid_chunk, Data}); + {error, {invalid_chunk, Data}}; {error, Reason} -> - erlang:error(Reason) + {error, Reason} end. %%------------------------------------------------------------------------------ @@ -796,35 +877,22 @@ read_trailers(Socket, Ssl, Trailers, Hdrs) -> %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ --spec reply_end_of_body(#client_state{}, any(), any()) -> {'no_return', any()}. -reply_end_of_body(#client_state{requester = Requester}, Trailers, Hdrs) -> - Requester ! {http_eob, self(), Trailers}, - {no_return, Hdrs}. +%-spec reply_end_of_body(#client_state{}, any(), any()) -> 'no_return'. +reply_end_of_body(#client_state{download_proc = To}, Trailers) -> + To ! {http_eob, Trailers}. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -read_partial_infinite_body(State = #client_state{requester = To}, Hdrs, 0) -> - receive - {ack, To} -> - read_partial_infinite_body(State, Hdrs, 1); - {'DOWN', _, process, To, _} -> - exit(normal) - end; -read_partial_infinite_body(State = #client_state{requester = To}, Hdrs, Window) - when Window >= 0 -> +read_partial_infinite_body(State, _Hdrs, 0) -> + State#client_state.download_proc ! {body_part, window_finished}; +read_partial_infinite_body(State, Hdrs, Window) + when Window >= 0 -> case read_infinite_body_part(State) of - http_eob -> reply_end_of_body(State, [], Hdrs); + http_eob -> reply_end_of_body(State, []); Bin -> - State#client_state.requester ! {body_part, self(), Bin}, - receive - {ack, To} -> - read_partial_infinite_body(State, Hdrs, Window); - {'DOWN', _, process, To, _} -> - exit(normal) - after 0 -> - read_partial_infinite_body(State, Hdrs, lhttpc_lib:dec(Window)) - end + State#client_state.download_proc ! {body_part, Bin}, + read_partial_infinite_body(State, Hdrs, lhttpc_lib:dec(Window)) end. %%------------------------------------------------------------------------------ @@ -884,25 +952,25 @@ read_until_closed(Socket, Acc, Hdrs, Ssl) -> %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -maybe_close_socket(Socket, Ssl, {1, Minor}, ReqHdrs, RespHdrs) when Minor >= 1-> +maybe_close_socket(State, {1, Minor}, ReqHdrs, RespHdrs) when Minor >= 1-> ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"), ServerConnection = ?CONNECTION_HDR(RespHdrs, "keep-alive"), if ClientConnection =:= "close"; ServerConnection =:= "close" -> - lhttpc_sock:close(Socket, Ssl), + close_socket(State), undefined; ClientConnection =/= "close", ServerConnection =/= "close" -> - Socket + State#client_state.socket end; -maybe_close_socket(Socket, Ssl, _, ReqHdrs, RespHdrs) -> +maybe_close_socket(State, _, ReqHdrs, RespHdrs) -> ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"), ServerConnection = ?CONNECTION_HDR(RespHdrs, "close"), if ClientConnection =:= "close"; ServerConnection =/= "keep-alive" -> - lhttpc_sock:close(Socket, Ssl), + close_socket(State), undefined; ClientConnection =/= "close", ServerConnection =:= "keep-alive" -> - Socket + State#client_state.socket end. %%------------------------------------------------------------------------------ @@ -929,3 +997,114 @@ is_ipv6_host(Host) -> end end end. + +% What about the timeout? +%%------------------------------------------------------------------------------ +%% @private +%% @doc If we are using a pool, it takes the socket from the pool if it exist, +%% otherwise it creates a new socket. If we are not using pool, it just creates +%% a new socket. If the option pool_ensure its set to true, it creates a +%% pool dinamically. +%% @end +%%------------------------------------------------------------------------------ +connect_socket(State = #client_state{attempts = 0}) -> + % Don't try again if the number of allowed attempts is 0. + {{error, connection_closed}, State}; +connect_socket(State = #client_state{init_options = Options, pool = Pool}) -> + case Pool of + undefined -> + %% TODO open new socket here + %{Host, Port, Ssl} = request_first_destination(State), + %Timeout = State#client_state.connect_timeout, + %ConnectOptions0 = State#client_state.connect_options, + %ConnectOptions = case (not lists:member(inet, ConnectOptions0)) andalso + % (not lists:member(inet6, ConnectOptions0)) andalso + % is_ipv6_host(Host) of + % true -> + % [inet6 | ConnectOptions0]; + % false -> + % ConnectOptions0 + % end, + % SocketOptions = [binary, {packet, http}, {active, false} | ConnectOptions], + % try lhttpc_sock:connect(Host, Port, SocketOptions, Timeout, Ssl) of + % {ok, Socket} -> + % {ok, State#client_state{socket = Socket}}; + % {error, etimedout} -> + % % TCP stack decided to give up + % {error, connect_timeout}; + % {error, timeout} -> + % {error, connect_timeout}; + % {error, 'record overflow'} -> + % {error, ssl_error}; + % {error, _} = Error -> + % Error + % catch + % exit:{{{badmatch, {error, {asn1, _}}}, _}, _} -> + % {error, ssl_decode_error}; + % Type:Error -> + % error_logger:error_msg("Socket connection error: ~p ~p, ~p", + % [Type, Error, erlang:get_stacktrace()]), + % {error, connection_error} + % end; + new_socket(State); + _ -> + {Host, Port, Ssl} = request_first_destination(State), + {ok, Socket} = lhttpc_manager:ensure_call(Pool, self(), Host, Port, Ssl, Options), + %ensure_call does not open a socket if the pool doesnt have one already!!! + case Socket of + undefined -> + new_socket(State); + socket -> + {ok, State#client_state{socket = Socket}} + end + end. + +%%------------------------------------------------------------------------------ +%% @private +%% @doc Creates a new socket using the options included in the client state. +%% end +%%------------------------------------------------------------------------------ +new_socket(State) -> + {Host, Port, Ssl} = request_first_destination(State), + Timeout = State#client_state.connect_timeout, + ConnectOptions0 = State#client_state.connect_options, + ConnectOptions = case (not lists:member(inet, ConnectOptions0)) andalso + (not lists:member(inet6, ConnectOptions0)) andalso + is_ipv6_host(Host) of + true -> + [inet6 | ConnectOptions0]; + false -> + ConnectOptions0 + end, + SocketOptions = [binary, {packet, http}, {active, false} | ConnectOptions], + try lhttpc_sock:connect(Host, Port, SocketOptions, Timeout, Ssl) of + {ok, Socket} -> + {ok, State#client_state{socket = Socket}}; + {error, etimedout} -> + % TCP stack decided to give up + {error, connect_timeout}; + {error, timeout} -> + {error, connect_timeout}; + {error, 'record overflow'} -> + {error, ssl_error}; + {error, _} = Error -> + Error + catch + exit:{{{badmatch, {error, {asn1, _}}}, _}, _} -> + {error, ssl_decode_error}; + Type:Error -> + error_logger:error_msg("Socket connection error: ~p ~p, ~p", + [Type, Error, erlang:get_stacktrace()]), + {error, connection_error} + end. + +%%------------------------------------------------------------------------------ +%% @private +%%------------------------------------------------------------------------------ +close_socket(_State = #client_state{socket = Socket, pool = Pool, ssl = Ssl}) -> + case Pool of + undefined -> + lhttpc_sock:close(Socket, Ssl); + _ -> + lhttpc_manager:close_socket(Pool, Socket) + end. diff --git a/src/lhttpc_manager.erl b/src/lhttpc_manager.erl index 80ee047c..28ca9499 100644 --- a/src/lhttpc_manager.erl +++ b/src/lhttpc_manager.erl @@ -27,6 +27,8 @@ %%------------------------------------------------------------------------------ %%% @author Oscar Hellström %%% @author Filipe David Manana +%%% @author Diana Parra Corbacho +%%% @author Ramon Lastres Guerrero %%% @doc Connection manager for the HTTP client. %%% This gen_server is responsible for keeping track of persistent %%% connections to HTTP servers. The only interesting API is @@ -46,7 +48,8 @@ list_pools/0, set_max_pool_size/2, ensure_call/6, - client_done/5 + client_done/5, + close_socket/2 ]). %% Callbacks @@ -162,6 +165,13 @@ connection_count(PidOrName, {Host, Port, Ssl}) -> update_connection_timeout(PidOrName, Milliseconds) -> gen_server:cast(PidOrName, {update_timeout, Milliseconds}). +%%------------------------------------------------------------------------------ +%% @doc +%% @end +%%------------------------------------------------------------------------------ +close_socket(PidOrName, Socket) -> + gen_server:cast(PidOrName, {remove_socket, Socket}). + %%------------------------------------------------------------------------------ %% @spec () -> {ok, pid()} %% @doc Starts and link to the gen server. @@ -204,7 +214,7 @@ ensure_call(Pool, Pid, Host, Port, Ssl, Options) -> S; no_socket -> %% Opening a new HTTP/1.1 connection - undefined + {ok, undefined} catch exit:{noproc, Reason} -> case proplists:get_value(pool_ensure, Options, false) of @@ -226,7 +236,8 @@ ensure_call(Pool, Pid, Host, Port, Ssl, Options) -> ensure_call(Pool, Pid, Host, Port, Ssl, Options); _ -> %% Failed to create pool, exit as expected - exit({noproc, Reason}) + %exit({noproc, Reason}) + exit({noproc, Reason}) end; false -> %% No dynamic pool creation, exit as expected @@ -328,6 +339,8 @@ handle_cast({update_timeout, Milliseconds}, State) -> {noreply, State#httpc_man{timeout = Milliseconds}}; handle_cast({set_max_pool_size, Size}, State) -> {noreply, State#httpc_man{max_pool_size = Size}}; +handle_cast({remove_socket, Socket}, State) -> + {noreply, remove_socket(Socket, State)}; handle_cast(_, State) -> {noreply, State}. From e8454f583cd06b70e5f5099c193d6b88ae90af1b Mon Sep 17 00:00:00 2001 From: Lastres Date: Tue, 22 Jan 2013 10:34:40 +0000 Subject: [PATCH 02/49] Remove lhttpc_manager from supervisor init(). Fix bug in ensure_call function. Before one pool called lhttpc_manager was created during initialzation. With the new redesign this is not necessary. --- src/lhttpc_client.erl | 2 +- src/lhttpc_manager.erl | 2 +- src/lhttpc_sup.erl | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 555377b3..6ae51ff3 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -1054,7 +1054,7 @@ connect_socket(State = #client_state{init_options = Options, pool = Pool}) -> case Socket of undefined -> new_socket(State); - socket -> + Socket -> {ok, State#client_state{socket = Socket}} end end. diff --git a/src/lhttpc_manager.erl b/src/lhttpc_manager.erl index 28ca9499..aa8d05f3 100644 --- a/src/lhttpc_manager.erl +++ b/src/lhttpc_manager.erl @@ -211,7 +211,7 @@ ensure_call(Pool, Pid, Host, Port, Ssl, Options) -> try gen_server:call(Pool, SocketRequest, infinity) of {ok, S} -> %% Re-using HTTP/1.1 connections - S; + {ok, S}; no_socket -> %% Opening a new HTTP/1.1 connection {ok, undefined} diff --git a/src/lhttpc_sup.erl b/src/lhttpc_sup.erl index ed917f9d..d1e58bb3 100644 --- a/src/lhttpc_sup.erl +++ b/src/lhttpc_sup.erl @@ -61,7 +61,8 @@ start_link() -> %% @hidden %%------------------------------------------------------------------------------ init(_) -> - LHTTPCManager = {lhttpc_manager, {lhttpc_manager, start_link, - [[{name, lhttpc_manager}]]}, - permanent, 10000, worker, [lhttpc_manager]}, - {ok, {{one_for_one, 10, 1}, [LHTTPCManager]}}. +% LHTTPCManager = {lhttpc_manager, {lhttpc_manager, start_link, +% [[{name, lhttpc_manager}]]}, +% permanent, 10000, worker, [lhttpc_manager]}, +% {ok, {{one_for_one, 10, 1}, [LHTTPCManager]}}. + {ok, {{one_for_one, 10, 1}, []}}. From 74510869a52d75d55f104f709b976ef19cafb94d Mon Sep 17 00:00:00 2001 From: Lastres Date: Tue, 22 Jan 2013 10:49:46 +0000 Subject: [PATCH 03/49] Improve format, remove comented lines and add doc to some functions. --- src/lhttpc.erl | 20 ++++++++++-- src/lhttpc_client.erl | 71 ------------------------------------------- src/lhttpc_sup.erl | 4 --- 3 files changed, 17 insertions(+), 78 deletions(-) diff --git a/src/lhttpc.erl b/src/lhttpc.erl index 564a01c2..8a305a14 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -186,15 +186,31 @@ disconnect_client(Client) -> %REQUESTS USING THE CLIENT + +%%------------------------------------------------------------------------------ +%% @doc Makes a request using a client already connected. +%% It can receive either a URL or a path +%% @end +%%------------------------------------------------------------------------------ -spec request_client(pid(), string(), method(), headers(), pos_timeout()) -> result(). request_client(Client, PathOrUrl, Method, Hdrs, Timeout) -> request_client(Client, PathOrUrl, Method, Hdrs, [], Timeout, []). +%%------------------------------------------------------------------------------ +%% @doc Makes a request using a client already connected. +%% It can receive either a URL or a path. It allows to add the body. +%% @end +%%------------------------------------------------------------------------------ -spec request_client(pid(), string(), method(), headers(), iodata(), pos_timeout()) -> result(). request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout) -> request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout, []). - +%%------------------------------------------------------------------------------ +%% @doc Makes a request using a client already connected. +%% It can receive either a URL or a path. It allows to add the body and specify +%% options, which are the same than for request (without client) functions. +%% @end +%%------------------------------------------------------------------------------ -spec request_client(pid(), string(), method(), headers(), iodata(), pos_timeout(), options()) -> result(). request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout, Options) -> @@ -216,9 +232,7 @@ request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout, Options) -> lists:keystore("Authorization", 1, Hdrs, {"Authorization", Auth}) end, {Path, Headers}; - %request_client(Client, Path, Method, Headers, Body, Timeout, Options); _ -> % its a path - %request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout, Options) {PathOrUrl} end, verify_options(Options), diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 6ae51ff3..9872ea2b 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -322,44 +322,6 @@ send_body_part(State = #client_state{socket = Socket, ssl = Ssl, %% handles the proxy connection. %% @end %%------------------------------------------------------------------------------ -%send_request(#client_state{attempts = 0} = State) -> - % Don't try again if the number of allowed attempts is 0. -% {{error, connection_closed}, State}; -%we need a socket. -%send_request(#client_state{socket = undefined} = State) -> % MOVED TO CONNECT SOCKET!! -% {Host, Port, Ssl} = request_first_destination(State), -% Timeout = State#client_state.connect_timeout, -% ConnectOptions0 = State#client_state.connect_options, -% ConnectOptions = case (not lists:member(inet, ConnectOptions0)) andalso -% (not lists:member(inet6, ConnectOptions0)) andalso -% is_ipv6_host(Host) of -% true -> -% [inet6 | ConnectOptions0]; -% false -> -% ConnectOptions0 -% end, -% SocketOptions = [binary, {packet, http}, {active, false} | ConnectOptions], -% Reply = try lhttpc_sock:connect(Host, Port, SocketOptions, Timeout, Ssl) of -% {ok, Socket} -> -% send_request(State#client_state{socket = Socket}); -% {error, etimedout} -> -% % TCP stack decided to give up -% {error, connect_timeout}; -% {error, timeout} -> -% {error, connect_timeout}; -% {error, 'record overflow'} -> -% {error, ssl_error}; -% {error, _} = Error -> -% Error -% catch -% exit:{{{badmatch, {error, {asn1, _}}}, _}, _} -> -% {error, ssl_decode_error}; -% Type:Error -> -% error_logger:error_msg("Socket connection error: ~p ~p, ~p", -% [Type, Error, erlang:get_stacktrace()]), -% {error, connection_error} -% end, -% {Reply, State}; send_request(#client_state{proxy = #lhttpc_url{}, proxy_setup = false, host = DestHost, port = Port, socket = Socket} = State) -> %% use a proxy. @@ -1013,39 +975,6 @@ connect_socket(State = #client_state{attempts = 0}) -> connect_socket(State = #client_state{init_options = Options, pool = Pool}) -> case Pool of undefined -> - %% TODO open new socket here - %{Host, Port, Ssl} = request_first_destination(State), - %Timeout = State#client_state.connect_timeout, - %ConnectOptions0 = State#client_state.connect_options, - %ConnectOptions = case (not lists:member(inet, ConnectOptions0)) andalso - % (not lists:member(inet6, ConnectOptions0)) andalso - % is_ipv6_host(Host) of - % true -> - % [inet6 | ConnectOptions0]; - % false -> - % ConnectOptions0 - % end, - % SocketOptions = [binary, {packet, http}, {active, false} | ConnectOptions], - % try lhttpc_sock:connect(Host, Port, SocketOptions, Timeout, Ssl) of - % {ok, Socket} -> - % {ok, State#client_state{socket = Socket}}; - % {error, etimedout} -> - % % TCP stack decided to give up - % {error, connect_timeout}; - % {error, timeout} -> - % {error, connect_timeout}; - % {error, 'record overflow'} -> - % {error, ssl_error}; - % {error, _} = Error -> - % Error - % catch - % exit:{{{badmatch, {error, {asn1, _}}}, _}, _} -> - % {error, ssl_decode_error}; - % Type:Error -> - % error_logger:error_msg("Socket connection error: ~p ~p, ~p", - % [Type, Error, erlang:get_stacktrace()]), - % {error, connection_error} - % end; new_socket(State); _ -> {Host, Port, Ssl} = request_first_destination(State), diff --git a/src/lhttpc_sup.erl b/src/lhttpc_sup.erl index d1e58bb3..36acdfe2 100644 --- a/src/lhttpc_sup.erl +++ b/src/lhttpc_sup.erl @@ -61,8 +61,4 @@ start_link() -> %% @hidden %%------------------------------------------------------------------------------ init(_) -> -% LHTTPCManager = {lhttpc_manager, {lhttpc_manager, start_link, -% [[{name, lhttpc_manager}]]}, -% permanent, 10000, worker, [lhttpc_manager]}, -% {ok, {{one_for_one, 10, 1}, [LHTTPCManager]}}. {ok, {{one_for_one, 10, 1}, []}}. From 6739c3fb6384fc984dfbec7251e7c40b3dabbd8b Mon Sep 17 00:00:00 2001 From: Lastres Date: Tue, 22 Jan 2013 12:12:51 +0000 Subject: [PATCH 04/49] Fix error to allow to specify only a patch when calling request_client. Before it used to crash due to a call to parse_url. --- src/lhttpc.erl | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/lhttpc.erl b/src/lhttpc.erl index 8a305a14..21ecc6ea 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -215,33 +215,31 @@ request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout) -> pos_timeout(), options()) -> result(). request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout, Options) -> {FinalPath, FinalHeaders} = - case lhttpc_lib:parse_url(PathOrUrl) of - #lhttpc_url{ %its an URL - host = _Host, - port = _Port, - path = Path, - is_ssl = _Ssl, - user = User, - password = Passwd - } -> % @TODO: check that the host is the right one. - Headers = case User of - "" -> - Hdrs; - _ -> - Auth = "Basic " ++ binary_to_list(base64:encode(User ++ ":" ++ Passwd)), - lists:keystore("Authorization", 1, Hdrs, {"Authorization", Auth}) - end, - {Path, Headers}; - _ -> % its a path - {PathOrUrl} - end, + try #lhttpc_url{ host = _Host, %its an URL + port = _Port, + path = Path, + is_ssl = _Ssl, + user = User, + password = Passwd} = lhttpc_lib:parse_url(PathOrUrl), + Headers = case User of + "" -> + Hdrs; + _ -> + Auth = "Basic " ++ binary_to_list(base64:encode(User ++ ":" ++ Passwd)), + lists:keystore("Authorization", 1, Hdrs, {"Authorization", Auth}) + end, + {Path, Headers} + catch %if parse_url crashes we assume it is a path. + _:_ -> + {PathOrUrl, Hdrs} + end, verify_options(Options), try - Reply = lhttpc_client:request(Client, FinalPath, Method, FinalHeaders, Body, Options, Timeout), - Reply + Reply = lhttpc_client:request(Client, FinalPath, Method, FinalHeaders, Body, Options, Timeout), + Reply catch - exit:{timeout, _} -> - {error, timeout} + exit:{timeout, _} -> + {error, timeout} end. %%------------------------------------------------------------------------------ From 7246e43ac0af08b9f3c5f34b433a479e1ff32618 Mon Sep 17 00:00:00 2001 From: Lastres Date: Tue, 22 Jan 2013 13:29:46 +0000 Subject: [PATCH 05/49] Fix client reconnect problem when server returns headers to close connection. It was not able to reconect once the server replied with a connection close header. Now it reconnects to the same host specified when the user starts the client using lhttp:connect_client() function. --- src/lhttpc_client.erl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 9872ea2b..71ae4e96 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -322,6 +322,14 @@ send_body_part(State = #client_state{socket = Socket, ssl = Ssl, %% handles the proxy connection. %% @end %%------------------------------------------------------------------------------ +send_request(#client_state{socket = undefined} = State) -> +% if we dont get a keep alive from the previous request, the socket is undefined. + case connect_socket(State) of + {ok, NewState} -> + send_request(NewState); + {error, Reason} -> + {{error, Reason}, State} + end; send_request(#client_state{proxy = #lhttpc_url{}, proxy_setup = false, host = DestHost, port = Port, socket = Socket} = State) -> %% use a proxy. From 01686bfa13078904e838d636fcbb59000451b60c Mon Sep 17 00:00:00 2001 From: Lastres Date: Tue, 22 Jan 2013 14:14:29 +0000 Subject: [PATCH 06/49] Change response format from Reply to {ok, Reply}. Made several tests fail and other clients reply with the new format. --- src/lhttpc_client.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 71ae4e96..a636ab69 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -519,7 +519,7 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> {noreply, NewState#client_state{socket = NewSocket}}; _ -> %{reply, Reply, NewState#client_state{socket = NewSocket}} - {Reply, NewState#client_state{socket = NewSocket}} + {{ok, Reply}, NewState#client_state{socket = NewSocket}} end; {error, closed} -> %% TODO does it work for partial uploads? I think should return an error From dfbd50aeae4ac77c4b0cab2bb62b8b90ecea7f2e Mon Sep 17 00:00:00 2001 From: Lastres Date: Tue, 22 Jan 2013 15:01:36 +0000 Subject: [PATCH 07/49] Do not close the socket in terminate if it is undefined. (it was already closed). Fix one test case. --- src/lhttpc_client.erl | 22 ++++++++++++++-------- test/lhttpc_tests.erl | 5 +++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index a636ab69..4b21d7c0 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -283,16 +283,22 @@ handle_info(_Info, State) -> %% @end %%-------------------------------------------------------------------- terminate(_Reason, State = #client_state{pool = Pool, host = Host, ssl = Ssl, socket = Socket, port = Port}) -> - case Pool of - undefined -> - % if we dont have pool we just close the socket. - close_socket(State), - ok; - _ -> - %return the control of the socket to the pool. - lhttpc_manager:client_done(Pool, Host, Port, Ssl, Socket) + case Socket of + undefined -> + ok; + _ -> + case Pool of + undefined -> + close_socket(State), + ok; + _ -> + %return the control of the socket to the pool. + lhttpc_manager:client_done(Pool, Host, Port, Ssl, Socket) + end end. + + %%-------------------------------------------------------------------- %% @private %% @doc diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index 7b6b809a..f482a6b8 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -380,8 +380,9 @@ pre_1_1_server_keep_alive() -> ]), URL = url(Port, "/close"), Body = pid_to_list(self()), - {ok, Response1} = lhttpc:request(URL, get, [], [], 1000), - {ok, Response2} = lhttpc:request(URL, put, [], Body, 1000), + %this test need to use a client now (or a pool). + {ok, Response1} = lhttpc:request(URL, get, [], [], 1000, [{pool_ensure, true}, {pool, pool_name}]), + {ok, Response2} = lhttpc:request(URL, put, [], Body, 1000, [{pool_ensure, true}, {pool, pool_name}]), ?assertEqual({200, "OK"}, status(Response1)), ?assertEqual({200, "OK"}, status(Response2)), ?assertEqual(<>, body(Response1)), From 59a0eef9f8dabe92a96892f980abd468816b0a73 Mon Sep 17 00:00:00 2001 From: Lastres Date: Tue, 22 Jan 2013 16:16:08 +0000 Subject: [PATCH 08/49] Fix some test cases. --- test/lhttpc_manager_tests.erl | 5 +++++ test/lhttpc_tests.erl | 20 +++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/test/lhttpc_manager_tests.erl b/test/lhttpc_manager_tests.erl index 4e24afe0..6f73edef 100644 --- a/test/lhttpc_manager_tests.erl +++ b/test/lhttpc_manager_tests.erl @@ -61,6 +61,7 @@ manager_test_() -> empty_manager() -> LS = socket_server:listen(), + lhttpc:add_pool(lhttpc_manager), link(whereis(lhttpc_manager)), % want to make sure it doesn't crash ?assertEqual(0, lhttpc_manager:connection_count(lhttpc_manager)), @@ -79,6 +80,7 @@ empty_manager() -> one_socket() -> LS = socket_server:listen(), + lhttpc:add_pool(lhttpc_manager), link(whereis(lhttpc_manager)), % want to make sure it doesn't crash ?assertEqual(0, lhttpc_manager:connection_count(lhttpc_manager)), @@ -111,6 +113,7 @@ one_socket() -> connection_timeout() -> LS = socket_server:listen(), + lhttpc:add_pool(lhttpc_manager), link(whereis(lhttpc_manager)), % want to make sure it doesn't crash ok = lhttpc_manager:update_connection_timeout(lhttpc_manager, 3000), erlang:yield(), % make sure lhttpc_manager processes the message @@ -149,6 +152,7 @@ connection_timeout() -> ok. many_sockets() -> + lhttpc:add_pool(lhttpc_manager), link(whereis(lhttpc_manager)), % want to make sure it doesn't crash LS = socket_server:listen(), Client1 = spawn_client(), @@ -279,6 +283,7 @@ many_sockets() -> closed_race_cond() -> LS = socket_server:listen(), + lhttpc:add_pool(lhttpc_manager), link(whereis(lhttpc_manager)), % want to make sure it doesn't crash ?assertEqual(0, lhttpc_manager:connection_count(lhttpc_manager)), diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index f482a6b8..1182009b 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -109,7 +109,7 @@ stop_app(_) -> ok = application:stop(ssl). tcp_test_() -> - {inorder, + {inorder, {setup, fun start_app/0, fun stop_app/1, [ ?_test(simple_get()), ?_test(simple_get_ipv6()), @@ -438,10 +438,10 @@ persistent_connection() -> fun copy_body/5 ]), URL = url(Port, "/persistent"), - {ok, FirstResponse} = lhttpc:request(URL, "GET", [], 1000), + {ok, FirstResponse} = lhttpc:request(URL, "GET", [], [], 1000, [{pool_ensure, true}, {pool, pool_name}]), Headers = [{"KeepAlive", "300"}], % shouldn't be needed - {ok, SecondResponse} = lhttpc:request(URL, "GET", Headers, 1000), - {ok, ThirdResponse} = lhttpc:request(URL, "POST", [], 1000), + {ok, SecondResponse} = lhttpc:request(URL, "GET", Headers, [], 1000, [{pool_ensure, true}, {pool, pool_name}]), + {ok, ThirdResponse} = lhttpc:request(URL, "POST", [], [], 1000, [{pool_ensure, true}, {pool, pool_name}]), ?assertEqual({200, "OK"}, status(FirstResponse)), ?assertEqual(<>, body(FirstResponse)), ?assertEqual({200, "OK"}, status(SecondResponse)), @@ -457,6 +457,7 @@ request_timeout() -> connection_timeout() -> Port = start(gen_tcp, [fun simple_response/5, fun simple_response/5]), URL = url(Port, "/close_conn"), + lhttpc:add_pool(lhttpc_manager), lhttpc_manager:update_connection_timeout(lhttpc_manager, 50), % very short keep alive {ok, Response} = lhttpc:request(URL, get, [], 100), ?assertEqual({200, "OK"}, status(Response)), @@ -469,16 +470,17 @@ connection_timeout() -> suspended_manager() -> Port = start(gen_tcp, [fun simple_response/5, fun simple_response/5]), URL = url(Port, "/persistent"), - {ok, FirstResponse} = lhttpc:request(URL, get, [], 50), + lhttpc:add_pool(lhttpc_manager), + {ok, FirstResponse} = lhttpc:request(URL, get, [], [], 50, [{pool, lhttpc_manager}]), ?assertEqual({200, "OK"}, status(FirstResponse)), ?assertEqual(<>, body(FirstResponse)), Pid = whereis(lhttpc_manager), true = erlang:suspend_process(Pid), - ?assertEqual({error, timeout}, lhttpc:request(URL, get, [], 50)), + ?assertEqual({error, timeout}, lhttpc:request(URL, get, [], [], 50, [{pool, lhttpc_manager}])), true = erlang:resume_process(Pid), ?assertEqual(1, lhttpc_manager:connection_count(lhttpc_manager, {"localhost", Port, false})), - {ok, SecondResponse} = lhttpc:request(URL, get, [], 50), + {ok, SecondResponse} = lhttpc:request(URL, get, [], [], 50, [{pool, lhttpc_manager}]), ?assertEqual({200, "OK"}, status(SecondResponse)), ?assertEqual(<>, body(SecondResponse)). @@ -559,7 +561,7 @@ partial_upload_chunked() -> ?assertEqual(<>, body(Response1)), ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response1))), - ?assertEqual(element(2, Trailer), + ?assertEqual(element(2, Trailer), lhttpc_lib:header_value("x-test-orig-trailer-1", headers(Response1))), % Make sure it works with no body part in the original request as well Headers = [{"Transfer-Encoding", "chunked"}], @@ -572,7 +574,7 @@ partial_upload_chunked() -> ?assertEqual(<>, body(Response2)), ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response2))), - ?assertEqual(element(2, Trailer), + ?assertEqual(element(2, Trailer), lhttpc_lib:header_value("x-test-orig-trailer-1", headers(Response2))). partial_upload_chunked_no_trailer() -> From d48be4fdef15d86526c04912fba31abecd5d7e71 Mon Sep 17 00:00:00 2001 From: Diana Corbacho Date: Wed, 23 Jan 2013 14:18:24 +0000 Subject: [PATCH 09/49] Fix connect functionality and attempts to reconnect --- src/lhttpc.erl | 16 +------- src/lhttpc_client.erl | 75 +++++++++++++++++++----------------- src/lhttpc_manager.erl | 38 +++++++++++++----- test/lhttpc_client_tests.erl | 38 ++++++++++++++++++ 4 files changed, 107 insertions(+), 60 deletions(-) create mode 100644 test/lhttpc_client_tests.erl diff --git a/src/lhttpc.erl b/src/lhttpc.erl index 21ecc6ea..fd477363 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -133,21 +133,7 @@ add_pool(Name, ConnTimeout) when is_atom(Name), -spec add_pool(atom(), non_neg_integer(), poolsize()) -> {ok, pid()} | {error, term()}. add_pool(Name, ConnTimeout, PoolSize) -> - ChildSpec = {Name, - {lhttpc_manager, start_link, [[{name, Name}, - {connection_timeout, ConnTimeout}, - {pool_size, PoolSize}]]}, - permanent, 10000, worker, [lhttpc_manager]}, - case supervisor:start_child(lhttpc_sup, ChildSpec) of - {error, {already_started, _Pid}} -> - {error, already_exists}; - {error, Error} -> - {error, Error}; - {ok, Pid} -> - {ok, Pid}; - {ok, Pid, _Info} -> - {ok, Pid} - end. + lhttpc_manager:new_pool(Name, ConnTimeout, PoolSize). %%------------------------------------------------------------------------------ %% @doc Delete a pool diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 4b21d7c0..04231cb3 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -69,7 +69,7 @@ socket, connect_timeout = infinity :: timeout(), connect_options = [] :: [any()], - attempts :: integer(), + attempts = 1 :: integer(), partial_upload = false :: boolean(), chunked_upload = false :: boolean(), upload_window :: non_neg_integer() | infinity, @@ -126,8 +126,6 @@ request(Client, Path, Method, Hdrs, Body, Options, Timeout) -> %%% gen_server callbacks %%%=================================================================== init({Destination, Options}) -> - %{Host, Port, Ssl, Options - %% TODO use pool if configured. Otherwise start socket alone Pool = proplists:get_value(pool, Options), State = case Destination of {Host, Port, Ssl} -> @@ -139,10 +137,7 @@ init({Destination, Options}) -> URL -> #lhttpc_url{host = Host, port = Port, - path = _Path, - is_ssl = Ssl, - user = _User, - password = _Passwd + is_ssl = Ssl } = lhttpc_lib:parse_url(URL), #client_state{host = Host, port = Port, @@ -150,12 +145,11 @@ init({Destination, Options}) -> pool = Pool, init_options = Options} end, - %Pool = proplists:get_value(pool, Options), %% Get a socket for the pool or exit case connect_socket(State) of {ok, NewState} -> {ok, NewState}; - {error, Reason} -> + {{error, Reason}, _} -> {stop, Reason} end. @@ -333,8 +327,8 @@ send_request(#client_state{socket = undefined} = State) -> case connect_socket(State) of {ok, NewState} -> send_request(NewState); - {error, Reason} -> - {{error, Reason}, State} + {{error, Reason}, NewState} -> + {{error, Reason}, NewState} end; send_request(#client_state{proxy = #lhttpc_url{}, proxy_setup = false, host = DestHost, port = Port, socket = Socket} = State) -> @@ -378,21 +372,19 @@ send_request(#client_state{socket = Socket, ssl = Ssl, request = Request} = Stat case lhttpc_sock:send(Socket, Request, Ssl) of ok -> if - % {partial_upload, WindowSize} is used. + %% {partial_upload, WindowSize} is used. State#client_state.partial_upload -> {{ok, partial_upload}, State#client_state{attempts = 1}}; not State#client_state.partial_upload -> read_response(State) end; {error, closed} -> close_socket(State), - NewState = State#client_state{ - socket = undefined, - attempts = State#client_state.attempts - 1}, - case connect_socket(NewState) of - {error, connection_closed} -> + case connect_socket(State#client_state{ + socket = undefined}) of + {{error, connection_closed}, NewState} -> {{error, connection_closed}, NewState}; - NewState2 -> - send_request(NewState2) + {ok, NewState} -> + send_request(NewState) end; {error, _Reason} -> close_socket(State), @@ -985,21 +977,34 @@ is_ipv6_host(Host) -> %%------------------------------------------------------------------------------ connect_socket(State = #client_state{attempts = 0}) -> % Don't try again if the number of allowed attempts is 0. - {{error, connection_closed}, State}; -connect_socket(State = #client_state{init_options = Options, pool = Pool}) -> - case Pool of - undefined -> + {{error, connection_closed}, State#client_state{attempts = 1}}; +connect_socket(State = #client_state{pool = Pool, + attempts = Attempts}) -> + Connection = case Pool of + undefined -> + new_socket(State); + _ -> + connect_pool(State) + end, + case Connection of + {ok, Socket} -> + {ok, State#client_state{socket = Socket}}; + {error, unknown_pool} = Error -> + {Error, State}; + {error, _} -> + connect_socket(State#client_state{attempts = Attempts - 1}) + end. + +-spec connect_pool(#client_state{}) -> {ok, socket()} | {error, atom()}. +connect_pool(State = #client_state{init_options = Options, + pool = Pool}) -> + {Host, Port, Ssl} = request_first_destination(State), + case lhttpc_manager:ensure_call(Pool, self(), Host, Port, Ssl, Options) of + {ok, undefined} -> + %% ensure_call does not open a socket if the pool doesnt have one new_socket(State); - _ -> - {Host, Port, Ssl} = request_first_destination(State), - {ok, Socket} = lhttpc_manager:ensure_call(Pool, self(), Host, Port, Ssl, Options), - %ensure_call does not open a socket if the pool doesnt have one already!!! - case Socket of - undefined -> - new_socket(State); - Socket -> - {ok, State#client_state{socket = Socket}} - end + Reply -> + Reply end. %%------------------------------------------------------------------------------ @@ -1022,9 +1027,9 @@ new_socket(State) -> SocketOptions = [binary, {packet, http}, {active, false} | ConnectOptions], try lhttpc_sock:connect(Host, Port, SocketOptions, Timeout, Ssl) of {ok, Socket} -> - {ok, State#client_state{socket = Socket}}; + {ok, Socket}; {error, etimedout} -> - % TCP stack decided to give up + %% TCP stack decided to give up {error, connect_timeout}; {error, timeout} -> {error, connect_timeout}; diff --git a/src/lhttpc_manager.erl b/src/lhttpc_manager.erl index aa8d05f3..8e02af4d 100644 --- a/src/lhttpc_manager.erl +++ b/src/lhttpc_manager.erl @@ -41,6 +41,7 @@ %% Exported functions -export([start_link/0, start_link/1, + new_pool/3, client_count/1, connection_count/1, connection_count/2, update_connection_timeout/2, @@ -205,18 +206,18 @@ start_link(Options0) -> %% @end %%------------------------------------------------------------------------------ -spec ensure_call(pool_id(), pid(), host(), port_num(), boolean(), options()) -> - socket() | 'no_socket'. + {ok, socket()} | {error, 'no_socket'} | {error, term()}. ensure_call(Pool, Pid, Host, Port, Ssl, Options) -> SocketRequest = {socket, Pid, Host, Port, Ssl}, try gen_server:call(Pool, SocketRequest, infinity) of {ok, S} -> %% Re-using HTTP/1.1 connections {ok, S}; - no_socket -> + {error, no_socket} -> %% Opening a new HTTP/1.1 connection {ok, undefined} catch - exit:{noproc, Reason} -> + exit:{noproc, _Reason} -> case proplists:get_value(pool_ensure, Options, false) of true -> {ok, DefaultTimeout} = application:get_env( @@ -231,20 +232,37 @@ ensure_call(Pool, Pid, Host, Port, Ssl, Options) -> PoolMaxSize = proplists:get_value(pool_max_size, Options, DefaultMaxPool), - case lhttpc:add_pool(Pool, ConnTimeout, PoolMaxSize) of + case new_pool(Pool, ConnTimeout, PoolMaxSize) of {ok, _Pid} -> ensure_call(Pool, Pid, Host, Port, Ssl, Options); - _ -> - %% Failed to create pool, exit as expected - %exit({noproc, Reason}) - exit({noproc, Reason}) + Error -> + Error end; false -> %% No dynamic pool creation, exit as expected - exit({noproc, Reason}) + {error, unknown_pool} end end. +-spec new_pool(atom(), non_neg_integer(), poolsize()) -> + {ok, pid()} | {error, term()}. +new_pool(Pool, ConnTimeout, PoolSize) -> + ChildSpec = {Pool, + {lhttpc_manager, start_link, [[{name, Pool}, + {connection_timeout, ConnTimeout}, + {pool_size, PoolSize}]]}, + permanent, 10000, worker, [lhttpc_manager]}, + case supervisor:start_child(lhttpc_sup, ChildSpec) of + {error, {already_started, _Pid}} -> + {error, already_exists}; + {error, Error} -> + {error, Error}; + {ok, Pid} -> + {ok, Pid}; + {ok, Pid, _Info} -> + {ok, Pid} + end. + %%------------------------------------------------------------------------------ %% @doc A client has finished one request and returns the socket to the pool, %% which can be new or not. @@ -305,7 +323,7 @@ handle_call({socket, Pid, Host, Port, Ssl}, {Pid, _Ref} = From, State) -> Queues2 = add_to_queue(Dest, From, Queues), {noreply, State2#httpc_man{queues = Queues2}}; false -> - {reply, no_socket, monitor_client(Dest, From, State2)} + {reply, {error, no_socket}, monitor_client(Dest, From, State2)} end end; handle_call(dump_settings, _, State) -> diff --git a/test/lhttpc_client_tests.erl b/test/lhttpc_client_tests.erl new file mode 100644 index 00000000..80da0c75 --- /dev/null +++ b/test/lhttpc_client_tests.erl @@ -0,0 +1,38 @@ +%%%============================================================================= +%%% @copyright (C) 1999-2012, Erlang Solutions Ltd +%%% @author Diana Corbacho +%%% @doc Unit tests for lhttpc_client +%%% @end +%%%============================================================================= +-module(lhttpc_client_tests). +-copyright("2012, Erlang Solutions Ltd."). + +-include_lib("eunit/include/eunit.hrl"). + +-compile(export_all). + +fail_connect_test() -> + ?assertEqual({error, connection_closed}, + lhttpc_client:start({{"localhost", 8080, false}, []}, [])). + +fail_connect_pool_test() -> + ?assertEqual({error, unknown_pool}, + lhttpc_client:start({{"localhost", 8080, false}, + [{pool, my_test_pool}]}, [])). + +success_connect_pool_test_() -> + {setup, + fun() -> + ok = application:start(ssl), + ok = application:start(lhttpc) + end, + fun(_) -> + application:stop(lhttpc) + end, + fun() -> + ?assertMatch({error, connection_closed}, + lhttpc_client:start({{"localhost", 8080, false}, + [{pool, my_test_pool}, + {pool_ensure, true}]}, [])) + end + }. From b0434e74c7701bed66440d3640b379f84056942c Mon Sep 17 00:00:00 2001 From: Diana Corbacho Date: Wed, 23 Jan 2013 14:19:25 +0000 Subject: [PATCH 10/49] Fix typo error in call parameters --- src/lhttpc_client.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 04231cb3..1a0f489b 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -233,7 +233,7 @@ handle_call({send_body_part, Data}, From, State = #client_state{partial_upload = %We send the parts to the specified Pid. handle_call({get_body_part, _Options}, From, State=#client_state{partial_download = true, download_info = {_Vsn, Hdrs}}) -> - gen_server:reply(ok, From), + gen_server:reply(From, ok), read_partial_body(State, body_type(Hdrs)), {noreply, State}. From 5c1a452972d10f35e0f4d920f81d6556f45a797a Mon Sep 17 00:00:00 2001 From: Diana Corbacho Date: Wed, 23 Jan 2013 15:19:00 +0000 Subject: [PATCH 11/49] Refactor test code to reuse in other suites --- test/lhttpc_client_tests.erl | 31 +- test/lhttpc_tests.erl | 544 +++++++---------------------------- test/webserver_utils.erl | 363 +++++++++++++++++++++++ 3 files changed, 479 insertions(+), 459 deletions(-) create mode 100644 test/webserver_utils.erl diff --git a/test/lhttpc_client_tests.erl b/test/lhttpc_client_tests.erl index 80da0c75..f7f50fe2 100644 --- a/test/lhttpc_client_tests.erl +++ b/test/lhttpc_client_tests.erl @@ -15,24 +15,27 @@ fail_connect_test() -> ?assertEqual({error, connection_closed}, lhttpc_client:start({{"localhost", 8080, false}, []}, [])). -fail_connect_pool_test() -> - ?assertEqual({error, unknown_pool}, - lhttpc_client:start({{"localhost", 8080, false}, - [{pool, my_test_pool}]}, [])). - -success_connect_pool_test_() -> - {setup, +fail_connect_pool_test_() -> + {foreach, fun() -> ok = application:start(ssl), ok = application:start(lhttpc) end, fun(_) -> - application:stop(lhttpc) + application:stop(lhttpc), + application:stop(ssl) end, - fun() -> - ?assertMatch({error, connection_closed}, - lhttpc_client:start({{"localhost", 8080, false}, - [{pool, my_test_pool}, - {pool_ensure, true}]}, [])) - end + [{"Fail to connect on ensure pool", + fun() -> + ?assertMatch({error, connection_closed}, + lhttpc_client:start({{"localhost", 8080, false}, + [{pool, my_test_pool}, + {pool_ensure, true}]}, [])) + end}, + {"Fail to connect - no pool", + fun() -> + ?assertEqual({error, unknown_pool}, + lhttpc_client:start({{"localhost", 8080, false}, + [{pool, my_test_pool}]}, [])) + end}] }. diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index 1182009b..c43cc406 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -32,65 +32,6 @@ -include_lib("eunit/include/eunit.hrl"). --define(DEFAULT_STRING, "Great success!"). --define(LONG_BODY_PART, - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - "This is a relatively long body, that we send to the client... " - ). - test_no(N, Tests) -> setelement(2, Tests, setelement(4, element(2, Tests), @@ -194,7 +135,7 @@ simple_get_ipv6() -> simple("GET", inet6). empty_get() -> - Port = start(gen_tcp, [fun empty_body/5]), + Port = start(gen_tcp, [fun webserver_utils:empty_body/5]), URL = url(Port, "/empty"), {ok, Response} = lhttpc:request(URL, "GET", [], 1000), ?assertEqual({200, "OK"}, status(Response)), @@ -203,7 +144,7 @@ empty_get() -> basic_auth() -> User = "foo", Passwd = "bar", - Port = start(gen_tcp, [basic_auth_responder(User, Passwd)]), + Port = start(gen_tcp, [webserver_utils:basic_auth_responder(User, Passwd)]), URL = url(Port, "/empty", User, Passwd), {ok, Response} = lhttpc:request(URL, "GET", [], 1000), ?assertEqual({200, "OK"}, status(Response)), @@ -212,7 +153,7 @@ basic_auth() -> missing_basic_auth() -> User = "foo", Passwd = "bar", - Port = start(gen_tcp, [basic_auth_responder(User, Passwd)]), + Port = start(gen_tcp, [webserver_utils:basic_auth_responder(User, Passwd)]), URL = url(Port, "/empty"), {ok, Response} = lhttpc:request(URL, "GET", [], 1000), ?assertEqual({401, "Unauthorized"}, status(Response)), @@ -221,50 +162,50 @@ missing_basic_auth() -> wrong_basic_auth() -> User = "foo", Passwd = "bar", - Port = start(gen_tcp, [basic_auth_responder(User, Passwd)]), + Port = start(gen_tcp, [webserver_utils:basic_auth_responder(User, Passwd)]), URL = url(Port, "/empty", User, "wrong_password"), {ok, Response} = lhttpc:request(URL, "GET", [], 1000), ?assertEqual({401, "Unauthorized"}, status(Response)), ?assertEqual(<<"wrong_auth">>, body(Response)). get_with_mandatory_hdrs() -> - Port = start(gen_tcp, [fun simple_response/5]), + Port = start(gen_tcp, [fun webserver_utils:simple_response/5]), URL = url(Port, "/host"), - Body = <>, + Body = list_to_binary(webserver_utils:default_string()), Hdrs = [ {"content-length", integer_to_list(size(Body))}, {"host", "localhost"} ], {ok, Response} = lhttpc:request(URL, "POST", Hdrs, Body, 1000), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)). + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). get_with_mandatory_hdrs_by_atoms() -> - Port = start(gen_tcp, [fun simple_response/5]), + Port = start(gen_tcp, [fun webserver_utils:simple_response/5]), URL = url(Port, "/host"), - Body = <>, + Body = list_to_binary(webserver_utils:default_string()), Hdrs = [ {'Content-Length', integer_to_list(size(Body))}, {'Host', "localhost"} ], {ok, Response} = lhttpc:request(URL, "POST", Hdrs, Body, 1000), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)). + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). get_with_mandatory_hdrs_by_binaries() -> - Port = start(gen_tcp, [fun simple_response/5]), + Port = start(gen_tcp, [fun webserver_utils:simple_response/5]), URL = url(Port, "/host"), - Body = <>, + Body = list_to_binary(webserver_utils:default_string()), Hdrs = [ {<<"Content-Length">>, integer_to_list(size(Body))}, {<<"Host">>, "localhost"} ], {ok, Response} = lhttpc:request(URL, "POST", Hdrs, Body, 1000), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)). + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). get_with_connect_options() -> - Port = start(gen_tcp, [fun empty_body/5]), + Port = start(gen_tcp, [fun webserver_utils:empty_body/5]), URL = url(Port, "/empty"), Options = [{connect_options, [{ip, {127, 0, 0, 1}}, {port, 0}]}], {ok, Response} = lhttpc:request(URL, "GET", [], [], 1000, Options), @@ -272,24 +213,24 @@ get_with_connect_options() -> ?assertEqual(<<>>, body(Response)). no_content_length() -> - Port = start(gen_tcp, [fun no_content_length/5]), + Port = start(gen_tcp, [fun webserver_utils:no_content_length/5]), URL = url(Port, "/no_cl"), {ok, Response} = lhttpc:request(URL, "GET", [], 1000), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)). + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). no_content_length_1_0() -> - Port = start(gen_tcp, [fun no_content_length_1_0/5]), + Port = start(gen_tcp, [fun webserver_utils:no_content_length_1_0/5]), URL = url(Port, "/no_cl"), {ok, Response} = lhttpc:request(URL, "GET", [], 1000), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)). + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). %% Check the header value is trimming spaces on header values %% which can cause crash in lhttpc_client:body_type when Content-Length %% is converted from list to integer trailing_space_header() -> - Port = start(gen_tcp, [fun trailing_space_header/5]), + Port = start(gen_tcp, [fun webserver_utils:trailing_space_header/5]), URL = url(Port, "/no_cl"), {ok, Response} = lhttpc:request(URL, "GET", [], 1000), Headers = headers(Response), @@ -297,65 +238,65 @@ trailing_space_header() -> ?assertEqual("14", ContentLength). get_not_modified() -> - Port = start(gen_tcp, [fun not_modified_response/5]), + Port = start(gen_tcp, [fun webserver_utils:not_modified_response/5]), URL = url(Port, "/not_modified"), {ok, Response} = lhttpc:request(URL, "GET", [], [], 1000), ?assertEqual({304, "Not Modified"}, status(Response)), ?assertEqual(<<>>, body(Response)). simple_head() -> - Port = start(gen_tcp, [fun head_response/5]), + Port = start(gen_tcp, [fun webserver_utils:head_response/5]), URL = url(Port, "/HEAD"), {ok, Response} = lhttpc:request(URL, "HEAD", [], 1000), ?assertEqual({200, "OK"}, status(Response)), ?assertEqual(<<>>, body(Response)). simple_head_atom() -> - Port = start(gen_tcp, [fun head_response/5]), + Port = start(gen_tcp, [fun webserver_utils:head_response/5]), URL = url(Port, "/head"), {ok, Response} = lhttpc:request(URL, head, [], 1000), ?assertEqual({200, "OK"}, status(Response)), ?assertEqual(<<>>, body(Response)). delete_no_content() -> - Port = start(gen_tcp, [fun no_content_response/5]), + Port = start(gen_tcp, [fun webserver_utils:no_content_response/5]), URL = url(Port, "/delete_no_content"), {ok, Response} = lhttpc:request(URL, delete, [], 1000), ?assertEqual({204, "OK"}, status(Response)), ?assertEqual(<<>>, body(Response)). delete_content() -> - Port = start(gen_tcp, [fun simple_response/5]), + Port = start(gen_tcp, [fun webserver_utils:simple_response/5]), URL = url(Port, "/delete_content"), {ok, Response} = lhttpc:request(URL, "DELETE", [], 1000), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)). + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). options_no_content() -> - Port = start(gen_tcp, [fun head_response/5]), + Port = start(gen_tcp, [fun webserver_utils:head_response/5]), URL = url(Port, "/options_no_content"), {ok, Response} = lhttpc:request(URL, "OPTIONS", [], 1000), ?assertEqual({200, "OK"}, status(Response)), ?assertEqual(<<>>, body(Response)). options_content() -> - Port = start(gen_tcp, [fun simple_response/5]), + Port = start(gen_tcp, [fun webserver_utils:simple_response/5]), URL = url(Port, "/options_content"), {ok, Response} = lhttpc:request(URL, "OPTIONS", [], 1000), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)). + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). server_connection_close() -> - Port = start(gen_tcp, [fun respond_and_close/5]), + Port = start(gen_tcp, [fun webserver_utils:respond_and_close/5]), URL = url(Port, "/close"), Body = pid_to_list(self()), {ok, Response} = lhttpc:request(URL, "PUT", [], Body, 1000), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)), receive closed -> ok end. client_connection_close() -> - Port = start(gen_tcp, [fun respond_and_wait/5]), + Port = start(gen_tcp, [fun webserver_utils:respond_and_wait/5]), URL = url(Port, "/close"), Body = pid_to_list(self()), Hdrs = [{"Connection", "close"}], @@ -364,7 +305,7 @@ client_connection_close() -> receive closed -> ok end. pre_1_1_server_connection() -> - Port = start(gen_tcp, [fun pre_1_1_server/5]), + Port = start(gen_tcp, [fun webserver_utils:pre_1_1_server/5]), URL = url(Port, "/close"), Body = pid_to_list(self()), {ok, _} = lhttpc:request(URL, put, [], Body, 1000), @@ -375,8 +316,8 @@ pre_1_1_server_connection() -> pre_1_1_server_keep_alive() -> Port = start(gen_tcp, [ - fun pre_1_1_server_keep_alive/5, - fun pre_1_1_server/5 + fun webserver_utils:pre_1_1_server_keep_alive/5, + fun webserver_utils:pre_1_1_server/5 ]), URL = url(Port, "/close"), Body = pid_to_list(self()), @@ -385,8 +326,8 @@ pre_1_1_server_keep_alive() -> {ok, Response2} = lhttpc:request(URL, put, [], Body, 1000, [{pool_ensure, true}, {pool, pool_name}]), ?assertEqual({200, "OK"}, status(Response1)), ?assertEqual({200, "OK"}, status(Response2)), - ?assertEqual(<>, body(Response1)), - ?assertEqual(<>, body(Response2)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response1)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response2)), % Wait for the server to see that socket has been closed. % The socket should be closed by us since the server responded with a % 1.0 version, and not the Connection: keep-alive header. @@ -397,7 +338,7 @@ simple_put() -> simple("PUT"). post() -> - Port = start(gen_tcp, [fun copy_body/5]), + Port = start(gen_tcp, [fun webserver_utils:copy_body/5]), URL = url(Port, "/post"), {X, Y, Z} = now(), Body = [ @@ -413,7 +354,7 @@ post() -> ?assertEqual(iolist_to_binary(Body), body(Response)). post_100_continue() -> - Port = start(gen_tcp, [fun copy_body_100_continue/5]), + Port = start(gen_tcp, [fun webserver_utils:copy_body_100_continue/5]), URL = url(Port, "/post"), {X, Y, Z} = now(), Body = [ @@ -433,9 +374,9 @@ bad_url() -> persistent_connection() -> Port = start(gen_tcp, [ - fun simple_response/5, - fun simple_response/5, - fun copy_body/5 + fun webserver_utils:simple_response/5, + fun webserver_utils:simple_response/5, + fun webserver_utils:copy_body/5 ]), URL = url(Port, "/persistent"), {ok, FirstResponse} = lhttpc:request(URL, "GET", [], [], 1000, [{pool_ensure, true}, {pool, pool_name}]), @@ -443,37 +384,37 @@ persistent_connection() -> {ok, SecondResponse} = lhttpc:request(URL, "GET", Headers, [], 1000, [{pool_ensure, true}, {pool, pool_name}]), {ok, ThirdResponse} = lhttpc:request(URL, "POST", [], [], 1000, [{pool_ensure, true}, {pool, pool_name}]), ?assertEqual({200, "OK"}, status(FirstResponse)), - ?assertEqual(<>, body(FirstResponse)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(FirstResponse)), ?assertEqual({200, "OK"}, status(SecondResponse)), - ?assertEqual(<>, body(SecondResponse)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(SecondResponse)), ?assertEqual({200, "OK"}, status(ThirdResponse)), ?assertEqual(<<>>, body(ThirdResponse)). request_timeout() -> - Port = start(gen_tcp, [fun very_slow_response/5]), + Port = start(gen_tcp, [fun webserver_utils:very_slow_response/5]), URL = url(Port, "/slow"), ?assertEqual({error, timeout}, lhttpc:request(URL, get, [], 50)). connection_timeout() -> - Port = start(gen_tcp, [fun simple_response/5, fun simple_response/5]), + Port = start(gen_tcp, [fun webserver_utils:simple_response/5, fun webserver_utils:simple_response/5]), URL = url(Port, "/close_conn"), lhttpc:add_pool(lhttpc_manager), lhttpc_manager:update_connection_timeout(lhttpc_manager, 50), % very short keep alive {ok, Response} = lhttpc:request(URL, get, [], 100), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)), timer:sleep(100), ?assertEqual(0, lhttpc_manager:connection_count(lhttpc_manager, {"localhost", Port, false})), lhttpc_manager:update_connection_timeout(lhttpc_manager, 300000). % set back suspended_manager() -> - Port = start(gen_tcp, [fun simple_response/5, fun simple_response/5]), + Port = start(gen_tcp, [fun webserver_utils:simple_response/5, fun webserver_utils:simple_response/5]), URL = url(Port, "/persistent"), lhttpc:add_pool(lhttpc_manager), {ok, FirstResponse} = lhttpc:request(URL, get, [], [], 50, [{pool, lhttpc_manager}]), ?assertEqual({200, "OK"}, status(FirstResponse)), - ?assertEqual(<>, body(FirstResponse)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(FirstResponse)), Pid = whereis(lhttpc_manager), true = erlang:suspend_process(Pid), ?assertEqual({error, timeout}, lhttpc:request(URL, get, [], [], 50, [{pool, lhttpc_manager}])), @@ -482,14 +423,14 @@ suspended_manager() -> lhttpc_manager:connection_count(lhttpc_manager, {"localhost", Port, false})), {ok, SecondResponse} = lhttpc:request(URL, get, [], [], 50, [{pool, lhttpc_manager}]), ?assertEqual({200, "OK"}, status(SecondResponse)), - ?assertEqual(<>, body(SecondResponse)). + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(SecondResponse)). chunked_encoding() -> - Port = start(gen_tcp, [fun chunked_response/5, fun chunked_response_t/5]), + Port = start(gen_tcp, [fun webserver_utils:chunked_response/5, fun webserver_utils:chunked_response_t/5]), URL = url(Port, "/chunked"), {ok, FirstResponse} = lhttpc:request(URL, get, [], 50), ?assertEqual({200, "OK"}, status(FirstResponse)), - ?assertEqual(<>, body(FirstResponse)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(FirstResponse)), ?assertEqual("chunked", lhttpc_lib:header_value("transfer-encoding", headers(FirstResponse))), {ok, SecondResponse} = lhttpc:request(URL, get, [], 50), @@ -503,7 +444,7 @@ chunked_encoding() -> headers(SecondResponse))). partial_upload_identity() -> - Port = start(gen_tcp, [fun simple_response/5, fun simple_response/5]), + Port = start(gen_tcp, [fun webserver_utils:simple_response/5, fun webserver_utils:simple_response/5]), URL = url(Port, "/partial_upload"), Body = [<<"This">>, <<" is ">>, <<"chunky">>, <<" stuff!">>], Hdrs = [{"Content-Length", integer_to_list(iolist_size(Body))}], @@ -512,7 +453,7 @@ partial_upload_identity() -> Response1 = lists:foldl(fun upload_parts/2, UploadState1, tl(Body) ++ [http_eob]), ?assertEqual({200, "OK"}, status(Response1)), - ?assertEqual(<>, body(Response1)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response1)), ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response1))), % Make sure it works with no body part in the original request as well @@ -520,12 +461,12 @@ partial_upload_identity() -> Response2 = lists:foldl(fun upload_parts/2, UploadState2, Body ++ [http_eob]), ?assertEqual({200, "OK"}, status(Response2)), - ?assertEqual(<>, body(Response2)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response2)), ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response2))). partial_upload_identity_iolist() -> - Port = start(gen_tcp, [fun simple_response/5, fun simple_response/5]), + Port = start(gen_tcp, [fun webserver_utils:simple_response/5, fun webserver_utils:simple_response/5]), URL = url(Port, "/partial_upload"), Body = ["This", [<<" ">>, $i, $s, [" "]], <<"chunky">>, [<<" stuff!">>]], Hdrs = [{"Content-Length", integer_to_list(iolist_size(Body))}], @@ -534,7 +475,7 @@ partial_upload_identity_iolist() -> Response1 = lists:foldl(fun upload_parts/2, UploadState1, tl(Body) ++ [http_eob]), ?assertEqual({200, "OK"}, status(Response1)), - ?assertEqual(<>, body(Response1)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response1)), ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response1))), % Make sure it works with no body part in the original request as well @@ -542,12 +483,12 @@ partial_upload_identity_iolist() -> Response2 = lists:foldl(fun upload_parts/2, UploadState2, Body ++ [http_eob]), ?assertEqual({200, "OK"}, status(Response2)), - ?assertEqual(<>, body(Response2)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response2)), ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response2))). partial_upload_chunked() -> - Port = start(gen_tcp, [fun chunked_upload/5, fun chunked_upload/5]), + Port = start(gen_tcp, [fun webserver_utils:chunked_upload/5, fun webserver_utils:chunked_upload/5]), URL = url(Port, "/partial_upload_chunked"), Body = ["This", [<<" ">>, $i, $s, [" "]], <<"chunky">>, [<<" stuff!">>]], Options = [{partial_upload, 1}], @@ -558,7 +499,7 @@ partial_upload_chunked() -> [Trailer] ), ?assertEqual({200, "OK"}, status(Response1)), - ?assertEqual(<>, body(Response1)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response1)), ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response1))), ?assertEqual(element(2, Trailer), @@ -571,14 +512,14 @@ partial_upload_chunked() -> [Trailer] ), ?assertEqual({200, "OK"}, status(Response2)), - ?assertEqual(<>, body(Response2)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response2)), ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response2))), ?assertEqual(element(2, Trailer), lhttpc_lib:header_value("x-test-orig-trailer-1", headers(Response2))). partial_upload_chunked_no_trailer() -> - Port = start(gen_tcp, [fun chunked_upload/5]), + Port = start(gen_tcp, [fun webserver_utils:chunked_upload/5]), URL = url(Port, "/partial_upload_chunked_no_trailer"), Body = [<<"This">>, <<" is ">>, <<"chunky">>, <<" stuff!">>], Options = [{partial_upload, 1}], @@ -588,7 +529,7 @@ partial_upload_chunked_no_trailer() -> http_eob ), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)), ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response))). @@ -597,8 +538,15 @@ partial_download_illegal_option() -> lhttpc:request("http://localhost/", get, [], <<>>, 1000, [{partial_download, [{foo, bar}]}])). +long_body_part(Size) -> + list_to_binary( + lists:foldl( + fun(_, Acc) -> + Acc ++ " " ++ webserver_utils:long_body_part() + end, webserver_utils:long_body_part(), lists:seq(1, Size))). + partial_download_identity() -> - Port = start(gen_tcp, [fun large_response/5]), + Port = start(gen_tcp, [fun webserver_utils:large_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ {window_size, 1} @@ -608,10 +556,10 @@ partial_download_identity() -> lhttpc:request(URL, get, [], <<>>, 1000, Options), Body = read_partial_body(Pid), ?assertEqual({200, "OK"}, Status), - ?assertEqual(<>, Body). + ?assertEqual(long_body_part(3), Body). partial_download_infinity_window() -> - Port = start(gen_tcp, [fun large_response/5]), + Port = start(gen_tcp, [fun webserver_utils:large_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ {window_size, infinity} @@ -620,10 +568,10 @@ partial_download_infinity_window() -> {ok, {Status, _, Pid}} = lhttpc:request(URL, get, [], <<>>, 1000, Options), Body = read_partial_body(Pid), ?assertEqual({200, "OK"}, Status), - ?assertEqual(<>, Body). + ?assertEqual(long_body_part(3), Body). partial_download_no_content_length() -> - Port = start(gen_tcp, [fun no_content_length/5]), + Port = start(gen_tcp, [fun webserver_utils:no_content_length/5]), URL = url(Port, "/no_cl"), PartialDownload = [ {window_size, 1} @@ -632,10 +580,10 @@ partial_download_no_content_length() -> {ok, {Status, _, Pid}} = lhttpc:request(URL, get, [], <<>>, 1000, Options), Body = read_partial_body(Pid), ?assertEqual({200, "OK"}, Status), - ?assertEqual(<>, Body). + ?assertEqual(list_to_binary(webserver_utils:default_string()), Body). partial_download_no_content() -> - Port = start(gen_tcp, [fun no_content_response/5]), + Port = start(gen_tcp, [fun webserver_utils:no_content_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ {window_size, 1} @@ -647,7 +595,7 @@ partial_download_no_content() -> ?assertEqual(undefined, Body). limited_partial_download_identity() -> - Port = start(gen_tcp, [fun large_response/5]), + Port = start(gen_tcp, [fun webserver_utils:large_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ {window_size, 1}, @@ -658,24 +606,24 @@ limited_partial_download_identity() -> lhttpc:request(URL, get, [], <<>>, 1000, Options), Body = read_partial_body(Pid, 512), ?assertEqual({200, "OK"}, Status), - ?assertEqual(<>, Body). + ?assertEqual(long_body_part(3), Body). partial_download_chunked() -> - Port = start(gen_tcp, [fun large_chunked_response/5]), + Port = start(gen_tcp, [fun webserver_utils:large_chunked_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ {window_size, 1}, - {part_size, length(?LONG_BODY_PART) * 3} + {part_size, length(webserver_utils:long_body_part()) * 3} ], Options = [{partial_download, PartialDownload}], {ok, {Status, _, Pid}} = lhttpc:request(URL, get, [], <<>>, 1000, Options), Body = read_partial_body(Pid), ?assertEqual({200, "OK"}, Status), - ?assertEqual(<>, Body). + ?assertEqual(long_body_part(3), Body). partial_download_chunked_infinite_part() -> - Port = start(gen_tcp, [fun large_chunked_response/5]), + Port = start(gen_tcp, [fun webserver_utils:large_chunked_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ {window_size, 1}, @@ -686,57 +634,57 @@ partial_download_chunked_infinite_part() -> lhttpc:request(URL, get, [], <<>>, 1000, Options), Body = read_partial_body(Pid), ?assertEqual({200, "OK"}, Status), - ?assertEqual(<>, Body). + ?assertEqual(long_body_part(3), Body). partial_download_smallish_chunks() -> - Port = start(gen_tcp, [fun large_chunked_response/5]), + Port = start(gen_tcp, [fun webserver_utils:large_chunked_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ {window_size, 1}, - {part_size, length(?LONG_BODY_PART) - 1} + {part_size, length(webserver_utils:long_body_part()) - 1} ], Options = [{partial_download, PartialDownload}], {ok, {Status, _, Pid}} = lhttpc:request(URL, get, [], <<>>, 1000, Options), Body = read_partial_body(Pid), ?assertEqual({200, "OK"}, Status), - ?assertEqual(<>, Body). + ?assertEqual(long_body_part(3), Body). partial_download_slow_chunks() -> - Port = start(gen_tcp, [fun slow_chunked_response/5]), + Port = start(gen_tcp, [fun webserver_utils:slow_chunked_response/5]), URL = url(Port, "/slow"), PartialDownload = [ {window_size, 1}, - {part_size, length(?LONG_BODY_PART) div 2} + {part_size, length(webserver_utils:long_body_part()) div 2} ], Options = [{partial_download, PartialDownload}], {ok, {Status, _, Pid}} = lhttpc:request(URL, get, [], <<>>, 1000, Options), Body = read_partial_body(Pid), ?assertEqual({200, "OK"}, Status), - ?assertEqual(<>, Body). + ?assertEqual(long_body_part(2), Body). close_connection() -> - Port = start(gen_tcp, [fun close_connection/5]), + Port = start(gen_tcp, [fun webserver_utils:close_connection/5]), URL = url(Port, "/close"), ?assertEqual({error, connection_closed}, lhttpc:request(URL, "GET", [], 1000)). ssl_get() -> - Port = start(ssl, [fun simple_response/5]), + Port = start(ssl, [fun webserver_utils:simple_response/5]), URL = ssl_url(Port, "/simple"), {ok, Response} = lhttpc:request(URL, "GET", [], 1000), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)). + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). ssl_get_ipv6() -> - Port = start(ssl, [fun simple_response/5], inet6), + Port = start(ssl, [fun webserver_utils:simple_response/5], inet6), URL = ssl_url(inet6, Port, "/simple"), {ok, Response} = lhttpc:request(URL, "GET", [], 1000), ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(<>, body(Response)). + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). ssl_post() -> - Port = start(ssl, [fun copy_body/5]), + Port = start(ssl, [fun webserver_utils:copy_body/5]), URL = ssl_url(Port, "/simple"), Body = "SSL Test ?assertEqual(BinaryBody, body(Response)). ssl_chunked() -> - Port = start(ssl, [fun chunked_response/5, fun chunked_response_t/5]), + Port = start(ssl, [fun webserver_utils:chunked_response/5, fun webserver_utils:chunked_response_t/5]), URL = ssl_url(Port, "/ssl_chunked"), FirstResult = lhttpc:request(URL, get, [], 100), ?assertMatch({ok, _}, FirstResult), {ok, FirstResponse} = FirstResult, ?assertEqual({200, "OK"}, status(FirstResponse)), - ?assertEqual(<>, body(FirstResponse)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(FirstResponse)), ?assertEqual("chunked", lhttpc_lib:header_value("transfer-encoding", headers(FirstResponse))), SecondResult = lhttpc:request(URL, get, [], 100), @@ -808,7 +756,7 @@ simple(Method) -> simple(Method, inet). simple(Method, Family) -> - case start(gen_tcp, [fun simple_response/5], Family) of + case start(gen_tcp, [fun webserver_utils:simple_response/5], Family) of {error, family_not_supported} when Family =:= inet6 -> % Localhost has no IPv6 support - not a big issue. ?debugMsg("WARNING: impossible to test IPv6 support~n"); @@ -818,7 +766,7 @@ simple(Method, Family) -> {StatusCode, ReasonPhrase} = status(Response), ?assertEqual(200, StatusCode), ?assertEqual("OK", ReasonPhrase), - ?assertEqual(<>, body(Response)) + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)) end. url(Port, Path) -> @@ -853,297 +801,3 @@ body({_, _, Body}) -> headers({_, Headers, _}) -> Headers. - -%%% Responders -simple_response(Module, Socket, _Request, _Headers, Body) -> - Module:send( - Socket, - [ - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\nContent-length: 14\r\n" - "X-Test-Orig-Body: ", Body, "\r\n\r\n" - ?DEFAULT_STRING - ] - ). - -large_response(Module, Socket, _, _, _) -> - BodyPart = <>, - ContentLength = 3 * size(BodyPart), - Module:send( - Socket, - [ - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\n" - "Content-length: ", integer_to_list(ContentLength), "\r\n\r\n" - ] - ), - Module:send(Socket, BodyPart), - Module:send(Socket, BodyPart), - Module:send(Socket, BodyPart). - -large_chunked_response(Module, Socket, _, _, _) -> - BodyPart = <>, - ChunkSize = erlang:integer_to_list(size(BodyPart), 16), - Chunk = [ChunkSize, "\r\n", BodyPart, "\r\n"], - Module:send( - Socket, - [ - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\n" - "Transfer-Encoding: chunked\r\n\r\n" - ] - ), - Module:send(Socket, Chunk), - Module:send(Socket, Chunk), - Module:send(Socket, Chunk), - Module:send(Socket, "0\r\n\r\n"). - -slow_chunked_response(Module, Socket, _, _, _) -> - ChunkSize = erlang:integer_to_list(length(?LONG_BODY_PART) * 2, 16), - Module:send( - Socket, - [ - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\n" - "Transfer-Encoding: chunked\r\n\r\n" - ]), - Module:send(Socket, [ChunkSize, "\r\n", <>]), - timer:sleep(200), - Module:send(Socket, [<>, "\r\n"]), - Module:send(Socket, "0\r\n\r\n"). - - -chunked_upload(Module, Socket, _, Headers, <<>>) -> - TransferEncoding = lhttpc_lib:header_value("transfer-encoding", Headers), - {Body, HeadersAndTrailers} = - webserver:read_chunked(Module, Socket, Headers), - Trailer1 = lhttpc_lib:header_value("x-trailer-1", HeadersAndTrailers, - "undefined"), - Module:send( - Socket, - [ - "HTTP/1.1 200 OK\r\n" - "Content-Length: 14\r\n" - "X-Test-Orig-Trailer-1:", Trailer1, "\r\n" - "X-Test-Orig-Enc: ", TransferEncoding, "\r\n" - "X-Test-Orig-Body: ", Body, "\r\n\r\n" - ?DEFAULT_STRING - ] - ). - -head_response(Module, Socket, _Request, _Headers, _Body) -> - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Server: Test server!\r\n\r\n" - ). - -no_content_response(Module, Socket, _Request, _Headers, _Body) -> - Module:send( - Socket, - "HTTP/1.1 204 OK\r\n" - "Server: Test server!\r\n\r\n" - ). - -empty_body(Module, Socket, _, _, _) -> - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\nContent-length: 0\r\n\r\n" - ). - -copy_body(Module, Socket, _, _, Body) -> - Module:send( - Socket, - [ - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\nContent-length: " - ++ integer_to_list(size(Body)) ++ "\r\n\r\n", - Body - ] - ). - -copy_body_100_continue(Module, Socket, _, _, Body) -> - Module:send( - Socket, - [ - "HTTP/1.1 100 Continue\r\n\r\n" - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\nContent-length: " - ++ integer_to_list(size(Body)) ++ "\r\n\r\n", - Body - ] - ). - -respond_and_close(Module, Socket, _, _, Body) -> - Pid = list_to_pid(binary_to_list(Body)), - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Connection: close\r\n" - "Content-type: text/plain\r\nContent-length: 14\r\n\r\n" - ?DEFAULT_STRING - ), - {error, closed} = Module:recv(Socket, 0), - Pid ! closed, - Module:close(Socket). - -respond_and_wait(Module, Socket, _, _, Body) -> - Pid = list_to_pid(binary_to_list(Body)), - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\nContent-length: 14\r\n\r\n" - ?DEFAULT_STRING - ), - % We didn't signal a connection close, but we want the client to do that - % any way - {error, closed} = Module:recv(Socket, 0), - Pid ! closed, - Module:close(Socket). - -pre_1_1_server(Module, Socket, _, _, Body) -> - Pid = list_to_pid(binary_to_list(Body)), - Module:send( - Socket, - "HTTP/1.0 200 OK\r\n" - "Content-type: text/plain\r\nContent-length: 14\r\n\r\n" - ?DEFAULT_STRING - ), - % We didn't signal a connection close, but we want the client to do that - % any way since we're 1.0 now - {error, closed} = Module:recv(Socket, 0), - Pid ! closed, - Module:close(Socket). - -pre_1_1_server_keep_alive(Module, Socket, _, _, _) -> - Module:send( - Socket, - "HTTP/1.0 200 OK\r\n" - "Content-type: text/plain\r\n" - "Connection: Keep-Alive\r\n" - "Content-length: 14\r\n\r\n" - ?DEFAULT_STRING - ). - -very_slow_response(Module, Socket, _, _, _) -> - timer:sleep(1000), - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\nContent-length: 14\r\n\r\n" - ?DEFAULT_STRING - ). - -no_content_length(Module, Socket, _, _, _) -> - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\nConnection: close\r\n\r\n" - ?DEFAULT_STRING - ). - -no_content_length_1_0(Module, Socket, _, _, _) -> - Module:send( - Socket, - "HTTP/1.0 200 OK\r\n" - "Content-type: text/plain\r\n\r\n" - ?DEFAULT_STRING - ). - -chunked_response(Module, Socket, _, _, _) -> - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n" - "5\r\n" - "Great\r\n" - "1\r\n" - " \r\n" - "8\r\n" - "success!\r\n" - "0\r\n" - "\r\n" - ). - -chunked_response_t(Module, Socket, _, _, _) -> - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\nTransfer-Encoding: ChUnKeD\r\n\r\n" - "7\r\n" - "Again, \r\n" - "E\r\n" - "great success!\r\n" - "0\r\n" - "Trailer-1: 1\r\n" - "Trailer-2: 2\r\n" - "\r\n" - ). - -close_connection(Module, Socket, _, _, _) -> - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\nContent-length: 14\r\n\r\n" - ), - Module:close(Socket). - -not_modified_response(Module, Socket, _Request, _Headers, _Body) -> - Module:send( - Socket, - [ - "HTTP/1.1 304 Not Modified\r\n" - "Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n\r\n" - ] - ). - -basic_auth_responder(User, Passwd) -> - fun(Module, Socket, _Request, Headers, _Body) -> - case proplists:get_value("Authorization", Headers) of - undefined -> - Module:send( - Socket, - [ - "HTTP/1.1 401 Unauthorized\r\n", - "Content-Type: text/plain\r\n", - "Content-Length: 12\r\n\r\n", - "missing_auth" - ] - ); - "Basic " ++ Auth -> - [U, P] = string:tokens( - binary_to_list(base64:decode(iolist_to_binary(Auth))), ":"), - case {U, P} of - {User, Passwd} -> - Module:send( - Socket, - [ - "HTTP/1.1 200 OK\r\n", - "Content-Type: text/plain\r\n", - "Content-Length: 2\r\n\r\n", - "OK" - ] - ); - _ -> - Module:send( - Socket, - [ - "HTTP/1.1 401 Unauthorized\r\n", - "Content-Type: text/plain\r\n", - "Content-Length: 10\r\n\r\n", - "wrong_auth" - ] - ) - end - end - end. - -trailing_space_header(Module, Socket, _, _, _) -> - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\n" - "Content-Length: 14 \r\n\r\n" - ?DEFAULT_STRING - ). diff --git a/test/webserver_utils.erl b/test/webserver_utils.erl new file mode 100644 index 00000000..19da40e0 --- /dev/null +++ b/test/webserver_utils.erl @@ -0,0 +1,363 @@ +-module(webserver_utils). + +-compile(export_all). + +-define(DEFAULT_STRING, "Great success!"). +-define(LONG_BODY_PART, + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + "This is a relatively long body, that we send to the client... " + ). + +default_string() -> + ?DEFAULT_STRING. + +long_body_part() -> + ?LONG_BODY_PART. + +%%% Responders +simple_response(Module, Socket, _Request, _Headers, Body) -> + Module:send( + Socket, + [ + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\nContent-length: 14\r\n" + "X-Test-Orig-Body: ", Body, "\r\n\r\n" + ?DEFAULT_STRING + ] + ). + +large_response(Module, Socket, _, _, _) -> + BodyPart = <>, + ContentLength = 3 * size(BodyPart), + Module:send( + Socket, + [ + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\n" + "Content-length: ", integer_to_list(ContentLength), "\r\n\r\n" + ] + ), + Module:send(Socket, BodyPart), + Module:send(Socket, BodyPart), + Module:send(Socket, BodyPart). + +large_chunked_response(Module, Socket, _, _, _) -> + BodyPart = <>, + ChunkSize = erlang:integer_to_list(size(BodyPart), 16), + Chunk = [ChunkSize, "\r\n", BodyPart, "\r\n"], + Module:send( + Socket, + [ + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + ] + ), + Module:send(Socket, Chunk), + Module:send(Socket, Chunk), + Module:send(Socket, Chunk), + Module:send(Socket, "0\r\n\r\n"). + +slow_chunked_response(Module, Socket, _, _, _) -> + ChunkSize = erlang:integer_to_list(length(?LONG_BODY_PART) * 2, 16), + Module:send( + Socket, + [ + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + ]), + Module:send(Socket, [ChunkSize, "\r\n", <>]), + timer:sleep(200), + Module:send(Socket, [<>, "\r\n"]), + Module:send(Socket, "0\r\n\r\n"). + + +chunked_upload(Module, Socket, _, Headers, <<>>) -> + TransferEncoding = lhttpc_lib:header_value("transfer-encoding", Headers), + {Body, HeadersAndTrailers} = + webserver:read_chunked(Module, Socket, Headers), + Trailer1 = lhttpc_lib:header_value("x-trailer-1", HeadersAndTrailers, + "undefined"), + Module:send( + Socket, + [ + "HTTP/1.1 200 OK\r\n" + "Content-Length: 14\r\n" + "X-Test-Orig-Trailer-1:", Trailer1, "\r\n" + "X-Test-Orig-Enc: ", TransferEncoding, "\r\n" + "X-Test-Orig-Body: ", Body, "\r\n\r\n" + ?DEFAULT_STRING + ] + ). + +head_response(Module, Socket, _Request, _Headers, _Body) -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Server: Test server!\r\n\r\n" + ). + +no_content_response(Module, Socket, _Request, _Headers, _Body) -> + Module:send( + Socket, + "HTTP/1.1 204 OK\r\n" + "Server: Test server!\r\n\r\n" + ). + +empty_body(Module, Socket, _, _, _) -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\nContent-length: 0\r\n\r\n" + ). + +copy_body(Module, Socket, _, _, Body) -> + Module:send( + Socket, + [ + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\nContent-length: " + ++ integer_to_list(size(Body)) ++ "\r\n\r\n", + Body + ] + ). + +copy_body_100_continue(Module, Socket, _, _, Body) -> + Module:send( + Socket, + [ + "HTTP/1.1 100 Continue\r\n\r\n" + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\nContent-length: " + ++ integer_to_list(size(Body)) ++ "\r\n\r\n", + Body + ] + ). + +respond_and_close(Module, Socket, _, _, Body) -> + Pid = list_to_pid(binary_to_list(Body)), + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Content-type: text/plain\r\nContent-length: 14\r\n\r\n" + ?DEFAULT_STRING + ), + {error, closed} = Module:recv(Socket, 0), + Pid ! closed, + Module:close(Socket). + +respond_and_wait(Module, Socket, _, _, Body) -> + Pid = list_to_pid(binary_to_list(Body)), + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\nContent-length: 14\r\n\r\n" + ?DEFAULT_STRING + ), + % We didn't signal a connection close, but we want the client to do that + % any way + {error, closed} = Module:recv(Socket, 0), + Pid ! closed, + Module:close(Socket). + +pre_1_1_server(Module, Socket, _, _, Body) -> + Pid = list_to_pid(binary_to_list(Body)), + Module:send( + Socket, + "HTTP/1.0 200 OK\r\n" + "Content-type: text/plain\r\nContent-length: 14\r\n\r\n" + ?DEFAULT_STRING + ), + % We didn't signal a connection close, but we want the client to do that + % any way since we're 1.0 now + {error, closed} = Module:recv(Socket, 0), + Pid ! closed, + Module:close(Socket). + +pre_1_1_server_keep_alive(Module, Socket, _, _, _) -> + Module:send( + Socket, + "HTTP/1.0 200 OK\r\n" + "Content-type: text/plain\r\n" + "Connection: Keep-Alive\r\n" + "Content-length: 14\r\n\r\n" + ?DEFAULT_STRING + ). + +very_slow_response(Module, Socket, _, _, _) -> + timer:sleep(1000), + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\nContent-length: 14\r\n\r\n" + ?DEFAULT_STRING + ). + +no_content_length(Module, Socket, _, _, _) -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\nConnection: close\r\n\r\n" + ?DEFAULT_STRING + ). + +no_content_length_1_0(Module, Socket, _, _, _) -> + Module:send( + Socket, + "HTTP/1.0 200 OK\r\n" + "Content-type: text/plain\r\n\r\n" + ?DEFAULT_STRING + ). + +chunked_response(Module, Socket, _, _, _) -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n" + "5\r\n" + "Great\r\n" + "1\r\n" + " \r\n" + "8\r\n" + "success!\r\n" + "0\r\n" + "\r\n" + ). + +chunked_response_t(Module, Socket, _, _, _) -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\nTransfer-Encoding: ChUnKeD\r\n\r\n" + "7\r\n" + "Again, \r\n" + "E\r\n" + "great success!\r\n" + "0\r\n" + "Trailer-1: 1\r\n" + "Trailer-2: 2\r\n" + "\r\n" + ). + +close_connection(Module, Socket, _, _, _) -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\nContent-length: 14\r\n\r\n" + ), + Module:close(Socket). + +not_modified_response(Module, Socket, _Request, _Headers, _Body) -> + Module:send( + Socket, + [ + "HTTP/1.1 304 Not Modified\r\n" + "Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n\r\n" + ] + ). + +basic_auth_responder(User, Passwd) -> + fun(Module, Socket, _Request, Headers, _Body) -> + case proplists:get_value("Authorization", Headers) of + undefined -> + Module:send( + Socket, + [ + "HTTP/1.1 401 Unauthorized\r\n", + "Content-Type: text/plain\r\n", + "Content-Length: 12\r\n\r\n", + "missing_auth" + ] + ); + "Basic " ++ Auth -> + [U, P] = string:tokens( + binary_to_list(base64:decode(iolist_to_binary(Auth))), ":"), + case {U, P} of + {User, Passwd} -> + Module:send( + Socket, + [ + "HTTP/1.1 200 OK\r\n", + "Content-Type: text/plain\r\n", + "Content-Length: 2\r\n\r\n", + "OK" + ] + ); + _ -> + Module:send( + Socket, + [ + "HTTP/1.1 401 Unauthorized\r\n", + "Content-Type: text/plain\r\n", + "Content-Length: 10\r\n\r\n", + "wrong_auth" + ] + ) + end + end + end. + +trailing_space_header(Module, Socket, _, _, _) -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\n" + "Content-Length: 14 \r\n\r\n" + ?DEFAULT_STRING + ). From 5acc7bc266d60e2dd5058f4a7a91280cde34f83f Mon Sep 17 00:00:00 2001 From: Lastres Date: Wed, 23 Jan 2013 15:34:59 +0000 Subject: [PATCH 12/49] Fix some issues related with read body part functions. Test cases that do not pass at the moment are also commented. --- src/lhttpc.erl | 10 +++++ src/lhttpc_client.erl | 44 +++++++++++++--------- test/lhttpc_tests.erl | 85 +++++++++++++++++++++++++------------------ 3 files changed, 87 insertions(+), 52 deletions(-) diff --git a/src/lhttpc.erl b/src/lhttpc.erl index fd477363..21eaacbd 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -732,6 +732,16 @@ verify_partial_download([{part_size, Size} | Options]) when verify_partial_download(Options); verify_partial_download([{part_size, infinity} | Options]) -> verify_partial_download(Options); +%verify_partial_download([{recv_proc, PidOrName} | Options]) when +% is_atom(PidOrName) -> +% Pid = whereis(PidOrName), +% is_alive(Pid) =:= true, +% verify_partial_download(Options); +%verify_partial_download([{recv_proc, PidOrName} | Options]) when +% is_alive(Pid) -> +% verify_partial_download(Options); +verify_partial_download([{recv_proc, _PidOrName} | Options]) -> + verify_partial_download(Options); verify_partial_download([Option | _Options]) -> erlang:error({bad_option, {partial_download, Option}}); verify_partial_download([]) -> diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 1a0f489b..97efc19a 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -35,11 +35,13 @@ %%------------------------------------------------------------------------------ -module(lhttpc_client). +%exported functions -export([start_link/2, start/2, request/7, send_body_part/3, send_trailers/3, + get_body_part/2, stop/1]). %% gen_server callbacks @@ -102,6 +104,9 @@ send_body_part(Client, Part, Timeout) -> send_trailers(Client, Trailers, Timeout) -> gen_server:call(Client, {send_trailers, Trailers}, Timeout). +get_body_part(Client, Timeout) -> + gen_server:call(Client, get_body_part, Timeout). + stop(Client) -> gen_server:cast(Client, stop). @@ -231,7 +236,7 @@ handle_call({send_body_part, Data}, From, State = #client_state{partial_upload = {_Reply, NewState} = send_body_part(State, Data), {noreply, NewState}; %We send the parts to the specified Pid. -handle_call({get_body_part, _Options}, From, +handle_call(get_body_part, From, State=#client_state{partial_download = true, download_info = {_Vsn, Hdrs}}) -> gen_server:reply(From, ok), read_partial_body(State, body_type(Hdrs)), @@ -509,14 +514,14 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> {ok, http_eoh} -> lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl), {Reply, NewState} = handle_response_body(State, Vsn, Status, Hdrs), - NewHdrs = element(2, Reply), - ReqHdrs = State#client_state.request_headers, - NewSocket = maybe_close_socket(State, Vsn, ReqHdrs, NewHdrs), case Reply of noreply -> - {noreply, NewState#client_state{socket = NewSocket}}; + %when partial_download is used. We do not close the socket. + {noreply, NewState#client_state{socket = Socket}}; _ -> - %{reply, Reply, NewState#client_state{socket = NewSocket}} + NewHdrs = element(2, Reply), + ReqHdrs = State#client_state.request_headers, + NewSocket = maybe_close_socket(State, Vsn, ReqHdrs, NewHdrs), {{ok, Reply}, NewState#client_state{socket = NewSocket}} end; {error, closed} -> @@ -564,7 +569,7 @@ handle_response_body(#client_state{partial_download = true} = State, Vsn, Method = State#client_state.method, case has_body(Method, element(1, Status), Hdrs) of true -> - Response = {Status, Hdrs, partial_download}, + Response = {ok, {Status, Hdrs, partial_download}}, gen_server:reply(State#client_state.requester, Response), NewState = State#client_state{download_info = {Vsn, Hdrs}}, read_partial_body(NewState, body_type(Hdrs)), @@ -652,23 +657,24 @@ read_partial_body(State=#client_state{download_info = {_Vsn, Hdrs}}, {fixed_leng %%% @private %%------------------------------------------------------------------------------ read_partial_finite_body(State , _Hdrs, 0, _Window) -> - reply_end_of_body(State, []); -read_partial_finite_body(#client_state{download_proc = To}, _Hdrs, _ContentLength, 0) -> + reply_end_of_body(State, []), + {noreply, State}; +read_partial_finite_body(#client_state{download_proc = To} = State, _Hdrs, _ContentLength, 0) -> %finished the window, reply to ask for another call to get_body_part - To ! {body_part, window_finished}; + To ! {body_part, window_finished}, + {noreply, State}; read_partial_finite_body(State, Hdrs, ContentLength, Window) when Window >= 0-> case read_body_part(State, ContentLength) of {ok, Bin} -> State#client_state.download_proc ! {body_part, Bin}, - Length = ContentLength - iolist_size(Bin), - read_partial_finite_body(State, Hdrs, Length, lhttpc_lib:dec(Window)); + Length = ContentLength - iolist_size(Bin), + read_partial_finite_body(State, Hdrs, Length, lhttpc_lib:dec(Window)); %TODO do we need to close the socket here? {error, Reason} -> State#client_state.download_proc ! {error, Reason}, exit(normal) end. - %%------------------------------------------------------------------------------ %%% @private %%------------------------------------------------------------------------------ @@ -702,7 +708,8 @@ read_length(Hdrs, Ssl, Socket, Length) -> %%------------------------------------------------------------------------------ read_partial_chunked_body(State, _Hdrs, 0, 0, _Buffer, 0) -> %we ask for another call to get_body_part - State#client_state.download_proc ! {body_part, http_eob}; + State#client_state.download_proc ! {body_part, http_eob}, + {noreply, State}; read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, 0) -> Socket = State#client_state.socket, Ssl = State#client_state.ssl, @@ -711,7 +718,8 @@ read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, 0) -> 0 -> reply_chunked_part(State, Buffer, Window), {Trailers, _NewHdrs} = read_trailers(Socket, Ssl, [], Hdrs), - reply_end_of_body(State, Trailers); + reply_end_of_body(State, Trailers), + {noreply, State}; ChunkSize when PartSize =:= infinity -> Chunk = read_chunk(Socket, Ssl, ChunkSize), NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), @@ -853,11 +861,13 @@ reply_end_of_body(#client_state{download_proc = To}, Trailers) -> %% @private %%------------------------------------------------------------------------------ read_partial_infinite_body(State, _Hdrs, 0) -> - State#client_state.download_proc ! {body_part, window_finished}; + State#client_state.download_proc ! {body_part, window_finished}, + {noreply, State}; read_partial_infinite_body(State, Hdrs, Window) when Window >= 0 -> case read_infinite_body_part(State) of - http_eob -> reply_end_of_body(State, []); + http_eob -> reply_end_of_body(State, []), + {noreply, State}; Bin -> State#client_state.download_proc ! {body_part, Bin}, read_partial_infinite_body(State, Hdrs, lhttpc_lib:dec(Window)) diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index c43cc406..0bc22ee5 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -82,26 +82,26 @@ tcp_test_() -> ?_test(persistent_connection()), ?_test(request_timeout()), ?_test(connection_timeout()), - ?_test(suspended_manager()), - ?_test(chunked_encoding()), - ?_test(partial_upload_identity()), - ?_test(partial_upload_identity_iolist()), - ?_test(partial_upload_chunked()), - ?_test(partial_upload_chunked_no_trailer()), + % ?_test(suspended_manager()), + % ?_test(chunked_encoding()), + % ?_test(partial_upload_identity()), + % ?_test(partial_upload_identity_iolist()), + % ?_test(partial_upload_chunked()), + % ?_test(partial_upload_chunked_no_trailer()), ?_test(partial_download_illegal_option()), - ?_test(partial_download_identity()), - ?_test(partial_download_infinity_window()), - ?_test(partial_download_no_content_length()), - ?_test(partial_download_no_content()), - ?_test(limited_partial_download_identity()), - ?_test(partial_download_chunked()), - ?_test(partial_download_chunked_infinite_part()), - ?_test(partial_download_smallish_chunks()), - ?_test(partial_download_slow_chunks()), - ?_test(close_connection()), - ?_test(message_queue()), - ?_test(trailing_space_header()), - ?_test(connection_count()) % just check that it's 0 (last) + ?_test(partial_download_identity()) + % ?_test(partial_download_infinity_window()), + % ?_test(partial_download_no_content_length()), + % ?_test(partial_download_no_content()), + % ?_test(limited_partial_download_identity()), + % ?_test(partial_download_chunked()), + % ?_test(partial_download_chunked_infinite_part()), + % ?_test(partial_download_smallish_chunks()), + % ?_test(partial_download_slow_chunks()), + % ?_test(close_connection()), + % ?_test(message_queue()), + % ?_test(trailing_space_header()), + % ?_test(connection_count()) % just check that it's 0 (last) ]} }. @@ -549,12 +549,15 @@ partial_download_identity() -> Port = start(gen_tcp, [fun webserver_utils:large_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1} + {window_size, 1}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], - {ok, {Status, _, Pid}} = - lhttpc:request(URL, get, [], <<>>, 1000, Options), - Body = read_partial_body(Pid), + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, {Status, _Hdrs, partial_download}} = + lhttpc:request_client(Client, URL, get, [], <<>>, 1000, Options), + ok = lhttpc:get_body_part(Client), + Body = read_partial_body(Client), ?assertEqual({200, "OK"}, Status), ?assertEqual(long_body_part(3), Body). @@ -732,24 +735,36 @@ upload_parts(BodyPart, CurrentState) -> {ok, NextState} = lhttpc:send_body_part(CurrentState, BodyPart, 1000), NextState. -read_partial_body(Pid) -> - read_partial_body(Pid, infinity, []). +read_partial_body(Client) -> + read_partial_body(Client, infinity, []). -read_partial_body(Pid, Size) -> - read_partial_body(Pid, Size, []). +read_partial_body(Client, Size) -> + read_partial_body(Client, Size, []). -read_partial_body(Pid, Size, Acc) -> - case lhttpc:get_body_part(Pid) of - {ok, {http_eob, []}} -> - list_to_binary(Acc); - {ok, Bin} -> - if +read_partial_body(Client, Size, Acc) -> + %ok = lhttpc:get_body_part(Client), + receive + {body_part, Bin} -> + if Size =:= infinity -> ok; Size =/= infinity -> ?assert(Size >= iolist_size(Bin)) - end, - read_partial_body(Pid, Size, [Acc, Bin]) + end, + ok = lhttpc:get_body_part(Client), + read_partial_body(Client, Size, [Acc, Bin]); + {http_eob, Trailers} -> + list_to_binary(Acc); + {body_part, http_eob} -> + list_to_binary(Acc); + {body_part, window_finished} -> + ok = lhttpc:get_body_part(Client), + read_partial_body(Client, Size, Acc); + {error, Reason} -> + {error, Reason} + after + 100 -> + {error, receive_clause} end. simple(Method) -> From ef3aacd4adb7c2a0d506aea1237d3ad781517b88 Mon Sep 17 00:00:00 2001 From: Diana Corbacho Date: Wed, 23 Jan 2013 18:43:47 +0000 Subject: [PATCH 13/49] Fix get_body_parts size control for fixed length bodies --- src/lhttpc_client.erl | 56 ++++++++++++++++++++--------------- src/lhttpc_manager.erl | 15 ++++------ test/lhttpc_manager_tests.erl | 6 ++-- test/lhttpc_tests.erl | 37 +++++++++++------------ test/webserver_utils.erl | 7 +++++ 5 files changed, 64 insertions(+), 57 deletions(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 97efc19a..dffe85ed 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -86,7 +86,8 @@ proxy_setup = false :: boolean(), download_info :: {term(), term()}, pool = undefined, - init_options + init_options, + body_length = undefined :: non_neg_integer() | undefined | chunked | infinity }). %%============================================================================== @@ -236,11 +237,12 @@ handle_call({send_body_part, Data}, From, State = #client_state{partial_upload = {_Reply, NewState} = send_body_part(State, Data), {noreply, NewState}; %We send the parts to the specified Pid. -handle_call(get_body_part, From, - State=#client_state{partial_download = true, download_info = {_Vsn, Hdrs}}) -> +handle_call(get_body_part, From, State=#client_state{partial_download = true}) -> gen_server:reply(From, ok), - read_partial_body(State, body_type(Hdrs)), - {noreply, State}. + {noreply, read_partial_body(State)}; +handle_call(_, _, State) -> + {reply, {error, unsupported_call}, State}. + %%-------------------------------------------------------------------- %% @private @@ -529,7 +531,7 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> % Either we only noticed that the socket was closed after we % sent the request, the server closed it just after we put - % the request on the wire or the server has some issues and is + % the request on the wire or the server has some isses and is % closing connections without sending responses. % If this the first attempt to send the request, we will try again. close_socket(State), @@ -571,9 +573,9 @@ handle_response_body(#client_state{partial_download = true} = State, Vsn, true -> Response = {ok, {Status, Hdrs, partial_download}}, gen_server:reply(State#client_state.requester, Response), - NewState = State#client_state{download_info = {Vsn, Hdrs}}, - read_partial_body(NewState, body_type(Hdrs)), - {noreply, NewState}; + NewState = State#client_state{download_info = {Vsn, Hdrs}, + body_length = body_type(Hdrs)}, + {noreply, read_partial_body(NewState)}; false -> {{Status, Hdrs, undefined}, State} end. @@ -644,13 +646,17 @@ read_body(_Vsn, Hdrs, Ssl, Socket, {fixed_length, ContentLength}) -> %%% @doc Called when {partial_download, PartialDownloadOptions} option is used. %%% @end %%------------------------------------------------------------------------------ -read_partial_body(State=#client_state{download_info = {_Vsn, Hdrs}}, chunked) -> +read_partial_body(State=#client_state{download_info = {_Vsn, Hdrs}, + body_length = chunked}) -> Window = State#client_state.download_window, read_partial_chunked_body(State, Hdrs, Window, 0, [], 0); -read_partial_body(State=#client_state{download_info = {Vsn, Hdrs}}, infinite) -> +read_partial_body(State=#client_state{download_info = {Vsn, Hdrs}, + body_length = infinite}) -> check_infinite_response(Vsn, Hdrs), read_partial_infinite_body(State, Hdrs, State#client_state.download_window); -read_partial_body(State=#client_state{download_info = {_Vsn, Hdrs}}, {fixed_length, ContentLength}) -> +read_partial_body(State=#client_state{ + download_info = {_Vsn, Hdrs}, + body_length = {fixed_length, ContentLength}}) -> read_partial_finite_body(State, Hdrs, ContentLength, State#client_state.download_window). %%------------------------------------------------------------------------------ @@ -658,21 +664,22 @@ read_partial_body(State=#client_state{download_info = {_Vsn, Hdrs}}, {fixed_leng %%------------------------------------------------------------------------------ read_partial_finite_body(State , _Hdrs, 0, _Window) -> reply_end_of_body(State, []), - {noreply, State}; -read_partial_finite_body(#client_state{download_proc = To} = State, _Hdrs, _ContentLength, 0) -> + State; +read_partial_finite_body(#client_state{download_proc = To} = State, _Hdrs, ContentLength, 0) -> %finished the window, reply to ask for another call to get_body_part To ! {body_part, window_finished}, - {noreply, State}; + State#client_state{body_length = {fixed_length, ContentLength}}; read_partial_finite_body(State, Hdrs, ContentLength, Window) when Window >= 0-> case read_body_part(State, ContentLength) of {ok, Bin} -> State#client_state.download_proc ! {body_part, Bin}, Length = ContentLength - iolist_size(Bin), read_partial_finite_body(State, Hdrs, Length, lhttpc_lib:dec(Window)); - %TODO do we need to close the socket here? {error, Reason} -> - State#client_state.download_proc ! {error, Reason}, - exit(normal) + State#client_state.download_proc ! {body_part_error, Reason}, + close_socket(State), + State#client_state{partial_download = false, + socket = undefined} end. %%------------------------------------------------------------------------------ @@ -709,7 +716,7 @@ read_length(Hdrs, Ssl, Socket, Length) -> read_partial_chunked_body(State, _Hdrs, 0, 0, _Buffer, 0) -> %we ask for another call to get_body_part State#client_state.download_proc ! {body_part, http_eob}, - {noreply, State}; + State; read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, 0) -> Socket = State#client_state.socket, Ssl = State#client_state.ssl, @@ -719,7 +726,7 @@ read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, 0) -> reply_chunked_part(State, Buffer, Window), {Trailers, _NewHdrs} = read_trailers(Socket, Ssl, [], Hdrs), reply_end_of_body(State, Trailers), - {noreply, State}; + State; ChunkSize when PartSize =:= infinity -> Chunk = read_chunk(Socket, Ssl, ChunkSize), NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), @@ -862,12 +869,13 @@ reply_end_of_body(#client_state{download_proc = To}, Trailers) -> %%------------------------------------------------------------------------------ read_partial_infinite_body(State, _Hdrs, 0) -> State#client_state.download_proc ! {body_part, window_finished}, - {noreply, State}; + State; read_partial_infinite_body(State, Hdrs, Window) when Window >= 0 -> case read_infinite_body_part(State) of - http_eob -> reply_end_of_body(State, []), - {noreply, State}; + http_eob -> + reply_end_of_body(State, []), + State; Bin -> State#client_state.download_proc ! {body_part, Bin}, read_partial_infinite_body(State, Hdrs, lhttpc_lib:dec(Window)) @@ -1010,7 +1018,7 @@ connect_pool(State = #client_state{init_options = Options, pool = Pool}) -> {Host, Port, Ssl} = request_first_destination(State), case lhttpc_manager:ensure_call(Pool, self(), Host, Port, Ssl, Options) of - {ok, undefined} -> + {ok, no_socket} -> %% ensure_call does not open a socket if the pool doesnt have one new_socket(State); Reply -> diff --git a/src/lhttpc_manager.erl b/src/lhttpc_manager.erl index 8e02af4d..bb76cae5 100644 --- a/src/lhttpc_manager.erl +++ b/src/lhttpc_manager.erl @@ -206,16 +206,11 @@ start_link(Options0) -> %% @end %%------------------------------------------------------------------------------ -spec ensure_call(pool_id(), pid(), host(), port_num(), boolean(), options()) -> - {ok, socket()} | {error, 'no_socket'} | {error, term()}. + {ok, socket()} | {ok, 'no_socket'} | {error, term()}. ensure_call(Pool, Pid, Host, Port, Ssl, Options) -> SocketRequest = {socket, Pid, Host, Port, Ssl}, - try gen_server:call(Pool, SocketRequest, infinity) of - {ok, S} -> - %% Re-using HTTP/1.1 connections - {ok, S}; - {error, no_socket} -> - %% Opening a new HTTP/1.1 connection - {ok, undefined} + try + gen_server:call(Pool, SocketRequest, infinity) catch exit:{noproc, _Reason} -> case proplists:get_value(pool_ensure, Options, false) of @@ -323,7 +318,7 @@ handle_call({socket, Pid, Host, Port, Ssl}, {Pid, _Ref} = From, State) -> Queues2 = add_to_queue(Dest, From, Queues), {noreply, State2#httpc_man{queues = Queues2}}; false -> - {reply, {error, no_socket}, monitor_client(Dest, From, State2)} + {reply, {ok, no_socket}, monitor_client(Dest, From, State2)} end end; handle_call(dump_settings, _, State) -> @@ -387,7 +382,7 @@ handle_info({'DOWN', MonRef, process, Pid, _Reason}, State) -> empty -> {noreply, State#httpc_man{clients = Clients2}}; {ok, From, Queues2} -> - gen_server:reply(From, no_socket), + gen_server:reply(From, {ok, no_socket}), State2 = State#httpc_man{queues = Queues2, clients = Clients2}, {noreply, monitor_client(Dest, From, State2)} end; diff --git a/test/lhttpc_manager_tests.erl b/test/lhttpc_manager_tests.erl index 6f73edef..fe86f55a 100644 --- a/test/lhttpc_manager_tests.erl +++ b/test/lhttpc_manager_tests.erl @@ -68,7 +68,7 @@ empty_manager() -> Client = spawn_client(), ?assertEqual(ok, ping_client(Client)), - ?assertEqual(no_socket, client_peek_socket(Client)), + ?assertEqual({ok, no_socket}, client_peek_socket(Client)), ?assertEqual(0, lhttpc_manager:connection_count(lhttpc_manager)), ?assertEqual(ok, stop_client(Client)), @@ -317,7 +317,7 @@ closed_race_cond() -> after 5000 -> erlang:error("Timeout receiving result from child process") end, - ?assertMatch(no_socket, Result2), + ?assertMatch({ok, no_socket}, Result2), ?assertEqual(0, lhttpc_manager:connection_count(lhttpc_manager)), ?assertEqual(ok, stop_client(Client)), @@ -350,7 +350,7 @@ client_loop(Parent, Socket) -> {connect, Ref} -> Args = {socket, self(), ?HOST, get_port(), ?SSL}, NewSocket = case gen_server:call(lhttpc_manager, Args, infinity) of - no_socket -> + {ok, no_socket}-> socket_server:connect(get_port()); {ok, S} -> S diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index 0bc22ee5..c6ee4dc1 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -540,10 +540,8 @@ partial_download_illegal_option() -> long_body_part(Size) -> list_to_binary( - lists:foldl( - fun(_, Acc) -> - Acc ++ " " ++ webserver_utils:long_body_part() - end, webserver_utils:long_body_part(), lists:seq(1, Size))). + lists:flatten( + [webserver_utils:long_body_part() || _ <- lists:seq(1, Size)])). partial_download_identity() -> Port = start(gen_tcp, [fun webserver_utils:large_response/5]), @@ -559,6 +557,7 @@ partial_download_identity() -> ok = lhttpc:get_body_part(Client), Body = read_partial_body(Client), ?assertEqual({200, "OK"}, Status), + ?assertEqual(size(long_body_part(3)), size(Body)), ?assertEqual(long_body_part(3), Body). partial_download_infinity_window() -> @@ -736,14 +735,18 @@ upload_parts(BodyPart, CurrentState) -> NextState. read_partial_body(Client) -> - read_partial_body(Client, infinity, []). + read_partial_body(Client, infinity, <<>>). read_partial_body(Client, Size) -> - read_partial_body(Client, Size, []). + read_partial_body(Client, Size, <<>>). read_partial_body(Client, Size, Acc) -> - %ok = lhttpc:get_body_part(Client), receive + {body_part, http_eob} -> + Acc; + {body_part, window_finished} -> + ok = lhttpc:get_body_part(Client), + read_partial_body(Client, Size, Acc); {body_part, Bin} -> if Size =:= infinity -> @@ -751,20 +754,14 @@ read_partial_body(Client, Size, Acc) -> Size =/= infinity -> ?assert(Size >= iolist_size(Bin)) end, - ok = lhttpc:get_body_part(Client), - read_partial_body(Client, Size, [Acc, Bin]); + read_partial_body(Client, Size, <>); {http_eob, Trailers} -> - list_to_binary(Acc); - {body_part, http_eob} -> - list_to_binary(Acc); - {body_part, window_finished} -> - ok = lhttpc:get_body_part(Client), - read_partial_body(Client, Size, Acc); - {error, Reason} -> - {error, Reason} - after - 100 -> - {error, receive_clause} + Acc; + {body_part_error, Reason} -> + {error, Reason, Acc} + after + 100 -> + {error, receive_clause, Acc} end. simple(Method) -> diff --git a/test/webserver_utils.erl b/test/webserver_utils.erl index 19da40e0..519040c7 100644 --- a/test/webserver_utils.erl +++ b/test/webserver_utils.erl @@ -68,6 +68,13 @@ default_string() -> long_body_part() -> ?LONG_BODY_PART. +long_body_part(Size) -> + list_to_binary( + lists:foldl( + fun(_, Acc) -> + Acc ++ " " ++ webserver_utils:long_body_part() + end, webserver_utils:long_body_part(), lists:seq(1, Size))). + %%% Responders simple_response(Module, Socket, _Request, _Headers, Body) -> Module:send( From afcb0b259da70b391f94b09cbb0d4ede5c6e878a Mon Sep 17 00:00:00 2001 From: Diana Corbacho Date: Thu, 24 Jan 2013 09:05:38 +0000 Subject: [PATCH 14/49] Fix get body parts for fixed length messages and some parameters configuration * upload_window is not used any more, the gen_server synchronous call regulate the flow * pool options are a grouped argument in connect call --- src/lhttpc.erl | 32 +++-- src/lhttpc_client.erl | 245 +++++++++++++++++------------------ test/lhttpc_client_tests.erl | 12 +- test/lhttpc_tests.erl | 23 ++-- 4 files changed, 159 insertions(+), 153 deletions(-) diff --git a/src/lhttpc.erl b/src/lhttpc.erl index 21eaacbd..36e16152 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -700,22 +700,34 @@ verify_options([{proxy, List} | Options]) when is_list(List) -> verify_options(Options); verify_options([{proxy_ssl_options, List} | Options]) when is_list(List) -> verify_options(Options); -verify_options([{pool, PidOrName} | Options]) +verify_options([{pool_options, PoolOptions} | Options]) -> + case verify_pool_options(PoolOptions) of + ok -> + verify_options(Options); + R -> + R + end; +verify_options([Option | _Rest]) -> + erlang:error({bad_option, Option}); +verify_options([]) -> + ok. + +verify_pool_options([{pool, PidOrName} | Options]) when is_pid(PidOrName); is_atom(PidOrName) -> - verify_options(Options); -verify_options([{pool_ensure, Bool} | Options]) + verify_pool_options(Options); +verify_pool_options([{pool_ensure, Bool} | Options]) when is_boolean(Bool) -> - verify_options(Options); -verify_options([{pool_connection_timeout, Size} | Options]) + verify_pool_options(Options); +verify_pool_options([{pool_connection_timeout, Size} | Options]) when is_integer(Size) -> - verify_options(Options); -verify_options([{pool_max_size, Size} | Options]) + verify_pool_options(Options); +verify_pool_options([{pool_max_size, Size} | Options]) when is_integer(Size) orelse Size =:= infinity-> - verify_options(Options); -verify_options([Option | _Rest]) -> + verify_pool_options(Options); +verify_pool_options([Option | _Rest]) -> erlang:error({bad_option, Option}); -verify_options([]) -> +verify_pool_options([]) -> ok. %%------------------------------------------------------------------------------ diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index dffe85ed..59ba5f7b 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -61,34 +61,34 @@ string:to_lower(lhttpc_lib:header_value("connection", HDRS, DEFAULT))). -record(client_state, { - host :: string(), - port = 80 :: port_num(), - ssl = false :: boolean(), - method :: string(), - request :: iolist(), - request_headers :: headers(), - requester, - socket, - connect_timeout = infinity :: timeout(), - connect_options = [] :: [any()], - attempts = 1 :: integer(), - partial_upload = false :: boolean(), - chunked_upload = false :: boolean(), - upload_window :: non_neg_integer() | infinity, - partial_download = false :: boolean(), - download_window = infinity :: timeout(), - download_proc :: pid(), - part_size :: non_neg_integer() | infinity, - %% in case of infinity we read whatever data we can get from - %% the wire at that point or in case of chunked one chunk - proxy :: undefined | #lhttpc_url{}, - proxy_ssl_options = [] :: [any()], - proxy_setup = false :: boolean(), - download_info :: {term(), term()}, + host :: string(), + port = 80 :: port_num(), + ssl = false :: boolean(), pool = undefined, - init_options, - body_length = undefined :: non_neg_integer() | undefined | chunked | infinity - }). + pool_options, + socket, + connect_timeout = infinity :: timeout(), + connect_options = [] :: [any()], + %% next fields are specific to particular requests + request :: iolist() | undefined, + method :: string(), + request_headers :: headers(), + requester, + partial_upload = false :: boolean(), + chunked_upload = false :: boolean(), + partial_download = false :: boolean(), + download_window = infinity :: timeout(), + download_proc :: pid(), + part_size :: non_neg_integer() | infinity, + %% in case of infinity we read whatever data we can get from + %% the wire at that point or in case of chunked one chunk + attempts = 0 :: integer(), + download_info :: {term(), term()}, + body_length = undefined :: non_neg_integer() | undefined | chunked | infinity, + proxy :: undefined | #lhttpc_url{}, + proxy_ssl_options = [] :: [any()], + proxy_setup = false :: boolean() + }). %%============================================================================== %% Exported functions @@ -132,14 +132,18 @@ request(Client, Path, Method, Hdrs, Body, Options, Timeout) -> %%% gen_server callbacks %%%=================================================================== init({Destination, Options}) -> - Pool = proplists:get_value(pool, Options), + PoolOptions = proplists:get_value(pool_options, Options, []), + Pool = proplists:get_value(pool, PoolOptions), State = case Destination of {Host, Port, Ssl} -> #client_state{host = Host, port = Port, ssl = Ssl, pool = Pool, - init_options = Options}; + connect_timeout = proplists:get_value(connect_timeout, Options, + infinity), + connect_options = proplists:get_value(connect_options, Options, []), + pool_options = PoolOptions}; URL -> #lhttpc_url{host = Host, port = Port, @@ -149,7 +153,10 @@ init({Destination, Options}) -> port = Port, ssl = Ssl, pool = Pool, - init_options = Options} + connect_timeout = proplists:get_value(connect_timeout, Options, + infinity), + connect_options = proplists:get_value(connect_options, Options, []), + pool_options = PoolOptions} end, %% Get a socket for the pool or exit case connect_socket(State) of @@ -168,8 +175,7 @@ init({Destination, Options}) -> handle_call({request, Path, Method, Hdrs, Body, Options}, From, State = #client_state{ssl = Ssl, host = Host, port = Port, socket = Socket}) -> - UploadWindowSize = proplists:get_value(partial_upload, Options), - PartialUpload = proplists:is_defined(partial_upload, Options), + PartialUpload = proplists:get_value(partial_upload, Options, false), PartialDownload = proplists:is_defined(partial_download, Options), PartialDownloadOptions = proplists:get_value(partial_download, Options, []), NormalizedMethod = lhttpc_lib:normalize_method(Method), @@ -177,8 +183,8 @@ handle_call({request, Path, Method, Hdrs, Body, Options}, From, undefined -> undefined; ProxyUrl when is_list(ProxyUrl), not Ssl -> - % The point of HTTP CONNECT proxying is to use TLS tunneled in - % a plain HTTP/1.1 connection to the proxy (RFC2817). + % The point of HTTP CONNECT proxying is to use TLS tunneled in + % a plain HTTP/1.1 connection to the proxy (RFC2817). throw(origin_server_not_https); ProxyUrl when is_list(ProxyUrl) -> lhttpc_lib:parse_url(ProxyUrl) @@ -191,12 +197,8 @@ handle_call({request, Path, Method, Hdrs, Body, Options}, From, request = Request, requester = From, request_headers = Hdrs, - connect_timeout = proplists:get_value(connect_timeout, Options, - infinity), - connect_options = proplists:get_value(connect_options, Options, []), - attempts = 1 + proplists:get_value(send_retry, Options, 1), + attempts = proplists:get_value(send_retry, Options, 1), partial_upload = PartialUpload, - upload_window = UploadWindowSize, chunked_upload = ChunkedUpload, partial_download = PartialDownload, download_window = proplists:get_value(window_size, @@ -211,37 +213,38 @@ handle_call({request, Path, Method, Hdrs, Body, Options}, From, }, {Response, NewState2} = send_request(NewState), {reply, Response, NewState2}; -handle_call({trailers, Trailers}, _From, State = #client_state{partial_upload = true}) -> +handle_call(_Msg, _From, #client_state{request = undefined} = State) -> + {reply, {error, no_pending_request}, State}; +handle_call({send_body_part, _}, _From, State = #client_state{partial_upload = false}) -> + {reply, {error, no_partial_upload}, State}; +handle_call({trailers, _}, _From, State = #client_state{partial_upload = false}) -> + {reply, {error, no_partial_upload}, State}; +handle_call(get_body_part, _From, State = #client_state{partial_download = false}) -> + {reply, {error, no_partial_download}, State}; +handle_call(_Msg, _From, #client_state{socket = undefined} = State) -> + {reply, {error, connection_closed}, State#client_state{request = undefined}}; +handle_call({trailers, Trailers}, _From, State) -> case send_trailers(State, Trailers) of {ok, NewState} -> read_response(NewState); {Error, NewState} -> {reply, Error, NewState} end; -handle_call({send_body_part, http_eob}, _From, State = #client_state{partial_upload = true, socket = undefined}) -> - {reply, {error, closed}, State}; -handle_call({send_body_part, http_eob}, From, State = #client_state{partial_upload = true}) -> +handle_call({send_body_part, http_eob}, From, State) -> case send_body_part(State, http_eob) of {ok, NewState} -> read_response(NewState#client_state{requester = From}); {Error, NewState} -> {reply, Error, NewState} end; -handle_call({send_body_part, Data}, _From, #client_state{partial_upload = true, - upload_window = 0} - = State) -> - {Reply, NewState} = send_body_part(State, Data), - {reply, Reply, NewState}; -handle_call({send_body_part, Data}, From, State = #client_state{partial_upload = true}) -> +handle_call({send_body_part, Data}, From, State) -> gen_server:reply(From, ok), {_Reply, NewState} = send_body_part(State, Data), {noreply, NewState}; -%We send the parts to the specified Pid. -handle_call(get_body_part, From, State=#client_state{partial_download = true}) -> + %We send the parts to the specified Pid. +handle_call(get_body_part, From, State) -> gen_server:reply(From, ok), - {noreply, read_partial_body(State)}; -handle_call(_, _, State) -> - {reply, {error, unsupported_call}, State}. + {noreply, read_partial_body(State)}. %%-------------------------------------------------------------------- @@ -317,11 +320,9 @@ code_change(_OldVsn, State, _Extra) -> %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -send_body_part(State = #client_state{socket = Socket, ssl = Ssl, - upload_window = Window}, BodyPart) -> +send_body_part(State = #client_state{socket = Socket, ssl = Ssl}, BodyPart) -> Data = encode_body_part(State, BodyPart), - check_send_result(State#client_state{upload_window = lhttpc_lib:dec(Window)}, - lhttpc_sock:send(Socket, Data, Ssl)). + check_send_result(State, lhttpc_sock:send(Socket, Data, Ssl)). %%------------------------------------------------------------------------------ %% @private @@ -329,13 +330,15 @@ send_body_part(State = #client_state{socket = Socket, ssl = Ssl, %% handles the proxy connection. %% @end %%------------------------------------------------------------------------------ +send_request(#client_state{attempts = 0} = State) -> + {{error, connection_closed}, State#client_state{request = undefined}}; send_request(#client_state{socket = undefined} = State) -> % if we dont get a keep alive from the previous request, the socket is undefined. case connect_socket(State) of {ok, NewState} -> send_request(NewState); - {{error, Reason}, NewState} -> - {{error, Reason}, NewState} + {Error, NewState} -> + {Error, NewState} end; send_request(#client_state{proxy = #lhttpc_url{}, proxy_setup = false, host = DestHost, port = Port, socket = Socket} = State) -> @@ -369,33 +372,31 @@ send_request(#client_state{proxy = #lhttpc_url{}, proxy_setup = false, read_proxy_connect_response(State, nil, nil); {error, closed} -> close_socket(State), - {{error, proxy_connection_closed}, State#client_state{socket = undefined}}; + {{error, proxy_connection_closed}, State#client_state{socket = undefined, request = undefined}}; {error, _Reason} -> close_socket(State), - {{error, proxy_connection_closed}, State#client_state{socket = undefined}} + {{error, proxy_connection_closed}, State#client_state{socket = undefined, request = undefined}} end; -send_request(#client_state{socket = Socket, ssl = Ssl, request = Request} = State) -> +send_request(#client_state{socket = Socket, ssl = Ssl, request = Request, + attempts = Attempts} = State) -> %% no proxy case lhttpc_sock:send(Socket, Request, Ssl) of ok -> if %% {partial_upload, WindowSize} is used. State#client_state.partial_upload -> - {{ok, partial_upload}, State#client_state{attempts = 1}}; - not State#client_state.partial_upload -> read_response(State) + {{ok, partial_upload}, State#client_state{attempts = 0}}; + not State#client_state.partial_upload -> + read_response(State) end; {error, closed} -> close_socket(State), - case connect_socket(State#client_state{ - socket = undefined}) of - {{error, connection_closed}, NewState} -> - {{error, connection_closed}, NewState}; - {ok, NewState} -> - send_request(NewState) - end; + send_request(State#client_state{socket = undefined, + attempts = Attempts - 1}); {error, _Reason} -> close_socket(State), - {{error, connection_closed}, State#client_state{socket = undefined}} + {{error, connection_closed}, State#client_state{socket = undefined, + request = undefined}} end. %%------------------------------------------------------------------------------ @@ -442,9 +443,9 @@ read_proxy_connect_response(State, StatusCode, StatusText) -> {{error, {proxy_connection_refused, StatusCode, StatusText}}, State}; {error, closed} -> close_socket(State), - {{error, proxy_connection_closed}, State#client_state{socket = undefined}}; + {{error, proxy_connection_closed}, State#client_state{socket = undefined, request = undefined}}; {error, Reason} -> - {{error, {proxy_connection_failed, Reason}}, State} + {{error, {proxy_connection_failed, Reason}}, State#client_state{request = undefined}} end. %%------------------------------------------------------------------------------ @@ -478,7 +479,7 @@ check_send_result(State, ok) -> {ok, State}; check_send_result(State, Error) -> close_socket(State), - {Error, State#client_state{socket = undefined}}. + {Error, State#client_state{socket = undefined, request = undefined}}. %%------------------------------------------------------------------------------ %% @private @@ -524,7 +525,8 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> NewHdrs = element(2, Reply), ReqHdrs = State#client_state.request_headers, NewSocket = maybe_close_socket(State, Vsn, ReqHdrs, NewHdrs), - {{ok, Reply}, NewState#client_state{socket = NewSocket}} + {{ok, Reply}, NewState#client_state{socket = NewSocket, + request = undefined}} end; {error, closed} -> %% TODO does it work for partial uploads? I think should return an error @@ -535,15 +537,12 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> % closing connections without sending responses. % If this the first attempt to send the request, we will try again. close_socket(State), - NewState = State#client_state{ - socket = undefined, - attempts = State#client_state.attempts - 1 - }, + NewState = State#client_state{socket = undefined}, send_request(NewState); {ok, {http_error, _} = Reason} -> - {reply, {error, Reason}, State}; + {reply, {error, Reason}, State#client_state{request = undefined}}; {error, Reason} -> - {reply, {error, Reason}, State} + {reply, {error, Reason}, State#client_state{request = undefined}} end. %%------------------------------------------------------------------------------ @@ -646,39 +645,36 @@ read_body(_Vsn, Hdrs, Ssl, Socket, {fixed_length, ContentLength}) -> %%% @doc Called when {partial_download, PartialDownloadOptions} option is used. %%% @end %%------------------------------------------------------------------------------ -read_partial_body(State=#client_state{download_info = {_Vsn, Hdrs}, - body_length = chunked}) -> +read_partial_body(State=#client_state{body_length = chunked}) -> Window = State#client_state.download_window, - read_partial_chunked_body(State, Hdrs, Window, 0, [], 0); + read_partial_chunked_body(State, Window, 0, [], 0); read_partial_body(State=#client_state{download_info = {Vsn, Hdrs}, body_length = infinite}) -> check_infinite_response(Vsn, Hdrs), - read_partial_infinite_body(State, Hdrs, State#client_state.download_window); -read_partial_body(State=#client_state{ - download_info = {_Vsn, Hdrs}, - body_length = {fixed_length, ContentLength}}) -> - read_partial_finite_body(State, Hdrs, ContentLength, State#client_state.download_window). + read_partial_infinite_body(State, State#client_state.download_window); +read_partial_body(State=#client_state{body_length = {fixed_length, ContentLength}}) -> + read_partial_finite_body(State, ContentLength, State#client_state.download_window). %%------------------------------------------------------------------------------ %%% @private %%------------------------------------------------------------------------------ -read_partial_finite_body(State , _Hdrs, 0, _Window) -> +read_partial_finite_body(State , 0, _Window) -> reply_end_of_body(State, []), - State; -read_partial_finite_body(#client_state{download_proc = To} = State, _Hdrs, ContentLength, 0) -> + State#client_state{request = undefined}; +read_partial_finite_body(#client_state{download_proc = To} = State, ContentLength, 0) -> %finished the window, reply to ask for another call to get_body_part To ! {body_part, window_finished}, State#client_state{body_length = {fixed_length, ContentLength}}; -read_partial_finite_body(State, Hdrs, ContentLength, Window) when Window >= 0-> +read_partial_finite_body(State, ContentLength, Window) when Window >= 0-> case read_body_part(State, ContentLength) of {ok, Bin} -> State#client_state.download_proc ! {body_part, Bin}, Length = ContentLength - iolist_size(Bin), - read_partial_finite_body(State, Hdrs, Length, lhttpc_lib:dec(Window)); + read_partial_finite_body(State, Length, lhttpc_lib:dec(Window)); {error, Reason} -> State#client_state.download_proc ! {body_part_error, Reason}, close_socket(State), - State#client_state{partial_download = false, + State#client_state{request = undefined, socket = undefined} end. @@ -713,35 +709,36 @@ read_length(Hdrs, Ssl, Socket, Length) -> %%------------------------------------------------------------------------------ %%% @private %%------------------------------------------------------------------------------ -read_partial_chunked_body(State, _Hdrs, 0, 0, _Buffer, 0) -> +read_partial_chunked_body(State, 0, 0, _Buffer, 0) -> %we ask for another call to get_body_part State#client_state.download_proc ! {body_part, http_eob}, State; -read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, 0) -> - Socket = State#client_state.socket, - Ssl = State#client_state.ssl, - PartSize = State#client_state.part_size, - case read_chunk_size(Socket, Ssl) of +read_partial_chunked_body(#client_state{download_info = {_Vsn, Hdrs}, + socket = Socket, + ssl = Ssl, + part_size = PartSize} = State, + Window, BufferSize, Buffer, 0) -> + case read_chunk_size(Socket, Ssl) of 0 -> reply_chunked_part(State, Buffer, Window), {Trailers, _NewHdrs} = read_trailers(Socket, Ssl, [], Hdrs), reply_end_of_body(State, Trailers), - State; + State#client_state{request = undefined}; ChunkSize when PartSize =:= infinity -> Chunk = read_chunk(Socket, Ssl, ChunkSize), NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), - read_partial_chunked_body(State, Hdrs, NewWindow, 0, [], 0); + read_partial_chunked_body(State, NewWindow, 0, [], 0); ChunkSize when BufferSize + ChunkSize >= PartSize -> {Chunk, RemSize} = read_partial_chunk(Socket, Ssl, PartSize - BufferSize, ChunkSize), NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), - read_partial_chunked_body(State, Hdrs, NewWindow, 0, [], RemSize); + read_partial_chunked_body(State, NewWindow, 0, [], RemSize); ChunkSize -> Chunk = read_chunk(Socket, Ssl, ChunkSize), - read_partial_chunked_body(State, Hdrs, Window, - BufferSize + ChunkSize, [Chunk | Buffer], 0) + read_partial_chunked_body(State, Window, + BufferSize + ChunkSize, [Chunk | Buffer], 0) end; -read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, RemSize) -> +read_partial_chunked_body(State, Window, BufferSize, Buffer, RemSize) -> Socket = State#client_state.socket, Ssl = State#client_state.ssl, PartSize = State#client_state.part_size, @@ -750,12 +747,12 @@ read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, RemSize) -> {Chunk, NewRemSize} = read_partial_chunk(Socket, Ssl, PartSize - BufferSize, RemSize), NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), - read_partial_chunked_body(State, Hdrs, NewWindow, 0, [], - NewRemSize); + read_partial_chunked_body(State, NewWindow, 0, [], + NewRemSize); BufferSize + RemSize < PartSize -> Chunk = read_chunk(Socket, Ssl, RemSize), - read_partial_chunked_body(State, Hdrs, Window, BufferSize + RemSize, - [Chunk | Buffer], 0) + read_partial_chunked_body(State, Window, BufferSize + RemSize, + [Chunk | Buffer], 0) end. %%------------------------------------------------------------------------------ @@ -867,18 +864,18 @@ reply_end_of_body(#client_state{download_proc = To}, Trailers) -> %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -read_partial_infinite_body(State, _Hdrs, 0) -> +read_partial_infinite_body(State, 0) -> State#client_state.download_proc ! {body_part, window_finished}, State; -read_partial_infinite_body(State, Hdrs, Window) +read_partial_infinite_body(State, Window) when Window >= 0 -> case read_infinite_body_part(State) of http_eob -> reply_end_of_body(State, []), - State; + State#client_state{request = undefined}; Bin -> State#client_state.download_proc ! {body_part, Bin}, - read_partial_infinite_body(State, Hdrs, lhttpc_lib:dec(Window)) + read_partial_infinite_body(State, lhttpc_lib:dec(Window)) end. %%------------------------------------------------------------------------------ @@ -993,11 +990,7 @@ is_ipv6_host(Host) -> %% pool dinamically. %% @end %%------------------------------------------------------------------------------ -connect_socket(State = #client_state{attempts = 0}) -> - % Don't try again if the number of allowed attempts is 0. - {{error, connection_closed}, State#client_state{attempts = 1}}; -connect_socket(State = #client_state{pool = Pool, - attempts = Attempts}) -> +connect_socket(State = #client_state{pool = Pool}) -> Connection = case Pool of undefined -> new_socket(State); @@ -1007,14 +1000,12 @@ connect_socket(State = #client_state{pool = Pool, case Connection of {ok, Socket} -> {ok, State#client_state{socket = Socket}}; - {error, unknown_pool} = Error -> - {Error, State}; - {error, _} -> - connect_socket(State#client_state{attempts = Attempts - 1}) + Error -> + {Error, State} end. -spec connect_pool(#client_state{}) -> {ok, socket()} | {error, atom()}. -connect_pool(State = #client_state{init_options = Options, +connect_pool(State = #client_state{pool_options = Options, pool = Pool}) -> {Host, Port, Ssl} = request_first_destination(State), case lhttpc_manager:ensure_call(Pool, self(), Host, Port, Ssl, Options) of diff --git a/test/lhttpc_client_tests.erl b/test/lhttpc_client_tests.erl index f7f50fe2..56e8f584 100644 --- a/test/lhttpc_client_tests.erl +++ b/test/lhttpc_client_tests.erl @@ -12,7 +12,7 @@ -compile(export_all). fail_connect_test() -> - ?assertEqual({error, connection_closed}, + ?assertEqual({error, econnrefused}, lhttpc_client:start({{"localhost", 8080, false}, []}, [])). fail_connect_pool_test_() -> @@ -27,15 +27,17 @@ fail_connect_pool_test_() -> end, [{"Fail to connect on ensure pool", fun() -> - ?assertMatch({error, connection_closed}, + ?assertMatch({error, econnrefused}, lhttpc_client:start({{"localhost", 8080, false}, - [{pool, my_test_pool}, - {pool_ensure, true}]}, [])) + [{pool_options, + [{pool, my_test_pool}, + {pool_ensure, true}]}]}, [])) end}, {"Fail to connect - no pool", fun() -> ?assertEqual({error, unknown_pool}, lhttpc_client:start({{"localhost", 8080, false}, - [{pool, my_test_pool}]}, [])) + [{pool_options, + [{pool, my_test_pool}]}]}, [])) end}] }. diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index c6ee4dc1..aaa27cf1 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -89,8 +89,8 @@ tcp_test_() -> % ?_test(partial_upload_chunked()), % ?_test(partial_upload_chunked_no_trailer()), ?_test(partial_download_illegal_option()), - ?_test(partial_download_identity()) - % ?_test(partial_download_infinity_window()), + ?_test(partial_download_identity()), + ?_test(partial_download_infinity_window()) % ?_test(partial_download_no_content_length()), % ?_test(partial_download_no_content()), % ?_test(limited_partial_download_identity()), @@ -322,8 +322,8 @@ pre_1_1_server_keep_alive() -> URL = url(Port, "/close"), Body = pid_to_list(self()), %this test need to use a client now (or a pool). - {ok, Response1} = lhttpc:request(URL, get, [], [], 1000, [{pool_ensure, true}, {pool, pool_name}]), - {ok, Response2} = lhttpc:request(URL, put, [], Body, 1000, [{pool_ensure, true}, {pool, pool_name}]), + {ok, Response1} = lhttpc:request(URL, get, [], [], 1000, [{pool_options, [{pool_ensure, true}, {pool, pool_name}]}]), + {ok, Response2} = lhttpc:request(URL, put, [], Body, 1000, [{pool_options, [{pool_ensure, true}, {pool, pool_name}]}]), ?assertEqual({200, "OK"}, status(Response1)), ?assertEqual({200, "OK"}, status(Response2)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response1)), @@ -379,10 +379,10 @@ persistent_connection() -> fun webserver_utils:copy_body/5 ]), URL = url(Port, "/persistent"), - {ok, FirstResponse} = lhttpc:request(URL, "GET", [], [], 1000, [{pool_ensure, true}, {pool, pool_name}]), + {ok, FirstResponse} = lhttpc:request(URL, "GET", [], [], 1000, [{pool_options, [{pool_ensure, true}, {pool, pool_name}]}]), Headers = [{"KeepAlive", "300"}], % shouldn't be needed - {ok, SecondResponse} = lhttpc:request(URL, "GET", Headers, [], 1000, [{pool_ensure, true}, {pool, pool_name}]), - {ok, ThirdResponse} = lhttpc:request(URL, "POST", [], [], 1000, [{pool_ensure, true}, {pool, pool_name}]), + {ok, SecondResponse} = lhttpc:request(URL, "GET", Headers, [], 1000, [{pool_options, [{pool_ensure, true}, {pool, pool_name}]}]), + {ok, ThirdResponse} = lhttpc:request(URL, "POST", [], [], 1000, [{pool_options, [{pool_ensure, true}, {pool, pool_name}]}]), ?assertEqual({200, "OK"}, status(FirstResponse)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(FirstResponse)), ?assertEqual({200, "OK"}, status(SecondResponse)), @@ -547,9 +547,9 @@ partial_download_identity() -> Port = start(gen_tcp, [fun webserver_utils:large_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1}, - {recv_proc, self()} - ], + {window_size, 1}, + {recv_proc, self()} + ], Options = [{partial_download, PartialDownload}], {ok, Client} = lhttpc:connect_client(URL, []), {ok, {Status, _Hdrs, partial_download}} = @@ -564,7 +564,8 @@ partial_download_infinity_window() -> Port = start(gen_tcp, [fun webserver_utils:large_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, infinity} + {window_size, infinity}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], {ok, {Status, _, Pid}} = lhttpc:request(URL, get, [], <<>>, 1000, Options), From 58bc3f93dc675f01c9291b37f27224492ade6e14 Mon Sep 17 00:00:00 2001 From: Lastres Date: Thu, 24 Jan 2013 13:05:49 +0000 Subject: [PATCH 15/49] Fix error related with connection_close test. (exception made gen_server crash). --- src/lhttpc_client.erl | 22 ++++++++++++++++++---- test/lhttpc_tests.erl | 8 ++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 59ba5f7b..87af5f99 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -521,6 +521,8 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> noreply -> %when partial_download is used. We do not close the socket. {noreply, NewState#client_state{socket = Socket}}; + {error, Reason} -> + {{error, Reason}, NewState#client_state{socket = undefined}}; _ -> NewHdrs = element(2, Reply), ReqHdrs = State#client_state.request_headers, @@ -559,11 +561,21 @@ handle_response_body(#client_state{partial_download = false} = State, Vsn, Socket = State#client_state.socket, Ssl = State#client_state.ssl, Method = State#client_state.method, - {Body, NewHdrs} = case has_body(Method, element(1, Status), Hdrs) of + %{Body, NewHdrs} = case has_body(Method, element(1, Status), Hdrs) of + % true -> read_body(Vsn, Hdrs, Ssl, Socket, body_type(Hdrs)); + % false -> {<<>>, Hdrs} + % end, + Reply = case has_body(Method, element(1, Status), Hdrs) of true -> read_body(Vsn, Hdrs, Ssl, Socket, body_type(Hdrs)); false -> {<<>>, Hdrs} - end, - {{Status, NewHdrs, Body}, State}; + end, + case Reply of + {error, Reason} -> + % NewState = State#client_state{socket = undefined}, + {{error, Reason}, State}; + {Body, NewHdrs} -> + {{Status, NewHdrs, Body}, State} + end; handle_response_body(#client_state{partial_download = true} = State, Vsn, Status, Hdrs) -> %when {partial_download, PartialDownloadOptions} option is used. @@ -703,7 +715,9 @@ read_length(Hdrs, Ssl, Socket, Length) -> {ok, Data} -> {Data, Hdrs}; {error, Reason} -> - erlang:error(Reason) + %erlang:error(Reason) + lhttpc_sock:close(Socket, Ssl), + {error, Reason} end. %%------------------------------------------------------------------------------ diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index aaa27cf1..b6b6765a 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -89,8 +89,8 @@ tcp_test_() -> % ?_test(partial_upload_chunked()), % ?_test(partial_upload_chunked_no_trailer()), ?_test(partial_download_illegal_option()), - ?_test(partial_download_identity()), - ?_test(partial_download_infinity_window()) + % ?_test(partial_download_identity()), + ?_test(partial_download_infinity_window()), % ?_test(partial_download_no_content_length()), % ?_test(partial_download_no_content()), % ?_test(limited_partial_download_identity()), @@ -98,7 +98,7 @@ tcp_test_() -> % ?_test(partial_download_chunked_infinite_part()), % ?_test(partial_download_smallish_chunks()), % ?_test(partial_download_slow_chunks()), - % ?_test(close_connection()), + ?_test(close_connection()) % ?_test(message_queue()), % ?_test(trailing_space_header()), % ?_test(connection_count()) % just check that it's 0 (last) @@ -669,7 +669,7 @@ partial_download_slow_chunks() -> close_connection() -> Port = start(gen_tcp, [fun webserver_utils:close_connection/5]), URL = url(Port, "/close"), - ?assertEqual({error, connection_closed}, lhttpc:request(URL, "GET", [], + ?assertEqual({error, closed}, lhttpc:request(URL, "GET", [], 1000)). ssl_get() -> From 3f8abe0a661bb04a20139ce8ed2bf526c494c563 Mon Sep 17 00:00:00 2001 From: Lastres Date: Thu, 24 Jan 2013 13:27:54 +0000 Subject: [PATCH 16/49] Remove message_queue test. Uncomment tests that already pass. Also remove some lines of old commented code. --- src/lhttpc_client.erl | 6 ------ test/lhttpc_tests.erl | 14 +++++--------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 87af5f99..d00034c4 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -561,17 +561,12 @@ handle_response_body(#client_state{partial_download = false} = State, Vsn, Socket = State#client_state.socket, Ssl = State#client_state.ssl, Method = State#client_state.method, - %{Body, NewHdrs} = case has_body(Method, element(1, Status), Hdrs) of - % true -> read_body(Vsn, Hdrs, Ssl, Socket, body_type(Hdrs)); - % false -> {<<>>, Hdrs} - % end, Reply = case has_body(Method, element(1, Status), Hdrs) of true -> read_body(Vsn, Hdrs, Ssl, Socket, body_type(Hdrs)); false -> {<<>>, Hdrs} end, case Reply of {error, Reason} -> - % NewState = State#client_state{socket = undefined}, {{error, Reason}, State}; {Body, NewHdrs} -> {{Status, NewHdrs, Body}, State} @@ -715,7 +710,6 @@ read_length(Hdrs, Ssl, Socket, Length) -> {ok, Data} -> {Data, Hdrs}; {error, Reason} -> - %erlang:error(Reason) lhttpc_sock:close(Socket, Ssl), {error, Reason} end. diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index b6b6765a..a751af20 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -1,7 +1,7 @@ %%% ---------------------------------------------------------------------------- %%% Copyright (c) 2009, Erlang Training and Consulting Ltd. %%% All rights reserved. -%%% +%%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions are met: %%% * Redistributions of source code must retain the above copyright @@ -12,7 +12,7 @@ %%% * Neither the name of Erlang Training and Consulting Ltd. nor the %%% names of its contributors may be used to endorse or promote products %%% derived from this software without specific prior written permission. -%%% +%%% %%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS'' %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -98,10 +98,9 @@ tcp_test_() -> % ?_test(partial_download_chunked_infinite_part()), % ?_test(partial_download_smallish_chunks()), % ?_test(partial_download_slow_chunks()), - ?_test(close_connection()) - % ?_test(message_queue()), - % ?_test(trailing_space_header()), - % ?_test(connection_count()) % just check that it's 0 (last) + ?_test(close_connection()), + ?_test(trailing_space_header()), + ?_test(connection_count()) % just check that it's 0 (last) ]} }. @@ -123,9 +122,6 @@ other_test_() -> %%% Tests -message_queue() -> - receive X -> erlang:error({unexpected_message, X}) after 0 -> ok end. - simple_get() -> simple(get), simple("GET"). From bc05108e2e6a58243e02c29bf2cf2bc9914d9f5f Mon Sep 17 00:00:00 2001 From: Lastres Date: Thu, 24 Jan 2013 14:35:35 +0000 Subject: [PATCH 17/49] Fix error related with suspende_manager test case (timeout when connecting client). --- src/lhttpc.erl | 22 +++++++++++++--------- src/lhttpc_manager.erl | 2 +- test/lhttpc_tests.erl | 9 ++++----- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/lhttpc.erl b/src/lhttpc.erl index 36e16152..4ba02b29 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -494,15 +494,19 @@ request(URL, Method, Hdrs, Body, Timeout, Options) -> headers(), iodata(), pos_timeout(), options()) -> result(). request(Host, Port, Ssl, Path, Method, Hdrs, Body, Timeout, Options) -> verify_options(Options), - {ok, Client} = connect_client({Host, Port, Ssl}, Options), - try - Reply = lhttpc_client:request(Client, Path, Method, Hdrs, Body, Options, Timeout), - disconnect_client(Client), - Reply - catch - exit:{timeout, _} -> - disconnect_client(Client), - {error, timeout} + case connect_client({Host, Port, Ssl}, Options) of + {ok, Client} -> + try + Reply = lhttpc_client:request(Client, Path, Method, Hdrs, Body, Options, Timeout), + disconnect_client(Client), + Reply + catch + exit:{timeout, _} -> + disconnect_client(Client), + {error, timeout} + end; + {error, {timeout, Reason}} -> + {error, connection_timeout} end. %%------------------------------------------------------------------------------ diff --git a/src/lhttpc_manager.erl b/src/lhttpc_manager.erl index bb76cae5..d87d9bb6 100644 --- a/src/lhttpc_manager.erl +++ b/src/lhttpc_manager.erl @@ -210,7 +210,7 @@ start_link(Options0) -> ensure_call(Pool, Pid, Host, Port, Ssl, Options) -> SocketRequest = {socket, Pid, Host, Port, Ssl}, try - gen_server:call(Pool, SocketRequest, infinity) + gen_server:call(Pool, SocketRequest, 100) catch exit:{noproc, _Reason} -> case proplists:get_value(pool_ensure, Options, false) of diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index a751af20..343ebcb9 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -82,7 +82,7 @@ tcp_test_() -> ?_test(persistent_connection()), ?_test(request_timeout()), ?_test(connection_timeout()), - % ?_test(suspended_manager()), + ?_test(suspended_manager()), % ?_test(chunked_encoding()), % ?_test(partial_upload_identity()), % ?_test(partial_upload_identity_iolist()), @@ -407,17 +407,16 @@ connection_timeout() -> suspended_manager() -> Port = start(gen_tcp, [fun webserver_utils:simple_response/5, fun webserver_utils:simple_response/5]), URL = url(Port, "/persistent"), - lhttpc:add_pool(lhttpc_manager), - {ok, FirstResponse} = lhttpc:request(URL, get, [], [], 50, [{pool, lhttpc_manager}]), + {ok, FirstResponse} = lhttpc:request(URL, get, [], [], 50, [{pool_options, [{pool_ensure, true}, {pool, lhttpc_manager}]}]), ?assertEqual({200, "OK"}, status(FirstResponse)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(FirstResponse)), Pid = whereis(lhttpc_manager), true = erlang:suspend_process(Pid), - ?assertEqual({error, timeout}, lhttpc:request(URL, get, [], [], 50, [{pool, lhttpc_manager}])), + ?assertEqual({error, connection_timeout}, lhttpc:request(URL, get, [], [], 50, [{pool_options, [{pool_ensure, true}, {pool, lhttpc_manager}]}])), true = erlang:resume_process(Pid), ?assertEqual(1, lhttpc_manager:connection_count(lhttpc_manager, {"localhost", Port, false})), - {ok, SecondResponse} = lhttpc:request(URL, get, [], [], 50, [{pool, lhttpc_manager}]), + {ok, SecondResponse} = lhttpc:request(URL, get, [], [], 50, [{pool_options, [{pool_ensure, true}, {pool, lhttpc_manager}]}]), ?assertEqual({200, "OK"}, status(SecondResponse)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(SecondResponse)). From 8930896a2c5fd58311d8d7b52037f29de3527b08 Mon Sep 17 00:00:00 2001 From: Lastres Date: Thu, 24 Jan 2013 15:38:02 +0000 Subject: [PATCH 18/49] Fix errors found by dialyzer. Now dialyzer test pass without problems. Still migth be necessary to improve the specs. --- include/lhttpc_types.hrl | 12 +++++++++++- src/lhttpc.erl | 14 +++++++------- src/lhttpc_client.erl | 8 +++++--- src/lhttpc_manager.erl | 2 +- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/include/lhttpc_types.hrl b/include/lhttpc_types.hrl index 3ae9e251..101997ba 100644 --- a/include/lhttpc_types.hrl +++ b/include/lhttpc_types.hrl @@ -65,6 +65,8 @@ {'part_size', non_neg_integer() | 'infinity'} | invalid_option(). + + -type option() :: {'connect_timeout', timeout()} | {'send_retry', non_neg_integer()} | @@ -73,9 +75,17 @@ {'connect_options', socket_options()} | {'proxy', string()} | {'proxy_ssl_options', socket_options()} | - {'pool', pid() | atom()} | + {'pool_options', pool_options()} | invalid_option(). +-type pool_option() :: + {'pool_ensure', boolean()} | + {'pool_connection_timeout', pos_timeout()} | + {'pool_max_size' | integer()} | + {'pool_name', pool_id()}. + +-type pool_options() :: [pool_option()]. + -type options() :: [option()]. -type host() :: string() | {integer(), integer(), integer(), integer()}. diff --git a/src/lhttpc.erl b/src/lhttpc.erl index 4ba02b29..99a7bf09 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -681,7 +681,7 @@ kill_client(Pid) -> %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ --spec verify_options(options()) -> ok. +-spec verify_options(options()) -> ok | any(). verify_options([{send_retry, N} | Options]) when is_integer(N), N >= 0 -> verify_options(Options); verify_options([{connect_timeout, infinity} | Options]) -> @@ -705,17 +705,17 @@ verify_options([{proxy, List} | Options]) when is_list(List) -> verify_options([{proxy_ssl_options, List} | Options]) when is_list(List) -> verify_options(Options); verify_options([{pool_options, PoolOptions} | Options]) -> - case verify_pool_options(PoolOptions) of - ok -> - verify_options(Options); - R -> - R - end; + ok = verify_pool_options(PoolOptions), + verify_options(Options); verify_options([Option | _Rest]) -> erlang:error({bad_option, Option}); verify_options([]) -> ok. +%%------------------------------------------------------------------------------ +%% @private +%%------------------------------------------------------------------------------ +-spec verify_pool_options(pool_options()) -> ok | no_return(). verify_pool_options([{pool, PidOrName} | Options]) when is_pid(PidOrName); is_atom(PidOrName) -> verify_pool_options(Options); diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index d00034c4..78de7e21 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -67,7 +67,7 @@ pool = undefined, pool_options, socket, - connect_timeout = infinity :: timeout(), + connect_timeout = 'infinity' :: timeout(), connect_options = [] :: [any()], %% next fields are specific to particular requests request :: iolist() | undefined, @@ -84,7 +84,7 @@ %% the wire at that point or in case of chunked one chunk attempts = 0 :: integer(), download_info :: {term(), term()}, - body_length = undefined :: non_neg_integer() | undefined | chunked | infinity, + body_length = undefined :: {'fixed_length', non_neg_integer()} | 'undefined' | 'chunked' | 'infinite', proxy :: undefined | #lhttpc_url{}, proxy_ssl_options = [] :: [any()], proxy_setup = false :: boolean() @@ -554,7 +554,9 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> %%------------------------------------------------------------------------------ -spec handle_response_body(#client_state{}, {integer(), integer()}, http_status(), headers()) -> {http_status(), headers(), body()} | - {http_status(), headers()}. + {http_status(), headers()} | + {'noreply', any()} | + {{'error', any()}, any()}. handle_response_body(#client_state{partial_download = false} = State, Vsn, Status, Hdrs) -> %when {partial_download, PartialDownloadOptions} option is NOT used. diff --git a/src/lhttpc_manager.erl b/src/lhttpc_manager.erl index d87d9bb6..6ccb60f5 100644 --- a/src/lhttpc_manager.erl +++ b/src/lhttpc_manager.erl @@ -205,7 +205,7 @@ start_link(Options0) -> %% destination and returns it if it exists, 'undefined' otherwise. %% @end %%------------------------------------------------------------------------------ --spec ensure_call(pool_id(), pid(), host(), port_num(), boolean(), options()) -> +-spec ensure_call(pool_id(), pid(), host(), port_num(), boolean(), pool_options()) -> {ok, socket()} | {ok, 'no_socket'} | {error, term()}. ensure_call(Pool, Pid, Host, Port, Ssl, Options) -> SocketRequest = {socket, Pid, Host, Port, Ssl}, From 43251f8f3872f7725ebee732b299f19c413e8610 Mon Sep 17 00:00:00 2001 From: Diana Corbacho Date: Thu, 24 Jan 2013 16:20:36 +0000 Subject: [PATCH 19/49] Fix partial upload/download --- src/lhttpc.erl | 4 +- src/lhttpc_client.erl | 26 +++--- test/lhttpc_tests.erl | 208 +++++++++++++++++++++++------------------- 3 files changed, 128 insertions(+), 110 deletions(-) diff --git a/src/lhttpc.erl b/src/lhttpc.erl index 99a7bf09..bb4d4e03 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -689,8 +689,8 @@ verify_options([{connect_timeout, infinity} | Options]) -> verify_options([{connect_timeout, MS} | Options]) when is_integer(MS), MS >= 0 -> verify_options(Options); -verify_options([{partial_upload, WindowSize} | Options]) - when is_integer(WindowSize), WindowSize >= 0 -> +verify_options([{partial_upload, Bool} | Options]) + when is_boolean(Bool) -> verify_options(Options); verify_options([{partial_upload, infinity} | Options]) -> verify_options(Options); diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 78de7e21..1d9b2876 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -211,8 +211,7 @@ handle_call({request, Path, Method, Hdrs, Body, Options}, From, proxy_setup = (Socket =/= undefined), proxy_ssl_options = proplists:get_value(proxy_ssl_options, Options, []) }, - {Response, NewState2} = send_request(NewState), - {reply, Response, NewState2}; + send_request(NewState); handle_call(_Msg, _From, #client_state{request = undefined} = State) -> {reply, {error, no_pending_request}, State}; handle_call({send_body_part, _}, _From, State = #client_state{partial_upload = false}) -> @@ -223,7 +222,7 @@ handle_call(get_body_part, _From, State = #client_state{partial_download = false {reply, {error, no_partial_download}, State}; handle_call(_Msg, _From, #client_state{socket = undefined} = State) -> {reply, {error, connection_closed}, State#client_state{request = undefined}}; -handle_call({trailers, Trailers}, _From, State) -> +handle_call({send_trailers, Trailers}, _From, State) -> case send_trailers(State, Trailers) of {ok, NewState} -> read_response(NewState); @@ -331,14 +330,14 @@ send_body_part(State = #client_state{socket = Socket, ssl = Ssl}, BodyPart) -> %% @end %%------------------------------------------------------------------------------ send_request(#client_state{attempts = 0} = State) -> - {{error, connection_closed}, State#client_state{request = undefined}}; + {reply, {error, connection_closed}, State#client_state{request = undefined}}; send_request(#client_state{socket = undefined} = State) -> % if we dont get a keep alive from the previous request, the socket is undefined. case connect_socket(State) of {ok, NewState} -> send_request(NewState); {Error, NewState} -> - {Error, NewState} + {reply, Error, NewState} end; send_request(#client_state{proxy = #lhttpc_url{}, proxy_setup = false, host = DestHost, port = Port, socket = Socket} = State) -> @@ -369,13 +368,14 @@ send_request(#client_state{proxy = #lhttpc_url{}, proxy_setup = false, ], case lhttpc_sock:send(Socket, ConnectRequest, Ssl) of ok -> - read_proxy_connect_response(State, nil, nil); + {Reply, NewState} = read_proxy_connect_response(State, nil, nil), + {reply, Reply, NewState}; {error, closed} -> close_socket(State), - {{error, proxy_connection_closed}, State#client_state{socket = undefined, request = undefined}}; + {reply, {error, proxy_connection_closed}, State#client_state{socket = undefined, request = undefined}}; {error, _Reason} -> close_socket(State), - {{error, proxy_connection_closed}, State#client_state{socket = undefined, request = undefined}} + {reply, {error, proxy_connection_closed}, State#client_state{socket = undefined, request = undefined}} end; send_request(#client_state{socket = Socket, ssl = Ssl, request = Request, attempts = Attempts} = State) -> @@ -385,7 +385,7 @@ send_request(#client_state{socket = Socket, ssl = Ssl, request = Request, if %% {partial_upload, WindowSize} is used. State#client_state.partial_upload -> - {{ok, partial_upload}, State#client_state{attempts = 0}}; + {reply, {ok, partial_upload}, State#client_state{attempts = 0}}; not State#client_state.partial_upload -> read_response(State) end; @@ -395,7 +395,7 @@ send_request(#client_state{socket = Socket, ssl = Ssl, request = Request, attempts = Attempts - 1}); {error, _Reason} -> close_socket(State), - {{error, connection_closed}, State#client_state{socket = undefined, + {reply, {error, connection_closed}, State#client_state{socket = undefined, request = undefined}} end. @@ -527,8 +527,8 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> NewHdrs = element(2, Reply), ReqHdrs = State#client_state.request_headers, NewSocket = maybe_close_socket(State, Vsn, ReqHdrs, NewHdrs), - {{ok, Reply}, NewState#client_state{socket = NewSocket, - request = undefined}} + {reply, {ok, Reply}, NewState#client_state{socket = NewSocket, + request = undefined}} end; {error, closed} -> %% TODO does it work for partial uploads? I think should return an error @@ -674,7 +674,7 @@ read_partial_finite_body(#client_state{download_proc = To} = State, ContentLengt %finished the window, reply to ask for another call to get_body_part To ! {body_part, window_finished}, State#client_state{body_length = {fixed_length, ContentLength}}; -read_partial_finite_body(State, ContentLength, Window) when Window >= 0-> +read_partial_finite_body(State, ContentLength, Window) when Window > 0-> case read_body_part(State, ContentLength) of {ok, Bin} -> State#client_state.download_proc ! {body_part, Bin}, diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index 343ebcb9..174d03e1 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -84,23 +84,24 @@ tcp_test_() -> ?_test(connection_timeout()), ?_test(suspended_manager()), % ?_test(chunked_encoding()), - % ?_test(partial_upload_identity()), - % ?_test(partial_upload_identity_iolist()), + ?_test(partial_upload_identity()), + ?_test(partial_upload_identity_iolist()), % ?_test(partial_upload_chunked()), % ?_test(partial_upload_chunked_no_trailer()), ?_test(partial_download_illegal_option()), - % ?_test(partial_download_identity()), - ?_test(partial_download_infinity_window()), - % ?_test(partial_download_no_content_length()), - % ?_test(partial_download_no_content()), - % ?_test(limited_partial_download_identity()), - % ?_test(partial_download_chunked()), - % ?_test(partial_download_chunked_infinite_part()), - % ?_test(partial_download_smallish_chunks()), - % ?_test(partial_download_slow_chunks()), - ?_test(close_connection()), - ?_test(trailing_space_header()), - ?_test(connection_count()) % just check that it's 0 (last) + ?_test(partial_download_identity()), + ?_test(partial_download_infinity_window()), + ?_test(partial_download_no_content_length()), + ?_test(partial_download_no_content()), + ?_test(limited_partial_download_identity()), + ?_test(partial_download_chunked()), + ?_test(partial_download_chunked_infinite_part()), + ?_test(partial_download_smallish_chunks()), + ?_test(partial_download_slow_chunks()) + % ?_test(close_connection()), + % ?_test(message_queue()), + % ?_test(trailing_space_header()), + % ?_test(connection_count()) % just check that it's 0 (last) ]} }. @@ -443,18 +444,18 @@ partial_upload_identity() -> URL = url(Port, "/partial_upload"), Body = [<<"This">>, <<" is ">>, <<"chunky">>, <<" stuff!">>], Hdrs = [{"Content-Length", integer_to_list(iolist_size(Body))}], - Options = [{partial_upload, 1}], - {ok, UploadState1} = lhttpc:request(URL, post, Hdrs, hd(Body), 1000, Options), - Response1 = lists:foldl(fun upload_parts/2, UploadState1, - tl(Body) ++ [http_eob]), + Options = [{partial_upload, true}], + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, partial_upload} = + lhttpc:request_client(Client, URL, post, Hdrs, hd(Body), 1000, Options), + {ok, Response1} = upload_parts(Client, tl(Body) ++ [http_eob]), ?assertEqual({200, "OK"}, status(Response1)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response1)), ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response1))), % Make sure it works with no body part in the original request as well - {ok, UploadState2} = lhttpc:request(URL, post, Hdrs, [], 1000, Options), - Response2 = lists:foldl(fun upload_parts/2, UploadState2, - Body ++ [http_eob]), + {ok, partial_upload} = lhttpc:request_client(Client, URL, post, Hdrs, [], 1000, Options), + {ok, Response2} = upload_parts(Client, Body ++ [http_eob]), ?assertEqual({200, "OK"}, status(Response2)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response2)), ?assertEqual("This is chunky stuff!", @@ -465,18 +466,18 @@ partial_upload_identity_iolist() -> URL = url(Port, "/partial_upload"), Body = ["This", [<<" ">>, $i, $s, [" "]], <<"chunky">>, [<<" stuff!">>]], Hdrs = [{"Content-Length", integer_to_list(iolist_size(Body))}], - Options = [{partial_upload, 1}], - {ok, UploadState1} = lhttpc:request(URL, post, Hdrs, hd(Body), 1000, Options), - Response1 = lists:foldl(fun upload_parts/2, UploadState1, - tl(Body) ++ [http_eob]), + Options = [{partial_upload, true}], + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, partial_upload} = + lhttpc:request_client(Client, URL, post, Hdrs, hd(Body), 1000, Options), + {ok, Response1} = upload_parts(Client, tl(Body) ++ [http_eob]), ?assertEqual({200, "OK"}, status(Response1)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response1)), ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response1))), % Make sure it works with no body part in the original request as well - {ok, UploadState2} = lhttpc:request(URL, post, Hdrs, [], 1000, Options), - Response2 = lists:foldl(fun upload_parts/2, UploadState2, - Body ++ [http_eob]), + {ok, UploadState2} = lhttpc:request_client(Client, URL, post, Hdrs, [], 1000, Options), + {ok, Response2} = upload_parts(Client, Body ++ [http_eob]), ?assertEqual({200, "OK"}, status(Response2)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response2)), ?assertEqual("This is chunky stuff!", @@ -486,13 +487,12 @@ partial_upload_chunked() -> Port = start(gen_tcp, [fun webserver_utils:chunked_upload/5, fun webserver_utils:chunked_upload/5]), URL = url(Port, "/partial_upload_chunked"), Body = ["This", [<<" ">>, $i, $s, [" "]], <<"chunky">>, [<<" stuff!">>]], - Options = [{partial_upload, 1}], - {ok, UploadState1} = lhttpc:request(URL, post, [], hd(Body), 1000, Options), + Options = [{partial_upload, true}], + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, partial_upload} = lhttpc:request_client(Client, URL, post, [], hd(Body), 1000, Options), Trailer = {"X-Trailer-1", "my tail is tailing me...."}, - {ok, Response1} = lhttpc:send_trailers( - lists:foldl(fun upload_parts/2, UploadState1, tl(Body)), - [Trailer] - ), + upload_parts(Client, tl(Body)), + {ok, Response1} = lhttpc:send_trailers(Client, [Trailer]), ?assertEqual({200, "OK"}, status(Response1)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response1)), ?assertEqual("This is chunky stuff!", @@ -502,10 +502,8 @@ partial_upload_chunked() -> % Make sure it works with no body part in the original request as well Headers = [{"Transfer-Encoding", "chunked"}], {ok, UploadState2} = lhttpc:request(URL, post, Headers, [], 1000, Options), - {ok, Response2} = lhttpc:send_trailers( - lists:foldl(fun upload_parts/2, UploadState2, Body), - [Trailer] - ), + upload_parts(Client, Body), + {ok, Response2} = lhttpc:send_trailers(Client, [Trailer]), ?assertEqual({200, "OK"}, status(Response2)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response2)), ?assertEqual("This is chunky stuff!", @@ -513,20 +511,20 @@ partial_upload_chunked() -> ?assertEqual(element(2, Trailer), lhttpc_lib:header_value("x-test-orig-trailer-1", headers(Response2))). -partial_upload_chunked_no_trailer() -> - Port = start(gen_tcp, [fun webserver_utils:chunked_upload/5]), - URL = url(Port, "/partial_upload_chunked_no_trailer"), - Body = [<<"This">>, <<" is ">>, <<"chunky">>, <<" stuff!">>], - Options = [{partial_upload, 1}], - {ok, UploadState1} = lhttpc:request(URL, post, [], hd(Body), 1000, Options), - {ok, Response} = lhttpc:send_body_part( - lists:foldl(fun upload_parts/2, UploadState1, tl(Body)), - http_eob - ), - ?assertEqual({200, "OK"}, status(Response)), - ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)), - ?assertEqual("This is chunky stuff!", - lhttpc_lib:header_value("x-test-orig-body", headers(Response))). +%% partial_upload_chunked_no_trailer() -> +%% Port = start(gen_tcp, [fun webserver_utils:chunked_upload/5]), +%% URL = url(Port, "/partial_upload_chunked_no_trailer"), +%% Body = [<<"This">>, <<" is ">>, <<"chunky">>, <<" stuff!">>], +%% Options = [{partial_upload, 1}], +%% ok = lhttpc:request(URL, post, [], hd(Body), 1000, Options), +%% {ok, Response} = lhttpc:send_body_part( +%% lists:foldl(fun upload_parts/2, Client, tl(Body)), +%% http_eob +%% ), +%% ?assertEqual({200, "OK"}, status(Response)), +%% ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)), +%% ?assertEqual("This is chunky stuff!", +%% lhttpc_lib:header_value("x-test-orig-body", headers(Response))). partial_download_illegal_option() -> ?assertError({bad_option, {partial_download, {foo, bar}}}, @@ -542,14 +540,13 @@ partial_download_identity() -> Port = start(gen_tcp, [fun webserver_utils:large_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1}, - {recv_proc, self()} - ], + {window_size, 1}, + {recv_proc, self()} + ], Options = [{partial_download, PartialDownload}], {ok, Client} = lhttpc:connect_client(URL, []), {ok, {Status, _Hdrs, partial_download}} = lhttpc:request_client(Client, URL, get, [], <<>>, 1000, Options), - ok = lhttpc:get_body_part(Client), Body = read_partial_body(Client), ?assertEqual({200, "OK"}, Status), ?assertEqual(size(long_body_part(3)), size(Body)), @@ -563,8 +560,10 @@ partial_download_infinity_window() -> {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], - {ok, {Status, _, Pid}} = lhttpc:request(URL, get, [], <<>>, 1000, Options), - Body = read_partial_body(Pid), + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, {Status, _Hdrs, partial_download}} = + lhttpc:request_client(Client, URL, get, [], <<>>, 1000, Options), + Body = read_partial_body(Client), ?assertEqual({200, "OK"}, Status), ?assertEqual(long_body_part(3), Body). @@ -572,11 +571,14 @@ partial_download_no_content_length() -> Port = start(gen_tcp, [fun webserver_utils:no_content_length/5]), URL = url(Port, "/no_cl"), PartialDownload = [ - {window_size, 1} + {window_size, 1}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], - {ok, {Status, _, Pid}} = lhttpc:request(URL, get, [], <<>>, 1000, Options), - Body = read_partial_body(Pid), + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, {Status, _Hdrs, partial_download}} = + lhttpc:request_client(Client, URL, get, [], <<>>, 1000, Options), + Body = read_partial_body(Client), ?assertEqual({200, "OK"}, Status), ?assertEqual(list_to_binary(webserver_utils:default_string()), Body). @@ -584,11 +586,13 @@ partial_download_no_content() -> Port = start(gen_tcp, [fun webserver_utils:no_content_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1} + {window_size, 1}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], - {ok, {Status, _, Body}} = - lhttpc:request(URL, get, [], <<>>, 1000, Options), + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, {Status, _Hdrs, Body}} = + lhttpc:request_client(Client, URL, get, [], <<>>, 1000, Options), ?assertEqual({204, "OK"}, Status), ?assertEqual(undefined, Body). @@ -596,13 +600,15 @@ limited_partial_download_identity() -> Port = start(gen_tcp, [fun webserver_utils:large_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1}, - {part_size, 512} % bytes - ], + {window_size, 1}, + {part_size, 512}, % bytes + {recv_proc, self()} + ], Options = [{partial_download, PartialDownload}], - {ok, {Status, _, Pid}} = - lhttpc:request(URL, get, [], <<>>, 1000, Options), - Body = read_partial_body(Pid, 512), + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, {Status, _Hdrs, partial_download}} = + lhttpc:request_client(Client, URL, get, [], <<>>, 1000, Options), + Body = read_partial_body(Client, 512), ?assertEqual({200, "OK"}, Status), ?assertEqual(long_body_part(3), Body). @@ -610,13 +616,15 @@ partial_download_chunked() -> Port = start(gen_tcp, [fun webserver_utils:large_chunked_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1}, - {part_size, length(webserver_utils:long_body_part()) * 3} + {window_size, 1}, + {part_size, length(webserver_utils:long_body_part()) * 3}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], - {ok, {Status, _, Pid}} = - lhttpc:request(URL, get, [], <<>>, 1000, Options), - Body = read_partial_body(Pid), + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, {Status, _Hdrs, partial_download}} = + lhttpc:request_client(Client, URL, get, [], <<>>, 1000, Options), + Body = read_partial_body(Client), ?assertEqual({200, "OK"}, Status), ?assertEqual(long_body_part(3), Body). @@ -624,27 +632,32 @@ partial_download_chunked_infinite_part() -> Port = start(gen_tcp, [fun webserver_utils:large_chunked_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1}, - {part_size, infinity} + {window_size, 1}, + {part_size, infinity}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], - {ok, {Status, _, Pid}} = - lhttpc:request(URL, get, [], <<>>, 1000, Options), - Body = read_partial_body(Pid), + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, {Status, _Hdrs, partial_download}} = + lhttpc:request_client(Client, URL, get, [], <<>>, 1000, Options), + Body = read_partial_body(Client), ?assertEqual({200, "OK"}, Status), + ?assertEqual(size(long_body_part(3)), size(Body)), ?assertEqual(long_body_part(3), Body). partial_download_smallish_chunks() -> Port = start(gen_tcp, [fun webserver_utils:large_chunked_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1}, - {part_size, length(webserver_utils:long_body_part()) - 1} + {window_size, 1}, + {part_size, length(webserver_utils:long_body_part()) - 1}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], - {ok, {Status, _, Pid}} = - lhttpc:request(URL, get, [], <<>>, 1000, Options), - Body = read_partial_body(Pid), + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, {Status, _Hdrs, partial_download}} = + lhttpc:request_client(Client, URL, get, [], <<>>, 1000, Options), + Body = read_partial_body(Client), ?assertEqual({200, "OK"}, Status), ?assertEqual(long_body_part(3), Body). @@ -652,12 +665,15 @@ partial_download_slow_chunks() -> Port = start(gen_tcp, [fun webserver_utils:slow_chunked_response/5]), URL = url(Port, "/slow"), PartialDownload = [ - {window_size, 1}, - {part_size, length(webserver_utils:long_body_part()) div 2} + {window_size, 1}, + {part_size, length(webserver_utils:long_body_part()) div 2}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], - {ok, {Status, _, Pid}} = lhttpc:request(URL, get, [], <<>>, 1000, Options), - Body = read_partial_body(Pid), + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, {Status, _Hdrs, partial_download}} = + lhttpc:request_client(Client, URL, get, [], <<>>, 1000, Options), + Body = read_partial_body(Client), ?assertEqual({200, "OK"}, Status), ?assertEqual(long_body_part(2), Body). @@ -725,10 +741,10 @@ invalid_options() -> %%% Helpers functions - -upload_parts(BodyPart, CurrentState) -> - {ok, NextState} = lhttpc:send_body_part(CurrentState, BodyPart, 1000), - NextState. +upload_parts(Client, Parts) -> + lists:foldl(fun(BodyPart, _) -> + lhttpc:send_body_part(Client, BodyPart, 1000) + end, ok, Parts). read_partial_body(Client) -> read_partial_body(Client, infinity, <<>>). @@ -739,7 +755,9 @@ read_partial_body(Client, Size) -> read_partial_body(Client, Size, Acc) -> receive {body_part, http_eob} -> - Acc; + %% chunked download + ok = lhttpc:get_body_part(Client), + read_partial_body(Client, Size, Acc); {body_part, window_finished} -> ok = lhttpc:get_body_part(Client), read_partial_body(Client, Size, Acc); @@ -756,7 +774,7 @@ read_partial_body(Client, Size, Acc) -> {body_part_error, Reason} -> {error, Reason, Acc} after - 100 -> + 1000 -> {error, receive_clause, Acc} end. From d5efe12bf0a586900341b746c423d30282e0abd2 Mon Sep 17 00:00:00 2001 From: Alexej Tessaro Date: Fri, 25 Jan 2013 16:15:39 +0000 Subject: [PATCH 20/49] some lhttpc unit tests fixed --- test/lhttpc_tests.erl | 59 +++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index 174d03e1..40b7594c 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -83,14 +83,14 @@ tcp_test_() -> ?_test(request_timeout()), ?_test(connection_timeout()), ?_test(suspended_manager()), - % ?_test(chunked_encoding()), + ?_test(chunked_encoding()), ?_test(partial_upload_identity()), ?_test(partial_upload_identity_iolist()), - % ?_test(partial_upload_chunked()), - % ?_test(partial_upload_chunked_no_trailer()), + ?_test(partial_upload_chunked()), + ?_test(partial_upload_chunked_no_trailer()), ?_test(partial_download_illegal_option()), ?_test(partial_download_identity()), - ?_test(partial_download_infinity_window()), + ?_test(partial_download_infinity_window()), ?_test(partial_download_no_content_length()), ?_test(partial_download_no_content()), ?_test(limited_partial_download_identity()), @@ -395,7 +395,7 @@ request_timeout() -> connection_timeout() -> Port = start(gen_tcp, [fun webserver_utils:simple_response/5, fun webserver_utils:simple_response/5]), URL = url(Port, "/close_conn"), - lhttpc:add_pool(lhttpc_manager), + {ok, _PoolManager} = lhttpc:add_pool(lhttpc_manager), lhttpc_manager:update_connection_timeout(lhttpc_manager, 50), % very short keep alive {ok, Response} = lhttpc:request(URL, get, [], 100), ?assertEqual({200, "OK"}, status(Response)), @@ -424,12 +424,13 @@ suspended_manager() -> chunked_encoding() -> Port = start(gen_tcp, [fun webserver_utils:chunked_response/5, fun webserver_utils:chunked_response_t/5]), URL = url(Port, "/chunked"), - {ok, FirstResponse} = lhttpc:request(URL, get, [], 50), + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, FirstResponse} = lhttpc:request_client(Client, URL, get, [], 1000), ?assertEqual({200, "OK"}, status(FirstResponse)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(FirstResponse)), ?assertEqual("chunked", lhttpc_lib:header_value("transfer-encoding", headers(FirstResponse))), - {ok, SecondResponse} = lhttpc:request(URL, get, [], 50), + {ok, SecondResponse} = lhttpc:request_client(Client, URL, get, [], 1000), ?assertEqual({200, "OK"}, status(SecondResponse)), ?assertEqual(<<"Again, great success!">>, body(SecondResponse)), ?assertEqual("ChUnKeD", lhttpc_lib:header_value("transfer-encoding", @@ -501,7 +502,7 @@ partial_upload_chunked() -> lhttpc_lib:header_value("x-test-orig-trailer-1", headers(Response1))), % Make sure it works with no body part in the original request as well Headers = [{"Transfer-Encoding", "chunked"}], - {ok, UploadState2} = lhttpc:request(URL, post, Headers, [], 1000, Options), + {ok, partial_upload} = lhttpc:request_client(Client, URL, post, Headers, [], 1000, Options), upload_parts(Client, Body), {ok, Response2} = lhttpc:send_trailers(Client, [Trailer]), ?assertEqual({200, "OK"}, status(Response2)), @@ -511,20 +512,22 @@ partial_upload_chunked() -> ?assertEqual(element(2, Trailer), lhttpc_lib:header_value("x-test-orig-trailer-1", headers(Response2))). -%% partial_upload_chunked_no_trailer() -> -%% Port = start(gen_tcp, [fun webserver_utils:chunked_upload/5]), -%% URL = url(Port, "/partial_upload_chunked_no_trailer"), -%% Body = [<<"This">>, <<" is ">>, <<"chunky">>, <<" stuff!">>], -%% Options = [{partial_upload, 1}], -%% ok = lhttpc:request(URL, post, [], hd(Body), 1000, Options), -%% {ok, Response} = lhttpc:send_body_part( -%% lists:foldl(fun upload_parts/2, Client, tl(Body)), -%% http_eob -%% ), -%% ?assertEqual({200, "OK"}, status(Response)), -%% ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)), -%% ?assertEqual("This is chunky stuff!", -%% lhttpc_lib:header_value("x-test-orig-body", headers(Response))). +partial_upload_chunked_no_trailer() -> + Port = start(gen_tcp, [fun webserver_utils:chunked_upload/5]), + URL = url(Port, "/partial_upload_chunked_no_trailer"), + Body = [<<"This">>, <<" is ">>, <<"chunky">>, <<" stuff!">>], + Options = [{partial_upload, true}], + {ok, Client} = lhttpc:connect_client(URL, []), + {ok, partial_upload} = lhttpc:request_client(Client, URL, post, [], hd(Body), 1000, Options), + + ok = upload_parts(Client, tl(Body)), + + {ok, Response} = lhttpc:send_body_part(Client, http_eob, 1000), + + ?assertEqual({200, "OK"}, status(Response)), + ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)), + ?assertEqual("This is chunky stuff!", + lhttpc_lib:header_value("x-test-orig-body", headers(Response))). partial_download_illegal_option() -> ?assertError({bad_option, {partial_download, {foo, bar}}}, @@ -686,14 +689,15 @@ close_connection() -> ssl_get() -> Port = start(ssl, [fun webserver_utils:simple_response/5]), URL = ssl_url(Port, "/simple"), - {ok, Response} = lhttpc:request(URL, "GET", [], 1000), + {ok, _PoolManager} = lhttpc:add_pool(lhttpc_manager), + {ok, Response} = lhttpc:request(URL, "GET", [], [], 1000, [{pool_options, [{pool_ensure, true}, {pool, lhttpc_manager}]}]), ?assertEqual({200, "OK"}, status(Response)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). ssl_get_ipv6() -> Port = start(ssl, [fun webserver_utils:simple_response/5], inet6), URL = ssl_url(inet6, Port, "/simple"), - {ok, Response} = lhttpc:request(URL, "GET", [], 1000), + {ok, Response} = lhttpc:request(URL, "GET", [], [], 1000, [{pool_options, [{pool_ensure, true}, {pool, lhttpc_manager}]}]), ?assertEqual({200, "OK"}, status(Response)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). @@ -702,21 +706,22 @@ ssl_post() -> URL = ssl_url(Port, "/simple"), Body = "SSL Test Port = start(ssl, [fun webserver_utils:chunked_response/5, fun webserver_utils:chunked_response_t/5]), URL = ssl_url(Port, "/ssl_chunked"), - FirstResult = lhttpc:request(URL, get, [], 100), + {ok, Client} = lhttpc:connect_client(URL, []), + FirstResult = lhttpc:request_client(Client, URL, get, [], [], 100, [{pool_options, [{pool_ensure, true}, {pool, lhttpc_manager}]}]), ?assertMatch({ok, _}, FirstResult), {ok, FirstResponse} = FirstResult, ?assertEqual({200, "OK"}, status(FirstResponse)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(FirstResponse)), ?assertEqual("chunked", lhttpc_lib:header_value("transfer-encoding", headers(FirstResponse))), - SecondResult = lhttpc:request(URL, get, [], 100), + SecondResult = lhttpc:request_client(Client, URL, get, [], [], 100, [{pool_options, [{pool_ensure, true}, {pool, lhttpc_manager}]}]), {ok, SecondResponse} = SecondResult, ?assertEqual({200, "OK"}, status(SecondResponse)), ?assertEqual(<<"Again, great success!">>, body(SecondResponse)), From ef95f8c05ec4e3765aac6f1719e3a7fa2496ff20 Mon Sep 17 00:00:00 2001 From: Lastres Date: Fri, 25 Jan 2013 15:49:42 +0000 Subject: [PATCH 21/49] Add cookie handling. It just extracts the cookies from the response headers and stores them in the State. Still cookies need to be added to following request headers. --- include/lhttpc.hrl | 6 +++++ src/lhttpc.erl | 2 +- src/lhttpc_client.erl | 5 +++- src/lhttpc_lib.erl | 57 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/include/lhttpc.hrl b/include/lhttpc.hrl index 331b9ad1..d2177c42 100644 --- a/include/lhttpc.hrl +++ b/include/lhttpc.hrl @@ -32,3 +32,9 @@ user = "" :: string(), password = "" :: string() }). + +-record(lhttpc_cookie, { + name = "" :: string(), + value = "" :: string(), + expires = never :: string() | atom() +}). diff --git a/src/lhttpc.erl b/src/lhttpc.erl index bb4d4e03..39a996b8 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -505,7 +505,7 @@ request(Host, Port, Ssl, Path, Method, Hdrs, Body, Timeout, Options) -> disconnect_client(Client), {error, timeout} end; - {error, {timeout, Reason}} -> + {error, {timeout, _Reason}} -> {error, connection_timeout} end. diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 1d9b2876..d03293b2 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -74,6 +74,7 @@ method :: string(), request_headers :: headers(), requester, + cookies = [] :: [#lhttpc_cookie{}], partial_upload = false :: boolean(), chunked_upload = false :: boolean(), partial_download = false :: boolean(), @@ -525,10 +526,12 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> {{error, Reason}, NewState#client_state{socket = undefined}}; _ -> NewHdrs = element(2, Reply), + NewCookies = [lhttpc_lib:get_cookies(NewHdrs) ++ State#client_state.cookies], ReqHdrs = State#client_state.request_headers, NewSocket = maybe_close_socket(State, Vsn, ReqHdrs, NewHdrs), {reply, {ok, Reply}, NewState#client_state{socket = NewSocket, - request = undefined}} + request = undefined, + cookies = NewCookies}} end; {error, closed} -> %% TODO does it work for partial uploads? I think should return an error diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index a83123b1..a074ea39 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -27,8 +27,9 @@ %%------------------------------------------------------------------------------ %%% @private %%% @author Oscar Hellström +%%% @author Ramon Lastres Guerrero %%% @doc -%%% This module implements various library functions used in lhttpc. +%%% This module implements various library functions used in lhttpc %%------------------------------------------------------------------------------ -module(lhttpc_lib). @@ -38,7 +39,8 @@ normalize_method/1, maybe_atom_to_list/1, format_hdrs/1, - dec/1 + dec/1, + get_cookies/1 ]). -include("lhttpc_types.hrl"). @@ -192,14 +194,59 @@ format_hdrs(Headers) -> NormalizedHeaders = normalize_headers(Headers), format_hdrs(NormalizedHeaders, []). +%%------------------------------------------------------------------------------ +%% @doc From a list of headers returned by the server, it returns a list of +%% cookie records, one record for each set-cookie line on the headers. +%% @end +%%------------------------------------------------------------------------------ +get_cookies(Hdrs) -> + Values = [Value || {"Set-Cookie", Value} <- Hdrs], + lists:map(fun create_cookie_record/1, Values). + + %%============================================================================== %% Internal functions %%============================================================================== %%------------------------------------------------------------------------------ %% @private -%% @doc -%% @end +%%------------------------------------------------------------------------------ +create_cookie_record(Cookie) -> + [NameValue | Rest] = string:tokens(Cookie, ";"), + Tokens = string:tokens(NameValue, "="), + {Atr, AtrValue} = case length(Tokens) of + 2 -> + [Name | [Value]] = Tokens, + {Name, Value}; + _ -> + [Name | _] = Tokens, + Length = length(Name) + 2, + Value = string:substr(NameValue, Length), + {Name, Value} + end, + CookieRec = #lhttpc_cookie{name = Atr, + value = AtrValue}, + other_cookie_elements(Rest, CookieRec). + +%%------------------------------------------------------------------------------ +%% @private +%%------------------------------------------------------------------------------ +other_cookie_elements([], Cookie) -> + Cookie; +% sometimes seems that the E is a capital letter... +other_cookie_elements([" Expires" ++ Value | Rest], Cookie) -> + "=" ++ FinalValue = Value, + other_cookie_elements(Rest, Cookie#lhttpc_cookie{expires = FinalValue}); +% ...sometimes it is not. +other_cookie_elements([" expires" ++ Value | Rest], Cookie) -> + "=" ++ FinalValue = Value, + other_cookie_elements(Rest, Cookie#lhttpc_cookie{expires = FinalValue}); +% for the moment we ignore the other attributes. +other_cookie_elements([_Element | Rest], Cookie) -> + other_cookie_elements(Rest, Cookie). + +%%------------------------------------------------------------------------------ +%% @private %%------------------------------------------------------------------------------ split_scheme("http://" ++ HostPortPath) -> {http, HostPortPath}; @@ -208,8 +255,6 @@ split_scheme("https://" ++ HostPortPath) -> %%------------------------------------------------------------------------------ %% @private -%% @doc -%% @end %%------------------------------------------------------------------------------ split_credentials(CredsHostPortPath) -> case string:tokens(CredsHostPortPath, "@") of From 9b76b9597a84e7e4f53431145269c968d0a78c1f Mon Sep 17 00:00:00 2001 From: Lastres Date: Fri, 25 Jan 2013 17:36:16 +0000 Subject: [PATCH 22/49] Include cookies in the request. Still need to: - Add option for using cookies or not - Handle the path attribute on the cookie line. - Handle the expire time of the cookies. - Rewrite cookies with same name, not add them. --- src/lhttpc_client.erl | 4 ++-- src/lhttpc_lib.erl | 45 ++++++++++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index d03293b2..34e5119d 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -175,7 +175,7 @@ init({Destination, Options}) -> %%------------------------------------------------------------------------------ handle_call({request, Path, Method, Hdrs, Body, Options}, From, State = #client_state{ssl = Ssl, host = Host, port = Port, - socket = Socket}) -> + socket = Socket, cookies = Cookies}) -> PartialUpload = proplists:get_value(partial_upload, Options, false), PartialDownload = proplists:is_defined(partial_download, Options), PartialDownloadOptions = proplists:get_value(partial_download, Options, []), @@ -192,7 +192,7 @@ handle_call({request, Path, Method, Hdrs, Body, Options}, From, end, {ChunkedUpload, Request} = lhttpc_lib:format_request( Path, NormalizedMethod, - Hdrs, Host, Port, Body, PartialUpload), + Hdrs, Host, Port, Body, PartialUpload, Cookies), NewState = State#client_state{ method = NormalizedMethod, request = Request, diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index a074ea39..f56a0745 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -34,7 +34,7 @@ -module(lhttpc_lib). -export([parse_url/1, - format_request/7, + format_request/8, header_value/2, header_value/3, normalize_method/1, maybe_atom_to_list/1, @@ -149,9 +149,9 @@ parse_url(URL) -> %% @end %%------------------------------------------------------------------------------ -spec format_request(iolist(), method(), headers(), string(), - integer(), iolist(), boolean()) -> {boolean(), iolist()}. -format_request(Path, Method, Hdrs, Host, Port, Body, PartialUpload) -> - AllHdrs = add_mandatory_hdrs(Method, Hdrs, Host, Port, Body, PartialUpload), + integer(), iolist(), boolean(), [#lhttpc_cookie{}]) -> {boolean(), iolist()}. +format_request(Path, Method, Hdrs, Host, Port, Body, PartialUpload, Cookies) -> + AllHdrs = add_mandatory_hdrs(Method, Hdrs, Host, Port, Body, PartialUpload, Cookies), IsChunked = is_chunked(AllHdrs), { IsChunked, @@ -398,15 +398,40 @@ format_body(Body, true) -> %% @end %%------------------------------------------------------------------------------ -spec add_mandatory_hdrs(method(), headers(), host(), port_num(), - iolist(), boolean()) -> headers(). -add_mandatory_hdrs(Method, Hdrs, Host, Port, Body, PartialUpload) -> + iolist(), boolean(), [#lhttpc_cookie{}]) -> headers(). +add_mandatory_hdrs(Method, Hdrs, Host, Port, Body, PartialUpload, Cookies) -> ContentHdrs = add_content_headers(Method, Hdrs, Body, PartialUpload), - add_host(ContentHdrs, Host, Port). + FinalHdrs = add_cookie_headers(ContentHdrs, Cookies), + add_host(FinalHdrs, Host, Port). + +%%------------------------------------------------------------------------------ +%% @private +%%------------------------------------------------------------------------------ +add_cookie_headers(Hdrs, []) -> + Hdrs; +add_cookie_headers(Hdrs, Cookies) -> + CookieString = make_cookie_string(Cookies, []), + [{"Cookie", CookieString} | Hdrs]. + +%%------------------------------------------------------------------------------ +%% @private +%%------------------------------------------------------------------------------ +make_cookie_string([], Acc) -> + Acc; +make_cookie_string([Cookie | []], Acc) -> + Last = cookie_string(Cookie) -- "; ", + make_cookie_string([], Acc ++ Last); +make_cookie_string([Cookie | Rest], Acc) -> + make_cookie_string(Rest, Acc ++ cookie_string(Cookie)). + +%%------------------------------------------------------------------------------ +%% @private +%%------------------------------------------------------------------------------ +cookie_string(#lhttpc_cookie{name = Name, value = Value}) -> + Name ++ "=" ++ Value ++ "; ". %%------------------------------------------------------------------------------ %% @private -%% @doc -%% @end %%------------------------------------------------------------------------------ -spec add_content_headers(string(), headers(), iolist(), boolean()) -> headers(). add_content_headers("POST", Hdrs, Body, PartialUpload) -> @@ -420,8 +445,6 @@ add_content_headers(_, Hdrs, _, _PartialUpload) -> %%------------------------------------------------------------------------------ %% @private -%% @doc -%% @end %%------------------------------------------------------------------------------ -spec add_content_headers(headers(), iolist(), boolean()) -> headers(). add_content_headers(Hdrs, Body, false) -> From a1a74a2d5ba303d9c83377e5729b4643d0c15c57 Mon Sep 17 00:00:00 2001 From: Lastres Date: Mon, 28 Jan 2013 13:32:46 +0000 Subject: [PATCH 23/49] Reewrite cookies with the same name instead of rewriting them, check path to include cookie when creating request. --- include/lhttpc.hrl | 3 ++- src/lhttpc_client.erl | 16 ++++++++++++---- src/lhttpc_lib.erl | 20 ++++++++++++++++---- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/include/lhttpc.hrl b/include/lhttpc.hrl index d2177c42..d931793f 100644 --- a/include/lhttpc.hrl +++ b/include/lhttpc.hrl @@ -36,5 +36,6 @@ -record(lhttpc_cookie, { name = "" :: string(), value = "" :: string(), - expires = never :: string() | atom() + expires = never :: string() | atom(), + path = undefined :: string() | atom() }). diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 34e5119d..88a67dc0 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -42,7 +42,8 @@ send_body_part/3, send_trailers/3, get_body_part/2, - stop/1]). + stop/1 + ]). %% gen_server callbacks -export([ @@ -525,13 +526,20 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> {error, Reason} -> {{error, Reason}, NewState#client_state{socket = undefined}}; _ -> - NewHdrs = element(2, Reply), - NewCookies = [lhttpc_lib:get_cookies(NewHdrs) ++ State#client_state.cookies], + NewHdrs = element(2, Reply), + NewCookies = lhttpc_lib:get_cookies(NewHdrs), + Names = [ X#lhttpc_cookie.name || X <- NewCookies], + A = fun(List) -> + fun(X) -> + length(List) =:= length(lists:usort(List -- [X#lhttpc_cookie.name])) end + end, + OldCookies = lists:filter(A(Names), State#client_state.cookies), + FinalCookies = NewCookies ++ OldCookies, ReqHdrs = State#client_state.request_headers, NewSocket = maybe_close_socket(State, Vsn, ReqHdrs, NewHdrs), {reply, {ok, Reply}, NewState#client_state{socket = NewSocket, request = undefined, - cookies = NewCookies}} + cookies = FinalCookies}} end; {error, closed} -> %% TODO does it work for partial uploads? I think should return an error diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index f56a0745..90db9b5e 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -151,7 +151,7 @@ parse_url(URL) -> -spec format_request(iolist(), method(), headers(), string(), integer(), iolist(), boolean(), [#lhttpc_cookie{}]) -> {boolean(), iolist()}. format_request(Path, Method, Hdrs, Host, Port, Body, PartialUpload, Cookies) -> - AllHdrs = add_mandatory_hdrs(Method, Hdrs, Host, Port, Body, PartialUpload, Cookies), + AllHdrs = add_mandatory_hdrs(Path, Method, Hdrs, Host, Port, Body, PartialUpload, Cookies), IsChunked = is_chunked(AllHdrs), { IsChunked, @@ -199,6 +199,7 @@ format_hdrs(Headers) -> %% cookie records, one record for each set-cookie line on the headers. %% @end %%------------------------------------------------------------------------------ +-spec get_cookies(headers()) -> [#lhttpc_cookie{}]. get_cookies(Hdrs) -> Values = [Value || {"Set-Cookie", Value} <- Hdrs], lists:map(fun create_cookie_record/1, Values). @@ -229,6 +230,9 @@ create_cookie_record(Cookie) -> other_cookie_elements(Rest, CookieRec). %%------------------------------------------------------------------------------ +%% @doc Extracts the interesting fields from the cookie in the header. We ignore +%% the domain since the client only connects to one domain at the same time. +%% @end %% @private %%------------------------------------------------------------------------------ other_cookie_elements([], Cookie) -> @@ -241,6 +245,12 @@ other_cookie_elements([" Expires" ++ Value | Rest], Cookie) -> other_cookie_elements([" expires" ++ Value | Rest], Cookie) -> "=" ++ FinalValue = Value, other_cookie_elements(Rest, Cookie#lhttpc_cookie{expires = FinalValue}); +other_cookie_elements([" Path" ++ Value | Rest], Cookie) -> + "=" ++ FinalValue = Value, + other_cookie_elements(Rest, Cookie#lhttpc_cookie{path = FinalValue}); +other_cookie_elements([" path" ++ Value | Rest], Cookie) -> + "=" ++ FinalValue = Value, + other_cookie_elements(Rest, Cookie#lhttpc_cookie{path = FinalValue}); % for the moment we ignore the other attributes. other_cookie_elements([_Element | Rest], Cookie) -> other_cookie_elements(Rest, Cookie). @@ -397,11 +407,13 @@ format_body(Body, true) -> %% @doc %% @end %%------------------------------------------------------------------------------ --spec add_mandatory_hdrs(method(), headers(), host(), port_num(), +-spec add_mandatory_hdrs(string(), method(), headers(), host(), port_num(), iolist(), boolean(), [#lhttpc_cookie{}]) -> headers(). -add_mandatory_hdrs(Method, Hdrs, Host, Port, Body, PartialUpload, Cookies) -> +add_mandatory_hdrs(Path, Method, Hdrs, Host, Port, Body, PartialUpload, Cookies) -> ContentHdrs = add_content_headers(Method, Hdrs, Body, PartialUpload), - FinalHdrs = add_cookie_headers(ContentHdrs, Cookies), + % only include cookies if the path matches. + IncludeCookies = [ X || X <- Cookies, X#lhttpc_cookie.path =:= Path], + FinalHdrs = add_cookie_headers(ContentHdrs, IncludeCookies), add_host(FinalHdrs, Host, Port). %%------------------------------------------------------------------------------ From 049d2e02743e7563939ba7189cac096ebff1e7c0 Mon Sep 17 00:00:00 2001 From: Lastres Date: Mon, 28 Jan 2013 14:30:00 +0000 Subject: [PATCH 24/49] Include option to automatically handle cookies or not {use_cookies, true | false}. --- include/lhttpc_types.hrl | 1 + src/lhttpc_client.erl | 32 +++++++++++++++++++++----------- src/lhttpc_lib.erl | 17 +++++++++++------ 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/include/lhttpc_types.hrl b/include/lhttpc_types.hrl index 101997ba..e57427d2 100644 --- a/include/lhttpc_types.hrl +++ b/include/lhttpc_types.hrl @@ -73,6 +73,7 @@ {'partial_upload', non_neg_integer() | 'infinity'} | {'partial_download', [partial_download_option()]} | {'connect_options', socket_options()} | + {'use_cookies', boolean()} | {'proxy', string()} | {'proxy_ssl_options', socket_options()} | {'pool_options', pool_options()} | diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 88a67dc0..4b745462 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -76,6 +76,7 @@ request_headers :: headers(), requester, cookies = [] :: [#lhttpc_cookie{}], + use_cookies = false :: boolean(), partial_upload = false :: boolean(), chunked_upload = false :: boolean(), partial_download = false :: boolean(), @@ -143,8 +144,9 @@ init({Destination, Options}) -> ssl = Ssl, pool = Pool, connect_timeout = proplists:get_value(connect_timeout, Options, - infinity), + infinity), connect_options = proplists:get_value(connect_options, Options, []), + use_cookies = proplists:get_value(use_cookies, Options, false), pool_options = PoolOptions}; URL -> #lhttpc_url{host = Host, @@ -158,6 +160,7 @@ init({Destination, Options}) -> connect_timeout = proplists:get_value(connect_timeout, Options, infinity), connect_options = proplists:get_value(connect_options, Options, []), + use_cookies = proplists:get_value(use_cookies, Options, false), pool_options = PoolOptions} end, %% Get a socket for the pool or exit @@ -176,7 +179,8 @@ init({Destination, Options}) -> %%------------------------------------------------------------------------------ handle_call({request, Path, Method, Hdrs, Body, Options}, From, State = #client_state{ssl = Ssl, host = Host, port = Port, - socket = Socket, cookies = Cookies}) -> + socket = Socket, cookies = Cookies, + use_cookies = UseCookies}) -> PartialUpload = proplists:get_value(partial_upload, Options, false), PartialDownload = proplists:is_defined(partial_download, Options), PartialDownloadOptions = proplists:get_value(partial_download, Options, []), @@ -193,7 +197,7 @@ handle_call({request, Path, Method, Hdrs, Body, Options}, From, end, {ChunkedUpload, Request} = lhttpc_lib:format_request( Path, NormalizedMethod, - Hdrs, Host, Port, Body, PartialUpload, Cookies), + Hdrs, Host, Port, Body, PartialUpload, {UseCookies, Cookies}), NewState = State#client_state{ method = NormalizedMethod, request = Request, @@ -527,14 +531,20 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> {{error, Reason}, NewState#client_state{socket = undefined}}; _ -> NewHdrs = element(2, Reply), - NewCookies = lhttpc_lib:get_cookies(NewHdrs), - Names = [ X#lhttpc_cookie.name || X <- NewCookies], - A = fun(List) -> - fun(X) -> - length(List) =:= length(lists:usort(List -- [X#lhttpc_cookie.name])) end - end, - OldCookies = lists:filter(A(Names), State#client_state.cookies), - FinalCookies = NewCookies ++ OldCookies, + case State#client_state.use_cookies of + true -> + NewCookies = lhttpc_lib:get_cookies(NewHdrs), + Names = [ X#lhttpc_cookie.name || X <- NewCookies], + A = fun(List) -> + fun(X) -> + length(List) =:= + length(lists:usort(List -- [X#lhttpc_cookie.name])) end + end, + OldCookies = lists:filter(A(Names), State#client_state.cookies), + FinalCookies = NewCookies ++ OldCookies; + _ -> + FinalCookies = [] + end, ReqHdrs = State#client_state.request_headers, NewSocket = maybe_close_socket(State, Vsn, ReqHdrs, NewHdrs), {reply, {ok, Reply}, NewState#client_state{socket = NewSocket, diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index 90db9b5e..c427684a 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -149,7 +149,7 @@ parse_url(URL) -> %% @end %%------------------------------------------------------------------------------ -spec format_request(iolist(), method(), headers(), string(), - integer(), iolist(), boolean(), [#lhttpc_cookie{}]) -> {boolean(), iolist()}. + integer(), iolist(), boolean(), {boolean(), [#lhttpc_cookie{}]}) -> {boolean(), iolist()}. format_request(Path, Method, Hdrs, Host, Port, Body, PartialUpload, Cookies) -> AllHdrs = add_mandatory_hdrs(Path, Method, Hdrs, Host, Port, Body, PartialUpload, Cookies), IsChunked = is_chunked(AllHdrs), @@ -408,12 +408,17 @@ format_body(Body, true) -> %% @end %%------------------------------------------------------------------------------ -spec add_mandatory_hdrs(string(), method(), headers(), host(), port_num(), - iolist(), boolean(), [#lhttpc_cookie{}]) -> headers(). -add_mandatory_hdrs(Path, Method, Hdrs, Host, Port, Body, PartialUpload, Cookies) -> + iolist(), boolean(), {boolean(), [#lhttpc_cookie{}]}) -> headers(). +add_mandatory_hdrs(Path, Method, Hdrs, Host, Port, Body, PartialUpload, {UseCookies, Cookies}) -> ContentHdrs = add_content_headers(Method, Hdrs, Body, PartialUpload), - % only include cookies if the path matches. - IncludeCookies = [ X || X <- Cookies, X#lhttpc_cookie.path =:= Path], - FinalHdrs = add_cookie_headers(ContentHdrs, IncludeCookies), + case UseCookies of + true -> + % only include cookies if the path matches. + IncludeCookies = [ X || X <- Cookies, X#lhttpc_cookie.path =:= Path], + FinalHdrs = add_cookie_headers(ContentHdrs, IncludeCookies); + _ -> + FinalHdrs = ContentHdrs + end, add_host(FinalHdrs, Host, Port). %%------------------------------------------------------------------------------ From 7d2bf873744e3b3e74abe7a6f2a3964403520f2c Mon Sep 17 00:00:00 2001 From: Lastres Date: Mon, 28 Jan 2013 17:46:05 +0000 Subject: [PATCH 25/49] Include support for Expires and Max-Age attributes in cookie handling. Cookies are automatically deleted when they expire or return "delete" as their value. --- include/lhttpc.hrl | 7 +++- src/lhttpc_client.erl | 21 ++++------ src/lhttpc_lib.erl | 96 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 104 insertions(+), 20 deletions(-) diff --git a/include/lhttpc.hrl b/include/lhttpc.hrl index d931793f..f7b69c9a 100644 --- a/include/lhttpc.hrl +++ b/include/lhttpc.hrl @@ -36,6 +36,9 @@ -record(lhttpc_cookie, { name = "" :: string(), value = "" :: string(), - expires = never :: string() | atom(), - path = undefined :: string() | atom() + expires = 'never' :: {{integer(), integer(), integer()}, + {integer(), integer(), integer()}} | atom(), + path = 'undefined' :: string() | atom(), + max_age = 'undefined' :: integer() | atom(), + timestamp = 'undefined' :: atom() | {integer(), integer(), integer()} }). diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 4b745462..98b838ac 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -531,20 +531,13 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> {{error, Reason}, NewState#client_state{socket = undefined}}; _ -> NewHdrs = element(2, Reply), - case State#client_state.use_cookies of - true -> - NewCookies = lhttpc_lib:get_cookies(NewHdrs), - Names = [ X#lhttpc_cookie.name || X <- NewCookies], - A = fun(List) -> - fun(X) -> - length(List) =:= - length(lists:usort(List -- [X#lhttpc_cookie.name])) end - end, - OldCookies = lists:filter(A(Names), State#client_state.cookies), - FinalCookies = NewCookies ++ OldCookies; - _ -> - FinalCookies = [] - end, + FinalCookies = + case State#client_state.use_cookies of + true -> + lhttpc_lib:update_cookies(NewHdrs, State#client_state.cookies); + _ -> + [] + end, ReqHdrs = State#client_state.request_headers, NewSocket = maybe_close_socket(State, Vsn, ReqHdrs, NewHdrs), {reply, {ok, Reply}, NewState#client_state{socket = NewSocket, diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index c427684a..8958a07b 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -40,7 +40,8 @@ maybe_atom_to_list/1, format_hdrs/1, dec/1, - get_cookies/1 + get_cookies/1, + update_cookies/2 ]). -include("lhttpc_types.hrl"). @@ -204,11 +205,48 @@ get_cookies(Hdrs) -> Values = [Value || {"Set-Cookie", Value} <- Hdrs], lists:map(fun create_cookie_record/1, Values). +%%------------------------------------------------------------------------------ +%% @private +%% @doc Updated the state of the cookies. after we receive a response. +%% @end +%%------------------------------------------------------------------------------ +-spec update_cookies(headers(), [#lhttpc_cookie{}]) -> [#lhttpc_cookie{}]. +update_cookies(RespHeaders, StateCookies) -> + ReceivedCookies = lhttpc_lib:get_cookies(RespHeaders), + %substitute the cookies with the same name, add the others. + Names = [ X#lhttpc_cookie.name || X <- ReceivedCookies], + A = fun(List) -> + fun(X) -> + length(List) =:= + length(lists:usort(List -- [X#lhttpc_cookie.name])) end + end, + OldCookies = lists:filter(A(Names), StateCookies), + FinalCookies = ReceivedCookies ++ OldCookies, + %delete the cookies whose value is set to "deleted" + DeleteCookies = [ X#lhttpc_cookie.name || X <- ReceivedCookies, X#lhttpc_cookie.name =:= "deleted"], + NewCookies = FinalCookies -- DeleteCookies, + %Delete the cookies that are expired (check max-age and expire fields). + delete_expired_cookies(NewCookies). %%============================================================================== %% Internal functions %%============================================================================== +%%------------------------------------------------------------------------------ +%% @private +%%------------------------------------------------------------------------------ +-spec delete_expired_cookies([#lhttpc_cookie{}]) -> [#lhttpc_cookie{}]. +delete_expired_cookies(Cookies) -> + MaxAges = [ X || X <- Cookies, X#lhttpc_cookie.max_age =/= undefined], + ToDelete1 = [ X || X <- MaxAges, + timer:now_diff(erlang:timestamp(), X#lhttpc_cookie.timestamp) > X#lhttpc_cookie.max_age], + NewCookies = Cookies -- ToDelete1, + Expires = [ X || X <- Cookies, X#lhttpc_cookie.expires =/= never], + ToDelete2 = [ X || X <- Expires, + calendar:datetime_to_gregorian_seconds(calendar:universal_time()) > + calendar:datetime_to_gregorian_seconds(X#lhttpc_cookie.expires)], + NewCookies -- ToDelete2. + %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ @@ -226,7 +264,7 @@ create_cookie_record(Cookie) -> {Name, Value} end, CookieRec = #lhttpc_cookie{name = Atr, - value = AtrValue}, + value = AtrValue}, other_cookie_elements(Rest, CookieRec). %%------------------------------------------------------------------------------ @@ -240,21 +278,71 @@ other_cookie_elements([], Cookie) -> % sometimes seems that the E is a capital letter... other_cookie_elements([" Expires" ++ Value | Rest], Cookie) -> "=" ++ FinalValue = Value, - other_cookie_elements(Rest, Cookie#lhttpc_cookie{expires = FinalValue}); + Expires = expires_to_datetime(FinalValue), + other_cookie_elements(Rest, Cookie#lhttpc_cookie{expires = Expires}); % ...sometimes it is not. other_cookie_elements([" expires" ++ Value | Rest], Cookie) -> "=" ++ FinalValue = Value, - other_cookie_elements(Rest, Cookie#lhttpc_cookie{expires = FinalValue}); + Expires = expires_to_datetime(FinalValue), + other_cookie_elements(Rest, Cookie#lhttpc_cookie{expires = Expires}); other_cookie_elements([" Path" ++ Value | Rest], Cookie) -> "=" ++ FinalValue = Value, other_cookie_elements(Rest, Cookie#lhttpc_cookie{path = FinalValue}); other_cookie_elements([" path" ++ Value | Rest], Cookie) -> "=" ++ FinalValue = Value, other_cookie_elements(Rest, Cookie#lhttpc_cookie{path = FinalValue}); +other_cookie_elements([" Max-Age" ++ Value | Rest], Cookie) -> + "=" ++ FinalValue = Value, + {Integer, _Rest} = string:to_integer(FinalValue), + MaxAge = Integer * 1000000, %we need it in microseconds + other_cookie_elements(Rest, Cookie#lhttpc_cookie{max_age = MaxAge, + timestamp = erlang:timestamp()}); +other_cookie_elements([" max-age" ++ Value | Rest], Cookie) -> + "=" ++ FinalValue = Value, + {Integer, _Rest} = string:to_integer(FinalValue), + MaxAge = Integer * 1000000, %we need it in microseconds + other_cookie_elements(Rest, Cookie#lhttpc_cookie{max_age = MaxAge, + timestamp = erlang:timestamp()}); % for the moment we ignore the other attributes. other_cookie_elements([_Element | Rest], Cookie) -> other_cookie_elements(Rest, Cookie). +%%------------------------------------------------------------------------------ +%% @private +%% @doc Parses the string contained in the expires field of a cookie and returns +%% the date in datetime() format defined in calendar module. +%% @end +%%------------------------------------------------------------------------------ +-spec expires_to_datetime(string()) -> + {{integer(), integer(), integer()}, + {integer(),integer(),integer()}}. +expires_to_datetime(ExpireDate) -> + {Day, _} = string:to_integer(string:substr(ExpireDate, 6, 2)), + Month = month_to_integer(string:substr(ExpireDate, 9, 3)), + {Year, _} = string:to_integer(string:substr(ExpireDate, 13, 4)), + {Hour, _} = string:to_integer(string:substr(ExpireDate, 18, 2)), + {Min, _} = string:to_integer(string:substr(ExpireDate, 21, 2)), + {Sec, _} = string:to_integer(string:substr(ExpireDate, 24, 2)), + {{Year, Month, Day}, {Hour, Min, Sec}}. + + +%%------------------------------------------------------------------------------ +%% @private +%%------------------------------------------------------------------------------ +-spec month_to_integer(string()) -> integer(). +month_to_integer("Jan") -> 1; +month_to_integer("Feb") -> 2; +month_to_integer("Mar") -> 3; +month_to_integer("Apr") -> 4; +month_to_integer("May") -> 5; +month_to_integer("Jun") -> 6; +month_to_integer("Jul") -> 7; +month_to_integer("Aug") -> 8; +month_to_integer("Sep") -> 9; +month_to_integer("Oct") -> 10; +month_to_integer("Nov") -> 11; +month_to_integer("Dic") -> 12. + %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ From 750c42649b8c2b075611e73ec62f6fec917c9c20 Mon Sep 17 00:00:00 2001 From: Lastres Date: Tue, 29 Jan 2013 11:22:19 +0000 Subject: [PATCH 26/49] Add tests for the cookie handling. Fix a couple of errors when handling cookie. --- src/lhttpc_lib.erl | 5 ++-- test/lhttpc_lib_tests.erl | 40 ++++++++++++++++++++++++-- test/lhttpc_tests.erl | 29 +++++++++++++------ test/webserver_utils.erl | 60 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 13 deletions(-) diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index 8958a07b..4e6ddd36 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -223,7 +223,7 @@ update_cookies(RespHeaders, StateCookies) -> OldCookies = lists:filter(A(Names), StateCookies), FinalCookies = ReceivedCookies ++ OldCookies, %delete the cookies whose value is set to "deleted" - DeleteCookies = [ X#lhttpc_cookie.name || X <- ReceivedCookies, X#lhttpc_cookie.name =:= "deleted"], + DeleteCookies = [ X#lhttpc_cookie.name || X <- ReceivedCookies, X#lhttpc_cookie.value =:= "deleted"], NewCookies = FinalCookies -- DeleteCookies, %Delete the cookies that are expired (check max-age and expire fields). delete_expired_cookies(NewCookies). @@ -502,7 +502,8 @@ add_mandatory_hdrs(Path, Method, Hdrs, Host, Port, Body, PartialUpload, {UseCook case UseCookies of true -> % only include cookies if the path matches. - IncludeCookies = [ X || X <- Cookies, X#lhttpc_cookie.path =:= Path], + IncludeCookies = [ X || X <- Cookies, X#lhttpc_cookie.path =:= Path orelse + X#lhttpc_cookie.path =:= undefined ], FinalHdrs = add_cookie_headers(ContentHdrs, IncludeCookies); _ -> FinalHdrs = ContentHdrs diff --git a/test/lhttpc_lib_tests.erl b/test/lhttpc_lib_tests.erl index c8e135a3..9cb0f74d 100644 --- a/test/lhttpc_lib_tests.erl +++ b/test/lhttpc_lib_tests.erl @@ -1,7 +1,7 @@ %%% ---------------------------------------------------------------------------- %%% Copyright (c) 2009, Erlang Training and Consulting Ltd. %%% All rights reserved. -%%% +%%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions are met: %%% * Redistributions of source code must retain the above copyright @@ -12,7 +12,7 @@ %%% * Neither the name of Erlang Training and Consulting Ltd. nor the %%% names of its contributors may be used to endorse or promote products %%% derived from this software without specific prior written permission. -%%% +%%% %%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS'' %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -31,6 +31,21 @@ -include("../include/lhttpc.hrl"). -include_lib("eunit/include/eunit.hrl"). +-define(HEADER1, [{"X-Frame-Options","SAMEORIGIN"}, + {"X-Xss-Protection","1; mode=block"}, + {"Content-Length","221"}, + {"Server","gws"}, + {"Date","Tue, 29 Jan 2013 10:31:52 GMT"}, + {"P3p", + "CP=\"This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info.\""}, + {"Set-Cookie", + "NID=67=gWDe_1hs0LbFdFRIiHXh8qQT_oh_2T2e2tPU3su6azclQH0FGbIpkHYkZJ1kIENFScdIWsnaHd3fUL-J8dZ8YApccTTmpfAgxgCStTspaZrCBRLG0SHRiAZz-Lkj8tyk; expires=Wed, 31-Jul-2013 10:31:52 GMT; path=/; domain=.google.com; HttpOnly"}, + {"Set-Cookie", + "PREF=ID=d8f03b98b080a98a:FF=0:TM=1359455512:LM=1359455512:S=x-lfwE8swDlcyxXl; expires=Thu, 29-Jan-2015 10:31:52 GMT; path=/; domain=.google.com"}, + {"Content-Type","text/html; charset=UTF-8"}, + {"Cache-Control","private"}, + {"Location","http://www.google.co.uk/"}]). + parse_url_test_() -> [ ?_assertEqual(#lhttpc_url{ @@ -224,3 +239,24 @@ parse_url_test_() -> }, lhttpc_lib:parse_url("http://www.example.com?a=b")) ]. + +get_cookies_test_() -> + [ + ?_assertEqual([#lhttpc_cookie{ + name = "NID", + value = "67=gWDe_1hs0LbFdFRIiHXh8qQT_oh_2T2e2tPU3su6azclQH0FGbIpkHYkZJ1kIENFScdIWsnaHd3fUL-J8dZ8YApccTTmpfAgxgCStTspaZrCBRLG0SHRiAZz-Lkj8tyk", + expires = {{2013, 7, 31}, {10, 31, 52}}, + path = "/", + max_age = undefined, + timestamp = undefined + }, + #lhttpc_cookie{ + name = "PREF", + value = "ID=d8f03b98b080a98a:FF=0:TM=1359455512:LM=1359455512:S=x-lfwE8swDlcyxXl", + expires = {{2015, 1, 29}, {10, 31, 52}}, + path = "/", + max_age = undefined, + timestamp = undefined + }], + lhttpc_lib:get_cookies(?HEADER1)) + ]. diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index 40b7594c..f458db5a 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -86,11 +86,11 @@ tcp_test_() -> ?_test(chunked_encoding()), ?_test(partial_upload_identity()), ?_test(partial_upload_identity_iolist()), - ?_test(partial_upload_chunked()), - ?_test(partial_upload_chunked_no_trailer()), + ?_test(partial_upload_chunked()), + ?_test(partial_upload_chunked_no_trailer()), ?_test(partial_download_illegal_option()), ?_test(partial_download_identity()), - ?_test(partial_download_infinity_window()), + ?_test(partial_download_infinity_window()), ?_test(partial_download_no_content_length()), ?_test(partial_download_no_content()), ?_test(limited_partial_download_identity()), @@ -118,7 +118,8 @@ ssl_test_() -> other_test_() -> [ - ?_test(invalid_options()) + ?_test(invalid_options()), + ?_test(cookies()) ]. %%% Tests @@ -424,7 +425,7 @@ suspended_manager() -> chunked_encoding() -> Port = start(gen_tcp, [fun webserver_utils:chunked_response/5, fun webserver_utils:chunked_response_t/5]), URL = url(Port, "/chunked"), - {ok, Client} = lhttpc:connect_client(URL, []), + {ok, Client} = lhttpc:connect_client(URL, []), {ok, FirstResponse} = lhttpc:request_client(Client, URL, get, [], 1000), ?assertEqual({200, "OK"}, status(FirstResponse)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(FirstResponse)), @@ -519,11 +520,8 @@ partial_upload_chunked_no_trailer() -> Options = [{partial_upload, true}], {ok, Client} = lhttpc:connect_client(URL, []), {ok, partial_upload} = lhttpc:request_client(Client, URL, post, [], hd(Body), 1000, Options), - ok = upload_parts(Client, tl(Body)), - {ok, Response} = lhttpc:send_body_part(Client, http_eob, 1000), - ?assertEqual({200, "OK"}, status(Response)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)), ?assertEqual("This is chunky stuff!", @@ -689,7 +687,7 @@ close_connection() -> ssl_get() -> Port = start(ssl, [fun webserver_utils:simple_response/5]), URL = ssl_url(Port, "/simple"), - {ok, _PoolManager} = lhttpc:add_pool(lhttpc_manager), + {ok, _PoolManager} = lhttpc:add_pool(lhttpc_manager), {ok, Response} = lhttpc:request(URL, "GET", [], [], 1000, [{pool_options, [{pool_ensure, true}, {pool, lhttpc_manager}]}]), ?assertEqual({200, "OK"}, status(Response)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)). @@ -744,6 +742,19 @@ invalid_options() -> lhttpc:request("http://localhost/", get, [], <<>>, 1000, [{foo, bar}, bad_option])). +cookies() -> + Port = start(gen_tcp, [fun webserver_utils:set_cookie_response/5, fun webserver_utils:expired_cookie_response/5, + fun webserver_utils:receive_right_cookies/5]), + URL = url(Port, "/cookies"), + Options = [{use_cookies, true}], + {ok, Client} = lhttpc:connect_client(URL, Options), + {ok, Response1} = lhttpc:request_client(Client, URL, get, [], 1000), + ?assertEqual({200, "OK"}, status(Response1)), + {ok, Response2} = lhttpc:request_client(Client, URL, get, [], 1000), + ?assertEqual({200, "OK"}, status(Response2)), + {ok, Response3} = lhttpc:request_client(Client, URL, get, [], 1000), + ?assertEqual({200, "OK"}, status(Response3)). + %%% Helpers functions upload_parts(Client, Parts) -> diff --git a/test/webserver_utils.erl b/test/webserver_utils.erl index 519040c7..be67c7ac 100644 --- a/test/webserver_utils.erl +++ b/test/webserver_utils.erl @@ -368,3 +368,63 @@ trailing_space_header(Module, Socket, _, _, _) -> "Content-Length: 14 \r\n\r\n" ?DEFAULT_STRING ). + +set_cookie_response(Module, Socket, _, _, _) -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Connection: Keep-Alive\r\n" + "Set-Cookie: name=value\r\n" + "Set-Cookie: name2=value2; Expires=Wed, 09-Jun-2021 10:18:14 GMT\r\n" + "Content-type: text/plain\r\n" + "Content-length: 0\r\n\r\n" + ). + + +expired_cookie_response(Module, Socket, _Request, Headers, _Body) -> + case lhttpc_lib:header_value("Cookie", Headers) of + undefined -> + Module:send( + Socket, + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-type: text/plain\r\n" + "Content-length: 0\r\n\r\n" + ); + "name=value; name2=value2" -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Connection: Keep-Alive\r\n" + "Set-Cookie: name2=value2; Expires=Wed, 09-Jun-1975 10:18:14 GMT\r\n" + "Content-type: text/plain\r\n" + "Content-length: 0\r\n\r\n" + ); + %The order should not matter. + "name2=value2; name=value"-> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Connection: Keep-Alive\r\n" + "Set-Cookie: name2=value2; Expires=Wed, 09-Jun-1975 10:18:14 GMT\r\n" + "Content-type: text/plain\r\n" + "Content-length: 0\r\n\r\n" + ) + end. + +receive_right_cookies(Module, Socket, _Request, Headers, _Body) -> + case proplists:get_value("Cookie", Headers) of + "name=value" -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\n" + "Content-length: 0\r\n\r\n" + ); + _ -> + Module:send( + Socket, + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-type: text/plain\r\n" + "Content-length: 0\r\n\r\n" + ) + end. From a33837be82c1e813dac376761b37a48b711dcec8 Mon Sep 17 00:00:00 2001 From: Lastres Date: Tue, 29 Jan 2013 14:49:08 +0000 Subject: [PATCH 27/49] Reestructure some code in cookie handling. --- src/lhttpc_lib.erl | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index 4e6ddd36..e96e4b1f 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -215,12 +215,11 @@ update_cookies(RespHeaders, StateCookies) -> ReceivedCookies = lhttpc_lib:get_cookies(RespHeaders), %substitute the cookies with the same name, add the others. Names = [ X#lhttpc_cookie.name || X <- ReceivedCookies], - A = fun(List) -> - fun(X) -> - length(List) =:= - length(lists:usort(List -- [X#lhttpc_cookie.name])) end + A = fun(X) -> + length(Names) =:= + length(lists:usort(Names -- [X#lhttpc_cookie.name])) end, - OldCookies = lists:filter(A(Names), StateCookies), + OldCookies = lists:filter(A, StateCookies), FinalCookies = ReceivedCookies ++ OldCookies, %delete the cookies whose value is set to "deleted" DeleteCookies = [ X#lhttpc_cookie.name || X <- ReceivedCookies, X#lhttpc_cookie.value =:= "deleted"], @@ -237,14 +236,13 @@ update_cookies(RespHeaders, StateCookies) -> %%------------------------------------------------------------------------------ -spec delete_expired_cookies([#lhttpc_cookie{}]) -> [#lhttpc_cookie{}]. delete_expired_cookies(Cookies) -> - MaxAges = [ X || X <- Cookies, X#lhttpc_cookie.max_age =/= undefined], - ToDelete1 = [ X || X <- MaxAges, - timer:now_diff(erlang:timestamp(), X#lhttpc_cookie.timestamp) > X#lhttpc_cookie.max_age], - NewCookies = Cookies -- ToDelete1, - Expires = [ X || X <- Cookies, X#lhttpc_cookie.expires =/= never], - ToDelete2 = [ X || X <- Expires, - calendar:datetime_to_gregorian_seconds(calendar:universal_time()) > - calendar:datetime_to_gregorian_seconds(X#lhttpc_cookie.expires)], + ToDelete = [ X || X <- Cookies, + X#lhttpc_cookie.max_age =/= undefined andalso + timer:now_diff(erlang:timestamp(), X#lhttpc_cookie.timestamp) > X#lhttpc_cookie.max_age], + NewCookies = Cookies -- ToDelete, + ToDelete2 = [ X || X <- Cookies, X#lhttpc_cookie.expires =/= never andalso + calendar:datetime_to_gregorian_seconds(calendar:universal_time()) > + calendar:datetime_to_gregorian_seconds(X#lhttpc_cookie.expires)], NewCookies -- ToDelete2. %%------------------------------------------------------------------------------ @@ -317,13 +315,14 @@ other_cookie_elements([_Element | Rest], Cookie) -> {{integer(), integer(), integer()}, {integer(),integer(),integer()}}. expires_to_datetime(ExpireDate) -> - {Day, _} = string:to_integer(string:substr(ExpireDate, 6, 2)), - Month = month_to_integer(string:substr(ExpireDate, 9, 3)), - {Year, _} = string:to_integer(string:substr(ExpireDate, 13, 4)), - {Hour, _} = string:to_integer(string:substr(ExpireDate, 18, 2)), - {Min, _} = string:to_integer(string:substr(ExpireDate, 21, 2)), - {Sec, _} = string:to_integer(string:substr(ExpireDate, 24, 2)), - {{Year, Month, Day}, {Hour, Min, Sec}}. + [_Expires, Day, Month, Year, Hour, Min, Sec, _GMT] = string:tokens(ExpireDate, ", -:"), + {FinalDay, _} = string:to_integer(Day), + {FinalYear, _} = string:to_integer(Year), + {FinalHour, _} = string:to_integer(Hour), + {FinalMin, _} =string:to_integer(Min), + {FinalSec, _} =string:to_integer(Sec), + FinalMonth = month_to_integer(Month), + {{FinalYear, FinalMonth, FinalDay}, {FinalHour, FinalMin, FinalSec}}. %%------------------------------------------------------------------------------ From cc25437ca856e630633ec300c9a7c8a5f8d07a35 Mon Sep 17 00:00:00 2001 From: Diana Corbacho Date: Tue, 29 Jan 2013 17:41:38 +0000 Subject: [PATCH 28/49] Code refactor --- src/lhttpc_lib.erl | 47 +++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index e96e4b1f..51abb18f 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -213,18 +213,14 @@ get_cookies(Hdrs) -> -spec update_cookies(headers(), [#lhttpc_cookie{}]) -> [#lhttpc_cookie{}]. update_cookies(RespHeaders, StateCookies) -> ReceivedCookies = lhttpc_lib:get_cookies(RespHeaders), - %substitute the cookies with the same name, add the others. - Names = [ X#lhttpc_cookie.name || X <- ReceivedCookies], - A = fun(X) -> - length(Names) =:= - length(lists:usort(Names -- [X#lhttpc_cookie.name])) - end, - OldCookies = lists:filter(A, StateCookies), - FinalCookies = ReceivedCookies ++ OldCookies, - %delete the cookies whose value is set to "deleted" - DeleteCookies = [ X#lhttpc_cookie.name || X <- ReceivedCookies, X#lhttpc_cookie.value =:= "deleted"], - NewCookies = FinalCookies -- DeleteCookies, - %Delete the cookies that are expired (check max-age and expire fields). + %% substitute the cookies with the same name, add the others. + Substituted = + lists:foldl(fun(X, Acc) -> + lists:keyreplace(X#lhttpc_cookie.name, #lhttpc_cookie.name, Acc, X) + end, StateCookies, ReceivedCookies), + %% delete the cookies whose value is set to "deleted" + NewCookies = [ X#lhttpc_cookie.name || X <- Substituted, X#lhttpc_cookie.value =:= "deleted"], + %% Delete the cookies that are expired (check max-age and expire fields). delete_expired_cookies(NewCookies). %%============================================================================== @@ -236,14 +232,13 @@ update_cookies(RespHeaders, StateCookies) -> %%------------------------------------------------------------------------------ -spec delete_expired_cookies([#lhttpc_cookie{}]) -> [#lhttpc_cookie{}]. delete_expired_cookies(Cookies) -> - ToDelete = [ X || X <- Cookies, - X#lhttpc_cookie.max_age =/= undefined andalso - timer:now_diff(erlang:timestamp(), X#lhttpc_cookie.timestamp) > X#lhttpc_cookie.max_age], - NewCookies = Cookies -- ToDelete, - ToDelete2 = [ X || X <- Cookies, X#lhttpc_cookie.expires =/= never andalso - calendar:datetime_to_gregorian_seconds(calendar:universal_time()) > - calendar:datetime_to_gregorian_seconds(X#lhttpc_cookie.expires)], - NewCookies -- ToDelete2. + [ X || X <- Cookies, + X#lhttpc_cookie.max_age =:= undefined orelse + timer:now_diff(erlang:timestamp(), X#lhttpc_cookie.timestamp) + =< X#lhttpc_cookie.max_age, + X#lhttpc_cookie.expires =:= never orelse + calendar:datetime_to_gregorian_seconds(calendar:universal_time()) + =< calendar:datetime_to_gregorian_seconds(X#lhttpc_cookie.expires)]. %%------------------------------------------------------------------------------ %% @private @@ -315,14 +310,10 @@ other_cookie_elements([_Element | Rest], Cookie) -> {{integer(), integer(), integer()}, {integer(),integer(),integer()}}. expires_to_datetime(ExpireDate) -> - [_Expires, Day, Month, Year, Hour, Min, Sec, _GMT] = string:tokens(ExpireDate, ", -:"), - {FinalDay, _} = string:to_integer(Day), - {FinalYear, _} = string:to_integer(Year), - {FinalHour, _} = string:to_integer(Hour), - {FinalMin, _} =string:to_integer(Min), - {FinalSec, _} =string:to_integer(Sec), - FinalMonth = month_to_integer(Month), - {{FinalYear, FinalMonth, FinalDay}, {FinalHour, FinalMin, FinalSec}}. + [_Expires, Day, Month, Year, Hour, Min, Sec, _GMT] + = string:tokens(ExpireDate, ", -:"), + {{list_to_integer(Year), month_to_integer(Month), list_to_integer(Day)}, + {list_to_integer(Hour), list_to_integer(Min), list_to_integer(Sec)}}. %%------------------------------------------------------------------------------ From b7a8643bd5a91c39ea8e32ddd1f089e540fd1151 Mon Sep 17 00:00:00 2001 From: Diana Corbacho Date: Tue, 29 Jan 2013 17:41:38 +0000 Subject: [PATCH 29/49] Code refactor --- src/lhttpc_lib.erl | 9 +++++---- test/lhttpc_tests.erl | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index 51abb18f..27045840 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -215,11 +215,12 @@ update_cookies(RespHeaders, StateCookies) -> ReceivedCookies = lhttpc_lib:get_cookies(RespHeaders), %% substitute the cookies with the same name, add the others. Substituted = - lists:foldl(fun(X, Acc) -> - lists:keyreplace(X#lhttpc_cookie.name, #lhttpc_cookie.name, Acc, X) - end, StateCookies, ReceivedCookies), + lists:foldl(fun(X, Acc) -> + lists:keystore(X#lhttpc_cookie.name, + #lhttpc_cookie.name, Acc, X) + end, StateCookies, ReceivedCookies), %% delete the cookies whose value is set to "deleted" - NewCookies = [ X#lhttpc_cookie.name || X <- Substituted, X#lhttpc_cookie.value =:= "deleted"], + NewCookies = [ X || X <- Substituted, X#lhttpc_cookie.value =/= "deleted"], %% Delete the cookies that are expired (check max-age and expire fields). delete_expired_cookies(NewCookies). diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index f458db5a..724fea09 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -116,11 +116,11 @@ ssl_test_() -> ]} }. -other_test_() -> - [ - ?_test(invalid_options()), - ?_test(cookies()) - ]. +options_test() -> + invalid_options(). + +cookies_test() -> + cookies(). %%% Tests From 681bfc906f2590469af67da1bbb4c531248c632f Mon Sep 17 00:00:00 2001 From: Lastres Date: Wed, 30 Jan 2013 10:36:32 +0000 Subject: [PATCH 30/49] Do not allow to send requests to a different host of port than to the one the client is connected. Before it was possible to send requests with URL different to the one to which the client is connected, and the host and port were ignored (used the ones connected to). Now it checks wheter they are the same or gives an error otherwise. --- src/lhttpc.erl | 21 +---------- src/lhttpc_client.erl | 81 ++++++++++++++++++++++++++++--------------- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/src/lhttpc.erl b/src/lhttpc.erl index 39a996b8..dd244a7a 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -200,28 +200,9 @@ request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout) -> -spec request_client(pid(), string(), method(), headers(), iodata(), pos_timeout(), options()) -> result(). request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout, Options) -> - {FinalPath, FinalHeaders} = - try #lhttpc_url{ host = _Host, %its an URL - port = _Port, - path = Path, - is_ssl = _Ssl, - user = User, - password = Passwd} = lhttpc_lib:parse_url(PathOrUrl), - Headers = case User of - "" -> - Hdrs; - _ -> - Auth = "Basic " ++ binary_to_list(base64:encode(User ++ ":" ++ Passwd)), - lists:keystore("Authorization", 1, Hdrs, {"Authorization", Auth}) - end, - {Path, Headers} - catch %if parse_url crashes we assume it is a path. - _:_ -> - {PathOrUrl, Hdrs} - end, verify_options(Options), try - Reply = lhttpc_client:request(Client, FinalPath, Method, FinalHeaders, Body, Options, Timeout), + Reply = lhttpc_client:request(Client, PathOrUrl, Method, Hdrs, Body, Options, Timeout), Reply catch exit:{timeout, _} -> diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 98b838ac..fa1d84b0 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -177,8 +177,8 @@ init({Destination, Options}) -> %% socket used is new, it also makes the pool gen_server its controlling process. %% @end %%------------------------------------------------------------------------------ -handle_call({request, Path, Method, Hdrs, Body, Options}, From, - State = #client_state{ssl = Ssl, host = Host, port = Port, +handle_call({request, PathOrUrl, Method, Hdrs, Body, Options}, From, + State = #client_state{ssl = Ssl, host = ClientHost, port = ClientPort, socket = Socket, cookies = Cookies, use_cookies = UseCookies}) -> PartialUpload = proplists:get_value(partial_upload, Options, false), @@ -189,35 +189,62 @@ handle_call({request, Path, Method, Hdrs, Body, Options}, From, undefined -> undefined; ProxyUrl when is_list(ProxyUrl), not Ssl -> - % The point of HTTP CONNECT proxying is to use TLS tunneled in - % a plain HTTP/1.1 connection to the proxy (RFC2817). + % The point of HTTP CONNECT proxying is to use TLS tunneled in + % a plain HTTP/1.1 connection to the proxy (RFC2817). throw(origin_server_not_https); ProxyUrl when is_list(ProxyUrl) -> lhttpc_lib:parse_url(ProxyUrl) end, - {ChunkedUpload, Request} = lhttpc_lib:format_request( - Path, NormalizedMethod, - Hdrs, Host, Port, Body, PartialUpload, {UseCookies, Cookies}), - NewState = State#client_state{ - method = NormalizedMethod, - request = Request, - requester = From, - request_headers = Hdrs, - attempts = proplists:get_value(send_retry, Options, 1), - partial_upload = PartialUpload, - chunked_upload = ChunkedUpload, - partial_download = PartialDownload, - download_window = proplists:get_value(window_size, - PartialDownloadOptions, infinity), - download_proc = proplists:get_value(recv_proc, - PartialDownloadOptions, infinity), - part_size = proplists:get_value(part_size, - PartialDownloadOptions, infinity), - proxy = Proxy, - proxy_setup = (Socket =/= undefined), - proxy_ssl_options = proplists:get_value(proxy_ssl_options, Options, []) - }, - send_request(NewState); + {FinalPath, FinalHeaders, Host, Port} = + try #lhttpc_url{ host = UrlHost, %its an URL + port = UrlPort, + path = Path, + is_ssl = _Ssl, + user = User, + password = Passwd} = lhttpc_lib:parse_url(PathOrUrl), + Headers = + case User of + "" -> + Hdrs; + _ -> + Auth = "Basic " ++ binary_to_list(base64:encode(User ++ ":" ++ Passwd)), + lists:keystore("Authorization", 1, Hdrs, {"Authorization", Auth}) + end, + {Path, Headers, UrlHost, UrlPort} + catch %if parse_url crashes we assume it is a path. + _:_ -> + {PathOrUrl, Hdrs, ClientHost, ClientPort} + end, + case {Host, Port} =:= {ClientHost, ClientPort} of + true -> + {ChunkedUpload, Request} = + lhttpc_lib:format_request( + FinalPath, NormalizedMethod, + FinalHeaders, Host, Port, Body, PartialUpload, {UseCookies, Cookies}), + NewState = + State#client_state{ + method = NormalizedMethod, + request = Request, + requester = From, + request_headers = Hdrs, + attempts = proplists:get_value(send_retry, Options, 1), + partial_upload = PartialUpload, + chunked_upload = ChunkedUpload, + partial_download = PartialDownload, + download_window = proplists:get_value(window_size, + PartialDownloadOptions, infinity), + download_proc = proplists:get_value(recv_proc, + PartialDownloadOptions, infinity), + part_size = proplists:get_value(part_size, + PartialDownloadOptions, infinity), + proxy = Proxy, + proxy_setup = (Socket =/= undefined), + proxy_ssl_options = proplists:get_value(proxy_ssl_options, Options, []) + }, + send_request(NewState); + _ -> + {reply, {error, host_or_port_different_to_connected}, State} + end; handle_call(_Msg, _From, #client_state{request = undefined} = State) -> {reply, {error, no_pending_request}, State}; handle_call({send_body_part, _}, _From, State = #client_state{partial_upload = false}) -> From e26b1033e66d7f6d38250cc38917449ebd9fd578 Mon Sep 17 00:00:00 2001 From: Lastres Date: Wed, 30 Jan 2013 11:34:36 +0000 Subject: [PATCH 31/49] Refactor some code to avoid too long functions. --- src/lhttpc_client.erl | 45 +++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index fa1d84b0..4b289af0 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -196,25 +196,7 @@ handle_call({request, PathOrUrl, Method, Hdrs, Body, Options}, From, lhttpc_lib:parse_url(ProxyUrl) end, {FinalPath, FinalHeaders, Host, Port} = - try #lhttpc_url{ host = UrlHost, %its an URL - port = UrlPort, - path = Path, - is_ssl = _Ssl, - user = User, - password = Passwd} = lhttpc_lib:parse_url(PathOrUrl), - Headers = - case User of - "" -> - Hdrs; - _ -> - Auth = "Basic " ++ binary_to_list(base64:encode(User ++ ":" ++ Passwd)), - lists:keystore("Authorization", 1, Hdrs, {"Authorization", Auth}) - end, - {Path, Headers, UrlHost, UrlPort} - catch %if parse_url crashes we assume it is a path. - _:_ -> - {PathOrUrl, Hdrs, ClientHost, ClientPort} - end, + url_extract(PathOrUrl, Hdrs, ClientHost, ClientPort), case {Host, Port} =:= {ClientHost, ClientPort} of true -> {ChunkedUpload, Request} = @@ -349,6 +331,31 @@ code_change(_OldVsn, State, _Extra) -> %%============================================================================== %% Internal functions %%============================================================================== + +%%------------------------------------------------------------------------------ +%% @private +%%------------------------------------------------------------------------------ +url_extract(PathOrUrl, Hdrs, ClientHost, ClientPort) -> + try #lhttpc_url{ host = UrlHost, %its an URL + port = UrlPort, + path = Path, + is_ssl = _Ssl, + user = User, + password = Passwd} = lhttpc_lib:parse_url(PathOrUrl), + Headers = + case User of + "" -> + Hdrs; + _ -> + Auth = "Basic " ++ binary_to_list(base64:encode(User ++ ":" ++ Passwd)), + lists:keystore("Authorization", 1, Hdrs, {"Authorization", Auth}) + end, + {Path, Headers, UrlHost, UrlPort} + catch %if parse_url crashes we assume it is a path. + _:_ -> + {PathOrUrl, Hdrs, ClientHost, ClientPort} + end. + %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ From ffa11997c436c326d04f7dac9768e3d3e2d37303 Mon Sep 17 00:00:00 2001 From: Lastres Date: Wed, 30 Jan 2013 15:48:28 +0000 Subject: [PATCH 32/49] Fix bug in lhttpc_lib.erl There were calls to erlang:timestamp(), which does not exist. --- src/lhttpc_lib.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index 27045840..15c9f022 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -235,7 +235,7 @@ update_cookies(RespHeaders, StateCookies) -> delete_expired_cookies(Cookies) -> [ X || X <- Cookies, X#lhttpc_cookie.max_age =:= undefined orelse - timer:now_diff(erlang:timestamp(), X#lhttpc_cookie.timestamp) + timer:now_diff(os:timestamp(), X#lhttpc_cookie.timestamp) =< X#lhttpc_cookie.max_age, X#lhttpc_cookie.expires =:= never orelse calendar:datetime_to_gregorian_seconds(calendar:universal_time()) @@ -290,13 +290,13 @@ other_cookie_elements([" Max-Age" ++ Value | Rest], Cookie) -> {Integer, _Rest} = string:to_integer(FinalValue), MaxAge = Integer * 1000000, %we need it in microseconds other_cookie_elements(Rest, Cookie#lhttpc_cookie{max_age = MaxAge, - timestamp = erlang:timestamp()}); + timestamp = os:timestamp()}); other_cookie_elements([" max-age" ++ Value | Rest], Cookie) -> "=" ++ FinalValue = Value, {Integer, _Rest} = string:to_integer(FinalValue), MaxAge = Integer * 1000000, %we need it in microseconds other_cookie_elements(Rest, Cookie#lhttpc_cookie{max_age = MaxAge, - timestamp = erlang:timestamp()}); + timestamp = os:timestamp()}); % for the moment we ignore the other attributes. other_cookie_elements([_Element | Rest], Cookie) -> other_cookie_elements(Rest, Cookie). From cea773d2db7e47c5b08de202dc4b447fc607455f Mon Sep 17 00:00:00 2001 From: Lastres Date: Wed, 30 Jan 2013 16:14:25 +0000 Subject: [PATCH 33/49] Fix error in the specs. --- src/lhttpc_client.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 4b289af0..53ed34d6 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -126,7 +126,7 @@ stop(Client) -> %% @doc %% @end %%------------------------------------------------------------------------------ --spec request(pid(), string(), method(), headers(), iolist(), options(), integer()) -> ok. +-spec request(pid(), string(), method(), headers(), iolist(), options(), integer()) -> result(). request(Client, Path, Method, Hdrs, Body, Options, Timeout) -> gen_server:call(Client, {request, Path, Method, Hdrs, Body, Options}, Timeout). From 7a9b9c6eac2f882a484f51f8695e8b23a6eda6a4 Mon Sep 17 00:00:00 2001 From: Max Lapshin Date: Tue, 19 Feb 2013 08:45:41 +0400 Subject: [PATCH 34/49] better handling connect errors such as econnrefused --- src/lhttpc.erl | 4 +++- test/lhttpc_tests.erl | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lhttpc.erl b/src/lhttpc.erl index dd244a7a..2f66a744 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -487,7 +487,9 @@ request(Host, Port, Ssl, Path, Method, Hdrs, Body, Timeout, Options) -> {error, timeout} end; {error, {timeout, _Reason}} -> - {error, connection_timeout} + {error, connection_timeout}; + {error, _Reason} = Error -> + Error end. %%------------------------------------------------------------------------------ diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index 724fea09..4df88494 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -55,6 +55,7 @@ tcp_test_() -> ?_test(simple_get()), ?_test(simple_get_ipv6()), ?_test(empty_get()), + ?_test(connection_refused()), ?_test(basic_auth()), ?_test(missing_basic_auth()), ?_test(wrong_basic_auth()), @@ -139,6 +140,10 @@ empty_get() -> ?assertEqual({200, "OK"}, status(Response)), ?assertEqual(<<>>, body(Response)). +connection_refused() -> + Response = lhttpc:request("http://127.0.0.1:50234/none", "GET", [], 100), + ?assertEqual({error, econnrefused}, Response). + basic_auth() -> User = "foo", Passwd = "bar", From 89be030aa31cb7ed4fcc2fa48fd92f274024c804 Mon Sep 17 00:00:00 2001 From: Lastres Date: Tue, 5 Mar 2013 16:01:35 +0000 Subject: [PATCH 35/49] Change "Dic" for "Dec" in the month_to_integer function. --- src/lhttpc_lib.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index 15c9f022..f41bd9a6 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -332,7 +332,7 @@ month_to_integer("Aug") -> 8; month_to_integer("Sep") -> 9; month_to_integer("Oct") -> 10; month_to_integer("Nov") -> 11; -month_to_integer("Dic") -> 12. +month_to_integer("Dec") -> 12. %%------------------------------------------------------------------------------ %% @private From a38e67a209a01dff4928c933f85d2bda03a1b741 Mon Sep 17 00:00:00 2001 From: Lastres Date: Tue, 5 Mar 2013 18:59:21 +0000 Subject: [PATCH 36/49] Quick fix that allows to add the cookies if the path is a prefix. Alexej needs it tonight, we had no time to fix the test... tomorrow will do amend. --- src/lhttpc_lib.erl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index f41bd9a6..6d561f35 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -492,9 +492,19 @@ add_mandatory_hdrs(Path, Method, Hdrs, Host, Port, Body, PartialUpload, {UseCook ContentHdrs = add_content_headers(Method, Hdrs, Body, PartialUpload), case UseCookies of true -> - % only include cookies if the path matches. - IncludeCookies = [ X || X <- Cookies, X#lhttpc_cookie.path =:= Path orelse - X#lhttpc_cookie.path =:= undefined ], + % only include cookies if the cookie path is a prefix of the request path + % see RFC http://www.ietf.org/rfc/rfc2109.txt section 4.3.4 + IncludeCookies = + lists:filter(fun(#lhttpc_cookie{path = undefined}) -> + false; + (X) -> + IsPrefix = string:str(Path, X#lhttpc_cookie.path), + if (IsPrefix =/= 1) -> + false; + true -> + true + end + end, Cookies), FinalHdrs = add_cookie_headers(ContentHdrs, IncludeCookies); _ -> FinalHdrs = ContentHdrs From 6f96e84186b31ed7b6c1271534a18524c938c96c Mon Sep 17 00:00:00 2001 From: Alexej Tessaro Date: Mon, 11 Mar 2013 09:54:56 +0000 Subject: [PATCH 37/49] fix: path is not required in a cookie, from http://www.ietf.org/rfc/rfc2109.txt --- src/lhttpc_lib.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index 6d561f35..7c05f329 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -496,7 +496,7 @@ add_mandatory_hdrs(Path, Method, Hdrs, Host, Port, Body, PartialUpload, {UseCook % see RFC http://www.ietf.org/rfc/rfc2109.txt section 4.3.4 IncludeCookies = lists:filter(fun(#lhttpc_cookie{path = undefined}) -> - false; + true; (X) -> IsPrefix = string:str(Path, X#lhttpc_cookie.path), if (IsPrefix =/= 1) -> From 14594cfa41668b856c85413f0bd78e210a21b17f Mon Sep 17 00:00:00 2001 From: Juraj Hlista Date: Wed, 24 Apr 2013 10:41:43 +0100 Subject: [PATCH 38/49] Fix edoc so it doesn't emit errors during compilation --- src/lhttpc_lib.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index 7c05f329..faeecf5b 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -138,7 +138,8 @@ parse_url(URL) -> }. %%------------------------------------------------------------------------------ -%% @spec (Path, Method, Headers, Host, Port, Body, PartialUpload) -> Request +%% @spec (Path, Method, Headers, Host, Port, Body, PartialUpload, Cookies) -> +%% Request %% Path = iolist() %% Method = atom() | string() %% Headers = [{atom() | string(), string()}] @@ -146,6 +147,7 @@ parse_url(URL) -> %% Port = integer() %% Body = iolist() %% PartialUpload = true | false +%% Cookies = [#lhttpc_cookie{}] %% @doc %% @end %%------------------------------------------------------------------------------ From 5641f3d7878d0c305abcd163019efeae186249f4 Mon Sep 17 00:00:00 2001 From: Juraj Hlista Date: Wed, 24 Apr 2013 10:42:30 +0100 Subject: [PATCH 39/49] Refactor connect_socket function --- src/lhttpc_client.erl | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index 53ed34d6..ec44a194 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -1048,19 +1048,15 @@ is_ipv6_host(Host) -> %% pool dinamically. %% @end %%------------------------------------------------------------------------------ -connect_socket(State = #client_state{pool = Pool}) -> - Connection = case Pool of - undefined -> - new_socket(State); - _ -> - connect_pool(State) - end, - case Connection of - {ok, Socket} -> - {ok, State#client_state{socket = Socket}}; - Error -> - {Error, State} - end. +connect_socket(#client_state{pool = undefined} = State) -> + connect_socket_return(new_socket(State), State); +connect_socket(#client_state{pool = _Pool} = State) -> + connect_socket_return(connect_pool(State), State). + +connect_socket_return({ok, Socket}, State) -> + {ok, State#client_state{socket = Socket}}; +connect_socket_return(Error, State) -> + {Error, State}. -spec connect_pool(#client_state{}) -> {ok, socket()} | {error, atom()}. connect_pool(State = #client_state{pool_options = Options, From fad2c7be6b435eca0990c8f1c4bce7023ed78eae Mon Sep 17 00:00:00 2001 From: Juraj Hlista Date: Wed, 24 Apr 2013 10:43:31 +0100 Subject: [PATCH 40/49] Code refactor and formating --- src/lhttpc.app.src | 2 +- src/lhttpc.erl | 89 ++--- src/lhttpc_client.erl | 645 +++++++++++++++++------------------ src/lhttpc_lib.erl | 167 +++++---- src/lhttpc_manager.erl | 72 ++-- src/lhttpc_sock.erl | 6 +- src/lhttpc_sup.erl | 2 +- test/lhttpc_client_tests.erl | 28 +- test/lhttpc_lib_tests.erl | 56 +-- test/lhttpc_tests.erl | 98 +++--- test/simple_load.erl | 96 +++--- test/webserver_utils.erl | 78 ++--- 12 files changed, 635 insertions(+), 704 deletions(-) diff --git a/src/lhttpc.app.src b/src/lhttpc.app.src index e853322a..76d07647 100644 --- a/src/lhttpc.app.src +++ b/src/lhttpc.app.src @@ -31,7 +31,7 @@ [{description, "Lightweight HTTP Client"}, {vsn, "1.2.6"}, {modules, []}, - {registered, [lhttpc_manager]}, + {registered, [lhttpc_sup, lhttpc_manager]}, {applications, [kernel, stdlib, ssl, crypto]}, {mod, {lhttpc, nil}}, {env, [{connection_timeout, 300000}, {pool_size, 50}]} diff --git a/src/lhttpc.erl b/src/lhttpc.erl index 2f66a744..cbc253da 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -37,15 +37,14 @@ -export([start/0, stop/0, start/2, stop/1, request/4, request/5, request/6, request/9, - request_client/5, request_client/6, request_client/7, + request_client/5, request_client/6, request_client/7, add_pool/1, add_pool/2, add_pool/3, delete_pool/1, - connect_client/2, - disconnect_client/1, + connect_client/2, + disconnect_client/1, send_body_part/2, send_body_part/3, send_trailers/2, send_trailers/3, - get_body_part/1, get_body_part/2 - ]). + get_body_part/1, get_body_part/2]). -include("lhttpc_types.hrl"). -include("lhttpc.hrl"). @@ -57,8 +56,7 @@ %%------------------------------------------------------------------------------ %% @hidden %%------------------------------------------------------------------------------ --spec start(normal | {takeover, node()} | {failover, node()}, any()) -> - {ok, pid()}. +-spec start(normal | {takeover, node()} | {failover, node()}, any()) -> {ok, pid()}. start(_, _) -> lhttpc_sup:start_link(). @@ -111,18 +109,14 @@ stop() -> add_pool(Name) when is_atom(Name) -> {ok, ConnTimeout} = application:get_env(lhttpc, connection_timeout), {ok, PoolSize} = application:get_env(lhttpc, pool_size), - add_pool(Name, - ConnTimeout, - PoolSize). + add_pool(Name, ConnTimeout, PoolSize). %%------------------------------------------------------------------------------ %% @doc Add a new httpc_manager to the supervisor tree %% @end %%------------------------------------------------------------------------------ -spec add_pool(atom(), non_neg_integer()) -> {ok, pid()} | {error, term()}. -add_pool(Name, ConnTimeout) when is_atom(Name), - is_integer(ConnTimeout), - ConnTimeout > 0 -> +add_pool(Name, ConnTimeout) when is_atom(Name), is_integer(ConnTimeout), ConnTimeout > 0 -> {ok, PoolSize} = application:get_env(lhttpc, pool_size), add_pool(Name, ConnTimeout, PoolSize). @@ -130,8 +124,7 @@ add_pool(Name, ConnTimeout) when is_atom(Name), %% @doc Add a new httpc_manager to the supervisor tree %% @end %%------------------------------------------------------------------------------ --spec add_pool(atom(), non_neg_integer(), poolsize()) -> - {ok, pid()} | {error, term()}. +-spec add_pool(atom(), non_neg_integer(), poolsize()) -> {ok, pid()} | {error, term()}. add_pool(Name, ConnTimeout, PoolSize) -> lhttpc_manager:new_pool(Name, ConnTimeout, PoolSize). @@ -146,9 +139,9 @@ delete_pool(PoolPid) when is_pid(PoolPid) -> delete_pool(PoolName) when is_atom(PoolName) -> case supervisor:terminate_child(lhttpc_sup, PoolName) of ok -> case supervisor:delete_child(lhttpc_sup, PoolName) of - ok -> ok; - {error, not_found} -> ok - end; + ok -> ok; + {error, not_found} -> ok + end; {error, Reason} -> {error, Reason} end. @@ -170,7 +163,6 @@ connect_client(Destination, Options) -> disconnect_client(Client) -> lhttpc_client:stop(Client). - %REQUESTS USING THE CLIENT %%------------------------------------------------------------------------------ @@ -198,7 +190,7 @@ request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout) -> %% @end %%------------------------------------------------------------------------------ -spec request_client(pid(), string(), method(), headers(), iodata(), - pos_timeout(), options()) -> result(). + pos_timeout(), options()) -> result(). request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout, Options) -> verify_options(Options), try @@ -304,19 +296,12 @@ request(URL, Method, Hdrs, Body, Timeout) -> %% @see request/9 %% @end %%------------------------------------------------------------------------------ --spec request(string(), method(), headers(), iodata(), - pos_timeout(), options()) -> result(). +-spec request(string(), method(), headers(), iodata(), pos_timeout(), options()) -> result(). request(URL, Method, Hdrs, Body, Timeout, Options) -> - #lhttpc_url{ - host = Host, - port = Port, - path = Path, - is_ssl = Ssl, - user = User, - password = Passwd - } = lhttpc_lib:parse_url(URL), + #lhttpc_url{host = Host, port = Port, path = Path, is_ssl = Ssl, + user = User,password = Passwd} = lhttpc_lib:parse_url(URL), Headers = case User of - "" -> + [] -> Hdrs; _ -> Auth = "Basic " ++ binary_to_list(base64:encode(User ++ ":" ++ Passwd)), @@ -472,24 +457,24 @@ request(URL, Method, Hdrs, Body, Timeout, Options) -> %% @end %%------------------------------------------------------------------------------ -spec request(string(), port_num(), boolean(), string(), method(), - headers(), iodata(), pos_timeout(), options()) -> result(). + headers(), iodata(), pos_timeout(), options()) -> result(). request(Host, Port, Ssl, Path, Method, Hdrs, Body, Timeout, Options) -> verify_options(Options), case connect_client({Host, Port, Ssl}, Options) of - {ok, Client} -> - try - Reply = lhttpc_client:request(Client, Path, Method, Hdrs, Body, Options, Timeout), - disconnect_client(Client), - Reply - catch - exit:{timeout, _} -> - disconnect_client(Client), - {error, timeout} - end; - {error, {timeout, _Reason}} -> - {error, connection_timeout}; - {error, _Reason} = Error -> - Error + {ok, Client} -> + try + Reply = lhttpc_client:request(Client, Path, Method, Hdrs, Body, Options, Timeout), + disconnect_client(Client), + Reply + catch + exit:{timeout, _} -> + disconnect_client(Client), + {error, timeout} + end; + {error, {timeout, _Reason}} -> + {error, connection_timeout}; + {error, _Reason} = Error -> + Error end. %%------------------------------------------------------------------------------ @@ -579,8 +564,7 @@ send_trailers(Client, Trailers) -> %% @end %%------------------------------------------------------------------------------ -spec send_trailers({pid(), window_size()}, headers(), timeout()) -> result(). -send_trailers({Pid, _Window}, Trailers, Timeout) - when is_list(Trailers), is_pid(Pid) -> +send_trailers({Pid, _Window}, Trailers, Timeout) when is_list(Trailers), is_pid(Pid) -> Pid ! {trailers, self(), Trailers}, read_response(Pid, Timeout). @@ -598,8 +582,7 @@ send_trailers({Pid, _Window}, Trailers, Timeout) %% `get_body_part(HTTPClient, infinity)'. %% @end %%------------------------------------------------------------------------------ --spec get_body_part(pid()) -> {ok, binary()} | - {ok, {http_eob, headers()}} | {error, term()}. +-spec get_body_part(pid()) -> {ok, binary()} | {ok, {http_eob, headers()}} | {error, term()}. get_body_part(Pid) -> get_body_part(Pid, infinity). @@ -621,7 +604,7 @@ get_body_part(Pid) -> %% @end %%------------------------------------------------------------------------------ -spec get_body_part(pid(), timeout()) -> {ok, binary()} | - {ok, {http_eob, headers()}} | {error, term()}. + {ok, {http_eob, headers()}} | {error, term()}. get_body_part(Client, Timeout) -> lhttpc_client:get_body_part(Client, Timeout). @@ -642,7 +625,7 @@ read_response(Pid, Timeout) -> {'EXIT', Pid, Reason} -> {error, Reason} after Timeout -> - kill_client(Pid) + kill_client(Pid) end. %%------------------------------------------------------------------------------ @@ -710,7 +693,7 @@ verify_pool_options([{pool_connection_timeout, Size} | Options]) verify_pool_options(Options); verify_pool_options([{pool_max_size, Size} | Options]) when is_integer(Size) orelse - Size =:= infinity-> + Size =:= infinity-> verify_pool_options(Options); verify_pool_options([Option | _Rest]) -> erlang:error({bad_option, Option}); diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index ec44a194..cb006f25 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -37,61 +37,60 @@ %exported functions -export([start_link/2, - start/2, + start/2, request/7, - send_body_part/3, - send_trailers/3, - get_body_part/2, - stop/1 - ]). + send_body_part/3, + send_trailers/3, + get_body_part/2, + stop/1]). %% gen_server callbacks --export([ - init/1, +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3 - ]). + code_change/3]). -include("lhttpc_types.hrl"). -include("lhttpc.hrl"). +-define(HTTP_LINE_END, "\r\n"). -define(CONNECTION_HDR(HDRS, DEFAULT), - string:to_lower(lhttpc_lib:header_value("connection", HDRS, DEFAULT))). + string:to_lower(lhttpc_lib:header_value("connection", HDRS, DEFAULT))). -record(client_state, { - host :: string(), - port = 80 :: port_num(), - ssl = false :: boolean(), - pool = undefined, - pool_options, - socket, - connect_timeout = 'infinity' :: timeout(), - connect_options = [] :: [any()], - %% next fields are specific to particular requests - request :: iolist() | undefined, - method :: string(), - request_headers :: headers(), - requester, - cookies = [] :: [#lhttpc_cookie{}], - use_cookies = false :: boolean(), - partial_upload = false :: boolean(), - chunked_upload = false :: boolean(), - partial_download = false :: boolean(), - download_window = infinity :: timeout(), - download_proc :: pid(), - part_size :: non_neg_integer() | infinity, - %% in case of infinity we read whatever data we can get from - %% the wire at that point or in case of chunked one chunk - attempts = 0 :: integer(), - download_info :: {term(), term()}, - body_length = undefined :: {'fixed_length', non_neg_integer()} | 'undefined' | 'chunked' | 'infinite', - proxy :: undefined | #lhttpc_url{}, - proxy_ssl_options = [] :: [any()], - proxy_setup = false :: boolean() - }). + host :: string(), + port = 80 :: port_num(), + ssl = false :: boolean(), + pool = undefined, + pool_options, + socket, + connect_timeout = 'infinity' :: timeout(), + connect_options = [] :: [any()], + %% next fields are specific to particular requests + request :: iolist() | undefined, + method :: string(), + request_headers :: headers(), + requester, + cookies = [] :: [#lhttpc_cookie{}], + use_cookies = false :: boolean(), + partial_upload = false :: boolean(), + chunked_upload = false :: boolean(), + partial_download = false :: boolean(), + download_window = infinity :: timeout(), + download_proc :: pid(), + part_size :: non_neg_integer() | infinity, + %% in case of infinity we read whatever data we can get from + %% the wire at that point or in case of chunked one chunk + attempts = 0 :: integer(), + download_info :: {term(), term()}, + body_length = undefined :: {'fixed_length', non_neg_integer()} | + 'undefined' | 'chunked' | 'infinite', + proxy :: undefined | #lhttpc_url{}, + proxy_ssl_options = [] :: [any()], + proxy_setup = false :: boolean() + }). %%============================================================================== %% Exported functions @@ -137,38 +136,28 @@ request(Client, Path, Method, Hdrs, Body, Options, Timeout) -> init({Destination, Options}) -> PoolOptions = proplists:get_value(pool_options, Options, []), Pool = proplists:get_value(pool, PoolOptions), - State = case Destination of - {Host, Port, Ssl} -> - #client_state{host = Host, - port = Port, - ssl = Ssl, - pool = Pool, - connect_timeout = proplists:get_value(connect_timeout, Options, - infinity), - connect_options = proplists:get_value(connect_options, Options, []), - use_cookies = proplists:get_value(use_cookies, Options, false), - pool_options = PoolOptions}; - URL -> - #lhttpc_url{host = Host, - port = Port, - is_ssl = Ssl - } = lhttpc_lib:parse_url(URL), - #client_state{host = Host, - port = Port, - ssl = Ssl, - pool = Pool, - connect_timeout = proplists:get_value(connect_timeout, Options, - infinity), - connect_options = proplists:get_value(connect_options, Options, []), - use_cookies = proplists:get_value(use_cookies, Options, false), - pool_options = PoolOptions} + ConnectTimeout = proplists:get_value(connect_timeout, Options, infinity), + ConnectOptions = proplists:get_value(connect_options, Options, []), + UseCookies = proplists:get_value(use_cookies, Options, false), + {Host, Port, Ssl} = case Destination of + {H, P, S} -> + {H, P, S}; + URL -> + #lhttpc_url{host = H, port = P, + is_ssl = S} = lhttpc_lib:parse_url(URL), + {H, P, S} end, + State = #client_state{host = Host, port = Port, ssl = Ssl, pool = Pool, + connect_timeout = ConnectTimeout, + connect_options = ConnectOptions, + use_cookies = UseCookies, + pool_options = PoolOptions}, %% Get a socket for the pool or exit case connect_socket(State) of - {ok, NewState} -> - {ok, NewState}; - {{error, Reason}, _} -> - {stop, Reason} + {ok, NewState} -> + {ok, NewState}; + {{error, Reason}, _} -> + {stop, Reason} end. %%------------------------------------------------------------------------------ @@ -178,54 +167,57 @@ init({Destination, Options}) -> %% @end %%------------------------------------------------------------------------------ handle_call({request, PathOrUrl, Method, Hdrs, Body, Options}, From, - State = #client_state{ssl = Ssl, host = ClientHost, port = ClientPort, - socket = Socket, cookies = Cookies, - use_cookies = UseCookies}) -> + State = #client_state{ssl = Ssl, host = ClientHost, port = ClientPort, + socket = Socket, cookies = Cookies, + use_cookies = UseCookies}) -> PartialUpload = proplists:get_value(partial_upload, Options, false), PartialDownload = proplists:is_defined(partial_download, Options), PartialDownloadOptions = proplists:get_value(partial_download, Options, []), NormalizedMethod = lhttpc_lib:normalize_method(Method), Proxy = case proplists:get_value(proxy, Options) of - undefined -> - undefined; - ProxyUrl when is_list(ProxyUrl), not Ssl -> - % The point of HTTP CONNECT proxying is to use TLS tunneled in - % a plain HTTP/1.1 connection to the proxy (RFC2817). - throw(origin_server_not_https); - ProxyUrl when is_list(ProxyUrl) -> - lhttpc_lib:parse_url(ProxyUrl) - end, - {FinalPath, FinalHeaders, Host, Port} = - url_extract(PathOrUrl, Hdrs, ClientHost, ClientPort), + undefined -> + undefined; + ProxyUrl when is_list(ProxyUrl), not Ssl -> + % The point of HTTP CONNECT proxying is to use TLS tunneled in + % a plain HTTP/1.1 connection to the proxy (RFC2817). + throw(origin_server_not_https); + ProxyUrl when is_list(ProxyUrl) -> + lhttpc_lib:parse_url(ProxyUrl) + end, + FinalPath, FinalHeaders, Host, Port} = + url_extract(PathOrUrl, Hdrs, ClientHost, ClientPort), case {Host, Port} =:= {ClientHost, ClientPort} of - true -> - {ChunkedUpload, Request} = - lhttpc_lib:format_request( - FinalPath, NormalizedMethod, - FinalHeaders, Host, Port, Body, PartialUpload, {UseCookies, Cookies}), - NewState = - State#client_state{ - method = NormalizedMethod, - request = Request, - requester = From, - request_headers = Hdrs, - attempts = proplists:get_value(send_retry, Options, 1), - partial_upload = PartialUpload, - chunked_upload = ChunkedUpload, - partial_download = PartialDownload, - download_window = proplists:get_value(window_size, - PartialDownloadOptions, infinity), - download_proc = proplists:get_value(recv_proc, - PartialDownloadOptions, infinity), - part_size = proplists:get_value(part_size, - PartialDownloadOptions, infinity), - proxy = Proxy, - proxy_setup = (Socket =/= undefined), - proxy_ssl_options = proplists:get_value(proxy_ssl_options, Options, []) - }, - send_request(NewState); - _ -> - {reply, {error, host_or_port_different_to_connected}, State} + true -> + {ChunkedUpload, Request} = + lhttpc_lib:format_request(FinalPath, NormalizedMethod, + FinalHeaders, Host, Port, Body, PartialUpload, + {UseCookies, Cookies}), + NewState = + State#client_state{ + method = NormalizedMethod, + request = Request, + requester = From, + request_headers = Hdrs, + attempts = proplists:get_value(send_retry, Options, 1), + partial_upload = PartialUpload, + chunked_upload = ChunkedUpload, + partial_download = PartialDownload, + download_window = + proplists:get_value(window_size, PartialDownloadOptions, + infinity), + download_proc = + proplists:get_value(recv_proc, PartialDownloadOptions, + infinity), + part_size = + proplists:get_value(part_size, PartialDownloadOptions, + infinity), + proxy = Proxy, + proxy_setup = (Socket /= undefined), + proxy_ssl_options = + proplists:get_value(proxy_ssl_options, Options, [])}, + send_request(NewState); + _ -> + {reply, {error, host_or_port_different_to_connected}, State} end; handle_call(_Msg, _From, #client_state{request = undefined} = State) -> {reply, {error, no_pending_request}, State}; @@ -255,7 +247,7 @@ handle_call({send_body_part, Data}, From, State) -> gen_server:reply(From, ok), {_Reply, NewState} = send_body_part(State, Data), {noreply, NewState}; - %We send the parts to the specified Pid. +%We send the parts to the specified Pid. handle_call(get_body_part, From, State) -> gen_server:reply(From, ok), {noreply, read_partial_body(State)}. @@ -274,7 +266,7 @@ handle_call(get_body_part, From, State) -> handle_cast(stop, State) -> {stop, normal, State}; handle_cast(_Msg, State) -> -{noreply, State}. + {noreply, State}. %%-------------------------------------------------------------------- %% @private @@ -287,7 +279,7 @@ handle_cast(_Msg, State) -> %% @end %%-------------------------------------------------------------------- handle_info(_Info, State) -> -{noreply, State}. + {noreply, State}. %%-------------------------------------------------------------------- %% @private @@ -300,7 +292,8 @@ handle_info(_Info, State) -> %% @spec terminate(Reason, State) -> void() %% @end %%-------------------------------------------------------------------- -terminate(_Reason, State = #client_state{pool = Pool, host = Host, ssl = Ssl, socket = Socket, port = Port}) -> +terminate(_Reason, State = #client_state{pool = Pool, host = Host, ssl = Ssl, + socket = Socket, port = Port}) -> case Socket of undefined -> ok; @@ -326,7 +319,7 @@ terminate(_Reason, State = #client_state{pool = Pool, host = Host, ssl = Ssl, so %% @end %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> -{ok, State}. + {ok, State}. %%============================================================================== %% Internal functions @@ -336,24 +329,19 @@ code_change(_OldVsn, State, _Extra) -> %% @private %%------------------------------------------------------------------------------ url_extract(PathOrUrl, Hdrs, ClientHost, ClientPort) -> - try #lhttpc_url{ host = UrlHost, %its an URL - port = UrlPort, - path = Path, - is_ssl = _Ssl, - user = User, - password = Passwd} = lhttpc_lib:parse_url(PathOrUrl), - Headers = - case User of - "" -> - Hdrs; - _ -> - Auth = "Basic " ++ binary_to_list(base64:encode(User ++ ":" ++ Passwd)), - lists:keystore("Authorization", 1, Hdrs, {"Authorization", Auth}) - end, - {Path, Headers, UrlHost, UrlPort} + try #lhttpc_url{host = UrlHost, port = UrlPort, path = Path, is_ssl = _Ssl, + user = User, password = Passwd} = lhttpc_lib:parse_url(PathOrUrl), + Headers = case User of + [] -> + Hdrs; + _ -> + Auth = "Basic " ++ binary_to_list(base64:encode(User ++ ":" ++ Passwd)), + lists:keystore("Authorization", 1, Hdrs, {"Authorization", Auth}) + end, + {Path, Headers, UrlHost, UrlPort} catch %if parse_url crashes we assume it is a path. - _:_ -> - {PathOrUrl, Hdrs, ClientHost, ClientPort} + _:_ -> + {PathOrUrl, Hdrs, ClientHost, ClientPort} end. %%------------------------------------------------------------------------------ @@ -372,7 +360,7 @@ send_body_part(State = #client_state{socket = Socket, ssl = Ssl}, BodyPart) -> send_request(#client_state{attempts = 0} = State) -> {reply, {error, connection_closed}, State#client_state{request = undefined}}; send_request(#client_state{socket = undefined} = State) -> -% if we dont get a keep alive from the previous request, the socket is undefined. + % if we dont get a keep alive from the previous request, the socket is undefined. case connect_socket(State) of {ok, NewState} -> send_request(NewState); @@ -382,61 +370,57 @@ send_request(#client_state{socket = undefined} = State) -> send_request(#client_state{proxy = #lhttpc_url{}, proxy_setup = false, host = DestHost, port = Port, socket = Socket} = State) -> %% use a proxy. - #lhttpc_url{ - user = User, - password = Passwd, - is_ssl = Ssl - } = State#client_state.proxy, + #lhttpc_url{user = User, password = Passwd, is_ssl = Ssl} = State#client_state.proxy, Host = case inet_parse:address(DestHost) of - {ok, {_, _, _, _, _, _, _, _}} -> - %% IPv6 address literals are enclosed by square brackets (RFC2732) - [$[, DestHost, $], $:, integer_to_list(Port)]; - _ -> - [DestHost, $:, integer_to_list(Port)] - end, + {ok, {_, _, _, _, _, _, _, _}} -> + %% IPv6 address literals are enclosed by square brackets (RFC2732) + [$[, DestHost, $], $:, integer_to_list(Port)]; + _ -> + [DestHost, $:, integer_to_list(Port)] + end, ConnectRequest = [ - "CONNECT ", Host, " HTTP/1.1\r\n", - "Host: ", Host, "\r\n", - case User of - "" -> - ""; - _ -> - ["Proxy-Authorization: Basic ", - base64:encode(User ++ ":" ++ Passwd), "\r\n"] - end, - "\r\n" - ], + "CONNECT ", Host, " HTTP/1.1", ?HTTP_LINE_END, + "Host: ", Host, ?HTTP_LINE_END, + case User of + [] -> + []; + _ -> + ["Proxy-Authorization: Basic ", + base64:encode(User ++ ":" ++ Passwd), ?HTTP_LINE_END] + end, + ?HTTP_LINE_END], case lhttpc_sock:send(Socket, ConnectRequest, Ssl) of ok -> {Reply, NewState} = read_proxy_connect_response(State, nil, nil), - {reply, Reply, NewState}; + {reply, Reply, NewState}; {error, closed} -> close_socket(State), - {reply, {error, proxy_connection_closed}, State#client_state{socket = undefined, request = undefined}}; + {reply, {error, proxy_connection_closed}, + State#client_state{socket = undefined, request = undefined}}; {error, _Reason} -> close_socket(State), - {reply, {error, proxy_connection_closed}, State#client_state{socket = undefined, request = undefined}} + {reply, {error, proxy_connection_closed}, + State#client_state{socket = undefined, request = undefined}} end; send_request(#client_state{socket = Socket, ssl = Ssl, request = Request, - attempts = Attempts} = State) -> + attempts = Attempts} = State) -> %% no proxy case lhttpc_sock:send(Socket, Request, Ssl) of ok -> if - %% {partial_upload, WindowSize} is used. + %% {partial_upload, WindowSize} is used. State#client_state.partial_upload -> {reply, {ok, partial_upload}, State#client_state{attempts = 0}}; not State#client_state.partial_upload -> - read_response(State) + read_response(State) end; {error, closed} -> close_socket(State), - send_request(State#client_state{socket = undefined, - attempts = Attempts - 1}); - {error, _Reason} -> + send_request(State#client_state{socket = undefined, attempts = Attempts - 1}); + {error, _Reason} -> close_socket(State), {reply, {error, connection_closed}, State#client_state{socket = undefined, - request = undefined}} + request = undefined}} end. %%------------------------------------------------------------------------------ @@ -459,43 +443,44 @@ read_proxy_connect_response(State, StatusCode, StatusText) -> {ok, {http_header, _, _Name, _, _Value}} -> read_proxy_connect_response(State, StatusCode, StatusText); {ok, http_eoh} when StatusCode >= 100, StatusCode =< 199 -> - %% RFC 2616, section 10.1: - %% A client MUST be prepared to accept one or more - %% 1xx status responses prior to a regular - %% response, even if the client does not expect a - %% 100 (Continue) status message. Unexpected 1xx - %% status responses MAY be ignored by a user agent. + %% RFC 2616, section 10.1: + %% A client MUST be prepared to accept one or more + %% 1xx status responses prior to a regular + %% response, even if the client does not expect a + %% 100 (Continue) status message. Unexpected 1xx + %% status responses MAY be ignored by a user agent. read_proxy_connect_response(State, nil, nil); {ok, http_eoh} when StatusCode >= 200, StatusCode < 300 -> - %% RFC2817, any 2xx code means success. + %% RFC2817, any 2xx code means success. ConnectOptions = State#client_state.connect_options, SslOptions = State#client_state.proxy_ssl_options, Timeout = State#client_state.connect_timeout, State2 = case ssl:connect(Socket, SslOptions ++ ConnectOptions, Timeout) of - {ok, SslSocket} -> - State#client_state{socket = SslSocket, proxy_setup = true}; - {error, Reason} -> - close_socket(State), - {{error, {proxy_connection_failed, Reason}}, State} - end, + {ok, SslSocket} -> + State#client_state{socket = SslSocket, proxy_setup = true}; + {error, Reason} -> + close_socket(State), + {{error, {proxy_connection_failed, Reason}}, State} + end, send_request(State2); {ok, http_eoh} -> {{error, {proxy_connection_refused, StatusCode, StatusText}}, State}; {error, closed} -> close_socket(State), - {{error, proxy_connection_closed}, State#client_state{socket = undefined, request = undefined}}; + {{error, proxy_connection_closed}, + State#client_state{socket = undefined, request = undefined}}; {error, Reason} -> - {{error, {proxy_connection_failed, Reason}}, State#client_state{request = undefined}} + {{error, {proxy_connection_failed, Reason}}, + State#client_state{request = undefined}} end. %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -send_trailers(State = #client_state{chunked_upload = true}, Trailers) -> - Socket = State#client_state.socket, - Ssl = State#client_state.ssl, - Data = [<<"0\r\n">>, lhttpc_lib:format_hdrs(Trailers)], - check_send_result(State, lhttpc_sock:send(Socket, Data, Ssl)); +send_trailers(#client_state{chunked_upload = true, socket = Socket, ssl= Ssl} = State, Trailers) -> + Data = list_to_binary("0" ++ ?HTTP_LINE_END), + Data2 = [Data, lhttpc_lib:format_hdrs(Trailers)], + check_send_result(State, lhttpc_sock:send(Socket, Data2, Ssl)); send_trailers(#client_state{chunked_upload = false} = State, _Trailers) -> {{error, trailers_not_allowed}, State}. @@ -503,12 +488,12 @@ send_trailers(#client_state{chunked_upload = false} = State, _Trailers) -> %% @private %%------------------------------------------------------------------------------ encode_body_part(#client_state{chunked_upload = true}, http_eob) -> - <<"0\r\n\r\n">>; % We don't send trailers after http_eob + list_to_binary("0" ++ ?HTTP_LINE_END ++ ?HTTP_LINE_END); encode_body_part(#client_state{chunked_upload = false}, http_eob) -> <<>>; encode_body_part(#client_state{chunked_upload = true}, Data) -> Size = list_to_binary(erlang:integer_to_list(iolist_size(Data), 16)), - [Size, <<"\r\n">>, Data, <<"\r\n">>]; + [Size, <>, Data, <>]; encode_body_part(#client_state{chunked_upload = false}, Data) -> Data. @@ -534,11 +519,11 @@ read_response(#client_state{socket = Socket, ssl = Ssl} = State) -> %% @doc @TODO This does not handle redirects at the moment. %% @end %%------------------------------------------------------------------------------ --spec read_response(#client_state{}, {integer(), integer()} | 'nil', http_status(), - any()) -> {any(), socket()} | no_return(). -read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> - Socket = State#client_state.socket, - Ssl = State#client_state.ssl, +-spec read_response(#client_state{}, {integer(), integer()} | 'nil', http_status(), any()) -> + {any(), socket()} | no_return(). +read_response(#client_state{socket = Socket, ssl = Ssl, use_cookies = UseCookies, + request_headers = ReqHdrs, cookies = Cookies} = State, + Vsn, {StatusCode, _} = Status, Hdrs) -> case lhttpc_sock:recv(Socket, Ssl) of {ok, {http_response, NewVsn, NewStatusCode, Reason}} -> NewStatus = {NewStatusCode, Reason}, @@ -557,27 +542,25 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> {ok, http_eoh} -> lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl), {Reply, NewState} = handle_response_body(State, Vsn, Status, Hdrs), - case Reply of - noreply -> - %when partial_download is used. We do not close the socket. - {noreply, NewState#client_state{socket = Socket}}; - {error, Reason} -> - {{error, Reason}, NewState#client_state{socket = undefined}}; - _ -> - NewHdrs = element(2, Reply), - FinalCookies = - case State#client_state.use_cookies of - true -> - lhttpc_lib:update_cookies(NewHdrs, State#client_state.cookies); - _ -> - [] - end, - ReqHdrs = State#client_state.request_headers, - NewSocket = maybe_close_socket(State, Vsn, ReqHdrs, NewHdrs), - {reply, {ok, Reply}, NewState#client_state{socket = NewSocket, - request = undefined, - cookies = FinalCookies}} - end; + case Reply of + noreply -> + %when partial_download is used. We do not close the socket. + {noreply, NewState#client_state{socket = Socket}}; + {error, Reason} -> + {reply, {error, Reason}, NewState#client_state{socket = undefined}}; + _ -> + NewHdrs = element(2, Reply), + FinalCookies = case UseCookies of + true -> + lhttpc_lib:update_cookies(NewHdrs, Cookies); + _ -> + [] + end, + NewSocket = maybe_close_socket(State, Vsn, ReqHdrs, NewHdrs), + {reply, {ok, Reply}, NewState#client_state{socket = NewSocket, + request = undefined, + cookies = FinalCookies}} + end; {error, closed} -> %% TODO does it work for partial uploads? I think should return an error @@ -600,37 +583,34 @@ read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> %% @doc Handles the reading of the response body. %% @end %%------------------------------------------------------------------------------ --spec handle_response_body(#client_state{}, {integer(), integer()}, - http_status(), headers()) -> {http_status(), headers(), body()} | - {http_status(), headers()} | - {'noreply', any()} | - {{'error', any()}, any()}. +-spec handle_response_body(#client_state{}, {integer(), integer()}, http_status(), headers()) -> + {http_status(), headers(), body()} | {http_status(), headers()} | + {'noreply', any()} | {{'error', any()}, any()}. handle_response_body(#client_state{partial_download = false} = State, Vsn, - Status, Hdrs) -> -%when {partial_download, PartialDownloadOptions} option is NOT used. + Status, Hdrs) -> + %when {partial_download, PartialDownloadOptions} option is NOT used. Socket = State#client_state.socket, Ssl = State#client_state.ssl, Method = State#client_state.method, Reply = case has_body(Method, element(1, Status), Hdrs) of - true -> read_body(Vsn, Hdrs, Ssl, Socket, body_type(Hdrs)); - false -> {<<>>, Hdrs} - end, + true -> read_body(Vsn, Hdrs, Ssl, Socket, body_type(Hdrs)); + false -> {<<>>, Hdrs} + end, case Reply of - {error, Reason} -> - {{error, Reason}, State}; - {Body, NewHdrs} -> - {{Status, NewHdrs, Body}, State} + {error, Reason} -> + {{error, Reason}, State}; + {Body, NewHdrs} -> + {{Status, NewHdrs, Body}, State} end; -handle_response_body(#client_state{partial_download = true} = State, Vsn, - Status, Hdrs) -> -%when {partial_download, PartialDownloadOptions} option is used. - Method = State#client_state.method, +handle_response_body(#client_state{partial_download = true, requester = + Requester, method = Method} = State, Vsn, Status, Hdrs) -> + %when {partial_download, PartialDownloadOptions} option is used. case has_body(Method, element(1, Status), Hdrs) of true -> Response = {ok, {Status, Hdrs, partial_download}}, - gen_server:reply(State#client_state.requester, Response), - NewState = State#client_state{download_info = {Vsn, Hdrs}, - body_length = body_type(Hdrs)}, + gen_server:reply(Requester, Response), + NewState = State#client_state{download_info = {Vsn, Hdrs}, + body_length = body_type(Hdrs)}, {noreply, read_partial_body(NewState)}; false -> {{Status, Hdrs, undefined}, State} @@ -675,8 +655,8 @@ body_type(Hdrs) -> case lhttpc_lib:header_value("content-length", Hdrs) of undefined -> TransferEncoding = string:to_lower( - lhttpc_lib:header_value("transfer-encoding", Hdrs, "undefined") - ), + lhttpc_lib:header_value("transfer-encoding", Hdrs, "undefined") + ), case TransferEncoding of "chunked" -> chunked; _ -> infinite @@ -705,8 +685,7 @@ read_body(_Vsn, Hdrs, Ssl, Socket, {fixed_length, ContentLength}) -> read_partial_body(State=#client_state{body_length = chunked}) -> Window = State#client_state.download_window, read_partial_chunked_body(State, Window, 0, [], 0); -read_partial_body(State=#client_state{download_info = {Vsn, Hdrs}, - body_length = infinite}) -> +read_partial_body(State=#client_state{download_info = {Vsn, Hdrs}, body_length = infinite}) -> check_infinite_response(Vsn, Hdrs), read_partial_infinite_body(State, State#client_state.download_window); read_partial_body(State=#client_state{body_length = {fixed_length, ContentLength}}) -> @@ -726,30 +705,24 @@ read_partial_finite_body(State, ContentLength, Window) when Window > 0-> case read_body_part(State, ContentLength) of {ok, Bin} -> State#client_state.download_proc ! {body_part, Bin}, - Length = ContentLength - iolist_size(Bin), - read_partial_finite_body(State, Length, lhttpc_lib:dec(Window)); + Length = ContentLength - iolist_size(Bin), + read_partial_finite_body(State, Length, lhttpc_lib:dec(Window)); {error, Reason} -> State#client_state.download_proc ! {body_part_error, Reason}, - close_socket(State), - State#client_state{request = undefined, - socket = undefined} + close_socket(State), + State#client_state{request = undefined, socket = undefined} end. %%------------------------------------------------------------------------------ %%% @private %%------------------------------------------------------------------------------ -read_body_part(#client_state{part_size = infinity} = State, _ContentLength) -> - lhttpc_sock:recv(State#client_state.socket, State#client_state.ssl); -read_body_part(#client_state{part_size = PartSize} = State, ContentLength) - when PartSize =< ContentLength -> - Socket = State#client_state.socket, - Ssl = State#client_state.ssl, - PartSize = State#client_state.part_size, +read_body_part(#client_state{part_size = infinity, socket = Socket, ssl = Ssl}, _ContentLength) -> + lhttpc_sock:recv(Socket, Ssl); +read_body_part(#client_state{socket = Socket, ssl = Ssl, part_size = PartSize}, + ContentLength) when PartSize =< ContentLength -> lhttpc_sock:recv(Socket, PartSize, Ssl); -read_body_part(#client_state{part_size = PartSize} = State, ContentLength) - when PartSize > ContentLength -> - Socket = State#client_state.socket, - Ssl = State#client_state.ssl, +read_body_part(#client_state{socket = Socket, ssl = Ssl, part_size = PartSize}, + ContentLength) when PartSize > ContentLength -> lhttpc_sock:recv(Socket, ContentLength, Ssl). %%------------------------------------------------------------------------------ @@ -760,57 +733,51 @@ read_length(Hdrs, Ssl, Socket, Length) -> {ok, Data} -> {Data, Hdrs}; {error, Reason} -> - lhttpc_sock:close(Socket, Ssl), - {error, Reason} + lhttpc_sock:close(Socket, Ssl), + {error, Reason} end. %%------------------------------------------------------------------------------ %%% @private %%------------------------------------------------------------------------------ -read_partial_chunked_body(State, 0, 0, _Buffer, 0) -> +read_partial_chunked_body(#client_state{download_proc = Pid} = State, 0, 0, _Buffer, 0) -> %we ask for another call to get_body_part - State#client_state.download_proc ! {body_part, http_eob}, + Pid ! {body_part, http_eob}, State; -read_partial_chunked_body(#client_state{download_info = {_Vsn, Hdrs}, - socket = Socket, - ssl = Ssl, - part_size = PartSize} = State, - Window, BufferSize, Buffer, 0) -> +read_partial_chunked_body(#client_state{download_info = {_Vsn, Hdrs}, socket = Socket, + ssl = Ssl, part_size = PartSize} = State, + Window, BufferSize, Buffer, 0) -> case read_chunk_size(Socket, Ssl) of 0 -> reply_chunked_part(State, Buffer, Window), {Trailers, _NewHdrs} = read_trailers(Socket, Ssl, [], Hdrs), reply_end_of_body(State, Trailers), - State#client_state{request = undefined}; + State#client_state{request = undefined}; ChunkSize when PartSize =:= infinity -> Chunk = read_chunk(Socket, Ssl, ChunkSize), NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), read_partial_chunked_body(State, NewWindow, 0, [], 0); ChunkSize when BufferSize + ChunkSize >= PartSize -> {Chunk, RemSize} = read_partial_chunk(Socket, Ssl, - PartSize - BufferSize, ChunkSize), + PartSize - BufferSize, ChunkSize), NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), read_partial_chunked_body(State, NewWindow, 0, [], RemSize); ChunkSize -> Chunk = read_chunk(Socket, Ssl, ChunkSize), - read_partial_chunked_body(State, Window, - BufferSize + ChunkSize, [Chunk | Buffer], 0) + read_partial_chunked_body(State, Window, + BufferSize + ChunkSize, [Chunk | Buffer], 0) end; -read_partial_chunked_body(State, Window, BufferSize, Buffer, RemSize) -> - Socket = State#client_state.socket, - Ssl = State#client_state.ssl, - PartSize = State#client_state.part_size, +read_partial_chunked_body(#client_state{socket = Socket, ssl = Ssl, part_size = PartSize} = State, + Window, BufferSize, Buffer, RemSize) -> if BufferSize + RemSize >= PartSize -> - {Chunk, NewRemSize} = - read_partial_chunk(Socket, Ssl, PartSize - BufferSize, RemSize), + {Chunk, NewRemSize} = read_partial_chunk(Socket, Ssl, + PartSize - BufferSize, RemSize), NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), - read_partial_chunked_body(State, NewWindow, 0, [], - NewRemSize); + read_partial_chunked_body(State, NewWindow, 0, [], NewRemSize); BufferSize + RemSize < PartSize -> Chunk = read_chunk(Socket, Ssl, RemSize), - read_partial_chunked_body(State, Window, BufferSize + RemSize, - [Chunk | Buffer], 0) + read_partial_chunked_body(State, Window, BufferSize + RemSize, [Chunk | Buffer], 0) end. %%------------------------------------------------------------------------------ @@ -859,7 +826,7 @@ chunk_size(Bin) -> %%------------------------------------------------------------------------------ chunk_size(<<$;, _/binary>>, Chars) -> Chars; -chunk_size(<<"\r\n", _/binary>>, Chars) -> +chunk_size(<>, Chars) -> Chars; chunk_size(<<$\s, Binary/binary>>, Chars) -> %% Facebook's HTTP server returns a chunk size like "6 \r\n" @@ -887,7 +854,7 @@ read_partial_chunk(Socket, Ssl, Size, ChunkSize) -> read_chunk(Socket, Ssl, Size) -> lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl), case lhttpc_sock:recv(Socket, Size + 2, Ssl) of - {ok, <>} -> + {ok, <>} -> Chunk; {ok, Data} -> {error, {invalid_chunk, Data}}; @@ -899,7 +866,7 @@ read_chunk(Socket, Ssl, Size) -> %% @private %%------------------------------------------------------------------------------ -spec read_trailers(socket(), boolean(), any(), any()) -> - {any(), any()} | no_return(). + {any(), any()} | no_return(). read_trailers(Socket, Ssl, Trailers, Hdrs) -> lhttpc_sock:setopts(Socket, [{packet, httph}], Ssl), case lhttpc_sock:recv(Socket, Ssl) of @@ -922,18 +889,18 @@ reply_end_of_body(#client_state{download_proc = To}, Trailers) -> %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -read_partial_infinite_body(State, 0) -> - State#client_state.download_proc ! {body_part, window_finished}, +read_partial_infinite_body(#client_state{download_proc = Pid} = State, 0) -> + Pid ! {body_part, window_finished}, State; -read_partial_infinite_body(State, Window) - when Window >= 0 -> +read_partial_infinite_body(#client_state{download_proc = Pid} = State, Window) + when Window >= 0 -> case read_infinite_body_part(State) of http_eob -> - reply_end_of_body(State, []), - State#client_state{request = undefined}; + reply_end_of_body(State, []), + State#client_state{request = undefined}; Bin -> - State#client_state.download_proc ! {body_part, Bin}, - read_partial_infinite_body(State, lhttpc_lib:dec(Window)) + Pid ! {body_part, Bin}, + read_partial_infinite_body(State, lhttpc_lib:dec(Window)) end. %%------------------------------------------------------------------------------ @@ -970,7 +937,7 @@ check_infinite_response(_, Hdrs) -> %% @private %%------------------------------------------------------------------------------ -spec read_infinite_body(socket(), headers(), boolean()) -> - {binary(), headers()} | no_return(). + {binary(), headers()} | no_return(). read_infinite_body(Socket, Hdrs, Ssl) -> read_until_closed(Socket, <<>>, Hdrs, Ssl). @@ -978,7 +945,7 @@ read_infinite_body(Socket, Hdrs, Ssl) -> %% @private %%------------------------------------------------------------------------------ -spec read_until_closed(socket(), binary(), any(), boolean()) -> - {binary(), any()} | no_return(). + {binary(), any()} | no_return(). read_until_closed(Socket, Acc, Hdrs, Ssl) -> case lhttpc_sock:recv(Socket, Ssl) of {ok, Body} -> @@ -993,25 +960,26 @@ read_until_closed(Socket, Acc, Hdrs, Ssl) -> %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -maybe_close_socket(State, {1, Minor}, ReqHdrs, RespHdrs) when Minor >= 1-> +maybe_close_socket(#client_state{socket = Socket} = State, {1, Minor}, + ReqHdrs, RespHdrs) when Minor >= 1-> ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"), ServerConnection = ?CONNECTION_HDR(RespHdrs, "keep-alive"), if - ClientConnection =:= "close"; ServerConnection =:= "close" -> + ClientConnection == "close"; ServerConnection == "close" -> close_socket(State), undefined; - ClientConnection =/= "close", ServerConnection =/= "close" -> - State#client_state.socket + ClientConnection /= "close", ServerConnection /= "close" -> + Socket end; -maybe_close_socket(State, _, ReqHdrs, RespHdrs) -> +maybe_close_socket(#client_state{socket = Socket} = State, _, ReqHdrs, RespHdrs) -> ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"), ServerConnection = ?CONNECTION_HDR(RespHdrs, "close"), if - ClientConnection =:= "close"; ServerConnection =/= "keep-alive" -> + ClientConnection == "close"; ServerConnection /= "keep-alive" -> close_socket(State), undefined; - ClientConnection =/= "close", ServerConnection =:= "keep-alive" -> - State#client_state.socket + ClientConnection /= "close", ServerConnection == "keep-alive" -> + Socket end. %%------------------------------------------------------------------------------ @@ -1060,14 +1028,14 @@ connect_socket_return(Error, State) -> -spec connect_pool(#client_state{}) -> {ok, socket()} | {error, atom()}. connect_pool(State = #client_state{pool_options = Options, - pool = Pool}) -> + pool = Pool}) -> {Host, Port, Ssl} = request_first_destination(State), case lhttpc_manager:ensure_call(Pool, self(), Host, Port, Ssl, Options) of - {ok, no_socket} -> - %% ensure_call does not open a socket if the pool doesnt have one - new_socket(State); - Reply -> - Reply + {ok, no_socket} -> + %% ensure_call does not open a socket if the pool doesnt have one + new_socket(State); + Reply -> + Reply end. %%------------------------------------------------------------------------------ @@ -1075,38 +1043,37 @@ connect_pool(State = #client_state{pool_options = Options, %% @doc Creates a new socket using the options included in the client state. %% end %%------------------------------------------------------------------------------ -new_socket(State) -> +new_socket(#client_state{connect_timeout = Timeout, connect_options = ConnectOptions} = State) -> {Host, Port, Ssl} = request_first_destination(State), - Timeout = State#client_state.connect_timeout, - ConnectOptions0 = State#client_state.connect_options, - ConnectOptions = case (not lists:member(inet, ConnectOptions0)) andalso - (not lists:member(inet6, ConnectOptions0)) andalso - is_ipv6_host(Host) of - true -> - [inet6 | ConnectOptions0]; - false -> - ConnectOptions0 - end, - SocketOptions = [binary, {packet, http}, {active, false} | ConnectOptions], + ConnectOptions2 = case (not lists:member(inet, ConnectOptions)) andalso + (not lists:member(inet6, ConnectOptions)) andalso + is_ipv6_host(Host) of + true -> + [inet6 | ConnectOptions]; + false -> + ConnectOptions + end, + SocketOptions = [binary, {packet, http}, {nodelay, true}, {reuseaddr, true}, + {active, false} | ConnectOptions2], try lhttpc_sock:connect(Host, Port, SocketOptions, Timeout, Ssl) of - {ok, Socket} -> - {ok, Socket}; - {error, etimedout} -> - %% TCP stack decided to give up - {error, connect_timeout}; - {error, timeout} -> - {error, connect_timeout}; - {error, 'record overflow'} -> - {error, ssl_error}; - {error, _} = Error -> - Error + {ok, Socket} -> + {ok, Socket}; + {error, etimedout} -> + %% TCP stack decided to give up + {error, connect_timeout}; + {error, timeout} -> + {error, connect_timeout}; + {error, 'record overflow'} -> + {error, ssl_error}; + {error, _} = Error -> + Error catch - exit:{{{badmatch, {error, {asn1, _}}}, _}, _} -> - {error, ssl_decode_error}; - Type:Error -> - error_logger:error_msg("Socket connection error: ~p ~p, ~p", - [Type, Error, erlang:get_stacktrace()]), - {error, connection_error} + exit:{{{badmatch, {error, {asn1, _}}}, _}, _} -> + {error, ssl_decode_error}; + Type:Error -> + error_logger:error_msg("Socket connection error: ~p ~p, ~p", + [Type, Error, erlang:get_stacktrace()]), + {error, connection_error} end. %%------------------------------------------------------------------------------ @@ -1114,8 +1081,8 @@ new_socket(State) -> %%------------------------------------------------------------------------------ close_socket(_State = #client_state{socket = Socket, pool = Pool, ssl = Ssl}) -> case Pool of - undefined -> - lhttpc_sock:close(Socket, Ssl); - _ -> - lhttpc_manager:close_socket(Pool, Socket) + undefined -> + lhttpc_sock:close(Socket, Ssl); + _ -> + lhttpc_manager:close_socket(Pool, Socket) end. diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index faeecf5b..2fc107f0 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -40,13 +40,14 @@ maybe_atom_to_list/1, format_hdrs/1, dec/1, - get_cookies/1, - update_cookies/2 - ]). + get_cookies/1, + update_cookies/2]). -include("lhttpc_types.hrl"). -include("lhttpc.hrl"). +-define(HTTP_LINE_END, "\r\n"). + %%============================================================================== %% Exported functions %%============================================================================== @@ -91,9 +92,9 @@ header_value(Hdr, [{ThisHdr, Value}| Hdrs], Default) when is_binary(ThisHdr) -> header_value(Hdr, [{ThisHdr, Value}| Hdrs], Default) -> case string:equal(string:to_lower(ThisHdr), Hdr) of true -> case is_list(Value) of - true -> string:strip(Value); - false -> Value - end; + true -> string:strip(Value); + false -> Value + end; false -> header_value(Hdr, Hdrs, Default) end; @@ -128,14 +129,8 @@ parse_url(URL) -> {User, Passwd, HostPortPath} = split_credentials(CredsHostPortPath), {Host, PortPath} = split_host(HostPortPath, []), {Port, Path} = split_port(Scheme, PortPath, []), - #lhttpc_url{ - host = string:to_lower(Host), - port = Port, - path = Path, - user = User, - password = Passwd, - is_ssl = (Scheme =:= https) - }. + #lhttpc_url{host = string:to_lower(Host), port = Port, path = Path, + user = User, password = Passwd, is_ssl = (Scheme =:= https)}. %%------------------------------------------------------------------------------ %% @spec (Path, Method, Headers, Host, Port, Body, PartialUpload, Cookies) -> @@ -151,19 +146,13 @@ parse_url(URL) -> %% @doc %% @end %%------------------------------------------------------------------------------ --spec format_request(iolist(), method(), headers(), string(), - integer(), iolist(), boolean(), {boolean(), [#lhttpc_cookie{}]}) -> {boolean(), iolist()}. +-spec format_request(iolist(), method(), headers(), string(), integer(), iolist(), + boolean(), {boolean(), [#lhttpc_cookie{}]}) -> {boolean(), iolist()}. format_request(Path, Method, Hdrs, Host, Port, Body, PartialUpload, Cookies) -> AllHdrs = add_mandatory_hdrs(Path, Method, Hdrs, Host, Port, Body, PartialUpload, Cookies), IsChunked = is_chunked(AllHdrs), - { - IsChunked, - [ - Method, " ", Path, " HTTP/1.1\r\n", - format_hdrs(AllHdrs), - format_body(Body, IsChunked) - ] - }. + {IsChunked, [Method, " ", Path, " HTTP/1.1", ?HTTP_LINE_END, format_hdrs(AllHdrs), + format_body(Body, IsChunked)]}. %%------------------------------------------------------------------------------ %% @spec normalize_method(AtomOrString) -> Method @@ -185,8 +174,10 @@ normalize_method(Method) -> %% @end %%------------------------------------------------------------------------------ -spec dec(timeout()) -> timeout(). -dec(Num) when is_integer(Num) -> Num - 1; -dec(Else) -> Else. +dec(Num) when is_integer(Num) -> + Num - 1; +dec(Else) -> + Else. %%------------------------------------------------------------------------------ %% @doc @@ -216,13 +207,12 @@ get_cookies(Hdrs) -> update_cookies(RespHeaders, StateCookies) -> ReceivedCookies = lhttpc_lib:get_cookies(RespHeaders), %% substitute the cookies with the same name, add the others. - Substituted = - lists:foldl(fun(X, Acc) -> - lists:keystore(X#lhttpc_cookie.name, - #lhttpc_cookie.name, Acc, X) - end, StateCookies, ReceivedCookies), + Substituted = lists:foldl(fun(X, Acc) -> + lists:keystore(X#lhttpc_cookie.name, + #lhttpc_cookie.name, Acc, X) + end, StateCookies, ReceivedCookies), %% delete the cookies whose value is set to "deleted" - NewCookies = [ X || X <- Substituted, X#lhttpc_cookie.value =/= "deleted"], + NewCookies = [ X || X <- Substituted, X#lhttpc_cookie.value /= "deleted"], %% Delete the cookies that are expired (check max-age and expire fields). delete_expired_cookies(NewCookies). @@ -236,12 +226,11 @@ update_cookies(RespHeaders, StateCookies) -> -spec delete_expired_cookies([#lhttpc_cookie{}]) -> [#lhttpc_cookie{}]. delete_expired_cookies(Cookies) -> [ X || X <- Cookies, - X#lhttpc_cookie.max_age =:= undefined orelse - timer:now_diff(os:timestamp(), X#lhttpc_cookie.timestamp) - =< X#lhttpc_cookie.max_age, - X#lhttpc_cookie.expires =:= never orelse - calendar:datetime_to_gregorian_seconds(calendar:universal_time()) - =< calendar:datetime_to_gregorian_seconds(X#lhttpc_cookie.expires)]. + X#lhttpc_cookie.max_age == undefined orelse + timer:now_diff(os:timestamp(), X#lhttpc_cookie.timestamp) + =< X#lhttpc_cookie.max_age, X#lhttpc_cookie.expires == never orelse + calendar:datetime_to_gregorian_seconds(calendar:universal_time()) + =< calendar:datetime_to_gregorian_seconds(X#lhttpc_cookie.expires)]. %%------------------------------------------------------------------------------ %% @private @@ -250,17 +239,17 @@ create_cookie_record(Cookie) -> [NameValue | Rest] = string:tokens(Cookie, ";"), Tokens = string:tokens(NameValue, "="), {Atr, AtrValue} = case length(Tokens) of - 2 -> - [Name | [Value]] = Tokens, - {Name, Value}; - _ -> - [Name | _] = Tokens, - Length = length(Name) + 2, - Value = string:substr(NameValue, Length), - {Name, Value} - end, + 2 -> + [Name | [Value]] = Tokens, + {Name, Value}; + _ -> + [Name | _] = Tokens, + Length = length(Name) + 2, + Value = string:substr(NameValue, Length), + {Name, Value} + end, CookieRec = #lhttpc_cookie{name = Atr, - value = AtrValue}, + value = AtrValue}, other_cookie_elements(Rest, CookieRec). %%------------------------------------------------------------------------------ @@ -292,13 +281,13 @@ other_cookie_elements([" Max-Age" ++ Value | Rest], Cookie) -> {Integer, _Rest} = string:to_integer(FinalValue), MaxAge = Integer * 1000000, %we need it in microseconds other_cookie_elements(Rest, Cookie#lhttpc_cookie{max_age = MaxAge, - timestamp = os:timestamp()}); + timestamp = os:timestamp()}); other_cookie_elements([" max-age" ++ Value | Rest], Cookie) -> "=" ++ FinalValue = Value, {Integer, _Rest} = string:to_integer(FinalValue), MaxAge = Integer * 1000000, %we need it in microseconds other_cookie_elements(Rest, Cookie#lhttpc_cookie{max_age = MaxAge, - timestamp = os:timestamp()}); + timestamp = os:timestamp()}); % for the moment we ignore the other attributes. other_cookie_elements([_Element | Rest], Cookie) -> other_cookie_elements(Rest, Cookie). @@ -310,11 +299,9 @@ other_cookie_elements([_Element | Rest], Cookie) -> %% @end %%------------------------------------------------------------------------------ -spec expires_to_datetime(string()) -> - {{integer(), integer(), integer()}, - {integer(),integer(),integer()}}. + {{integer(), integer(), integer()},{integer(),integer(),integer()}}. expires_to_datetime(ExpireDate) -> - [_Expires, Day, Month, Year, Hour, Min, Sec, _GMT] - = string:tokens(ExpireDate, ", -:"), + [_Expires, Day, Month, Year, Hour, Min, Sec, _GMT] = string:tokens(ExpireDate, ", -:"), {{list_to_integer(Year), month_to_integer(Month), list_to_integer(Day)}, {list_to_integer(Hour), list_to_integer(Min), list_to_integer(Sec)}}. @@ -440,15 +427,14 @@ normalize_headers(Headers) -> %%------------------------------------------------------------------------------ -spec normalize_headers(raw_headers(), headers()) -> headers(). normalize_headers([{Header, Value} | T], Acc) when is_list(Header) -> - NormalizedHeader = try list_to_existing_atom(Header) - catch - error:badarg -> Header - end, - NewAcc = [{NormalizedHeader, Value} | Acc], - normalize_headers(T, NewAcc); + NormalizedHeader = try + list_to_existing_atom(Header) + catch + error:badarg -> Header + end, + normalize_headers(T, [{NormalizedHeader, Value} | Acc]); normalize_headers([{Header, Value} | T], Acc) -> - NewAcc = [{Header, Value} | Acc], - normalize_headers(T, NewAcc); + normalize_headers(T, [{Header, Value} | Acc]); normalize_headers([], Acc) -> Acc. @@ -457,12 +443,13 @@ normalize_headers([], Acc) -> %% @doc %% @end %%------------------------------------------------------------------------------ -format_hdrs([{Hdr, Value} | T], Acc) -> - NewAcc = - [maybe_atom_to_list(Hdr), ": ", maybe_atom_to_list(Value), "\r\n" | Acc], +format_hdrs([{Header, Value} | T], Acc) -> + Header2 = maybe_atom_to_list(Header), + Value2 = maybe_atom_to_list(Value), + NewAcc = [Header2, ": ", Value2, ?HTTP_LINE_END | Acc], format_hdrs(T, NewAcc); format_hdrs([], Acc) -> - [Acc, "\r\n"]. + [Acc, ?HTTP_LINE_END]. %%------------------------------------------------------------------------------ %% @private @@ -477,10 +464,8 @@ format_body(Body, true) -> 0 -> <<>>; Size -> - [ - erlang:integer_to_list(Size, 16), <<"\r\n">>, - Body, <<"\r\n">> - ] + [erlang:integer_to_list(Size, 16), <>, + Body, <>] end. %%------------------------------------------------------------------------------ @@ -493,23 +478,23 @@ format_body(Body, true) -> add_mandatory_hdrs(Path, Method, Hdrs, Host, Port, Body, PartialUpload, {UseCookies, Cookies}) -> ContentHdrs = add_content_headers(Method, Hdrs, Body, PartialUpload), case UseCookies of - true -> - % only include cookies if the cookie path is a prefix of the request path - % see RFC http://www.ietf.org/rfc/rfc2109.txt section 4.3.4 - IncludeCookies = - lists:filter(fun(#lhttpc_cookie{path = undefined}) -> - true; - (X) -> - IsPrefix = string:str(Path, X#lhttpc_cookie.path), - if (IsPrefix =/= 1) -> - false; - true -> - true - end - end, Cookies), - FinalHdrs = add_cookie_headers(ContentHdrs, IncludeCookies); - _ -> - FinalHdrs = ContentHdrs + true -> + % only include cookies if the cookie path is a prefix of the request path + % see RFC http://www.ietf.org/rfc/rfc2109.txt section 4.3.4 + IncludeCookies = lists:filter( + fun(#lhttpc_cookie{path = undefined}) -> + true; + (X) -> + IsPrefix = string:str(Path, X#lhttpc_cookie.path), + if (IsPrefix =/= 1) -> + false; + true -> + true + end + end, Cookies), + FinalHdrs = add_cookie_headers(ContentHdrs, IncludeCookies); + _ -> + FinalHdrs = ContentHdrs end, add_host(FinalHdrs, Host, Port). @@ -566,13 +551,13 @@ add_content_headers(Hdrs, Body, false) -> end; add_content_headers(Hdrs, _Body, true) -> case {header_value("content-length", Hdrs), - header_value("transfer-encoding", Hdrs)} of + header_value("transfer-encoding", Hdrs)} of {undefined, undefined} -> [{"Transfer-Encoding", "chunked"} | Hdrs]; {undefined, TransferEncoding} -> case string:to_lower(TransferEncoding) of - "chunked" -> Hdrs; - _ -> erlang:error({error, unsupported_transfer_encoding}) + "chunked" -> Hdrs; + _ -> erlang:error({error, unsupported_transfer_encoding}) end; {_Length, undefined} -> Hdrs; @@ -602,7 +587,7 @@ add_host(Hdrs, Host, Port) -> -spec is_chunked(headers()) -> boolean(). is_chunked(Hdrs) -> TransferEncoding = string:to_lower( - header_value("transfer-encoding", Hdrs, "undefined")), + header_value("transfer-encoding", Hdrs, "undefined")), case TransferEncoding of "chunked" -> true; _ -> false diff --git a/src/lhttpc_manager.erl b/src/lhttpc_manager.erl index 6ccb60f5..fa3f12d6 100644 --- a/src/lhttpc_manager.erl +++ b/src/lhttpc_manager.erl @@ -41,7 +41,7 @@ %% Exported functions -export([start_link/0, start_link/1, - new_pool/3, + new_pool/3, client_count/1, connection_count/1, connection_count/2, update_connection_timeout/2, @@ -50,8 +50,7 @@ set_max_pool_size/2, ensure_call/6, client_done/5, - close_socket/2 - ]). + close_socket/2]). %% Callbacks -export([init/1, @@ -59,8 +58,7 @@ handle_cast/2, handle_info/2, code_change/3, - terminate/2 - ]). + terminate/2]). -behaviour(gen_server). @@ -68,13 +66,12 @@ -include("lhttpc.hrl"). -record(httpc_man, { - destinations = dict:new(), - sockets = dict:new(), - clients = dict:new(), % Pid => {Dest, MonRef} - queues = dict:new(), % Dest => queue of Froms - max_pool_size = 50 :: non_neg_integer(), - timeout = 300000 :: non_neg_integer() - }). + destinations = dict:new(), + sockets = dict:new(), + clients = dict:new(), % Pid => {Dest, MonRef} + queues = dict:new(), % Dest => queue of Froms + max_pool_size = 50 :: non_neg_integer(), + timeout = 300000 :: non_neg_integer()}). %%============================================================================== %% Exported functions @@ -106,13 +103,13 @@ set_max_pool_size(PidOrName, Size) when is_integer(Size), Size > 0 -> list_pools() -> Children = supervisor:which_children(lhttpc_sup), lists:foldl(fun(In, Acc) -> - case In of - {N, P, _, [lhttpc_manager]} -> - [{N, dump_settings(P)} | Acc]; - _ -> - Acc - end - end, [], Children). + case In of + {N, P, _, [lhttpc_manager]} -> + [{N, dump_settings(P)} | Acc]; + _ -> + Acc + end + end, [], Children). %%------------------------------------------------------------------------------ %% @spec (PoolPidOrName) -> Count @@ -206,24 +203,24 @@ start_link(Options0) -> %% @end %%------------------------------------------------------------------------------ -spec ensure_call(pool_id(), pid(), host(), port_num(), boolean(), pool_options()) -> - {ok, socket()} | {ok, 'no_socket'} | {error, term()}. + {ok, socket()} | {ok, 'no_socket'} | {error, term()}. ensure_call(Pool, Pid, Host, Port, Ssl, Options) -> SocketRequest = {socket, Pid, Host, Port, Ssl}, try - gen_server:call(Pool, SocketRequest, 100) + gen_server:call(Pool, SocketRequest, 100) catch exit:{noproc, _Reason} -> case proplists:get_value(pool_ensure, Options, false) of true -> {ok, DefaultTimeout} = application:get_env( - lhttpc, - connection_timeout), + lhttpc, + connection_timeout), ConnTimeout = proplists:get_value(pool_connection_timeout, Options, DefaultTimeout), {ok, DefaultMaxPool} = application:get_env( - lhttpc, - pool_size), + lhttpc, + pool_size), PoolMaxSize = proplists:get_value(pool_max_size, Options, DefaultMaxPool), @@ -240,7 +237,7 @@ ensure_call(Pool, Pid, Host, Port, Ssl, Options) -> end. -spec new_pool(atom(), non_neg_integer(), poolsize()) -> - {ok, pid()} | {error, term()}. + {ok, pid()} | {error, term()}. new_pool(Pool, ConnTimeout, PoolSize) -> ChildSpec = {Pool, {lhttpc_manager, start_link, [[{name, Pool}, @@ -305,7 +302,7 @@ handle_call({socket, Pid, Host, Port, Ssl}, {Pid, _Ref} = From, State) -> max_pool_size = MaxSize, clients = Clients, queues = Queues - } = State, + } = State, Dest = {Host, Port, Ssl}, {Reply0, State2} = find_socket(Dest, Pid, State), case Reply0 of @@ -322,7 +319,8 @@ handle_call({socket, Pid, Host, Port, Ssl}, {Pid, _Ref} = From, State) -> end end; handle_call(dump_settings, _, State) -> - {reply, [{max_pool_size, State#httpc_man.max_pool_size}, {timeout, State#httpc_man.timeout}], State}; + {reply, [{max_pool_size, State#httpc_man.max_pool_size}, + {timeout, State#httpc_man.timeout}], State}; handle_call(client_count, _, State) -> {reply, dict:size(State#httpc_man.clients), State}; handle_call(connection_count, _, State) -> @@ -420,9 +418,9 @@ find_socket({_, _, Ssl} = Dest, Pid, State) -> {_, Timer} = dict:fetch(Socket, State#httpc_man.sockets), cancel_timer(Timer, Socket), NewState = State#httpc_man{ - destinations = update_dest(Dest, Sockets, Dests), - sockets = dict:erase(Socket, State#httpc_man.sockets) - }, + destinations = update_dest(Dest, Sockets, Dests), + sockets = dict:erase(Socket, State#httpc_man.sockets) + }, {{ok, Socket}, NewState}; {error, badarg} -> % Pid has timed out, reuse for someone else lhttpc_sock:setopts(Socket, [{active, once}], Ssl), @@ -447,7 +445,7 @@ remove_socket(Socket, State) -> State#httpc_man{ destinations = update_dest(Dest, Sockets, Dests), sockets = dict:erase(Socket, State#httpc_man.sockets) - }; + }; error -> State end. @@ -468,7 +466,7 @@ store_socket({_, _, Ssl} = Dest, Socket, State) -> State#httpc_man{ destinations = dict:store(Dest, Sockets, Dests), sockets = dict:store(Socket, {Dest, Timer}, State#httpc_man.sockets) - }. + }. %------------------------------------------------------------------------------ %% @private @@ -533,8 +531,8 @@ queue_out({_Host, _Port, _Ssl} = Dest, Queues) -> %------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -deliver_socket(Socket, {_, _, Ssl} = Dest, State) -> - case queue_out(Dest, State#httpc_man.queues) of +deliver_socket(Socket, {_, _, Ssl} = Dest, #httpc_man{queues = Queues} = State) -> + case queue_out(Dest, Queues) of empty -> store_socket(Dest, Socket, State); {ok, {PidWaiter, _} = FromWaiter, Queues2} -> @@ -555,9 +553,9 @@ deliver_socket(Socket, {_, _, Ssl} = Dest, State) -> %------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -monitor_client(Dest, {Pid, _} = _From, State) -> +monitor_client(Dest, {Pid, _} = _From, #httpc_man{clients = Clients} = State) -> MonRef = erlang:monitor(process, Pid), - Clients2 = dict:store(Pid, {Dest, MonRef}, State#httpc_man.clients), + Clients2 = dict:store(Pid, {Dest, MonRef}, Clients), State#httpc_man{clients = Clients2}. %------------------------------------------------------------------------------ diff --git a/src/lhttpc_sock.erl b/src/lhttpc_sock.erl index 306779d5..4c241630 100644 --- a/src/lhttpc_sock.erl +++ b/src/lhttpc_sock.erl @@ -38,8 +38,7 @@ send/3, controlling_process/3, setopts/3, - close/2 - ]). + close/2]). -include("lhttpc_types.hrl"). @@ -137,8 +136,7 @@ send(Socket, Request, false) -> %% Sets the controlling proces for the `Socket'. %% @end %%------------------------------------------------------------------------------ --spec controlling_process(socket(), pid() | atom(), boolean()) -> - ok | {error, atom()}. +-spec controlling_process(socket(), pid() | atom(), boolean()) -> ok | {error, atom()}. controlling_process(Socket, Controller, IsSsl) when is_atom(Controller) -> controlling_process(Socket, whereis(Controller), IsSsl); controlling_process(Socket, Pid, true) -> diff --git a/src/lhttpc_sup.erl b/src/lhttpc_sup.erl index 36acdfe2..9446f53c 100644 --- a/src/lhttpc_sup.erl +++ b/src/lhttpc_sup.erl @@ -61,4 +61,4 @@ start_link() -> %% @hidden %%------------------------------------------------------------------------------ init(_) -> - {ok, {{one_for_one, 10, 1}, []}}. + {ok, {{one_for_one, 10, 1}, []}}. diff --git a/test/lhttpc_client_tests.erl b/test/lhttpc_client_tests.erl index 56e8f584..19d5e836 100644 --- a/test/lhttpc_client_tests.erl +++ b/test/lhttpc_client_tests.erl @@ -13,31 +13,31 @@ fail_connect_test() -> ?assertEqual({error, econnrefused}, - lhttpc_client:start({{"localhost", 8080, false}, []}, [])). + lhttpc_client:start({{"localhost", 8080, false}, []}, [])). fail_connect_pool_test_() -> {foreach, fun() -> - ok = application:start(ssl), - ok = application:start(lhttpc) + ok = application:start(ssl), + ok = application:start(lhttpc) end, fun(_) -> - application:stop(lhttpc), - application:stop(ssl) + application:stop(lhttpc), + application:stop(ssl) end, [{"Fail to connect on ensure pool", fun() -> - ?assertMatch({error, econnrefused}, - lhttpc_client:start({{"localhost", 8080, false}, - [{pool_options, - [{pool, my_test_pool}, - {pool_ensure, true}]}]}, [])) + ?assertMatch({error, econnrefused}, + lhttpc_client:start({{"localhost", 8080, false}, + [{pool_options, + [{pool, my_test_pool}, + {pool_ensure, true}]}]}, [])) end}, {"Fail to connect - no pool", fun() -> - ?assertEqual({error, unknown_pool}, - lhttpc_client:start({{"localhost", 8080, false}, - [{pool_options, - [{pool, my_test_pool}]}]}, [])) + ?assertEqual({error, unknown_pool}, + lhttpc_client:start({{"localhost", 8080, false}, + [{pool_options, + [{pool, my_test_pool}]}]}, [])) end}] }. diff --git a/test/lhttpc_lib_tests.erl b/test/lhttpc_lib_tests.erl index 9cb0f74d..01565bf2 100644 --- a/test/lhttpc_lib_tests.erl +++ b/test/lhttpc_lib_tests.erl @@ -32,19 +32,19 @@ -include_lib("eunit/include/eunit.hrl"). -define(HEADER1, [{"X-Frame-Options","SAMEORIGIN"}, - {"X-Xss-Protection","1; mode=block"}, - {"Content-Length","221"}, - {"Server","gws"}, - {"Date","Tue, 29 Jan 2013 10:31:52 GMT"}, - {"P3p", - "CP=\"This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info.\""}, - {"Set-Cookie", - "NID=67=gWDe_1hs0LbFdFRIiHXh8qQT_oh_2T2e2tPU3su6azclQH0FGbIpkHYkZJ1kIENFScdIWsnaHd3fUL-J8dZ8YApccTTmpfAgxgCStTspaZrCBRLG0SHRiAZz-Lkj8tyk; expires=Wed, 31-Jul-2013 10:31:52 GMT; path=/; domain=.google.com; HttpOnly"}, - {"Set-Cookie", - "PREF=ID=d8f03b98b080a98a:FF=0:TM=1359455512:LM=1359455512:S=x-lfwE8swDlcyxXl; expires=Thu, 29-Jan-2015 10:31:52 GMT; path=/; domain=.google.com"}, - {"Content-Type","text/html; charset=UTF-8"}, - {"Cache-Control","private"}, - {"Location","http://www.google.co.uk/"}]). + {"X-Xss-Protection","1; mode=block"}, + {"Content-Length","221"}, + {"Server","gws"}, + {"Date","Tue, 29 Jan 2013 10:31:52 GMT"}, + {"P3p", + "CP=\"This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info.\""}, + {"Set-Cookie", + "NID=67=gWDe_1hs0LbFdFRIiHXh8qQT_oh_2T2e2tPU3su6azclQH0FGbIpkHYkZJ1kIENFScdIWsnaHd3fUL-J8dZ8YApccTTmpfAgxgCStTspaZrCBRLG0SHRiAZz-Lkj8tyk; expires=Wed, 31-Jul-2013 10:31:52 GMT; path=/; domain=.google.com; HttpOnly"}, + {"Set-Cookie", + "PREF=ID=d8f03b98b080a98a:FF=0:TM=1359455512:LM=1359455512:S=x-lfwE8swDlcyxXl; expires=Thu, 29-Jan-2015 10:31:52 GMT; path=/; domain=.google.com"}, + {"Content-Type","text/html; charset=UTF-8"}, + {"Cache-Control","private"}, + {"Location","http://www.google.co.uk/"}]). parse_url_test_() -> [ @@ -243,20 +243,20 @@ parse_url_test_() -> get_cookies_test_() -> [ ?_assertEqual([#lhttpc_cookie{ - name = "NID", - value = "67=gWDe_1hs0LbFdFRIiHXh8qQT_oh_2T2e2tPU3su6azclQH0FGbIpkHYkZJ1kIENFScdIWsnaHd3fUL-J8dZ8YApccTTmpfAgxgCStTspaZrCBRLG0SHRiAZz-Lkj8tyk", - expires = {{2013, 7, 31}, {10, 31, 52}}, - path = "/", - max_age = undefined, - timestamp = undefined - }, - #lhttpc_cookie{ - name = "PREF", - value = "ID=d8f03b98b080a98a:FF=0:TM=1359455512:LM=1359455512:S=x-lfwE8swDlcyxXl", - expires = {{2015, 1, 29}, {10, 31, 52}}, - path = "/", - max_age = undefined, - timestamp = undefined - }], + name = "NID", + value = "67=gWDe_1hs0LbFdFRIiHXh8qQT_oh_2T2e2tPU3su6azclQH0FGbIpkHYkZJ1kIENFScdIWsnaHd3fUL-J8dZ8YApccTTmpfAgxgCStTspaZrCBRLG0SHRiAZz-Lkj8tyk", + expires = {{2013, 7, 31}, {10, 31, 52}}, + path = "/", + max_age = undefined, + timestamp = undefined + }, + #lhttpc_cookie{ + name = "PREF", + value = "ID=d8f03b98b080a98a:FF=0:TM=1359455512:LM=1359455512:S=x-lfwE8swDlcyxXl", + expires = {{2015, 1, 29}, {10, 31, 52}}, + path = "/", + max_age = undefined, + timestamp = undefined + }], lhttpc_lib:get_cookies(?HEADER1)) ]. diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index 4df88494..4336693c 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -523,14 +523,14 @@ partial_upload_chunked_no_trailer() -> URL = url(Port, "/partial_upload_chunked_no_trailer"), Body = [<<"This">>, <<" is ">>, <<"chunky">>, <<" stuff!">>], Options = [{partial_upload, true}], - {ok, Client} = lhttpc:connect_client(URL, []), + {ok, Client} = lhttpc:connect_client(URL, []), {ok, partial_upload} = lhttpc:request_client(Client, URL, post, [], hd(Body), 1000, Options), - ok = upload_parts(Client, tl(Body)), - {ok, Response} = lhttpc:send_body_part(Client, http_eob, 1000), + ok = upload_parts(Client, tl(Body)), + {ok, Response} = lhttpc:send_body_part(Client, http_eob, 1000), ?assertEqual({200, "OK"}, status(Response)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response)), ?assertEqual("This is chunky stuff!", - lhttpc_lib:header_value("x-test-orig-body", headers(Response))). + lhttpc_lib:header_value("x-test-orig-body", headers(Response))). partial_download_illegal_option() -> ?assertError({bad_option, {partial_download, {foo, bar}}}, @@ -540,14 +540,14 @@ partial_download_illegal_option() -> long_body_part(Size) -> list_to_binary( lists:flatten( - [webserver_utils:long_body_part() || _ <- lists:seq(1, Size)])). + [webserver_utils:long_body_part() || _ <- lists:seq(1, Size)])). partial_download_identity() -> Port = start(gen_tcp, [fun webserver_utils:large_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ {window_size, 1}, - {recv_proc, self()} + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], {ok, Client} = lhttpc:connect_client(URL, []), @@ -562,8 +562,8 @@ partial_download_infinity_window() -> Port = start(gen_tcp, [fun webserver_utils:large_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, infinity}, - {recv_proc, self()} + {window_size, infinity}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], {ok, Client} = lhttpc:connect_client(URL, []), @@ -577,8 +577,8 @@ partial_download_no_content_length() -> Port = start(gen_tcp, [fun webserver_utils:no_content_length/5]), URL = url(Port, "/no_cl"), PartialDownload = [ - {window_size, 1}, - {recv_proc, self()} + {window_size, 1}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], {ok, Client} = lhttpc:connect_client(URL, []), @@ -592,8 +592,8 @@ partial_download_no_content() -> Port = start(gen_tcp, [fun webserver_utils:no_content_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1}, - {recv_proc, self()} + {window_size, 1}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], {ok, Client} = lhttpc:connect_client(URL, []), @@ -606,10 +606,10 @@ limited_partial_download_identity() -> Port = start(gen_tcp, [fun webserver_utils:large_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1}, - {part_size, 512}, % bytes - {recv_proc, self()} - ], + {window_size, 1}, + {part_size, 512}, % bytes + {recv_proc, self()} + ], Options = [{partial_download, PartialDownload}], {ok, Client} = lhttpc:connect_client(URL, []), {ok, {Status, _Hdrs, partial_download}} = @@ -622,9 +622,9 @@ partial_download_chunked() -> Port = start(gen_tcp, [fun webserver_utils:large_chunked_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1}, - {part_size, length(webserver_utils:long_body_part()) * 3}, - {recv_proc, self()} + {window_size, 1}, + {part_size, length(webserver_utils:long_body_part()) * 3}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], {ok, Client} = lhttpc:connect_client(URL, []), @@ -638,9 +638,9 @@ partial_download_chunked_infinite_part() -> Port = start(gen_tcp, [fun webserver_utils:large_chunked_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1}, - {part_size, infinity}, - {recv_proc, self()} + {window_size, 1}, + {part_size, infinity}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], {ok, Client} = lhttpc:connect_client(URL, []), @@ -655,9 +655,9 @@ partial_download_smallish_chunks() -> Port = start(gen_tcp, [fun webserver_utils:large_chunked_response/5]), URL = url(Port, "/partial_download_identity"), PartialDownload = [ - {window_size, 1}, - {part_size, length(webserver_utils:long_body_part()) - 1}, - {recv_proc, self()} + {window_size, 1}, + {part_size, length(webserver_utils:long_body_part()) - 1}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], {ok, Client} = lhttpc:connect_client(URL, []), @@ -671,9 +671,9 @@ partial_download_slow_chunks() -> Port = start(gen_tcp, [fun webserver_utils:slow_chunked_response/5]), URL = url(Port, "/slow"), PartialDownload = [ - {window_size, 1}, - {part_size, length(webserver_utils:long_body_part()) div 2}, - {recv_proc, self()} + {window_size, 1}, + {part_size, length(webserver_utils:long_body_part()) div 2}, + {recv_proc, self()} ], Options = [{partial_download, PartialDownload}], {ok, Client} = lhttpc:connect_client(URL, []), @@ -716,7 +716,7 @@ ssl_post() -> ssl_chunked() -> Port = start(ssl, [fun webserver_utils:chunked_response/5, fun webserver_utils:chunked_response_t/5]), URL = ssl_url(Port, "/ssl_chunked"), - {ok, Client} = lhttpc:connect_client(URL, []), + {ok, Client} = lhttpc:connect_client(URL, []), FirstResult = lhttpc:request_client(Client, URL, get, [], [], 100, [{pool_options, [{pool_ensure, true}, {pool, lhttpc_manager}]}]), ?assertMatch({ok, _}, FirstResult), {ok, FirstResponse} = FirstResult, @@ -749,7 +749,7 @@ invalid_options() -> cookies() -> Port = start(gen_tcp, [fun webserver_utils:set_cookie_response/5, fun webserver_utils:expired_cookie_response/5, - fun webserver_utils:receive_right_cookies/5]), + fun webserver_utils:receive_right_cookies/5]), URL = url(Port, "/cookies"), Options = [{use_cookies, true}], {ok, Client} = lhttpc:connect_client(URL, Options), @@ -764,8 +764,8 @@ cookies() -> %%% Helpers functions upload_parts(Client, Parts) -> lists:foldl(fun(BodyPart, _) -> - lhttpc:send_body_part(Client, BodyPart, 1000) - end, ok, Parts). + lhttpc:send_body_part(Client, BodyPart, 1000) + end, ok, Parts). read_partial_body(Client) -> read_partial_body(Client, infinity, <<>>). @@ -775,28 +775,28 @@ read_partial_body(Client, Size) -> read_partial_body(Client, Size, Acc) -> receive - {body_part, http_eob} -> - %% chunked download - ok = lhttpc:get_body_part(Client), - read_partial_body(Client, Size, Acc); - {body_part, window_finished} -> - ok = lhttpc:get_body_part(Client), - read_partial_body(Client, Size, Acc); - {body_part, Bin} -> - if + {body_part, http_eob} -> + %% chunked download + ok = lhttpc:get_body_part(Client), + read_partial_body(Client, Size, Acc); + {body_part, window_finished} -> + ok = lhttpc:get_body_part(Client), + read_partial_body(Client, Size, Acc); + {body_part, Bin} -> + if Size =:= infinity -> ok; Size =/= infinity -> ?assert(Size >= iolist_size(Bin)) - end, - read_partial_body(Client, Size, <>); - {http_eob, Trailers} -> - Acc; - {body_part_error, Reason} -> - {error, Reason, Acc} + end, + read_partial_body(Client, Size, <>); + {http_eob, Trailers} -> + Acc; + {body_part_error, Reason} -> + {error, Reason, Acc} after - 1000 -> - {error, receive_clause, Acc} + 1000 -> + {error, receive_clause, Acc} end. simple(Method) -> diff --git a/test/simple_load.erl b/test/simple_load.erl index 0e49b273..288cc67a 100644 --- a/test/simple_load.erl +++ b/test/simple_load.erl @@ -9,75 +9,75 @@ %%% Client part start_client(Port, Clients) -> - start_client("localhost", Port, Clients). + start_client("localhost", Port, Clients). start_client(Host, Port, Clients) when Clients > 0 -> - start_applications([crypto, ssl, lhttpc]), - process_flag(trap_exit, true), - {ok, Body} = file:read_file("test/1M"), - URL = "http://" ++ Host ++ ":" ++ integer_to_list(Port) ++ "/static/1M", - start(Clients, URL, Body, Clients). + start_applications([crypto, ssl, lhttpc]), + process_flag(trap_exit, true), + {ok, Body} = file:read_file("test/1M"), + URL = "http://" ++ Host ++ ":" ++ integer_to_list(Port) ++ "/static/1M", + start(Clients, URL, Body, Clients). start(0, _, _, No) -> - wait_exit(No, []); + wait_exit(No, []); start(Clients, URL, Body, No) -> - spawn_link(?MODULE, client, [URL, Body]), - start(Clients - 1, URL, Body, No). + spawn_link(?MODULE, client, [URL, Body]), + start(Clients - 1, URL, Body, No). wait_exit(0, []) -> - ok; + ok; wait_exit(0, Errors) -> - {error, Errors}; + {error, Errors}; wait_exit(No, Errors) -> - receive - {'EXIT', _, normal} -> - wait_exit(No - 1, Errors); - {'EXIT', _, Reason} -> - wait_exit(No - 1, [Reason | Errors]) - end. + receive + {'EXIT', _, normal} -> + wait_exit(No - 1, Errors); + {'EXIT', _, Reason} -> + wait_exit(No - 1, [Reason | Errors]) + end. client(URL, Body) -> - case lhttpc:request(URL, "POST", [], Body, 60000) of - {ok, {{200, _}, _, Body}} -> - ok; - Other -> - exit({bad_result, Other}) - end. + case lhttpc:request(URL, "POST", [], Body, 60000) of + {ok, {{200, _}, _, Body}} -> + ok; + Other -> + exit({bad_result, Other}) + end. %%% Server part start_server() -> - SockOpts = [{backlog, 10000}], - {ok, Pid} = gen_httpd:start_link(?MODULE, nil, 0, 600000, SockOpts), - gen_httpd:port(Pid). + SockOpts = [{backlog, 10000}], + {ok, Pid} = gen_httpd:start_link(?MODULE, nil, 0, 600000, SockOpts), + gen_httpd:port(Pid). init(_, _) -> - {ok, nil}. + {ok, nil}. handle_continue(_Method, _URI, _Vsn, _ReqHdrs, CBState) -> - {continue, [], CBState}. + {continue, [], CBState}. handle_request(_Method, "/static/1M", {1,1}, _, EntityBody, State) -> - case EntityBody of - {identity, EntityState} -> - case gen_httpd:read_body(complete, 50000, EntityState) of - {ok, {Body, http_eob}} -> - {reply, 200, [], Body, State}; - {error, Reason} -> - {reply, 500, [], io_lib:format("~p", [Reason]), State} - end; - _ -> - {reply, 406, [], <<"No request body">>, State} - end. + case EntityBody of + {identity, EntityState} -> + case gen_httpd:read_body(complete, 50000, EntityState) of + {ok, {Body, http_eob}} -> + {reply, 200, [], Body, State}; + {error, Reason} -> + {reply, 500, [], io_lib:format("~p", [Reason]), State} + end; + _ -> + {reply, 406, [], <<"No request body">>, State} + end. terminate(_, _) -> - ok. + ok. start_applications(Apps) -> - Started = lists:map(fun({Name, _, _}) -> Name end, - application:which_applications()), - lists:foreach(fun(App) -> - case lists:member(App, Started) of - false -> ok = application:start(App); - true -> ok - end - end, Apps). + Started = lists:map(fun({Name, _, _}) -> Name end, + application:which_applications()), + lists:foreach(fun(App) -> + case lists:member(App, Started) of + false -> ok = application:start(App); + true -> ok + end + end, Apps). diff --git a/test/webserver_utils.erl b/test/webserver_utils.erl index be67c7ac..474e9c6c 100644 --- a/test/webserver_utils.erl +++ b/test/webserver_utils.erl @@ -71,9 +71,9 @@ long_body_part() -> long_body_part(Size) -> list_to_binary( lists:foldl( - fun(_, Acc) -> - Acc ++ " " ++ webserver_utils:long_body_part() - end, webserver_utils:long_body_part(), lists:seq(1, Size))). + fun(_, Acc) -> + Acc ++ " " ++ webserver_utils:long_body_part() + end, webserver_utils:long_body_part(), lists:seq(1, Size))). %%% Responders simple_response(Module, Socket, _Request, _Headers, Body) -> @@ -313,10 +313,10 @@ close_connection(Module, Socket, _, _, _) -> not_modified_response(Module, Socket, _Request, _Headers, _Body) -> Module:send( Socket, - [ - "HTTP/1.1 304 Not Modified\r\n" - "Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n\r\n" - ] + [ + "HTTP/1.1 304 Not Modified\r\n" + "Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n\r\n" + ] ). basic_auth_responder(User, Passwd) -> @@ -386,45 +386,45 @@ expired_cookie_response(Module, Socket, _Request, Headers, _Body) -> undefined -> Module:send( Socket, - "HTTP/1.1 500 Internal Server Error\r\n" - "Content-type: text/plain\r\n" - "Content-length: 0\r\n\r\n" + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-type: text/plain\r\n" + "Content-length: 0\r\n\r\n" ); - "name=value; name2=value2" -> - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Connection: Keep-Alive\r\n" - "Set-Cookie: name2=value2; Expires=Wed, 09-Jun-1975 10:18:14 GMT\r\n" - "Content-type: text/plain\r\n" + "name=value; name2=value2" -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Connection: Keep-Alive\r\n" + "Set-Cookie: name2=value2; Expires=Wed, 09-Jun-1975 10:18:14 GMT\r\n" + "Content-type: text/plain\r\n" "Content-length: 0\r\n\r\n" - ); - %The order should not matter. - "name2=value2; name=value"-> - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Connection: Keep-Alive\r\n" - "Set-Cookie: name2=value2; Expires=Wed, 09-Jun-1975 10:18:14 GMT\r\n" - "Content-type: text/plain\r\n" + ); + %The order should not matter. + "name2=value2; name=value"-> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Connection: Keep-Alive\r\n" + "Set-Cookie: name2=value2; Expires=Wed, 09-Jun-1975 10:18:14 GMT\r\n" + "Content-type: text/plain\r\n" "Content-length: 0\r\n\r\n" - ) + ) end. receive_right_cookies(Module, Socket, _Request, Headers, _Body) -> case proplists:get_value("Cookie", Headers) of - "name=value" -> - Module:send( - Socket, - "HTTP/1.1 200 OK\r\n" - "Content-type: text/plain\r\n" - "Content-length: 0\r\n\r\n" - ); - _ -> - Module:send( + "name=value" -> + Module:send( + Socket, + "HTTP/1.1 200 OK\r\n" + "Content-type: text/plain\r\n" + "Content-length: 0\r\n\r\n" + ); + _ -> + Module:send( Socket, - "HTTP/1.1 500 Internal Server Error\r\n" - "Content-type: text/plain\r\n" - "Content-length: 0\r\n\r\n" + "HTTP/1.1 500 Internal Server Error\r\n" + "Content-type: text/plain\r\n" + "Content-length: 0\r\n\r\n" ) end. From 1efb0307555d83de80bd57f782768506fc440c54 Mon Sep 17 00:00:00 2001 From: Juraj Hlista Date: Wed, 24 Apr 2013 10:44:12 +0100 Subject: [PATCH 41/49] Replace string:to_lower with a faster version --- src/lhttpc_client.erl | 10 ++++---- src/lhttpc_lib.erl | 53 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl index cb006f25..29ee7e7c 100644 --- a/src/lhttpc_client.erl +++ b/src/lhttpc_client.erl @@ -57,7 +57,7 @@ -define(HTTP_LINE_END, "\r\n"). -define(CONNECTION_HDR(HDRS, DEFAULT), - string:to_lower(lhttpc_lib:header_value("connection", HDRS, DEFAULT))). + lhttpc_lib:to_lower(lhttpc_lib:header_value("connection", HDRS, DEFAULT))). -record(client_state, { host :: string(), @@ -184,7 +184,7 @@ handle_call({request, PathOrUrl, Method, Hdrs, Body, Options}, From, ProxyUrl when is_list(ProxyUrl) -> lhttpc_lib:parse_url(ProxyUrl) end, - FinalPath, FinalHeaders, Host, Port} = + {FinalPath, FinalHeaders, Host, Port} = url_extract(PathOrUrl, Hdrs, ClientHost, ClientPort), case {Host, Port} =:= {ClientHost, ClientPort} of true -> @@ -654,7 +654,7 @@ has_body(_, _, _) -> body_type(Hdrs) -> case lhttpc_lib:header_value("content-length", Hdrs) of undefined -> - TransferEncoding = string:to_lower( + TransferEncoding = lhttpc_lib:to_lower( lhttpc_lib:header_value("transfer-encoding", Hdrs, "undefined") ), case TransferEncoding of @@ -922,13 +922,13 @@ read_infinite_body_part(#client_state{socket = Socket, ssl = Ssl}) -> %%------------------------------------------------------------------------------ check_infinite_response({1, Minor}, Hdrs) when Minor >= 1 -> HdrValue = lhttpc_lib:header_value("connection", Hdrs, "keep-alive"), - case string:to_lower(HdrValue) of + case lhttpc_lib:to_lower(HdrValue) of "close" -> ok; _ -> erlang:error(no_content_length) end; check_infinite_response(_, Hdrs) -> HdrValue = lhttpc_lib:header_value("connection", Hdrs, "close"), - case string:to_lower(HdrValue) of + case lhttpc_lib:to_lower(HdrValue) of "keep-alive" -> erlang:error(no_content_length); _ -> ok end. diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl index 2fc107f0..7c6d8e57 100644 --- a/src/lhttpc_lib.erl +++ b/src/lhttpc_lib.erl @@ -41,7 +41,8 @@ format_hdrs/1, dec/1, get_cookies/1, - update_cookies/2]). + update_cookies/2, + to_lower/1]). -include("lhttpc_types.hrl"). -include("lhttpc.hrl"). @@ -90,7 +91,7 @@ header_value(Hdr, [{ThisHdr, Value}| Hdrs], Default) when is_atom(ThisHdr) -> header_value(Hdr, [{ThisHdr, Value}| Hdrs], Default) when is_binary(ThisHdr) -> header_value(Hdr, [{binary_to_list(ThisHdr), Value}| Hdrs], Default); header_value(Hdr, [{ThisHdr, Value}| Hdrs], Default) -> - case string:equal(string:to_lower(ThisHdr), Hdr) of + case string:equal(lhttpc_lib:to_lower(ThisHdr), Hdr) of true -> case is_list(Value) of true -> string:strip(Value); false -> Value @@ -129,7 +130,7 @@ parse_url(URL) -> {User, Passwd, HostPortPath} = split_credentials(CredsHostPortPath), {Host, PortPath} = split_host(HostPortPath, []), {Port, Path} = split_port(Scheme, PortPath, []), - #lhttpc_url{host = string:to_lower(Host), port = Port, path = Path, + #lhttpc_url{host = lhttpc_lib:to_lower(Host), port = Port, path = Path, user = User, password = Passwd, is_ssl = (Scheme =:= https)}. %%------------------------------------------------------------------------------ @@ -216,6 +217,15 @@ update_cookies(RespHeaders, StateCookies) -> %% Delete the cookies that are expired (check max-age and expire fields). delete_expired_cookies(NewCookies). + +%%------------------------------------------------------------------------------ +%% @doc Converts characters in a string ro lower case. +%% @end +%%------------------------------------------------------------------------------ +-spec to_lower(string()) -> string(). +to_lower(String) -> + [char_to_lower(X) || X <- String]. + %%============================================================================== %% Internal functions %%============================================================================== @@ -555,7 +565,7 @@ add_content_headers(Hdrs, _Body, true) -> {undefined, undefined} -> [{"Transfer-Encoding", "chunked"} | Hdrs]; {undefined, TransferEncoding} -> - case string:to_lower(TransferEncoding) of + case lhttpc_lib:to_lower(TransferEncoding) of "chunked" -> Hdrs; _ -> erlang:error({error, unsupported_transfer_encoding}) end; @@ -586,7 +596,7 @@ add_host(Hdrs, Host, Port) -> %%------------------------------------------------------------------------------ -spec is_chunked(headers()) -> boolean(). is_chunked(Hdrs) -> - TransferEncoding = string:to_lower( + TransferEncoding = lhttpc_lib:to_lower( header_value("transfer-encoding", Hdrs, "undefined")), case TransferEncoding of "chunked" -> true; @@ -619,3 +629,36 @@ maybe_ipv6_enclose(Host) -> _ -> Host end. + +%%------------------------------------------------------------------------------ +%% @private +%% @doc +%% @end +%%------------------------------------------------------------------------------ +char_to_lower($A) -> $a; +char_to_lower($B) -> $b; +char_to_lower($C) -> $c; +char_to_lower($D) -> $d; +char_to_lower($E) -> $e; +char_to_lower($F) -> $f; +char_to_lower($G) -> $g; +char_to_lower($H) -> $h; +char_to_lower($I) -> $i; +char_to_lower($J) -> $j; +char_to_lower($K) -> $k; +char_to_lower($L) -> $l; +char_to_lower($M) -> $m; +char_to_lower($N) -> $n; +char_to_lower($O) -> $o; +char_to_lower($P) -> $p; +char_to_lower($Q) -> $q; +char_to_lower($R) -> $r; +char_to_lower($S) -> $s; +char_to_lower($T) -> $t; +char_to_lower($U) -> $u; +char_to_lower($V) -> $v; +char_to_lower($W) -> $w; +char_to_lower($X) -> $x; +char_to_lower($Y) -> $y; +char_to_lower($Z) -> $z; +char_to_lower(Ch) -> Ch. From bb3d2a428a25bf0ef6dcfd9a85b9b3efa31c626a Mon Sep 17 00:00:00 2001 From: Juraj Hlista Date: Wed, 24 Apr 2013 10:54:34 +0100 Subject: [PATCH 42/49] Remove warning from tests --- test/lhttpc_tests.erl | 4 ++-- test/simple_load.erl | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl index 4336693c..57cabd1a 100644 --- a/test/lhttpc_tests.erl +++ b/test/lhttpc_tests.erl @@ -483,7 +483,7 @@ partial_upload_identity_iolist() -> ?assertEqual("This is chunky stuff!", lhttpc_lib:header_value("x-test-orig-body", headers(Response1))), % Make sure it works with no body part in the original request as well - {ok, UploadState2} = lhttpc:request_client(Client, URL, post, Hdrs, [], 1000, Options), + {ok, _UploadState2} = lhttpc:request_client(Client, URL, post, Hdrs, [], 1000, Options), {ok, Response2} = upload_parts(Client, Body ++ [http_eob]), ?assertEqual({200, "OK"}, status(Response2)), ?assertEqual(list_to_binary(webserver_utils:default_string()), body(Response2)), @@ -790,7 +790,7 @@ read_partial_body(Client, Size, Acc) -> ?assert(Size >= iolist_size(Bin)) end, read_partial_body(Client, Size, <>); - {http_eob, Trailers} -> + {http_eob, _Trailers} -> Acc; {body_part_error, Reason} -> {error, Reason, Acc} diff --git a/test/simple_load.erl b/test/simple_load.erl index 288cc67a..2a29f84d 100644 --- a/test/simple_load.erl +++ b/test/simple_load.erl @@ -1,5 +1,4 @@ -module(simple_load). --behaviour(gen_httpd). -export([start_client/2, start_client/3]). -export([client/2]). From 4d97d863d56e47fa0b6bdcdbeee2524db0ac2914 Mon Sep 17 00:00:00 2001 From: Lastres Date: Thu, 29 Aug 2013 16:15:49 +0100 Subject: [PATCH 43/49] Improvements on the documentation * Remove duplicated specs. * Make the docs to include the type specs defined in .hrl files. * Change the documentation to reflect all the changes that were lately done to the library. --- doc/overview.edoc | 25 +++- include/lhttpc_types.hrl | 22 ++- rebar.config | 1 + src/lhttpc.app.src | 2 + src/lhttpc.erl | 293 ++++++++++++++++++--------------------- src/lhttpc_client.erl | 15 -- src/lhttpc_lib.erl | 27 ---- src/lhttpc_manager.erl | 35 ++--- src/lhttpc_sock.erl | 34 ----- src/lhttpc_sup.erl | 4 +- 10 files changed, 186 insertions(+), 272 deletions(-) diff --git a/doc/overview.edoc b/doc/overview.edoc index d625b27d..2f78dec5 100644 --- a/doc/overview.edoc +++ b/doc/overview.edoc @@ -1,11 +1,28 @@ @author Oscar Hellström -@doc A lightweight HTTP client. -The only functions of much interest right now are {@link +@author Diana Parra Corbacho +@author Ramon Lastres Guerrero +@doc A Lightweight HTTP client. + +Users can send simple requests using standalone request functions {@link lhttpc:request/4}, {@link lhttpc:request/5}, {@link lhttpc:request/6} and {@link lhttpc:request/9}. -

Configuration

+It is also possible to independently create a client process that can be reused for several requests, using {@link connect_client/2} +and then generate requests with {@link lhttpc:request_client/5}, {@link lhttpc:request_client/6}, {@link lhttpc:request_client/7} and +{@link lhttpc:request_client/9}. + +It supports pools of connections that can be added and deleted: +{@link add_pool/1} +{@link add_pool/2} +{@link delete_pool/1} + +The {@link lhttpc_manager} module provides basic functionalities to handle the different pools. + +

Configuration Parameters

+Configuration parameters specified in the app.src file.

`connection_timeout'

The maximum time (in milliseconds) the client will keep a TCP connection -open to a server. +open to a server. Default parameter that can be overriden by the request options. +

`pool_size'

+The size of every pool created. Default parameter that can be overriden by the request options. @end diff --git a/include/lhttpc_types.hrl b/include/lhttpc_types.hrl index e57427d2..8132fce9 100644 --- a/include/lhttpc_types.hrl +++ b/include/lhttpc_types.hrl @@ -52,20 +52,15 @@ -type poolsize() :: non_neg_integer() | atom(). --type invalid_option() :: any(). - -type pool_id() :: pid() | atom(). --type destination() :: {string(), pos_integer(), boolean()}. +-type destination() :: {host(), pos_integer(), boolean()} | string(). -type raw_headers() :: [{atom() | binary() | string(), binary() | string()}]. -type partial_download_option() :: {'window_size', window_size()} | - {'part_size', non_neg_integer() | 'infinity'} | - invalid_option(). - - + {'part_size', non_neg_integer() | 'infinity'}. -type option() :: {'connect_timeout', timeout()} | @@ -76,8 +71,7 @@ {'use_cookies', boolean()} | {'proxy', string()} | {'proxy_ssl_options', socket_options()} | - {'pool_options', pool_options()} | - invalid_option(). + {'pool_options', pool_options()}. -type pool_option() :: {'pool_ensure', boolean()} | @@ -99,11 +93,13 @@ -type upload_state() :: {partial_upload, pid()}. --type body() :: binary() | - 'undefined' | % HEAD request. - pid(). % When partial_download option is used. +-type response() :: {{pos_integer(), string()}, headers(), body()}. + +-type body() :: binary() | + 'undefined' | % HEAD request. + pid(). % When partial_download option is used. -type result() :: - {ok, {{pos_integer(), string()}, headers(), body()}} | + {ok, response()} | {ok, upload_state()} | {error, atom()}. diff --git a/rebar.config b/rebar.config index 8b424ed3..640f3405 100644 --- a/rebar.config +++ b/rebar.config @@ -3,3 +3,4 @@ {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}. {cover_enabled, true}. {cover_export_enabled, true}. +{edoc_opts, [{preprocess, true}]}. diff --git a/src/lhttpc.app.src b/src/lhttpc.app.src index 76d07647..7ef984b6 100644 --- a/src/lhttpc.app.src +++ b/src/lhttpc.app.src @@ -25,6 +25,8 @@ %%% ---------------------------------------------------------------------------- %%% @author Oscar Hellström +%%% @author Diana Parra Corbacho +%%% @author Ramon Lastres Guerrero %%% @doc This is the specification for the lhttpc application. %%% @end {application, lhttpc, diff --git a/src/lhttpc.erl b/src/lhttpc.erl index cbc253da..aa79b6a8 100644 --- a/src/lhttpc.erl +++ b/src/lhttpc.erl @@ -48,6 +48,7 @@ -include("lhttpc_types.hrl"). -include("lhttpc.hrl"). +%% @headerfile "lhttpc.hrl" %%============================================================================== %% Exported functions @@ -69,8 +70,6 @@ stop(_) -> %%------------------------------------------------------------------------------ -%% @spec () -> ok | {error, Reason} -%% Reason = term() %% @doc Start the application. %% This is a helper function that will call `application:start(lhttpc)' to %% allow the library to be started using the `-s' flag. @@ -85,8 +84,6 @@ start() -> application:start(lhttpc). %%------------------------------------------------------------------------------ -%% @spec () -> ok | {error, Reason} -%% Reason = term() %% @doc Stops the application. %% This is a helper function that will call `application:stop(lhttpc)'. %% @@ -98,11 +95,7 @@ stop() -> application:stop(lhttpc). %%------------------------------------------------------------------------------ -%% @spec (Name) -> {ok, Pid} | {error, Reason} -%% Name = atom() -%% Pid = pid() -%% Reason = term() -%% @doc Add a new named httpc_manager pool to the supervisor tree +%% @doc Create a new named httpc_manager pool. %% @end %%------------------------------------------------------------------------------ -spec add_pool(atom()) -> {ok, pid()} | {error, term()}. @@ -112,7 +105,8 @@ add_pool(Name) when is_atom(Name) -> add_pool(Name, ConnTimeout, PoolSize). %%------------------------------------------------------------------------------ -%% @doc Add a new httpc_manager to the supervisor tree +%% @doc Create a new named httpc_manager pool with the specified connection +%% timeout. %% @end %%------------------------------------------------------------------------------ -spec add_pool(atom(), non_neg_integer()) -> {ok, pid()} | {error, term()}. @@ -121,7 +115,8 @@ add_pool(Name, ConnTimeout) when is_atom(Name), is_integer(ConnTimeout), ConnTim add_pool(Name, ConnTimeout, PoolSize). %%------------------------------------------------------------------------------ -%% @doc Add a new httpc_manager to the supervisor tree +%% @doc Create a new named httpc_manager pool with the specified connection +%% timeout and size. %% @end %%------------------------------------------------------------------------------ -spec add_pool(atom(), non_neg_integer(), poolsize()) -> {ok, pid()} | {error, term()}. @@ -129,7 +124,7 @@ add_pool(Name, ConnTimeout, PoolSize) -> lhttpc_manager:new_pool(Name, ConnTimeout, PoolSize). %%------------------------------------------------------------------------------ -%% @doc Delete a pool +%% @doc Delete an existing pool %% @end %%------------------------------------------------------------------------------ -spec delete_pool(atom() | pid()) -> ok. @@ -146,17 +141,61 @@ delete_pool(PoolName) when is_atom(PoolName) -> end. %%------------------------------------------------------------------------------ -%% @doc Starts a Client. +%% @doc Starts a Client process and connects it to a Destination, which can be +%% either an URL or a tuple +%% `{Host, Port, Ssl}' +%% If the pool is provided whithin the options, it uses the pool to get an +%% existing connection or creates a new one managed by it. +%% This is intended to be able to create a client process that will generate many +%% requests to a given destination, keeping the process alive, using +%% {@link request_client/5}, {@link request_client/5} or {@link request_client/7}. +%% +%% The only relevant options to be included here (other options will be ignored) are: +%% +%% `{connect_timeout, Milliseconds}' specifies how many milliseconds the +%% client can spend trying to establish a connection to the server. This +%% doesn't affect the overall request timeout. However, if it's longer than +%% the overall timeout it will be ignored. Also note that the TCP layer my +%% choose to give up earlier than the connect timeout, in which case the +%% client will also give up. The default value is infinity, which means that +%% it will either give up when the TCP stack gives up, or when the overall +%% request timeout is reached. +%% +%% `{connect_options, Options}' specifies options to pass to the socket at +%% connect time. This makes it possible to specify both SSL options and +%% regular socket options, such as which IP/Port to connect from etc. +%% Some options must not be included here, namely the mode, `binary' +%% or `list', `{active, boolean()}', `{active, once}' or `{packet, Packet}'. +%% These options would confuse the client if they are included. +%% Please note that these options will only have an effect on *new* +%% connections, and it isn't possible for different requests +%% to the same host uses different options unless the connection is closed +%% between the requests. Using HTTP/1.0 or including the "Connection: close" +%% header would make the client close the connection after the first +%% response is received. +%% +%% `{use_cookies, UseCookies}' If set to true, the client will automatically +%% handle the cookies for the user. Considering that the Cookies get stored +%% in the client process state, the usage of this option only has effect when +%% using the request_client() functions, NOT when using standalone requests, +%% since in such case a new process is spawned each time. +%% +%% `{pool_options, PoolOptions}' This are the configuration options regarding the +%% pool of connections usage. If the `pool_ensure' option is true, the pool with +%% the given name in the `pool_name' option will be created and then used if it +%% does not exist. +%% The `pool_connection_timeout' specifies the time that the pool keeps a +%% connection open before it gets closed, `pool_max_size' specifies the number of +%% connections the pool can handle. %% @end %%------------------------------------------------------------------------------ -%-spec connect( ,options()) -> {ok,Pid} | ignore | {error,Error}. -% WHICH TIMEOUT TO USE? +-spec connect_client(destination(), options()) -> {ok, pid()} | ignore | {error, term()}. connect_client(Destination, Options) -> - %Gs_Options = ?? lhttpc_client:start({Destination, Options}, []). %%------------------------------------------------------------------------------ -%% @doc Stops a Client. +%% @doc Stops a Client process and closes the connection (if no pool is used) or +%% it returns the connection to the pool (if pool is used). %% @end %%------------------------------------------------------------------------------ -spec disconnect_client(pid()) -> ok. @@ -167,7 +206,8 @@ disconnect_client(Client) -> %%------------------------------------------------------------------------------ %% @doc Makes a request using a client already connected. -%% It can receive either a URL or a path +%% It can receive either a URL or a path. +%% Would be the same as calling {@link request_client/6} with an empty body. %% @end %%------------------------------------------------------------------------------ -spec request_client(pid(), string(), method(), headers(), pos_timeout()) -> result(). @@ -176,7 +216,8 @@ request_client(Client, PathOrUrl, Method, Hdrs, Timeout) -> %%------------------------------------------------------------------------------ %% @doc Makes a request using a client already connected. -%% It can receive either a URL or a path. It allows to add the body. +%% It can receive either a URL or a path. +%% %% Would be the same as calling {@link request_client/7} with no options. %% @end %%------------------------------------------------------------------------------ -spec request_client(pid(), string(), method(), headers(), iodata(), pos_timeout()) -> result(). @@ -186,7 +227,66 @@ request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout) -> %%------------------------------------------------------------------------------ %% @doc Makes a request using a client already connected. %% It can receive either a URL or a path. It allows to add the body and specify -%% options, which are the same than for request (without client) functions. +%% options. The only relevant options here are: +%% +%% `{send_retry, N}' specifies how many times the client should retry +%% sending a request if the connection is closed after the data has been +%% sent. The default value is `1'. If `{partial_upload, WindowSize}' +%% (see below) is specified, the client cannot retry after the first part +%% of the body has been sent since it doesn't keep the whole entitity body +%% in memory. +%% +%% `{partial_upload, WindowSize}' means that the request entity body will be +%% supplied in parts to the client by the calling process. The `WindowSize' +%% specifies how many parts can be sent to the process controlling the socket +%% before waiting for an acknowledgement. This is to create a kind of +%% internal flow control if the network is slow and the client process is +%% blocked by the TCP stack. Flow control is disabled if `WindowSize' is +%% `infinity'. If `WindowSize' is an integer, it must be >= 0. If partial +%% upload is specified and no `Content-Length' is specified in `Hdrs' the +%% client will use chunked transfer encoding to send the entity body. +%% If a content length is specified, this must be the total size of the entity +%% body. +%% The call to {@link request/6} will return `{ok, UploadState}'. The +%% `UploadState' is supposed to be used as the first argument to the {@link +%% send_body_part/2} or {@link send_body_part/3} functions to send body parts. +%% Partial upload is intended to avoid keeping large request bodies in +%% memory but can also be used when the complete size of the body isn't known +%% when the request is started. +%% +%% `{partial_download, PartialDownloadOptions}' means that the response body +%% will be supplied in parts by the client to the calling process. The partial +%% download option `{window_size, WindowSize}' specifies how many part will be +%% sent to the calling process before waiting for an acknowledgement. This is +%% to create a kind of internal flow control if the calling process is slow to +%% process the body part and the network and server are considerably faster. +%% Flow control is disabled if `WindowSize' is `infinity'. If `WindowSize' +%% is an integer it must be >=0. The partial download option `{part_size, +%% PartSize}' specifies the size the body parts should come in. Note however +%% that if the body size is not determinable (e.g entity body is termintated +%% by closing the socket) it will be delivered in pieces as it is read from +%% the wire. There is no caching of the body parts until the amount reaches +%% body size. If the body size is bounded (e.g `Content-Length' specified or +%% `Transfer-Encoding: chunked' specified) it will be delivered in `PartSize' +%% pieces. Note however that the last piece might be smaller than `PartSize'. +%% Size bounded entity bodies are handled the same way as unbounded ones if +%% `PartSize' is `infinity'. If `PartSize' is integer it must be >= 0. +%% If `{partial_download, PartialDownloadOptions}' is specified the +%% `ResponseBody' will be a `pid()' unless the response has no body +%% (for example in case of `HEAD' requests). In that case it will be be +%% `undefined'. The functions {@link get_body_part/1} and +%% {@link get_body_part/2} can be used to read body parts in the calling +%% process. +%% +%% `{proxy, ProxyUrl}' if this option is specified, a proxy server is used as +%% an intermediary for all communication with the destination server. The link +%% to the proxy server is established with the HTTP CONNECT method (RFC2817). +%% Example value: {proxy, "http://john:doe@myproxy.com:3128"} +%% +%% `{proxy_ssl_options, SslOptions}' this is a list of SSL options to use for +%% the SSL session created after the proxy connection is established. For a +%% list of all available options, please check OTP's ssl module manpage. +%% %% @end %%------------------------------------------------------------------------------ -spec request_client(pid(), string(), method(), headers(), iodata(), @@ -202,19 +302,6 @@ request_client(Client, PathOrUrl, Method, Hdrs, Body, Timeout, Options) -> end. %%------------------------------------------------------------------------------ -%% @spec (URL, Method, Hdrs, Timeout) -> Result -%% URL = string() -%% Method = string() | atom() -%% Hdrs = [{Header, Value}] -%% Header = string() | binary() | atom() -%% Value = string() | binary() -%% Timeout = integer() | infinity -%% Result = {ok, {{StatusCode, ReasonPhrase}, Hdrs, ResponseBody}} -%% | {error, Reason} -%% StatusCode = integer() -%% ReasonPhrase = string() -%% ResponseBody = binary() -%% Reason = connection_closed | connect_timeout | timeout %% @doc Sends a request without a body. %% Would be the same as calling {@link request/5} with an empty body, %% `request(URL, Method, Hdrs, [], Timeout)' or @@ -227,20 +314,6 @@ request(URL, Method, Hdrs, Timeout) -> request(URL, Method, Hdrs, [], Timeout, []). %%------------------------------------------------------------------------------ -%% @spec (URL, Method, Hdrs, RequestBody, Timeout) -> Result -%% URL = string() -%% Method = string() | atom() -%% Hdrs = [{Header, Value}] -%% Header = string() | binary() | atom() -%% Value = string() | binary() -%% RequestBody = iodata() -%% Timeout = integer() | infinity -%% Result = {ok, {{StatusCode, ReasonPhrase}, Hdrs, ResponseBody}} -%% | {error, Reason} -%% StatusCode = integer() -%% ReasonPhrase = string() -%% ResponseBody = binary() -%% Reason = connection_closed | connect_timeout | timeout %% @doc Sends a request with a body. %% Would be the same as calling {@link request/6} with no options, %% `request(URL, Method, Hdrs, Body, Timeout, [])'. @@ -252,39 +325,6 @@ request(URL, Method, Hdrs, Body, Timeout) -> request(URL, Method, Hdrs, Body, Timeout, []). %%------------------------------------------------------------------------------ -%% @spec (URL, Method, Hdrs, RequestBody, Timeout, Options) -> Result -%% URL = string() -%% Method = string() | atom() -%% Hdrs = [{Header, Value}] -%% Header = string() | binary() | atom() -%% Value = string() | binary() -%% RequestBody = iodata() -%% Timeout = integer() | infinity -%% Options = [Option] -%% Option = {connect_timeout, Milliseconds | infinity} | -%% {connect_options, [ConnectOptions]} | -%% {send_retry, integer()} | -%% {partial_upload, WindowSize} | -%% {partial_download, PartialDownloadOptions} | -%% {proxy, ProxyUrl} | -%% {proxy_ssl_options, SslOptions} | -%% {pool, LhttcPool} -%% Milliseconds = integer() -%% ConnectOptions = term() -%% WindowSize = integer() | infinity -%% PartialDownloadOptions = [PartialDownloadOption] -%% PartialDowloadOption = {window_size, WindowSize} | -%% {part_size, PartSize} -%% ProxyUrl = string() -%% SslOptions = [any()] -%% LhttcPool = pid() | atom() -%% PartSize = integer() | infinity -%% Result = {ok, {{StatusCode, ReasonPhrase}, Hdrs, ResponseBody}} | -%% {ok, UploadState} | {error, Reason} -%% StatusCode = integer() -%% ReasonPhrase = string() -%% ResponseBody = binary() | pid() | undefined -%% Reason = connection_closed | connect_timeout | timeout %% @doc Sends a request with a body. %% Would be the same as calling
 %% #lhttpc_url{host = Host, port = Port, path = Path, is_ssl = Ssl} = lhttpc_lib:parse_url(URL),
@@ -310,43 +350,9 @@ request(URL, Method, Hdrs, Body, Timeout, Options) ->
     request(Host, Port, Ssl, Path, Method, Headers, Body, Timeout, Options).
 
 %%------------------------------------------------------------------------------
-%% @spec (Host, Port, Ssl, Path, Method, Hdrs, RequestBody, Timeout, Options) ->
-%%                                                                        Result
-%%   Host = string()
-%%   Port = integer()
-%%   Ssl = boolean()
-%%   Path = string()
-%%   Method = string() | atom()
-%%   Hdrs = [{Header, Value}]
-%%   Header = string() | binary() | atom()
-%%   Value = string() | binary()
-%%   RequestBody = iodata()
-%%   Timeout = integer() | infinity
-%%   Options = [Option]
-%%   Option = {connect_timeout, Milliseconds | infinity} |
-%%            {connect_options, [ConnectOptions]} |
-%%            {send_retry, integer()} |
-%%            {partial_upload, WindowSize} |
-%%            {partial_download, PartialDownloadOptions} |
-%%            {proxy, ProxyUrl} |
-%%            {proxy_ssl_options, SslOptions} |
-%%            {pool, LhttcPool}
-%%   Milliseconds = integer()
-%%   WindowSize = integer()
-%%   PartialDownloadOptions = [PartialDownloadOption]
-%%   PartialDowloadOption = {window_size, WindowSize} |
-%%                          {part_size, PartSize}
-%%   ProxyUrl = string()
-%%   SslOptions = [any()]
-%%   LhttcPool = pid() | atom()
-%%   PartSize = integer() | infinity
-%%   Result = {ok, {{StatusCode, ReasonPhrase}, Hdrs, ResponseBody}}
-%%          | {error, Reason}
-%%   StatusCode = integer()
-%%   ReasonPhrase = string()
-%%   ResponseBody = binary() | pid() | undefined
-%%   Reason = connection_closed | connect_timeout | timeout
-%% @doc Sends a request with a body.
+%% @doc Sends a request with a body. For this, a new client process is created,
+%% it creates a connection or takes it from a pool (depends of the options) and
+%% uses that client process to send the request. Then it stops the process.
 %%
 %% Instead of building and parsing URLs the target server is specified with
 %% a host, port, weither SSL should be used or not and a path on the server.
@@ -446,6 +452,12 @@ request(URL, Method, Hdrs, Body, Timeout, Options) ->
 %% {@link get_body_part/2} can be used to read body parts in the calling
 %% process.
 %%
+%% `{use_cookies, UseCookies}' If set to true, the client will automatically
+%% handle the cookies for the user. Considering that the Cookies get stored
+%% in the client process state, the usage of this option only has effect when
+%% using the request_client() functions, NOT when using standalone requests,
+%% since in such case a new process is spawned each time.
+%%
 %% `{proxy, ProxyUrl}' if this option is specified, a proxy server is used as
 %% an intermediary for all communication with the destination server. The link
 %% to the proxy server is established with the HTTP CONNECT method (RFC2817).
@@ -454,9 +466,17 @@ request(URL, Method, Hdrs, Body, Timeout, Options) ->
 %% `{proxy_ssl_options, SslOptions}' this is a list of SSL options to use for
 %% the SSL session created after the proxy connection is established. For a
 %% list of all available options, please check OTP's ssl module manpage.
+%%
+%% `{pool_options, PoolOptions}' This are the configuration options regarding the
+%% pool of connections usage. If the `pool_ensure' option is true, the pool with
+%% the given name in the `pool_name' option will be created and then used if it
+%% does not exist.
+%% The `pool_connection_timeout' specifies the time that the pool keeps a
+%% connection open before it gets closed, `pool_max_size' specifies the number of
+%% connections the pool can handle.
 %% @end
 %%------------------------------------------------------------------------------
--spec request(string(), port_num(), boolean(), string(), method(),
+-spec request(host(), port_num(), boolean(), string(), method(),
               headers(), iodata(), pos_timeout(), options()) -> result().
 request(Host, Port, Ssl, Path, Method, Hdrs, Body, Timeout, Options) ->
     verify_options(Options),
@@ -478,11 +498,6 @@ request(Host, Port, Ssl, Path, Method, Hdrs, Body, Timeout, Options) ->
     end.
 
 %%------------------------------------------------------------------------------
-%% @spec (UploadState :: UploadState, BodyPart :: BodyPart) -> Result
-%%   BodyPart = iodata() | binary()
-%%   Timeout = integer() | infinity
-%%   Result = {error, Reason} | UploadState
-%%   Reason = connection_closed | connect_timeout | timeout
 %% @doc Sends a body part to an ongoing request when
 %% `{partial_upload, WindowSize}' is used. The default timeout, `infinity'
 %% will be used. Notice that if `WindowSize' is infinity, this call will never
@@ -496,11 +511,6 @@ send_body_part({Pid, Window}, IoList) ->
     send_body_part({Pid, Window}, IoList, infinity).
 
 %%------------------------------------------------------------------------------
-%% @spec (UploadState :: UploadState, BodyPart :: BodyPart, Timeout) -> Result
-%%   BodyPart = iodata() | binary()
-%%   Timeout = integer() | infinity
-%%   Result = {error, Reason} | UploadState
-%%   Reason = connection_closed | connect_timeout | timeout
 %% @doc Sends a body part to an ongoing request when
 %% `{partial_upload, WindowSize}' is used.
 %% `Timeout' is the timeout for the request in milliseconds.
@@ -523,12 +533,6 @@ send_body_part(Client, BodyPart, Timeout)  ->
     lhttpc_client:send_body_part(Client, BodyPart, Timeout).
 
 %%------------------------------------------------------------------------------
-%% @spec (UploadState :: UploadState, Trailers) -> Result
-%%   Header = string() | binary() | atom()
-%%   Value = string() | binary()
-%%   Result = {ok, {{StatusCode, ReasonPhrase}, Hdrs, ResponseBody}}
-%%            | {error, Reason}
-%%   Reason = connection_closed | connect_timeout | timeout
 %% @doc Sends trailers to an ongoing request when `{partial_upload,
 %% WindowSize}' is used and no `Content-Length' was specified. The default
 %% timout `infinity' will be used. Plase note that after this the request is
@@ -542,14 +546,6 @@ send_trailers(Client, Trailers) ->
     lhttpc_client:send_trailers(Client, Trailers, infinity).
 
 %%------------------------------------------------------------------------------
-%% @spec (UploadState :: UploadState, Trailers, Timeout) -> Result
-%%   Trailers = [{Header, Value}]
-%%   Header = string() | binary() | atom()
-%%   Value = string() | binary()
-%%   Timeout = integer() | infinity
-%%   Result = {ok, {{StatusCode, ReasonPhrase}, Hdrs, ResponseBody}}
-%%            | {error, Reason}
-%%   Reason = connection_closed | connect_timeout | timeout
 %% @doc Sends trailers to an ongoing request when
 %% `{partial_upload, WindowSize}' is used and no `Content-Length' was
 %% specified.
@@ -569,12 +565,6 @@ send_trailers({Pid, _Window}, Trailers, Timeout) when is_list(Trailers), is_pid(
     read_response(Pid, Timeout).
 
 %%------------------------------------------------------------------------------
-%% @spec (HTTPClient :: pid()) -> Result
-%%   Result = {ok, BodyPart} | {ok, {http_eob, Trailers}}
-%%   BodyPart = binary()
-%%   Trailers = [{Header, Value}]
-%%   Header = string() | binary() | atom()
-%%   Value = string() | binary()
 %% @doc Reads a body part from an ongoing response when
 %% `{partial_download, PartialDownloadOptions}' is used. The default timeout,
 %% `infinity' will be used.
@@ -587,13 +577,6 @@ get_body_part(Pid) ->
     get_body_part(Pid, infinity).
 
 %%------------------------------------------------------------------------------
-%% @spec (HTTPClient :: pid(), Timeout:: Timeout) -> Result
-%%   Timeout = integer() | infinity
-%%   Result = {ok, BodyPart} | {ok, {http_eob, Trailers}}
-%%   BodyPart = binary()
-%%   Trailers = [{Header, Value}]
-%%   Header = string() | binary() | atom()
-%%   Value = string() | binary()
 %% @doc Reads a body part from an ongoing response when
 %% `{partial_download, PartialDownloadOptions}' is used.
 %% `Timeout' is the timeout for reading the next body part in milliseconds.
diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl
index 29ee7e7c..077af59a 100644
--- a/src/lhttpc_client.erl
+++ b/src/lhttpc_client.erl
@@ -114,14 +114,6 @@ stop(Client) ->
     gen_server:cast(Client, stop).
 
 %%------------------------------------------------------------------------------
-%% @spec (Client, Path, Method, Hdrs, RequestBody, Options, Timeout) -> ok
-%%    From = pid()
-%%    Method = atom() | string()
-%%    Hdrs = [Header]
-%%    Header = {string() | atom(), string()}
-%%    Body = iolist()
-%%    Options = [Option]
-%%    Option = {connect_timeout, Milliseconds}
 %% @doc
 %% @end
 %%------------------------------------------------------------------------------
@@ -257,10 +249,6 @@ handle_call(get_body_part, From, State) ->
 %% @private
 %% @doc
 %% Handling cast messages
-%%
-%% @spec handle_cast(Msg, State) -> {noreply, State} |
-%%                                  {noreply, State, Timeout} |
-%%                                  {stop, Reason, State}
 %% @end
 %%--------------------------------------------------------------------
 handle_cast(stop, State) ->
@@ -289,7 +277,6 @@ handle_info(_Info, State) ->
 %% necessary cleaning up. When it returns, the gen_server terminates
 %% with Reason. The return value is ignored.
 %%
-%% @spec terminate(Reason, State) -> void()
 %% @end
 %%--------------------------------------------------------------------
 terminate(_Reason, State = #client_state{pool = Pool, host = Host, ssl = Ssl,
@@ -314,8 +301,6 @@ terminate(_Reason, State = #client_state{pool = Pool, host = Host, ssl = Ssl,
 %% @private
 %% @doc
 %% Convert process state when code is changed
-%%
-%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
 %% @end
 %%--------------------------------------------------------------------
 code_change(_OldVsn, State, _Extra) ->
diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl
index 7c6d8e57..c18ee8eb 100644
--- a/src/lhttpc_lib.erl
+++ b/src/lhttpc_lib.erl
@@ -54,10 +54,6 @@
 %%==============================================================================
 
 %%------------------------------------------------------------------------------
-%% @spec header_value(Header, Headers) -> undefined | term()
-%% Header = string()
-%% Headers = [{header(), term()}]
-%% Value = term()
 %% @doc
 %% Returns the value associated with the `Header' in `Headers'.
 %% `Header' must be a lowercase string, since every header is mangled to
@@ -69,11 +65,6 @@ header_value(Hdr, Hdrs) ->
     header_value(Hdr, Hdrs, undefined).
 
 %%------------------------------------------------------------------------------
-%% @spec header_value(Header, Headers, Default) -> Default | term()
-%% Header = string()
-%% Headers = [{string(), term()}]
-%% Value = term()
-%% Default = term()
 %% @doc
 %% Returns the value associated with the `Header' in `Headers'.
 %% `Header' must be a lowercase string, since every header is mangled to
@@ -103,9 +94,6 @@ header_value(_, [], Default) ->
     Default.
 
 %%------------------------------------------------------------------------------
-%% @spec (Item) -> OtherItem
-%%   Item = atom() | list()
-%%   OtherItem = list()
 %% @doc
 %% Will make any item, being an atom or a list, in to a list. If it is a
 %% list, it is simple returned.
@@ -118,8 +106,6 @@ maybe_atom_to_list(List) ->
     List.
 
 %%------------------------------------------------------------------------------
-%% @spec (URL) -> #lhttpc_url{}
-%%   URL = string()
 %% @doc
 %% @end
 %%------------------------------------------------------------------------------
@@ -134,16 +120,6 @@ parse_url(URL) ->
                 user = User, password = Passwd, is_ssl = (Scheme =:= https)}.
 
 %%------------------------------------------------------------------------------
-%% @spec (Path, Method, Headers, Host, Port, Body, PartialUpload, Cookies) ->
-%%    Request
-%% Path = iolist()
-%% Method = atom() | string()
-%% Headers = [{atom() | string(), string()}]
-%% Host = string()
-%% Port = integer()
-%% Body = iolist()
-%% PartialUpload = true | false
-%% Cookies = [#lhttpc_cookie{}]
 %% @doc
 %% @end
 %%------------------------------------------------------------------------------
@@ -156,9 +132,6 @@ format_request(Path, Method, Hdrs, Host, Port, Body, PartialUpload, Cookies) ->
      format_body(Body, IsChunked)]}.
 
 %%------------------------------------------------------------------------------
-%% @spec normalize_method(AtomOrString) -> Method
-%%   AtomOrString = atom() | string()
-%%   Method = string()
 %% @doc
 %% Turns the method in to a string suitable for inclusion in a HTTP request
 %% line.
diff --git a/src/lhttpc_manager.erl b/src/lhttpc_manager.erl
index fa3f12d6..b94949ff 100644
--- a/src/lhttpc_manager.erl
+++ b/src/lhttpc_manager.erl
@@ -30,11 +30,8 @@
 %%% @author Diana Parra Corbacho 
 %%% @author Ramon Lastres Guerrero 
 %%% @doc Connection manager for the HTTP client.
-%%% This gen_server is responsible for keeping track of persistent
-%%% connections to HTTP servers. The only interesting API is
-%%% `connection_count/0' and `connection_count/1'.
-%%% The gen_server is supposed to be started by a supervisor, which is
-%%% normally {@link lhttpc_sup}.
+%%% This module provides the pool of connections functionality. It exports funcions
+%%% intended to provide information and manage the existing pools.
 %%% @end
 %%------------------------------------------------------------------------------
 -module(lhttpc_manager).
@@ -78,7 +75,6 @@
 %%==============================================================================
 
 %%------------------------------------------------------------------------------
-%% @spec (PoolPidOrName) -> list()
 %% @doc Returns the current settings in state for the
 %% specified lhttpc pool (manager).
 %% @end
@@ -112,8 +108,6 @@ list_pools() ->
         end, [], Children).
 
 %%------------------------------------------------------------------------------
-%% @spec (PoolPidOrName) -> Count
-%%    Count = integer()
 %% @doc Returns the total number of active clients maintained by the
 %% specified lhttpc pool (manager).
 %% @end
@@ -123,8 +117,6 @@ client_count(PidOrName) ->
     gen_server:call(PidOrName, client_count).
 
 %%------------------------------------------------------------------------------
-%% @spec (PoolPidOrName) -> Count
-%%    Count = integer()
 %% @doc Returns the total number of active connections maintained by the
 %% specified lhttpc pool (manager).
 %% @end
@@ -134,26 +126,16 @@ connection_count(PidOrName) ->
     gen_server:call(PidOrName, connection_count).
 
 %%------------------------------------------------------------------------------
-%% @spec (PoolPidOrName, Destination) -> Count
-%%    PoolPidOrName = pid() | atom()
-%%    Destination = {Host, Port, Ssl}
-%%    Host = string()
-%%    Port = integer()
-%%    Ssl = boolean()
-%%    Count = integer()
 %% @doc Returns the number of active connections to the specific
 %% `Destination' maintained by the httpc manager.
 %% @end
 %%------------------------------------------------------------------------------
--spec connection_count(pool_id(), destination()) -> non_neg_integer().
+-spec connection_count(pool_id(), Destination :: destination()) -> non_neg_integer().
 connection_count(PidOrName, {Host, Port, Ssl}) ->
     Destination = {string:to_lower(Host), Port, Ssl},
     gen_server:call(PidOrName, {connection_count, Destination}).
 
 %%------------------------------------------------------------------------------
-%% @spec (PoolPidOrName, Timeout) -> ok
-%%    PoolPidOrName = pid() | atom()
-%%    Timeout = integer()
 %% @doc Updates the timeout for persistent connections.
 %% This will only affect future sockets handed to the manager. The sockets
 %% already managed will keep their timers.
@@ -164,6 +146,7 @@ update_connection_timeout(PidOrName, Milliseconds) ->
     gen_server:cast(PidOrName, {update_timeout, Milliseconds}).
 
 %%------------------------------------------------------------------------------
+%% @private
 %% @doc
 %% @end
 %%------------------------------------------------------------------------------
@@ -171,7 +154,7 @@ close_socket(PidOrName, Socket) ->
     gen_server:cast(PidOrName, {remove_socket, Socket}).
 
 %%------------------------------------------------------------------------------
-%% @spec () -> {ok, pid()}
+%% @private
 %% @doc Starts and link to the gen server.
 %% This is normally called by a supervisor.
 %% @end
@@ -182,6 +165,7 @@ start_link() ->
 
 
 %%------------------------------------------------------------------------------
+%% @private
 %% @doc
 %% @end
 %%------------------------------------------------------------------------------
@@ -197,6 +181,7 @@ start_link(Options0) ->
     end.
 
 %%------------------------------------------------------------------------------
+%% @private
 %% @doc If call contains pool_ensure option, dynamically create the pool with
 %% configured parameters. Checks the pool for a socket connected to the
 %% destination and returns it if it exists, 'undefined' otherwise.
@@ -236,6 +221,11 @@ ensure_call(Pool, Pid, Host, Port, Ssl, Options) ->
             end
     end.
 
+%%------------------------------------------------------------------------------
+%% @private
+%% @doc Creates a new pool.
+%% @end
+%%------------------------------------------------------------------------------
 -spec new_pool(atom(), non_neg_integer(), poolsize()) ->
     {ok, pid()} | {error, term()}.
 new_pool(Pool, ConnTimeout, PoolSize) ->
@@ -256,6 +246,7 @@ new_pool(Pool, ConnTimeout, PoolSize) ->
     end.
 
 %%------------------------------------------------------------------------------
+%% @private
 %% @doc A client has finished one request and returns the socket to the pool,
 %% which can be new or not.
 %% @end
diff --git a/src/lhttpc_sock.erl b/src/lhttpc_sock.erl
index 4c241630..2a6f06b1 100644
--- a/src/lhttpc_sock.erl
+++ b/src/lhttpc_sock.erl
@@ -47,14 +47,6 @@
 %%==============================================================================
 
 %%------------------------------------------------------------------------------
-%% @spec (Host, Port, Options, Timeout, SslFlag) -> {ok, Socket} | {error, Reason}
-%%   Host = string() | ip_address()
-%%   Port = integer()
-%%   Options = [{atom(), term()} | atom()]
-%%   Timeout = infinity | integer()
-%%   SslFlag = boolean()
-%%   Socket = socket()
-%%   Reason = atom()
 %% @doc
 %% Connects to `Host' and `Port'.
 %% Will use the `ssl' module if `SslFlag' is `true' and gen_tcp otherwise.
@@ -69,12 +61,6 @@ connect(Host, Port, Options, Timeout, false) ->
     gen_tcp:connect(Host, Port, Options, Timeout).
 
 %%------------------------------------------------------------------------------
-%% @spec (Socket, SslFlag) -> {ok, Data} | {error, Reason}
-%%   Socket = socket()
-%%   Length = integer()
-%%   SslFlag = boolean()
-%%   Data = term()
-%%   Reason = atom()
 %% @doc
 %% Reads available bytes from `Socket'.
 %% Will block untill data is available on the socket and return the first
@@ -89,12 +75,6 @@ recv(Socket, false) ->
     gen_tcp:recv(Socket, 0).
 
 %%------------------------------------------------------------------------------
-%% @spec (Socket, Length, SslFlag) -> {ok, Data} | {error, Reason}
-%%   Socket = socket()
-%%   Length = integer()
-%%   SslFlag = boolean()
-%%   Data = term()
-%%   Reason = atom()
 %% @doc
 %% Receives `Length' bytes from `Socket'.
 %% Will block untill `Length' bytes is available.
@@ -109,11 +89,6 @@ recv(Socket, Length, false) ->
     gen_tcp:recv(Socket, Length).
 
 %%------------------------------------------------------------------------------
-%% @spec (Socket, Data, SslFlag) -> ok | {error, Reason}
-%%   Socket = socket()
-%%   Data = iolist()
-%%   SslFlag = boolean()
-%%   Reason = atom()
 %% @doc
 %% Sends data on a socket.
 %% Will use the `ssl' module if `SslFlag' is set to `true', otherwise the
@@ -145,11 +120,6 @@ controlling_process(Socket, Pid, false) ->
     gen_tcp:controlling_process(Socket, Pid).
 
 %%------------------------------------------------------------------------------
-%% @spec (Socket, Options, SslFlag) -> ok | {error, Reason}
-%%   Socket = socket()
-%%   Options = [atom() | {atom(), term()}]
-%%   SslFlag = boolean()
-%%   Reason = atom()
 %% @doc
 %% Sets options for a socket. Look in `inet:setopts/2' for more info.
 %% @end
@@ -161,10 +131,6 @@ setopts(Socket, Options, false) ->
     inet:setopts(Socket, Options).
 
 %%------------------------------------------------------------------------------
-%% @spec (Socket, SslFlag) -> ok | {error, Reason}
-%%   Socket = socket()
-%%   SslFlag = boolean()
-%%   Reason = atom()
 %% @doc
 %% Closes a socket.
 %% @end
diff --git a/src/lhttpc_sup.erl b/src/lhttpc_sup.erl
index 9446f53c..2d73dd47 100644
--- a/src/lhttpc_sup.erl
+++ b/src/lhttpc_sup.erl
@@ -25,6 +25,7 @@
 %%% ----------------------------------------------------------------------------
 
 %%------------------------------------------------------------------------------
+%%% @private
 %%% @author Oscar Hellström 
 %%% @doc Top supervisor for the lhttpc application.
 %%% This is normally started by the application behaviour implemented in
@@ -42,8 +43,7 @@
 -export([init/1]).
 
 %%------------------------------------------------------------------------------
-%% @spec () -> {ok, pid()} | {error, Reason}
-%% Reason = atom()
+%% @private
 %% @doc Starts and links to the supervisor.
 %% This is normally called from an application behaviour or from another
 %% supervisor.

From b9705c98c9973ae2e94a3458374515330030047e Mon Sep 17 00:00:00 2001
From: Lastres 
Date: Thu, 29 Aug 2013 17:30:49 +0100
Subject: [PATCH 44/49] Use a nicer style for the edocs

Taken from https://github.com/basho/riaknostic/blob/develop/priv/edoc.css
---
 priv/edoc.css | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++
 rebar.config  |   3 +-
 2 files changed, 132 insertions(+), 1 deletion(-)
 create mode 100644 priv/edoc.css

diff --git a/priv/edoc.css b/priv/edoc.css
new file mode 100644
index 00000000..1d50defe
--- /dev/null
+++ b/priv/edoc.css
@@ -0,0 +1,130 @@
+/* Baseline rhythm */
+body {
+   font-size: 16px;
+   font-family: Helvetica, sans-serif;
+   margin: 8px;
+}
+
+p {
+   font-size: 1em; /* 16px */
+   line-height: 1.5em; /* 24px */
+   margin: 0 0 1.5em 0;
+}
+
+h1 {
+   font-size: 1.5em; /* 24px */
+   line-height: 1em; /* 24px */
+   margin-top: 1em;
+   margin-bottom: 0em;
+}
+
+h2 {
+   font-size: 1.375em; /* 22px */
+   line-height: 1.0909em; /* 24px */
+   margin-top: 1.0909em;
+   margin-bottom: 0em;
+}
+
+h3 {
+   font-size: 1.25em; /* 20px */
+   line-height: 1.2em; /* 24px */
+   margin-top: 1.2em;
+   margin-bottom: 0em;
+}
+
+h4 {
+   font-size: 1.125em; /* 18px */
+   line-height: 1.3333em; /* 24px */
+   margin-top: 1.3333em;
+   margin-bottom: 0em;
+}
+
+.class-for-16px {
+   font-size: 1em; /* 16px */
+   line-height: 1.5em; /* 24px */
+   margin-top: 1.5em;
+   margin-bottom: 0em;
+}
+
+.class-for-14px {
+   font-size: 0.875em; /* 14px */
+   line-height: 1.7143em; /* 24px */
+   margin-top: 1.7143em;
+   margin-bottom: 0em;
+}
+
+ul {
+   margin: 0 0 1.5em 0;
+}
+
+/* Customizations */
+body {
+   color: #333;
+}
+
+tt, code, pre {
+   font-family: "Andale Mono", "Inconsolata", "Monaco", "DejaVu Sans Mono", monospaced;
+}
+
+tt, code { font-size: 0.875em }
+
+pre {
+   font-size: 0.875em; /* 14px */
+   line-height: 1.7143em; /* 24px */
+   margin: 0 1em 1.7143em;
+   padding: 0 1em;
+   background: #eee;
+}
+
+.navbar img, hr { display: none }
+
+table {
+   border-collapse: collapse;
+}
+
+h1 {
+   border-left: 0.5em solid #fa0;
+   padding-left: 0.5em;
+}
+
+h2.indextitle {
+   font-size: 1.25em; /* 20px */
+   line-height: 1.2em; /* 24px */
+   margin: -8px -8px 0.6em;
+   background-color: #fa0;
+   color: white;
+   padding: 0.3em;
+}
+
+ul.index {
+   list-style: none;
+   margin-left: 0em;
+   padding-left: 0;
+}
+
+ul.index li {
+   display: inline;
+   padding-right: 0.75em
+}
+
+div.spec p {
+   margin-bottom: 0;
+   padding-left: 1.25em;
+   background-color: #eee;
+}
+
+h3.function {
+   border-left: 0.5em solid #fa0;
+   padding-left: 0.5em;
+   background: #fc9;
+}
+a, a:visited, a:hover, a:active { color: #C60 }
+h2 a, h3 a { color: #333 }
+
+i {
+   font-size: 0.875em; /* 14px */
+   line-height: 1.7143em; /* 24px */
+   margin-top: 1.7143em;
+   margin-bottom: 0em;
+   font-style: normal;
+}
diff --git a/rebar.config b/rebar.config
index 640f3405..3f850748 100644
--- a/rebar.config
+++ b/rebar.config
@@ -3,4 +3,5 @@
 {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}.
 {cover_enabled, true}.
 {cover_export_enabled, true}.
-{edoc_opts, [{preprocess, true}]}.
+{edoc_opts, [{preprocess, true},
+	    {stylesheet_file, "./priv/edoc.css"}]}.

From 13be6eaa4f148a6bca1bf36c6bb7a4ec244f73d2 Mon Sep 17 00:00:00 2001
From: Lastres 
Date: Fri, 30 Aug 2013 16:33:33 +0100
Subject: [PATCH 45/49] Fix type errors. Remove unused functions. Fix
 documentation

---
 include/lhttpc_types.hrl |  8 ++---
 src/lhttpc.erl           | 76 +++++++++-------------------------------
 2 files changed, 20 insertions(+), 64 deletions(-)

diff --git a/include/lhttpc_types.hrl b/include/lhttpc_types.hrl
index 8132fce9..57bf4b37 100644
--- a/include/lhttpc_types.hrl
+++ b/include/lhttpc_types.hrl
@@ -65,7 +65,7 @@
 -type option() ::
         {'connect_timeout', timeout()} |
         {'send_retry', non_neg_integer()} |
-        {'partial_upload', non_neg_integer() | 'infinity'} |
+        {'partial_upload', boolean()} |
         {'partial_download', [partial_download_option()]} |
         {'connect_options', socket_options()} |
 	{'use_cookies', boolean()} |
@@ -77,7 +77,7 @@
 	{'pool_ensure', boolean()} |
 	{'pool_connection_timeout', pos_timeout()} |
 	{'pool_max_size' | integer()} |
-	{'pool_name', pool_id()}.
+	{'pool', pool_id()}.
 
 -type pool_options() :: [pool_option()].
 
@@ -91,8 +91,6 @@
 
 -type window_size() :: non_neg_integer() | 'infinity'.
 
--type upload_state() :: {partial_upload, pid()}.
-
 -type response() ::  {{pos_integer(), string()}, headers(), body()}.
 
 -type body() :: binary()    |
@@ -101,5 +99,5 @@
 
 -type result() ::
         {ok, response()} |
-        {ok, upload_state()} |
+        {ok, partial_upload} |
         {error, atom()}.
diff --git a/src/lhttpc.erl b/src/lhttpc.erl
index aa79b6a8..e9a89683 100644
--- a/src/lhttpc.erl
+++ b/src/lhttpc.erl
@@ -182,7 +182,7 @@ delete_pool(PoolName) when is_atom(PoolName) ->
 %%
 %% `{pool_options, PoolOptions}' This are the configuration options regarding the
 %% pool of connections usage. If the `pool_ensure' option is true, the pool with
-%% the given name in the `pool_name' option will be created and then used if it
+%% the given name in the `pool' option will be created and then used if it
 %% does not exist.
 %% The `pool_connection_timeout' specifies the time that the pool keeps a
 %% connection open before it gets closed, `pool_max_size' specifies the number of
@@ -469,7 +469,7 @@ request(URL, Method, Hdrs, Body, Timeout, Options) ->
 %%
 %% `{pool_options, PoolOptions}' This are the configuration options regarding the
 %% pool of connections usage. If the `pool_ensure' option is true, the pool with
-%% the given name in the `pool_name' option will be created and then used if it
+%% the given name in the `pool' option will be created and then used if it
 %% does not exist.
 %% The `pool_connection_timeout' specifies the time that the pool keeps a
 %% connection open before it gets closed, `pool_max_size' specifies the number of
@@ -499,55 +499,46 @@ request(Host, Port, Ssl, Path, Method, Hdrs, Body, Timeout, Options) ->
 
 %%------------------------------------------------------------------------------
 %% @doc Sends a body part to an ongoing request when
-%% `{partial_upload, WindowSize}' is used. The default timeout, `infinity'
-%% will be used. Notice that if `WindowSize' is infinity, this call will never
-%% block.
-%% Would be the same as calling
-%% `send_body_part(UploadState, BodyPart, infinity)'.
+%% `{partial_upload, true}' is used. The default timeout, `infinity'
+%% will be used. Would be the same as calling
+%% `send_body_part(Client, BodyPart, infinity)'.
 %% @end
 %%------------------------------------------------------------------------------
--spec send_body_part(upload_state(), bodypart()) -> result().
-send_body_part({Pid, Window}, IoList) ->
-    send_body_part({Pid, Window}, IoList, infinity).
+-spec send_body_part(pid(), bodypart()) -> result().
+send_body_part(Client, BodyPart) ->
+    send_body_part(Client, BodyPart, infinity).
 
 %%------------------------------------------------------------------------------
 %% @doc Sends a body part to an ongoing request when
-%% `{partial_upload, WindowSize}' is used.
+%% `{partial_upload, true}' is used.
 %% `Timeout' is the timeout for the request in milliseconds.
 %%
-%% If the window size reaches 0 the call will block for at maximum Timeout
-%% milliseconds. If there is no acknowledgement received during that time the
-%% the request is cancelled and `{error, timeout}' is returned.
-%%
-%% As long as the window size is larger than 0 the function will return
-%% immediately after sending the body part to the request handling process.
-%%
 %% The `BodyPart' `http_eob' signals an end of the entity body, the request
 %% is considered sent and the response will be read from the socket. If
 %% there is no response within `Timeout' milliseconds, the request is
 %% canceled and `{error, timeout}' is returned.
 %% @end
 %%------------------------------------------------------------------------------
--spec send_body_part(upload_state(), bodypart(), timeout()) -> result().
+-spec send_body_part(pid(), bodypart(), timeout()) -> result().
 send_body_part(Client, BodyPart, Timeout)  ->
     lhttpc_client:send_body_part(Client, BodyPart, Timeout).
 
 %%------------------------------------------------------------------------------
 %% @doc Sends trailers to an ongoing request when `{partial_upload,
-%% WindowSize}' is used and no `Content-Length' was specified. The default
+%% true}' is used and no `Content-Length' was specified. The default
 %% timout `infinity' will be used. Plase note that after this the request is
 %% considered complete and the response will be read from the socket.
 %% Would be the same as calling
-%% `send_trailers(UploadState, BodyPart, infinity)'.
+%% `send_trailers(Client, BodyPart, infinity)'.
 %% @end
 %%------------------------------------------------------------------------------
--spec send_trailers({pid(), window_size()}, headers()) -> result().
+-spec send_trailers(pid(), headers()) -> result().
 send_trailers(Client, Trailers) ->
     lhttpc_client:send_trailers(Client, Trailers, infinity).
 
 %%------------------------------------------------------------------------------
 %% @doc Sends trailers to an ongoing request when
-%% `{partial_upload, WindowSize}' is used and no `Content-Length' was
+%% `{partial_upload, true}' is used and no `Content-Length' was
 %% specified.
 %% `Timeout' is the timeout for sending the trailers and reading the
 %% response in milliseconds.
@@ -559,10 +550,9 @@ send_trailers(Client, Trailers) ->
 %% returned.
 %% @end
 %%------------------------------------------------------------------------------
--spec send_trailers({pid(), window_size()}, headers(), timeout()) -> result().
-send_trailers({Pid, _Window}, Trailers, Timeout) when is_list(Trailers), is_pid(Pid) ->
-    Pid ! {trailers, self(), Trailers},
-    read_response(Pid, Timeout).
+-spec send_trailers(pid(), headers(), timeout()) -> result().
+send_trailers(Client, Trailers, Timeout) when is_list(Trailers), is_pid(Client) ->
+    lhttpc_client:send_trailers(Client, Trailers, Timeout).
 
 %%------------------------------------------------------------------------------
 %% @doc Reads a body part from an ongoing response when
@@ -595,38 +585,6 @@ get_body_part(Client, Timeout) ->
 %% Internal functions
 %%==============================================================================
 
-%%------------------------------------------------------------------------------
-%% @private
-%%------------------------------------------------------------------------------
--spec read_response(pid(), timeout()) -> result().
-read_response(Pid, Timeout) ->
-    receive
-        {ack, Pid} ->
-            read_response(Pid, Timeout);
-        {response, Pid, R} ->
-            R;
-        {'EXIT', Pid, Reason} ->
-            {error, Reason}
-    after Timeout ->
-            kill_client(Pid)
-    end.
-
-%%------------------------------------------------------------------------------
-%% @private
-%%------------------------------------------------------------------------------
--spec kill_client(pid()) -> any() | {error, any()}.
-kill_client(Pid) ->
-    Monitor = erlang:monitor(process, Pid),
-    unlink(Pid), % or we'll kill ourself :O
-    exit(Pid, timeout),
-    receive
-        {response, Pid, R} ->
-            erlang:demonitor(Monitor, [flush]),
-            R;
-        {'DOWN', _, process, Pid, Reason}  ->
-            {error, Reason}
-    end.
-
 %%------------------------------------------------------------------------------
 %% @private
 %%------------------------------------------------------------------------------

From e72afef2adec4cb801dd541e197edd5e5bd34312 Mon Sep 17 00:00:00 2001
From: Lastres 
Date: Fri, 30 Aug 2013 16:38:46 +0100
Subject: [PATCH 46/49] Add a better README file

---
 README    | 12 ---------
 README.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 81 insertions(+), 12 deletions(-)
 delete mode 100644 README
 create mode 100644 README.md

diff --git a/README b/README
deleted file mode 100644
index 7ca418e4..00000000
--- a/README
+++ /dev/null
@@ -1,12 +0,0 @@
-Dependencies:
- * Erlang/OTP R13-B or newer
-   * Application compiler to build, kernel, stdlib and ssl to run
-
-Building: 
-For versions > 1.2.5, lhttpc is built using rebar. Take a look at http://bitbucket.org/basho/rebar/wiki/Home for more information. There is still a Makefile with some of the old make targets, such as all, doc, test etc. for those who prefer that. The makefile will however just call rebar.
-
-Configuration: (environment variables)
- * connection_timeout: The time (in milliseconds) the client will try to
-                       kepp a HTTP/1.1 connection open. Changing this value
-                       in runtime has no effect, this can however be done
-                       through lhttpc_manager:update_connection_timeout/1.
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..f84d936c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,81 @@
+# LHTTPC - Lightweight HTTP Client #
+
+Copyright (c) 2009-2013 Erlang Solutions Ltd.
+
+## Starting
+
+Download the sources or clone the git repository. Then you can build with:
+
+```
+make all
+```
+
+which will generate .beam files and documentation. To start the lhttpc OTP application, you first need to start the applications it depends on:
+
+```
+$ erl -pa ebin
+1> application:start(crypto),
+1> application:start(public_key),
+1> application:start(ssl),
+1> lhttpc:start().
+ok
+```
+
+## Usage
+Lhttpc allows the user to send requests by always spawning a process for each of them, or reusing a single client process for several requests. It also allows the usage of pools of connections in order to keep connections alive and reuse them.
+
+### Send a simple request
+A single request without using a client process, will just spawn a process, do the request, and then stop the process.
+
+```erlang
+Method = get,
+URL = "http://www.erlang-solutions.com",
+Headers = [],
+Timeout = 100,
+{ok,{{StatusCode, Status}, Headers, Body}} = lhttpc:request(URL, Method, Headers, Timeout).
+```
+Using the function `request/9` it is also possible to specify the target server using `Host`, `Port` and `Ssl` and a relative `Path`. All the available options are listed in the documentation.
+
+### Reuse a client process
+
+It is possible to first connect a client process to the target server, and then do a requests specifing just the relative Path:
+
+```erlang
+{ok, Client} = lhttpc:connect_client("http://erlang-solutions.com", []),
+{ok,{{StatusCode, Status}, Headers, Body}} = lhttpc:request_client(Client, "/", get, [], 100).
+```
+
+And then reuse the same client to do more requests to the same server.
+
+### Use connection pools
+
+Lhttpc supports pools of connections. They keep the connections to the different servers independently of the client processes. Therefore, if we do a requests specifing a pool, the client process will try to retrieve the connection from the pool if there is one, use it, and then return it to the pool before stopping. This makes it possible to share connections between different client processes and keep connections alive.
+
+```erlang
+lhttpc:add_pool(my_pool),
+lhttpc:request("http://www.erlang-solutions.com", get, [], [], 100, [{pool_options, [{pool, my_pool}]}]).
+```
+
+The `lhttpc_manager` module provides functions to retrive information about the pools:
+
+```
+>lhttpc_manager:connection_count(my_pool).
+0
+>lhttpc:request("http://www.erlang-solutions.com", get, [], [], 100, [{pool_options, [{pool, my_pool}]}]).
+>lhttpc_manager:connection_count(my_pool).
+1
+>
+>lhttpc_manager:list_pools().
+[{my_pool,[{max_pool_size,50},{timeout,300000}]}]
+>lhttpc_manager:update_connection_timeout(my_pool, 1000).
+ok
+>lhttpc_manager:list_pools().
+[{my_pool,[{max_pool_size,50},{timeout,1000}]}]
+```
+
+### Automatic cookie handling
+Lhttpc supports basic cookie handling. If you want the client process to automatically handle the cookies, use the option `{use_cookies, true}`.
+
+### Transfering the body by chunks
+
+If you want to send the body of the request by chunks, you can specify the `{partial_upload, true}` option. Then use the `send_body_part/2` and `send_body_part/3` functions to send the body parts.
\ No newline at end of file

From 83d4262377e40bac163a0c99d9fdcd3fddcd92bc Mon Sep 17 00:00:00 2001
From: Lastres 
Date: Mon, 2 Sep 2013 12:18:26 +0100
Subject: [PATCH 47/49] Update the copyright lines

---
 LICENCE                       |  8 ++++----
 include/lhttpc.hrl            |  2 +-
 include/lhttpc_types.hrl      |  8 ++++----
 src/lhttpc.app.src            | 12 ++++++------
 src/lhttpc.erl                |  8 ++++----
 src/lhttpc_client.erl         |  8 ++++----
 src/lhttpc_lib.erl            |  6 +++---
 src/lhttpc_manager.erl        |  8 ++++----
 src/lhttpc_sock.erl           |  8 ++++----
 src/lhttpc_sup.erl            |  8 ++++----
 test/lhttpc_client_tests.erl  |  2 +-
 test/lhttpc_lib_tests.erl     |  8 ++++----
 test/lhttpc_manager_tests.erl | 12 ++++++------
 test/lhttpc_tests.erl         |  8 ++++----
 test/socket_server.erl        | 12 ++++++------
 test/webserver.erl            | 12 ++++++------
 util/make_doc.erl             | 12 ++++++------
 util/releaser                 | 12 ++++++------
 util/run_test.erl             | 12 ++++++------
 19 files changed, 83 insertions(+), 83 deletions(-)

diff --git a/LICENCE b/LICENCE
index 71fd1882..1f254344 100644
--- a/LICENCE
+++ b/LICENCE
@@ -1,4 +1,4 @@
-Copyright (c) 2009, Erlang Training and Consulting Ltd.
+Copyright (c) 2009-2013, Erlang Solutions Ltd.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -8,14 +8,14 @@ modification, are permitted provided that the following conditions are met:
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
-   * Neither the name of Erlang Training and Consulting Ltd. nor the
+   * Neither the name of Erlang Solutions Ltd. nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.
 
-THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/include/lhttpc.hrl b/include/lhttpc.hrl
index f7b69c9a..7375d0fd 100644
--- a/include/lhttpc.hrl
+++ b/include/lhttpc.hrl
@@ -1,5 +1,5 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
 %%%
 %%% Redistribution and use in source and binary forms, with or without
diff --git a/include/lhttpc_types.hrl b/include/lhttpc_types.hrl
index 57bf4b37..d8a2dd2e 100644
--- a/include/lhttpc_types.hrl
+++ b/include/lhttpc_types.hrl
@@ -1,5 +1,5 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
 %%%
 %%% Redistribution and use in source and binary forms, with or without
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
 %%%
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/src/lhttpc.app.src b/src/lhttpc.app.src
index 7ef984b6..e100f9d7 100644
--- a/src/lhttpc.app.src
+++ b/src/lhttpc.app.src
@@ -1,7 +1,7 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
-%%% 
+%%%
 %%% Redistribution and use in source and binary forms, with or without
 %%% modification, are permitted provided that the following conditions are met:
 %%%    * Redistributions of source code must retain the above copyright
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
-%%% 
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%%
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/src/lhttpc.erl b/src/lhttpc.erl
index e9a89683..f4f38616 100644
--- a/src/lhttpc.erl
+++ b/src/lhttpc.erl
@@ -1,5 +1,5 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
 %%%
 %%% Redistribution and use in source and binary forms, with or without
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
 %%%
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/src/lhttpc_client.erl b/src/lhttpc_client.erl
index 077af59a..8b38b9fa 100644
--- a/src/lhttpc_client.erl
+++ b/src/lhttpc_client.erl
@@ -1,5 +1,5 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
 %%%
 %%% Redistribution and use in source and binary forms, with or without
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
 %%%
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/src/lhttpc_lib.erl b/src/lhttpc_lib.erl
index c18ee8eb..9c0352fa 100644
--- a/src/lhttpc_lib.erl
+++ b/src/lhttpc_lib.erl
@@ -1,5 +1,5 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
 %%%
 %%% Redistribution and use in source and binary forms, with or without
@@ -13,10 +13,10 @@
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
 %%%
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/src/lhttpc_manager.erl b/src/lhttpc_manager.erl
index b94949ff..7504e671 100644
--- a/src/lhttpc_manager.erl
+++ b/src/lhttpc_manager.erl
@@ -1,5 +1,5 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
 %%%
 %%% Redistribution and use in source and binary forms, with or without
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
 %%%
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/src/lhttpc_sock.erl b/src/lhttpc_sock.erl
index 2a6f06b1..c8b9e646 100644
--- a/src/lhttpc_sock.erl
+++ b/src/lhttpc_sock.erl
@@ -1,5 +1,5 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
 %%%
 %%% Redistribution and use in source and binary forms, with or without
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
 %%%
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/src/lhttpc_sup.erl b/src/lhttpc_sup.erl
index 2d73dd47..f5c59d7a 100644
--- a/src/lhttpc_sup.erl
+++ b/src/lhttpc_sup.erl
@@ -1,5 +1,5 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
 %%%
 %%% Redistribution and use in source and binary forms, with or without
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
 %%%
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/test/lhttpc_client_tests.erl b/test/lhttpc_client_tests.erl
index 19d5e836..43a6d5bf 100644
--- a/test/lhttpc_client_tests.erl
+++ b/test/lhttpc_client_tests.erl
@@ -1,5 +1,5 @@
 %%%=============================================================================
-%%% @copyright (C) 1999-2012, Erlang Solutions Ltd
+%%% @copyright (C) 1999-2013, Erlang Solutions Ltd
 %%% @author Diana Corbacho 
 %%% @doc Unit tests for lhttpc_client
 %%% @end
diff --git a/test/lhttpc_lib_tests.erl b/test/lhttpc_lib_tests.erl
index 01565bf2..24132efd 100644
--- a/test/lhttpc_lib_tests.erl
+++ b/test/lhttpc_lib_tests.erl
@@ -1,5 +1,5 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Training and Consulting Ltd.
 %%% All rights reserved.
 %%%
 %%% Redistribution and use in source and binary forms, with or without
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
 %%%
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/test/lhttpc_manager_tests.erl b/test/lhttpc_manager_tests.erl
index fe86f55a..3630292e 100644
--- a/test/lhttpc_manager_tests.erl
+++ b/test/lhttpc_manager_tests.erl
@@ -1,7 +1,7 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
-%%% 
+%%%
 %%% Redistribution and use in source and binary forms, with or without
 %%% modification, are permitted provided that the following conditions are met:
 %%%    * Redistributions of source code must retain the above copyright
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
-%%% 
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%%
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/test/lhttpc_tests.erl b/test/lhttpc_tests.erl
index 57cabd1a..9ba2d29f 100644
--- a/test/lhttpc_tests.erl
+++ b/test/lhttpc_tests.erl
@@ -1,5 +1,5 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
 %%%
 %%% Redistribution and use in source and binary forms, with or without
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
 %%%
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/test/socket_server.erl b/test/socket_server.erl
index 15fdcd83..412b8c76 100644
--- a/test/socket_server.erl
+++ b/test/socket_server.erl
@@ -1,7 +1,7 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
-%%% 
+%%%
 %%% Redistribution and use in source and binary forms, with or without
 %%% modification, are permitted provided that the following conditions are met:
 %%%    * Redistributions of source code must retain the above copyright
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
-%%% 
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%%
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/test/webserver.erl b/test/webserver.erl
index 7da8cc63..b72d891c 100644
--- a/test/webserver.erl
+++ b/test/webserver.erl
@@ -1,7 +1,7 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
-%%% 
+%%%
 %%% Redistribution and use in source and binary forms, with or without
 %%% modification, are permitted provided that the following conditions are met:
 %%%    * Redistributions of source code must retain the above copyright
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutionsg Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
-%%% 
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%%
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/util/make_doc.erl b/util/make_doc.erl
index 53beba11..0e0981e8 100644
--- a/util/make_doc.erl
+++ b/util/make_doc.erl
@@ -1,7 +1,7 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
-%%% 
+%%%
 %%% Redistribution and use in source and binary forms, with or without
 %%% modification, are permitted provided that the following conditions are met:
 %%%    * Redistributions of source code must retain the above copyright
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
-%%% 
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%%
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/util/releaser b/util/releaser
index 8d4400c0..31050255 100755
--- a/util/releaser
+++ b/util/releaser
@@ -1,8 +1,8 @@
 #!/bin/sh
 # ----------------------------------------------------------------------------
-# Copyright (c) 2009, Erlang Training and Consulting Ltd.
+# Copyright (c) 2009-2013, Erlang Solutions Ltd.
 # All rights reserved.
-# 
+#
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are met:
 #    * Redistributions of source code must retain the above copyright
@@ -10,14 +10,14 @@
 #    * Redistributions in binary form must reproduce the above copyright
 #      notice, this list of conditions and the following disclaimer in the
 #      documentation and/or other materials provided with the distribution.
-#    * Neither the name of Erlang Training and Consulting Ltd. nor the
+#    * Neither the name of Erlang Solutions Ltd. nor the
 #      names of its contributors may be used to endorse or promote products
 #      derived from this software without specific prior written permission.
-# 
-# THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+#
+# THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+# ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 # LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
diff --git a/util/run_test.erl b/util/run_test.erl
index b759e664..5796ac05 100644
--- a/util/run_test.erl
+++ b/util/run_test.erl
@@ -1,7 +1,7 @@
 %%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009, Erlang Training and Consulting Ltd.
+%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
 %%% All rights reserved.
-%%% 
+%%%
 %%% Redistribution and use in source and binary forms, with or without
 %%% modification, are permitted provided that the following conditions are met:
 %%%    * Redistributions of source code must retain the above copyright
@@ -9,14 +9,14 @@
 %%%    * Redistributions in binary form must reproduce the above copyright
 %%%      notice, this list of conditions and the following disclaimer in the
 %%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Training and Consulting Ltd. nor the
+%%%    * Neither the name of Erlang Solutions Ltd. nor the
 %%%      names of its contributors may be used to endorse or promote products
 %%%      derived from this software without specific prior written permission.
-%%% 
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS''
+%%%
+%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
 %%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 %%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
 %%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 %%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR

From 41ea97f835b116357b3f897bb21b60c73ca6b5b1 Mon Sep 17 00:00:00 2001
From: Lastres 
Date: Mon, 2 Sep 2013 14:16:48 +0100
Subject: [PATCH 48/49] Remove old files not needed anymore

Now we are using Rebar to build.
---
 util/make_doc.erl |  37 --------------
 util/releaser     | 117 --------------------------------------------
 util/run_test.erl | 122 ----------------------------------------------
 3 files changed, 276 deletions(-)
 delete mode 100644 util/make_doc.erl
 delete mode 100755 util/releaser
 delete mode 100644 util/run_test.erl

diff --git a/util/make_doc.erl b/util/make_doc.erl
deleted file mode 100644
index 0e0981e8..00000000
--- a/util/make_doc.erl
+++ /dev/null
@@ -1,37 +0,0 @@
-%%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
-%%% All rights reserved.
-%%%
-%%% Redistribution and use in source and binary forms, with or without
-%%% modification, are permitted provided that the following conditions are met:
-%%%    * Redistributions of source code must retain the above copyright
-%%%      notice, this list of conditions and the following disclaimer.
-%%%    * Redistributions in binary form must reproduce the above copyright
-%%%      notice, this list of conditions and the following disclaimer in the
-%%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Solutions Ltd. nor the
-%%%      names of its contributors may be used to endorse or promote products
-%%%      derived from this software without specific prior written permission.
-%%%
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
-%%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-%%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
-%%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
-%%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-%%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-%%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-%%% ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-%%% ----------------------------------------------------------------------------
-
-%%% @author Oscar Hellström 
--module(make_doc).
--export([edoc/0]).
-
-edoc() ->
-    try
-        edoc:application(lhttpc, "./", [{doc, "doc/"}])
-    catch _:_ ->
-        halt(1)
-    end,
-    halt(0).
diff --git a/util/releaser b/util/releaser
deleted file mode 100755
index 31050255..00000000
--- a/util/releaser
+++ /dev/null
@@ -1,117 +0,0 @@
-#!/bin/sh
-# ----------------------------------------------------------------------------
-# Copyright (c) 2009-2013, Erlang Solutions Ltd.
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#    * Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#    * Redistributions in binary form must reproduce the above copyright
-#      notice, this list of conditions and the following disclaimer in the
-#      documentation and/or other materials provided with the distribution.
-#    * Neither the name of Erlang Solutions Ltd. nor the
-#      names of its contributors may be used to endorse or promote products
-#      derived from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
-# LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
-# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-# ----------------------------------------------------------------------------
-# Script for making a release of lhttpc. Or any program in hg really. Check
-# the Makefile target release for how to use it.
-
-NAME=$1
-VSN=$2
-
-yesno() {
-    prompt=$1
-    while true; do
-        read -p "$1 [Y/n] " answer
-        case "x$answer" in
-            "x")
-            return 0
-            ;;
-            "xY")
-            return 0
-            ;;
-            "xy")
-            return 0
-            ;;
-            "xN")
-            return 1
-            ;;
-            "xn")
-            return 1
-            ;;
-            *)
-            ;;
-        esac
-    done
-}
-
-get_version() {
-    while true; do
-        read -p "What is the version of the release? [$VSN] " release_vsn
-
-        if [ "$release_vsn" = "" ]; then
-            release_vsn=$VSN
-        fi
-
-        if $(echo "$TAGS" | grep -q "^$release_vsn\$"); then
-            if yesno "A tag exists for version $release_vsn, is this correct?"; then
-                break
-            fi
-        else
-            if yesno "A tag doesn't exist for version $release_vsn, should one be created?"; then
-                hg tag $release_vsn
-            fi
-            break
-        fi
-    done
-    echo $release_vsn
-}
-
-if ! hg identify 1>/dev/null 2>&1; then
-    echo "No hg repository here..."
-    exit 1
-fi
-
-if ! [ "$(hg identify | awk '{print $2};')" = "tip" ]; then
-    if ! yesno "Repository is not at tip, do you want to continue?"; then
-        exit 1
-    fi
-fi
-
-if ! yesno "Did the compilation run without warnings?"; then
-    echo "Try again..."
-    exit 1
-fi
-
-if ! yesno "Is the changelog up to date?"; then
-    echo "Try again..."
-    exit 1
-fi
-
-if ! yesno "Did dialyzer run without warnings?"; then
-    echo "Try again..."
-    exit 1
-fi
-
-TAGS=$(hg tags | awk '{print $1 };' | grep -v "^tip$")
-LATEST_TAG=$(echo "$TAGS" | head -n 1)
-
-RELEASE_VSN=$(get_version)
-echo "Creating a release for $NAME-$RELEASE_VSN now."
-archive="./$NAME-$RELEASE_VSN.tar.gz"
-if [ -e $archive ]; then
-    echo "$archive exists, giving up."
-    exit 1
-fi
-hg archive -t tgz -X ".hg*" $archive
diff --git a/util/run_test.erl b/util/run_test.erl
deleted file mode 100644
index 5796ac05..00000000
--- a/util/run_test.erl
+++ /dev/null
@@ -1,122 +0,0 @@
-%%% ----------------------------------------------------------------------------
-%%% Copyright (c) 2009-2013, Erlang Solutions Ltd.
-%%% All rights reserved.
-%%%
-%%% Redistribution and use in source and binary forms, with or without
-%%% modification, are permitted provided that the following conditions are met:
-%%%    * Redistributions of source code must retain the above copyright
-%%%      notice, this list of conditions and the following disclaimer.
-%%%    * Redistributions in binary form must reproduce the above copyright
-%%%      notice, this list of conditions and the following disclaimer in the
-%%%      documentation and/or other materials provided with the distribution.
-%%%    * Neither the name of Erlang Solutions Ltd. nor the
-%%%      names of its contributors may be used to endorse or promote products
-%%%      derived from this software without specific prior written permission.
-%%%
-%%% THIS SOFTWARE IS PROVIDED BY Erlang Solutions Ltd. ''AS IS''
-%%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-%%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Solutions Ltd. BE
-%%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
-%%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-%%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-%%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-%%% ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-%%% ----------------------------------------------------------------------------
-
-%%% @author Oscar Hellström 
--module(run_test).
--export([run/0]).
-
--include_lib("eunit/include/eunit.hrl").
--include_lib("stdlib/include/ms_transform.hrl").
-
--define(TEST_LOG, "test/error_logger.log").
--define(SASL_LOG, "test/sasl.log").
--define(FILE_NAME(MODULE),
-    "cover_report/" ++ atom_to_list(MODULE) ++ ".html").
-
-run() ->
-    Modules = get_modules(),
-    ok = cover_compile(Modules),
-    start_logging(),
-    Result = eunit:test(?MODULE, [verbose]),
-    filelib:ensure_dir("cover_report/index.html"),
-    html_report(Modules),
-    write_report(Modules),
-    stop_logging(),
-    io:format("Cover report in cover_report/index.html~n"),
-    io:format("Test logs in ~s and ~s~n", [?TEST_LOG, ?SASL_LOG]),
-    if
-        Result =:= ok -> halt(0);
-        Result =/= ok -> halt(1)
-    end.
-
-start_logging() ->
-    application:load(sasl),
-    application:set_env(sasl, sasl_error_logger, {file, ?SASL_LOG}),
-    file:delete(?TEST_LOG),
-    file:delete(?SASL_LOG),
-    error_logger:tty(false),
-    error_logger:logfile({open, ?TEST_LOG}),
-    application:start(sasl).
-
-stop_logging() ->
-    error_logger:logfile(close),
-    application:stop(sasl).
-
-html_report([Module | Modules]) ->
-    cover:analyse_to_file(Module, ?FILE_NAME(Module), [html]),
-    html_report(Modules);
-html_report([]) ->
-    ok.
-
-write_report(Modules) ->
-    {TotalPercentage, ModulesPersentage} = percentage(Modules, 0, 0, []),
-    file:write_file("cover_report/index.html",
-        [
-            "\nCover report index\n"
-            "\n"
-            "

Cover report for lhttpc

" - "Total coverage: ", integer_to_list(TotalPercentage), "%" - "

Cover for individual modules

\n" - "
    \n\t", - lists:foldl(fun({Module, Percentage}, Acc) -> - Name = atom_to_list(Module), - [ - "
  • " - "", - Name, - " ", integer_to_list(Percentage), "%" - "
  • \n\t" | - Acc - ] - end, [], ModulesPersentage), - "
" - ]). - -percentage([Module | Modules], TotCovered, TotLines, Percentages) -> - {ok, Analasys} = cover:analyse(Module, coverage, line), - {Covered, Lines} = lists:foldl(fun({_, {C, _}}, {Covered, Lines}) -> - {C + Covered, Lines + 1} - end, {0, 0}, Analasys), - Percent = (Covered * 100) div Lines, - NewPercentages = [{Module, Percent} | Percentages], - percentage(Modules, Covered + TotCovered, Lines + TotLines, NewPercentages); -percentage([], Covered, Lines, Percentages) -> - {(Covered * 100) div Lines, Percentages}. - -get_modules() -> - application:load(lhttpc), - {ok, Modules} = application:get_key(lhttpc, modules), - Modules. - -cover_compile([Module | Modules]) -> - {ok, Module} = cover:compile_beam(Module), - cover_compile(Modules); -cover_compile([]) -> - ok. - -%%% Eunit functions -application_test_() -> - {application, lhttpc}. From 13488875bca12390703f860e0d8c10c89e1d06bf Mon Sep 17 00:00:00 2001 From: Lastres Date: Mon, 2 Sep 2013 14:54:04 +0100 Subject: [PATCH 49/49] Small additions to the Readme.md --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f84d936c..cdd42ace 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,18 @@ Copyright (c) 2009-2013 Erlang Solutions Ltd. +## Features + +Some of the basic features provided by Lhttpc: + +- HTTP basic auth +- SSL support +- Keepalive connections +- Pools for managing connections +- Support for IPv6 +- Optional automatic cookie handling +- Chunked encoding + ## Starting Download the sources or clone the git repository. Then you can build with: @@ -78,4 +90,12 @@ Lhttpc supports basic cookie handling. If you want the client process to automat ### Transfering the body by chunks -If you want to send the body of the request by chunks, you can specify the `{partial_upload, true}` option. Then use the `send_body_part/2` and `send_body_part/3` functions to send the body parts. \ No newline at end of file +If you want to send the body of the request by chunks, you can specify the `{partial_upload, true}` option. Then use the `send_body_part/2` and `send_body_part/3` functions to send the body parts. `http_eob` signals the end of the body. As an example: + +```erlang +{ok, Client} = lhttpc:connect_client("http://erlang-solutions.com", []), +lhttpc:request_client(Client, "/", get, [], [], 100, [{partial_upload, true}]), +lhttpc:send_body_part(Client, <<"some part of the body">>), +lhttpc:send_body_part(Client, <<"more body">>), +lhttpc:send_body_part(Client, http_eob). +``` \ No newline at end of file