Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/lhttpc.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
%%% ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
%%% ----------------------------------------------------------------------------

%%% @author Oscar Hellstr�m <[email protected]>
%%% @author Oscar Hellström <[email protected]>
%%% @doc This is the specification for the lhttpc application.
%%% @end
{application, lhttpc,
Expand Down
2 changes: 1 addition & 1 deletion src/lhttpc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
%%% ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
%%% ----------------------------------------------------------------------------

%%% @author Oscar Hellstr�m <[email protected]>
%%% @author Oscar Hellström <[email protected]>
%%% @doc Main interface to the lightweight http client.
%%% See {@link request/4}, {@link request/5} and {@link request/6} functions.
-module(lhttpc).
Expand Down
95 changes: 77 additions & 18 deletions src/lhttpc_client.erl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
%%% ----------------------------------------------------------------------------

%%% @private
%%% @author Oscar Hellstr�m <[email protected]>
%%% @author Oscar Hellström <[email protected]>
%%% @doc
%%% This module implements the HTTP request handling. This should normally
%%% not be called directly since it should be spawned by the lhttpc module.
Expand Down Expand Up @@ -105,30 +105,26 @@ execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, 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),

%% this can be target host's or proxy's host/port/ssl !?!?
%% How do we handle situation like
%% > request(host1, proxy1)
%% > request(host1, proxy2)
%% ???
SocketRequest = {socket, self(), Host, Port, Ssl},
Pool = proplists:get_value(pool, Options, whereis(lhttpc_manager)),
Socket = case gen_server:call(Pool, SocketRequest, infinity) of
{ok, S} -> S; % Re-using HTTP/1.1 connections
no_socket -> undefined % Opening a new HTTP/1.1 connection
end,


State = #client_state{
host = Host,
port = Port,
ssl = Ssl,
method = NormalizedMethod,
request = Request,
%% request = Request,
requester = From,
request_headers = Hdrs,
socket = Socket,
Expand All @@ -138,17 +134,21 @@ execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) ->
attempts = 1 + proplists:get_value(send_retry, Options, 1),
partial_upload = PartialUpload,
upload_window = UploadWindowSize,
chunked_upload = ChunkedUpload,
%% 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 = Proxy,
%% proxy_setup = (Socket =/= undefined),
proxy_ssl_options = proplists:get_value(proxy_ssl_options, Options, [])
},
Response = case send_request(State) of

State2 = configure_proxy(State, Body, Path, Ssl,
proplists:get_value(proxy, Options)),

Response = case send_request(State2) of
{R, undefined} ->
{ok, R};
{R, NewSocket} ->
Expand All @@ -171,6 +171,65 @@ execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) ->
end,
{response, self(), Response}.

configure_proxy(State, Body, Path, _Ssl, undefined) ->
%% no proxy at all
set_request(State#client_state{proxy=undefined}, Body, Path);
configure_proxy(State, Body, Path, true, ProxyUrl) when is_list(ProxyUrl) ->
%% connect through ssl/not ssl proxy to ssl host using CONNECT
Proxy = lhttpc_lib:parse_url(ProxyUrl),
set_request(State#client_state{
proxy = Proxy,
proxy_setup = (State#client_state.socket =/= undefined)},
Body, Path);
configure_proxy(State, Body, Path, false, ProxyUrl) ->
%% connect through ssl/not ssl proxy to not ssl host
%% just replace Path with full host URL
#client_state{request_headers=Hdrs,
host=DestHost,
port=DestPort} = State,
Proxy = lhttpc_lib:parse_url(ProxyUrl),
#lhttpc_url{
%% host = Host,
%% port = Port,
user = User,
password = Passwd
%% is_ssl = SslProxy
} = Proxy,
NewPath = lhttpc_lib:format_url(
#lhttpc_url{host=DestHost,
port=DestPort,
path=Path,
is_ssl=false}),
Hdrs2 = case User of
"" ->
Hdrs;
User ->
AuthHdr = {"Proxy-Authorization",
"Basic " ++ base64:encode_to_string(User ++ ":" ++ Passwd)},
[AuthHdr | Hdrs]
end,
set_request(State#client_state{
%% ssl = SslProxy, % see request_first_destination
%% host = Host,
%% port = Port,
request_headers = Hdrs2,
proxy = Proxy,
proxy_setup = true},
Body, NewPath).


set_request(State, Body, Path) ->
#client_state{partial_upload=PartialUpload,
method=NormalizedMethod,
request_headers=Hdrs,
host=Host,
port=Port} = State,
{ChunkedUpload, Request} = lhttpc_lib:format_request(
Path, NormalizedMethod, Hdrs, Host, Port, Body, PartialUpload),
State#client_state{request=Request,
chunked_upload = ChunkedUpload}.


send_request(#client_state{attempts = 0}) ->
% Don't try again if the number of allowed attempts is 0.
throw(connection_closed);
Expand Down
46 changes: 45 additions & 1 deletion src/lhttpc_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
%%% ----------------------------------------------------------------------------

%%% @private
%%% @author Oscar Hellstr�m <[email protected]>
%%% @author Oscar Hellström <[email protected]>
%%% @doc
%%% This module implements various library functions used in lhttpc.
-module(lhttpc_lib).

-export([
parse_url/1,
format_url/1,
format_request/7,
header_value/2,
header_value/3,
Expand Down Expand Up @@ -96,6 +97,49 @@ maybe_atom_to_list(Atom) when is_atom(Atom) ->
maybe_atom_to_list(List) ->
List.

format_url(URL) ->
#lhttpc_url{
host = Host,
port = Port,
path = Path,
user = User,
password = Passwd,
is_ssl = IsSSL
} = URL,
U1 = add_scheme("", IsSSL),
U2 = add_credentials(U1, User, Passwd),
U3 = add_host(U2, Host),
U4 = add_port(U3, Port, IsSSL),
lists:flatten(add_path(U4, Path)).

add_scheme(_, true) ->
"https://";
add_scheme(_, false) ->
"http://".

add_credentials(Scheme, "", "") ->
Scheme;
add_credentials(Scheme, User, "") ->
[Scheme, User, "@"];
add_credentials(Scheme, User, Passwd) ->
[Scheme, User, ":", Passwd, "@"].

add_host(SUP, Host) ->
Host2 = maybe_ipv6_enclose(Host),
[SUP, Host2].

add_port(SUPH, 80, false) ->
SUPH;
add_port(SUPH, 443, true) ->
SUPH;
add_port(SUPH, Port, _IsSSL) ->
[SUPH, ":", integer_to_list(Port)].

add_path(SUPHP, Path) ->
[SUPHP, Path].



%% @spec (URL) -> #lhttpc_url{}
%% URL = string()
%% @doc
Expand Down
2 changes: 1 addition & 1 deletion src/lhttpc_manager.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
%%% ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
%%% ----------------------------------------------------------------------------

%%% @author Oscar Hellstr�m <[email protected]>
%%% @author Oscar Hellström <[email protected]>
%%% @author Filipe David Manana <[email protected]>
%%% @doc Connection manager for the HTTP client.
%%% This gen_server is responsible for keeping track of persistent
Expand Down
2 changes: 1 addition & 1 deletion src/lhttpc_sock.erl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
%%% ----------------------------------------------------------------------------

%%% @private
%%% @author Oscar Hellstr�m <[email protected]>
%%% @author Oscar Hellström <[email protected]>
%%% @doc
%%% This module implements wrappers for socket operations.
%%% Makes it possible to have the same interface to ssl and tcp sockets.
Expand Down
2 changes: 1 addition & 1 deletion src/lhttpc_sup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
%%% ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
%%% ----------------------------------------------------------------------------

%%% @author Oscar Hellstr�m <[email protected]>
%%% @author Oscar Hellström <[email protected]>
%%% @doc Top supervisor for the lhttpc application.
%%% This is normally started by the application behaviour implemented in
%%% {@link lhttpc}.
Expand Down
92 changes: 91 additions & 1 deletion test/lhttpc_lib_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -201,5 +201,95 @@ parse_url_test_() ->
user = "joe",
password = "erlang"
},
lhttpc_lib:parse_url("http://joe:erlang@[1080:0:0:0:8:800:200C:417A]:180/foo/bar"))
lhttpc_lib:parse_url("http://joe:erlang@[1080:0:0:0:8:800:200C:417A]:180/foo/bar")),

%% Serialisation
?_assertEqual("http://host/",
lhttpc_lib:format_url(#lhttpc_url{
host = "host",
port = 80,
path = "/",
is_ssl = false,
user = "",
password = ""
})),

?_assertEqual("https://host/",
lhttpc_lib:format_url(#lhttpc_url{
host = "host",
port = 443,
path = "/",
is_ssl = true,
user = "",
password = ""
})),

?_assertEqual("http://host:180/",
lhttpc_lib:format_url(#lhttpc_url{
host = "host",
port = 180,
path = "/",
is_ssl = false,
user = "",
password = ""
})),

?_assertEqual("https://host:180/",
lhttpc_lib:format_url(#lhttpc_url{
host = "host",
port = 180,
path = "/",
is_ssl = true,
user = "",
password = ""
})),

?_assertEqual("http://host:180/path",
lhttpc_lib:format_url(#lhttpc_url{
host = "host",
port = 180,
path = "/path",
is_ssl = false,
user = "",
password = ""
})),

?_assertEqual("http://user@host:180/path",
lhttpc_lib:format_url(#lhttpc_url{
host = "host",
port = 180,
path = "/path",
is_ssl = false,
user = "user",
password = ""
})),

?_assertEqual("http://user:pass@host:180/path",
lhttpc_lib:format_url(#lhttpc_url{
host = "host",
port = 180,
path = "/path",
is_ssl = false,
user = "user",
password = "pass"
})),
?_assertEqual("http://:pass@host:180/path",
lhttpc_lib:format_url(#lhttpc_url{
host = "host",
port = 180,
path = "/path",
is_ssl = false,
user = "",
password = "pass"
})),

?_assertEqual("http://user:pass@[::1]:180/path",
lhttpc_lib:format_url(#lhttpc_url{
host = "::1",
port = 180,
path = "/path",
is_ssl = false,
user = "user",
password = "pass"
}))
].