Skip to content

Commit

Permalink
Update lhttpc to latest version
Browse files Browse the repository at this point in the history
This simplifies error handling and fixes many
dialyzer warnings. Related upstream pull requests
are:

* esl/lhttpc#10
* esl/lhttpc#11
* esl/lhttpc#12

This is part of CBD-73

Change-Id: I5c7239ee67cbb1fa5b88fd51e9ce803e9b244f6a
Reviewed-on: http://review.couchbase.org/15099
Tested-by: buildbot <build@couchbase.com>
Reviewed-by: Volker Mische <volker.mische@gmail.com>
  • Loading branch information
fdmanana authored and Damien Katz committed Apr 23, 2012
1 parent fb6fd46 commit eeff685
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 131 deletions.
2 changes: 1 addition & 1 deletion Makefile.am
Expand Up @@ -161,4 +161,4 @@ rebuild_plt:
$(MAKE) $(COUCHDB_PLT)

dialyzer: all $(COUCHDB_PLT)
dialyzer --plt $(COUCHDB_PLT) -pa src/couchdb -pa src/couch_set_view/ebin -pa src/mapreduce -c src/couchdb -c src/couch_set_view/ebin
dialyzer --plt $(COUCHDB_PLT) -pa src/couchdb -pa src/couch_set_view/ebin -pa src/mapreduce -c src/couchdb -c src/couch_set_view/ebin -c src/lhttpc
54 changes: 21 additions & 33 deletions src/couchdb/couch_api_wrap_httpc.erl
Expand Up @@ -162,46 +162,34 @@ error_cause(Cause) ->


stream_data_self(#httpdb{timeout = T} = HttpDb, Params, Pid, Callback) ->
try
case lhttpc:get_body_part(Pid, T) of
{ok, {http_eob, _Trailers}} ->
{<<>>, fun() -> throw({maybe_retry_req, more_data_expected}) end};
{ok, Data} ->
{Data, fun() -> stream_data_self(HttpDb, Params, Pid, Callback) end};
Error ->
throw({maybe_retry_req, Error})
end
catch exit:ExitReason ->
throw({maybe_retry_req, ExitReason})
case lhttpc:get_body_part(Pid, T) of
{ok, {http_eob, _Trailers}} ->
{<<>>, fun() -> throw({maybe_retry_req, more_data_expected}) end};
{ok, Data} ->
{Data, fun() -> stream_data_self(HttpDb, Params, Pid, Callback) end};
Error ->
throw({maybe_retry_req, Error})
end.


make_upload_fun(UploadState, #httpdb{timeout = Timeout} = HttpDb) ->
fun(eof) ->
try
case lhttpc:send_body_part(UploadState, http_eob, Timeout) of
{ok, {{Code, _}, Headers, Body}} when ?NOT_HTTP_ERROR(Code) ->
{ok, Code, Headers, decode_body(Body)};
{ok, {{Code, _}, Headers, _Body}} when ?IS_HTTP_REDIRECT(Code) ->
throw({redirect_req, Code, Headers});
{ok, {{Code, _}, _Headers, _Body}} ->
throw({maybe_retry_req, {code, Code}});
Error ->
throw({maybe_retry_req, Error})
end
catch exit:ExitReason ->
throw({maybe_retry_req, ExitReason})
case lhttpc:send_body_part(UploadState, http_eob, Timeout) of
{ok, {{Code, _}, Headers, Body}} when ?NOT_HTTP_ERROR(Code) ->
{ok, Code, Headers, decode_body(Body)};
{ok, {{Code, _}, Headers, _Body}} when ?IS_HTTP_REDIRECT(Code) ->
throw({redirect_req, Code, Headers});
{ok, {{Code, _}, _Headers, _Body}} ->
throw({maybe_retry_req, {code, Code}});
Error ->
throw({maybe_retry_req, Error})
end;
(BodyPart) ->
try
case lhttpc:send_body_part(UploadState, BodyPart, Timeout) of
{ok, UploadState2} ->
{ok, make_upload_fun(UploadState2, HttpDb)};
Error ->
throw({maybe_retry_req, Error})
end
catch exit:ExitReason ->
throw({maybe_retry_req, ExitReason})
case lhttpc:send_body_part(UploadState, BodyPart, Timeout) of
{ok, UploadState2} ->
{ok, make_upload_fun(UploadState2, HttpDb)};
Error ->
throw({maybe_retry_req, Error})
end
end.

Expand Down
12 changes: 3 additions & 9 deletions src/couchdb/couch_index_merger.erl
Expand Up @@ -309,9 +309,9 @@ get_ddoc(#httpdb{} = HttpDb, Id) ->
"database `~s`: ~s", [Id, db_uri(HttpDb), Error]),
throw({error, iolist_to_binary(Msg)})
end;
Error ->
{error, Error} ->
Msg = io_lib:format("Error getting design document `~s` from database "
"`~s`: ~s", [Id, db_uri(HttpDb), lhttpc_error_msg(Error)]),
"`~s`: ~s", [Id, db_uri(HttpDb), to_binary(Error)]),
throw({error, iolist_to_binary(Msg)})
end;
get_ddoc(Db, Id) ->
Expand Down Expand Up @@ -354,10 +354,6 @@ ddoc_not_found_msg(DbName, DDocId) ->
[DDocId, db_uri(DbName)]),
iolist_to_binary(Msg).

lhttpc_error_msg({error, Reason}) ->
to_binary(Reason);
lhttpc_error_msg(Reason) ->
to_binary(Reason).

lhttpc_options(#httpdb{timeout = T}) ->
% TODO: add SSL options like verify and cacertfile, which should
Expand Down Expand Up @@ -655,9 +651,7 @@ stream_data(Pid, Timeout) ->
{ok, Data} ->
{Data, fun() -> stream_data(Pid, Timeout) end};
{error, _} = Error ->
throw(Error);
Error ->
throw({error, Error})
throw(Error)
end.


Expand Down
145 changes: 63 additions & 82 deletions src/lhttpc/lhttpc.erl
Expand Up @@ -46,8 +46,15 @@
-include("lhttpc_types.hrl").
-include("lhttpc.hrl").

-type result() :: {ok, {{pos_integer(), string()}, headers(), binary()}} |
{error, atom()}.
-type upload_state() :: {pid(), window_size()}.
-type body() :: binary() |
'undefined' | % HEAD request.
pid(). % When partial_download option is used.

-type result() ::
{ok, {{pos_integer(), string()}, headers(), body()}} |
{ok, upload_state()} |
{error, atom()}.

%% @hidden
-spec start(normal | {takeover, node()} | {failover, node()}, any()) ->
Expand Down Expand Up @@ -346,22 +353,17 @@ request(URL, Method, Hdrs, Body, Timeout, Options) ->
-spec request(string(), 1..65535, true | false, string(), atom() | string(),
headers(), iolist(), pos_integer(), [option()]) -> result().
request(Host, Port, Ssl, Path, Method, Hdrs, Body, Timeout, Options) ->
verify_options(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} ->
% We would rather want to exit here, instead of letting the
% linked client send us an exit signal, since this can be
% caught by the caller.
exit(Reason);
{'EXIT', Pid, Reason} ->
% This could happen if the process we're running in taps exits
% 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
exit(Reason)
{error, Reason}
after Timeout ->
kill_client(Pid)
end.
Expand All @@ -378,8 +380,7 @@ request(Host, Port, Ssl, Path, Method, Hdrs, Body, Timeout, Options) ->
%% Would be the same as calling
%% `send_body_part(UploadState, BodyPart, infinity)'.
%% @end
-spec send_body_part({pid(), window_size()}, iolist()) ->
{pid(), window_size()} | result().
-spec send_body_part(upload_state(), iolist() | 'http_eob') -> result().
send_body_part({Pid, Window}, IoList) ->
send_body_part({Pid, Window}, IoList, infinity).

Expand All @@ -404,8 +405,7 @@ send_body_part({Pid, Window}, IoList) ->
%% there is no response within `Timeout' milliseconds, the request is
%% canceled and `{error, timeout}' is returned.
%% @end
-spec send_body_part({pid(), window_size()}, iolist(), timeout()) ->
{ok, {pid(), window_size()}} | result().
-spec send_body_part(upload_state(), iolist() | 'http_eob', timeout()) -> result().
send_body_part({Pid, _Window}, http_eob, Timeout) when is_pid(Pid) ->
Pid ! {body_part, self(), http_eob},
read_response(Pid, Timeout);
Expand All @@ -415,10 +415,8 @@ send_body_part({Pid, 0}, IoList, Timeout) when is_pid(Pid) ->
send_body_part({Pid, 1}, IoList, Timeout);
{response, Pid, R} ->
R;
{exit, Pid, Reason} ->
exit(Reason);
{'EXIT', Pid, Reason} ->
exit(Reason)
{error, Reason}
after Timeout ->
kill_client(Pid)
end;
Expand All @@ -430,10 +428,8 @@ send_body_part({Pid, Window}, IoList, _Timeout) when Window > 0, is_pid(Pid) ->
{ok, {Pid, Window}};
{response, Pid, R} ->
R;
{exit, Pid, Reason} ->
exit(Reason);
{'EXIT', Pid, Reason} ->
exit(Reason)
{error, Reason}
after 0 ->
{ok, {Pid, lhttpc_lib:dec(Window)}}
end.
Expand Down Expand Up @@ -494,7 +490,9 @@ send_trailers({Pid, _Window}, Trailers, Timeout)
%% Would be the same as calling
%% `get_body_part(HTTPClient, infinity)'.
%% @end
-spec get_body_part(pid()) -> {ok, binary()} | {ok, {http_eob, headers()}}.
-spec get_body_part(pid()) -> {ok, binary()} |
{ok, {http_eob, headers()}} |
{error, term()}.
get_body_part(Pid) ->
get_body_part(Pid, infinity).

Expand All @@ -512,7 +510,9 @@ get_body_part(Pid) ->
%% response those are returned with `http_eob' as well.
%% @end
-spec get_body_part(pid(), timeout()) ->
{ok, binary()} | {ok, {http_eob, headers()}}.
{ok, binary()} |
{ok, {http_eob, headers()}} |
{error, term()}.
get_body_part(Pid, Timeout) ->
receive
{body_part, Pid, Bin} ->
Expand All @@ -533,10 +533,8 @@ read_response(Pid, Timeout) ->
read_response(Pid, Timeout);
{response, Pid, R} ->
R;
{exit, Pid, Reason} ->
exit(Reason);
{'EXIT', Pid, Reason} ->
exit(Reason)
{error, Reason}
after Timeout ->
kill_client(Pid)
end.
Expand All @@ -549,70 +547,53 @@ kill_client(Pid) ->
{response, Pid, R} ->
erlang:demonitor(Monitor, [flush]),
R;
{'DOWN', _, process, Pid, timeout} ->
{error, timeout};
{'DOWN', _, process, Pid, Reason} ->
erlang:error(Reason)
{error, Reason}
end.

-spec verify_options(options(), options()) -> ok.
verify_options([{send_retry, N} | Options], Errors)
when is_integer(N), N >= 0 ->
verify_options(Options, Errors);
verify_options([{connect_timeout, infinity} | Options], Errors) ->
verify_options(Options, Errors);
verify_options([{connect_timeout, MS} | Options], Errors)
-spec verify_options(options()) -> ok.
verify_options([{send_retry, N} | Options]) when is_integer(N), N >= 0 ->
verify_options(Options);
verify_options([{connect_timeout, infinity} | Options]) ->
verify_options(Options);
verify_options([{connect_timeout, MS} | Options])
when is_integer(MS), MS >= 0 ->
verify_options(Options, Errors);
verify_options([{partial_upload, WindowSize} | Options], Errors)
verify_options(Options);
verify_options([{partial_upload, WindowSize} | Options])
when is_integer(WindowSize), WindowSize >= 0 ->
verify_options(Options, Errors);
verify_options([{partial_upload, infinity} | Options], Errors) ->
verify_options(Options, Errors);
verify_options([{partial_download, DownloadOptions} | Options], Errors)
verify_options(Options);
verify_options([{partial_upload, infinity} | Options]) ->
verify_options(Options);
verify_options([{partial_download, DownloadOptions} | Options])
when is_list(DownloadOptions) ->
case verify_partial_download(DownloadOptions, []) of
[] ->
verify_options(Options, Errors);
OptionErrors ->
NewErrors = [{partial_download, OptionErrors} | Errors],
verify_options(Options, NewErrors)
end;
verify_options([{connect_options, List} | Options], Errors)
when is_list(List) ->
verify_options(Options, Errors);
verify_options([{proxy, List} | Options], Errors)
when is_list(List) ->
verify_options(Options, Errors);
verify_options([{proxy_ssl_options, List} | Options], Errors)
when is_list(List) ->
verify_options(Options, Errors);
verify_options([{pool, PidOrName} | Options], Errors)
verify_partial_download(DownloadOptions),
verify_options(Options);
verify_options([{connect_options, List} | Options]) when is_list(List) ->
verify_options(Options);
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])
when is_pid(PidOrName); is_atom(PidOrName) ->
verify_options(Options, Errors);
verify_options([Option | Options], Errors) ->
verify_options(Options, [Option | Errors]);
verify_options([], []) ->
ok;
verify_options([], Errors) ->
bad_options(Errors).

-spec bad_options(options()) -> no_return().
bad_options(Errors) ->
erlang:error({bad_options, Errors}).
verify_options(Options);
verify_options([Option | _Rest]) ->
erlang:error({bad_option, Option});
verify_options([]) ->
ok.

-spec verify_partial_download(options(), options()) -> options().
verify_partial_download([{window_size, infinity} | Options], Errors)->
verify_partial_download(Options, Errors);
verify_partial_download([{window_size, Size} | Options], Errors) when
-spec verify_partial_download(options()) -> ok.
verify_partial_download([{window_size, infinity} | Options])->
verify_partial_download(Options);
verify_partial_download([{window_size, Size} | Options]) when
is_integer(Size), Size >= 0 ->
verify_partial_download(Options, Errors);
verify_partial_download([{part_size, Size} | Options], Errors) when
verify_partial_download(Options);
verify_partial_download([{part_size, Size} | Options]) when
is_integer(Size), Size >= 0 ->
verify_partial_download(Options, Errors);
verify_partial_download([{part_size, infinity} | Options], Errors) ->
verify_partial_download(Options, Errors);
verify_partial_download([Option | Options], Errors) ->
verify_partial_download(Options, [Option | Errors]);
verify_partial_download([], Errors) ->
Errors.
verify_partial_download(Options);
verify_partial_download([{part_size, infinity} | Options]) ->
verify_partial_download(Options);
verify_partial_download([Option | _Options]) ->
erlang:error({bad_option, {partial_download, Option}});
verify_partial_download([]) ->
ok.
5 changes: 3 additions & 2 deletions src/lhttpc/lhttpc_client.erl
Expand Up @@ -86,8 +86,9 @@ request(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) ->
{response, self(), {error, Reason}};
error:closed ->
{response, self(), {error, connection_closed}};
error:Error ->
{exit, self(), {Error, erlang:get_stacktrace()}}
error:Reason ->
Stack = erlang:get_stacktrace(),
{response, self(), {error, {Reason, Stack}}}
end,
case Result of
{response, _, {ok, {no_return, _}}} -> ok;
Expand Down
8 changes: 5 additions & 3 deletions src/lhttpc/lhttpc_sock.erl
Expand Up @@ -115,16 +115,18 @@ send(Socket, Request, true) ->
send(Socket, Request, false) ->
gen_tcp:send(Socket, Request).

%% @spec (Socket, Pid, SslFlag) -> ok | {error, Reason}
%% @spec (Socket, Process, SslFlag) -> ok | {error, Reason}
%% Socket = socket()
%% Pid = pid()
%% Process = pid() | atom()
%% SslFlag = boolean()
%% Reason = atom()
%% @doc
%% Sets the controlling proces for the `Socket'.
%% @end
-spec controlling_process(socket(), pid(), boolean()) ->
-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) ->
ssl:controlling_process(Socket, Pid);
controlling_process(Socket, Pid, false) ->
Expand Down
14 changes: 13 additions & 1 deletion src/lhttpc/lhttpc_types.hrl
Expand Up @@ -29,11 +29,23 @@

-type socket() :: _.

-type invalid_option() :: any().

-type partial_download_option() ::
{window_size, window_size()} |
{part_size, non_neg_integer() | infinity} |
invalid_option().

-type option() ::
{connect_timeout, timeout()} |
{send_retry, non_neg_integer()} |
{partial_upload, non_neg_integer() | infinity} |
{partial_download, pid(), non_neg_integer() | infinity}.
{partial_download, [partial_download_option()]} |
{connect_options, socket_options()} |
{proxy, string()} |
{proxy_ssl_options, socket_options()} |
{pool, pid() | atom()} |
invalid_option().

-type options() :: [option()].

Expand Down

0 comments on commit eeff685

Please sign in to comment.