Skip to content
Browse files

Initial commit

  • Loading branch information...
0 parents commit b8f2f937b9fd3635dac56bfd01cc245eac02fefd @oscarh oscarh committed Jun 6, 2009
Showing with 1,113 additions and 0 deletions.
  1. +15 −0 .hgignore
  2. +23 −0 LICENCE
  3. +48 −0 Makefile
  4. +6 −0 README
  5. +5 −0 doc/overview.edoc
  6. +39 −0 src/lhttpc.app.src
  7. +126 −0 src/lhttpc.erl
  8. +211 −0 src/lhttpc_client.erl
  9. +173 −0 src/lhttpc_lib.erl
  10. +222 −0 src/lhttpc_manager.erl
  11. +159 −0 src/lhttpc_sock.erl
  12. +57 −0 src/lhttpc_sup.erl
  13. +28 −0 src/lhttpc_types.hrl
  14. +1 −0 vsn.mk
15 .hgignore
@@ -0,0 +1,15 @@
+syntax: glob
+
+
+.args
+.depend
+
+*.sw{p,o}
+
+erl_crash.dump
+
+ebin/{*.beam,lhttpc.app}
+test/*.beam
+doc/{edoc-info,*.{png,css,html}}
+cover_report/*
+
23 LICENCE
@@ -0,0 +1,23 @@
+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
+ 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 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
+ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting 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.
48 Makefile
@@ -0,0 +1,48 @@
+
+
+APPLICATION := lhttpc
+APP_FILE:=ebin/$(APPLICATION).app
+SOURCES:=$(wildcard src/*.erl)
+HEADERS:=$(wildcard src/*.hrl)
+MODULES:=$(patsubst src/%.erl,%,$(SOURCES))
+BEAMS:=$(patsubst %,ebin/%.beam,$(MODULES))
+
+comma := ,
+e :=
+space := $(e) $(e)
+MODULELIST := $(subst $(space),$(comma),$(MODULES))
+
+
+include vsn.mk
+
+.PHONY: all clean dialyzer
+
+all: $(APPLICATION) doc
+
+$(APPLICATION): $(BEAMS) $(APP_FILE)
+
+$(APP_FILE): src/$(APPLICATION).app.src
+ @echo Generating $@
+ @sed -e 's/@MODULES@/$(MODULELIST)/' -e 's/@VSN@/$(VSN)/' $< > $@
+
+ebin/%.beam: src/%.erl $(HEADERS) $(filter-out $(wildcard ebin), ebin)
+ @echo Compiling $<
+ @erlc -o ebin/ $<
+
+ebin:
+ @echo Creating ebin/
+ @mkdir ebin/
+
+doc: doc/edoc-info
+
+dialyzer:
+ @echo Running dialyzer on sources
+ @dialyzer --src -r src/
+
+doc/edoc-info: doc/overview.edoc $(SOURCES)
+ @echo Generating documentation from edoc
+ @erl -noinput -eval 'edoc:application(gen_httpd, "./", [{doc, "doc/"}])' -s erlang halt
+
+clean:
+ @echo Cleaning
+ @rm -f ebin/*.{beam,app} doc/*.{html,css,png} doc/edoc-info
6 README
@@ -0,0 +1,6 @@
+Dependencies:
+* Erlang/OTP R12-B or newer (compiler to build, kernel and stdlib to run)
+* GNU Make (might actually build with some other make as well)
+
+To bulid the application simply run 'make'. This should build .beam, .app
+files and documentation.
5 doc/overview.edoc
@@ -0,0 +1,5 @@
+@author Oscar Hellström <oscar@erlang-consulting.com>
+@doc A lightweight HTTP client.
+The only functions of much interest right now are {@link
+lhttpc:request/4} and {@link lhttpc:request/5}.
+@end
39 src/lhttpc.app.src
@@ -0,0 +1,39 @@
+%%% ----------------------------------------------------------------------------
+%%% 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
+%%% 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 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
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting 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 <oscar@erlang-consulting.com>
+%%% @doc This is the specification for the lhttpc application.
+%%% @end
+{application, lhttpc,
+ [{description, "Lightweight HTTP Client"},
+ {vsn, "@VSN@"},
+ {modules, [@MODULES@]},
+ {registered, [lhttpc_manager]},
+ {applications, [kernel, stdlib]},
+ {mod, {lhttpc, nil}},
+ {env, []}
+ ]}.
+
126 src/lhttpc.erl
@@ -0,0 +1,126 @@
+%%% ----------------------------------------------------------------------------
+%%% 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
+%%% 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 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
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting 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 <oscar@erlang-consulting.com>
+%%% @doc Main interface to the lightweight http client.
+%%% See `request/4' and `request/5' functions.
+%%% @end
+-module(lhttpc).
+-behaviour(application).
+
+-export([request/4, request/5]).
+-export([start/2, stop/1]).
+
+-include("lhttpc_types.hrl").
+
+-type result() :: {ok, {{pos_integer(), string()}, headers(), binary()}} |
+ {error, atom()}.
+
+%% @hidden
+-spec start(normal | {takeover, node()} | {failover, node()}, any()) ->
+ {ok, pid()}.
+start(_, _) ->
+ lhttpc_sup:start_link().
+
+%% @hidden
+-spec stop(any()) -> ok.
+stop(_) ->
+ ok.
+
+%% @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, Body}}
+%% | {error, Reason}
+%% StatusCode = integer()
+%% ReasonPhrase = string()
+%% Body = binary()
+%% @doc Sends a request without a body.
+%% Would be the same as calling `request(URL, Method, Hdrs, [], Timeout)',
+%% that is `request/5' with an empty body (`Body' could also be `<<>>').
+%% @end
+-spec request(string(), string() | atom(), headers(), pos_integer() |
+ infinity) -> result().
+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 = iolist()
+%% Timeout = integer() | infinity
+%% Result = {ok, {{StatusCode, ReasonPhrase}, Hdrs, ResponseBody}}
+%% | {error, Reason}
+%% StatusCode = integer()
+%% ReasonPhrase = string()
+%% ResponseBody = binary()
+%% @doc Sends a request with a body.
+%% `URL' is expected to be a valid URL:
+%% `scheme://host[:port][/path]'.
+%% `Method' is either a string, stating the HTTP method exactly as in the
+%% protocol, i.e: `"POST"' or `"GET"'. It could also be an atom, which is
+%% then made in to uppercase, if it isn't already.
+%% `Hdrs' is a list of headers to send. Mandatory headers such as
+%% `Host' or `Content-Length' (for some requests) are added.
+%% `Body' is the entity to send in the request. Please don't include entity
+%% bodies where there shouldn't be any (such as for `GET').
+%% `Timeout' is the timeout for the request in milliseconds.
+%% @end
+-spec request(string(), string() | atom(), headers(), iolist(),
+ pos_integer() | infinity) -> result().
+request(URL, Method, Hdrs, Body, Timeout) ->
+ Args = [self(), URL, Method, Hdrs, Body],
+ 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 taps exits
+ erlang:error(Reason)
+ after Timeout ->
+ kill_client(Pid)
+ end.
+
+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, timeout} ->
+ {error, timeout};
+ {'DOWN', _, process, Pid, Reason} ->
+ erlang:error(Reason)
+ end.
211 src/lhttpc_client.erl
@@ -0,0 +1,211 @@
+%%% ----------------------------------------------------------------------------
+%%% 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
+%%% 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 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
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting 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.
+%%% ----------------------------------------------------------------------------
+
+%%% @private
+%%% @author Oscar Hellström <oscar@erlang-consulting.com>
+%%% @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/5]).
+
+-include("lhttpc_types.hrl").
+
+-spec request(pid(), string(), string() | atom(), headers(), iolist()) ->
+ no_return().
+%% @spec (From, URL, Method, Hdrs, Body) -> void()
+%% From = pid()
+%% URL = string()
+%% Method = atom() | string()
+%% Hdrs = [Header]
+%% Header = {string() | atom(), string()}
+%% Body = iolist()
+request(From, URL, Method, Hdrs, Body) ->
+ case catch execute(From, URL, Method, Hdrs, Body) of
+ {'EXIT', Reason} -> exit(Reason);
+ _ -> exit(normal) % this should never return
+ end.
+
+execute(From, URL, Method, Hdrs, Body) ->
+ {Host, Port, Path, Ssl} = lhttpc_lib:parse_url(URL),
+ Request = lhttpc_lib:format_request(Path, Method, Hdrs, Host, Body),
+ SocketRequest = {socket, self(), Host, Port, Ssl},
+ Socket = case gen_server:call(lhttpc_manager, SocketRequest, infinity) of
+ {ok, S} -> S; % Re-using HTTP/1.1 connections
+ no_socket -> undefined % Opening a new HTTP/1.1 connection
+ end,
+ Response = case send_request(Host, Port, Ssl, Request, Socket, 1) of
+ {ok, R, undefined} ->
+ {ok, R};
+ {ok, 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.
+ ManagerPid = whereis(lhttpc_manager),
+ % If this fails, we're still the owner and the socket is closed
+ % when we exit, which is fine.
+ lhttpc_sock:controlling_process(NewSocket, ManagerPid, Ssl),
+ gen_server:cast(lhttpc_manager, {done, Host, Port, Ssl, NewSocket}),
+ {ok, R};
+ {error, Reason} ->
+ {error, Reason}
+ end,
+ From ! {response, self(), Response},
+ ok.
+
+send_request(_, _, _, _, _, 3) ->
+ % Only do two attempts to connect to the server and send the request,
+ % if it closes the connection on us twice, something is very wrong.
+ {error, connection_closed};
+send_request(Host, Port, Ssl, Request, undefined, Attempt) ->
+ Options = [binary, {packet, http}, {active, false}],
+ case lhttpc_sock:connect(Host, Port, Options, Ssl) of
+ {ok, Socket} ->
+ send_request(Host, Port, Ssl, Request, Socket, Attempt);
+ {error, etimedout} ->
+ % Connect timed out (the TCP stack decided so), try again.
+ % Notice, no attempt to actually send the data has been made
+ % here.
+ send_request(Host, Port, Ssl, Request, undefined, Attempt);
+ {error, Reason} ->
+ {error, Reason}
+ end;
+send_request(Host, Port, Ssl, Request, Socket, Attempt) ->
+ case lhttpc_sock:send(Socket, Request, Ssl) of
+ ok ->
+ Acc = {nil, nil, [], <<>>},
+ lhttpc_sock:setopts(Socket, [{packet, http}], Ssl),
+ case read_response(Host, Port, Ssl, Request, Acc, Socket, Attempt) of
+ {ok, Response, NewSocket} ->
+ {ok, Response, NewSocket};
+ {error, Reason} ->
+ lhttpc_sock:close(Socket, Ssl),
+ {error, Reason}
+ end;
+ {error, closed} ->
+ lhttpc_sock:close(Socket, Ssl),
+ send_request(Host, Port, Ssl, Request, undefined, Attempt + 1);
+ Other ->
+ lhttpc_sock:close(Socket, Ssl),
+ Other
+ end.
+
+read_response(Host, Port, Ssl, Request, Acc, Socket, Attempt) ->
+ {Vsn, Status, Hdrs, Body} = Acc,
+ case lhttpc_sock:read(Socket, Ssl) of
+ {ok, {http_response, NewVsn, StatusCode, Reason}} ->
+ NewStatus = {StatusCode, Reason},
+ NewAcc = {NewVsn, NewStatus, Hdrs, Body},
+ read_response(Host, Port, Ssl, Request, NewAcc, Socket, Attempt);
+ {ok, {http_header, _, Name, _, Value}} ->
+ Header = {lhttpc_lib:maybe_atom_to_list(Name), Value},
+ NewAcc = {Vsn, Status, [Header | Hdrs], Body},
+ read_response(Host, Port, Ssl, Request, NewAcc, Socket, Attempt);
+ {ok, http_eoh} ->
+ lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl),
+ case read_body(Vsn, Hdrs, Ssl, Socket) of
+ {ok, NewBody} ->
+ {ok, {Status, Hdrs, NewBody}, Socket};
+ {ok, NewBody, NewSocket} ->
+ {ok, {Status, Hdrs, NewBody}, NewSocket};
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ {error, closed} ->
+ % 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),
+ send_request(Host, Port, Ssl, Request, Socket, Attempt + 1);
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+read_body(Vsn, Hdrs, Ssl, Socket) ->
+ % Find out how to read the entity body from the request.
+ % * If we have a Content-Length, just use that and read the complete
+ % entity.
+ % * If Transfer-Encoding is set to chunked, we should read one chunk at
+ % the time
+ % * If neither of this is true, we need to read until the socket is
+ % closed (this was common in versions before 1.1).
+ case lhttpc_lib:header_value("content-length", Hdrs) of
+ undefined ->
+ case lhttpc_lib:header_value("transfer-encoding", Hdrs) of
+ "chunked" -> read_chunked_body(Socket, Ssl);
+ undefined -> read_infinite_body(Socket, Vsn, Hdrs, Ssl)
+ end;
+ ContentLength ->
+ read_length(Hdrs, Ssl, Socket, list_to_integer(ContentLength))
+ end.
+
+read_length(Hdrs, Ssl, Socket, Length) ->
+ Response = lhttpc_sock:read(Socket, Length, Ssl),
+ case Response of
+ {ok, Data} ->
+ NewSocket = case lhttpc_lib:header_value("connection", Hdrs) of
+ "close" ->
+ lhttpc_sock:close(Socket, Ssl),
+ undefined;
+ _ ->
+ Socket
+ end,
+ {ok, Data, NewSocket};
+ {error, closed} ->
+ {error, connection_closed};
+ Other ->
+ Other
+ end.
+
+read_chunked_body(_, _) ->
+ % TODO: Implement chunked reading
+ erlang:error(not_imlemented).
+
+read_infinite_body(Socket, {1, 1}, Hdrs, Ssl) ->
+ case lhttpc_lib:header_value("connection", Hdrs) of
+ "close" -> read_until_closed(Socket, <<>>, Ssl);
+ _ -> {error, bad_response}
+ end;
+read_infinite_body(Socket, _, Hdrs, Ssl) ->
+ case lhttpc_lib:header_value("KeepAlive", Hdrs) of
+ undefined -> read_until_closed(Socket, <<>>, Ssl);
+ _ -> {error, bad_response}
+ end.
+
+read_until_closed(Socket, Acc, Ssl) ->
+ case lhttpc_sock:read(Socket, Ssl) of
+ {ok, Body} ->
+ NewAcc = <<Acc/binary, Body/binary>>,
+ read_until_closed(Socket, NewAcc, Ssl);
+ {error, closed} ->
+ lhttpc_sock:close(Socket, Ssl),
+ {ok, Acc};
+ Other ->
+ Other
+ end.
173 src/lhttpc_lib.erl
@@ -0,0 +1,173 @@
+%%% ----------------------------------------------------------------------------
+%%% 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
+%%% 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 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
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting 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.
+%%% ----------------------------------------------------------------------------
+
+%%% @private
+%%% @author Oscar Hellström <oscar@erlang-consulting.com>
+%%% @doc
+%%% This module implements various library functions used in lhttpc.
+%%% @end
+-module(lhttpc_lib).
+
+-export([parse_url/1, format_request/5, header_value/2]).
+-export([maybe_atom_to_list/1]).
+
+-include("lhttpc_types.hrl").
+
+%% @spec header_value(Header, Headers) -> undefined | term()
+%% Header = string()
+%% Headers = [{string(), term()}]
+%% Value = term()
+%% @doc
+%% Returns the value associated with the `Header' in `Headers'.
+%% `Header' must be a lowercase string, since every hader is mangled to
+%% check the match.
+%% @end
+-spec header_value(string(), [{string(), any()}]) -> undefined | string().
+header_value(Hdr, Hdrs) ->
+ header_value(Hdr, Hdrs, undefined).
+
+header_value(Hdr, [{Hdr, Value} | _], _) ->
+ Value;
+header_value(Hdr, [{ThisHdr, Value}| Hdrs], Default) ->
+ case string:equal(string:to_lower(ThisHdr), Hdr) of
+ true -> Value;
+ false -> header_value(Hdr, Hdrs, Default)
+ end;
+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.
+%% @end
+-spec maybe_atom_to_list(atom() | list()) -> list().
+maybe_atom_to_list(Atom) when is_atom(Atom) ->
+ atom_to_list(Atom);
+maybe_atom_to_list(List) when is_list(List) ->
+ List.
+
+%% @spec (URL) -> {Host, Port, Path, Ssl}
+%% URL = string()
+%% Host = string()
+%% Port = integer()
+%% Path = string()
+%% Ssl = bool()
+%% @doc
+-spec parse_url(string()) -> {string(), integer(), string(), bool()}.
+parse_url(URL) ->
+ % XXX This should be possible to do with the re module?
+ {Scheme, HostPortPath} = split_scheme(URL),
+ {Host, PortPath} = split_host(HostPortPath, []),
+ {Port, Path} = split_port(Scheme, PortPath, []),
+ {string:to_lower(Host), Port, Path, Scheme =:= https}.
+
+split_scheme("http://" ++ HostPortPath) ->
+ {http, HostPortPath};
+split_scheme("https://" ++ HostPortPath) ->
+ {https, HostPortPath}.
+
+split_host([$: | PortPath], Host) ->
+ {lists:reverse(Host), PortPath};
+split_host([$/ | _] = PortPath, Host) ->
+ {lists:reverse(Host), PortPath};
+split_host([H | T], Host) ->
+ split_host(T, [H | Host]);
+split_host([], Host) ->
+ {lists:reverse(Host), []}.
+
+split_port(http, [$/ | _] = Path, []) ->
+ {80, Path};
+split_port(https, [$/ | _] = Path, []) ->
+ {443, Path};
+split_port(http, [], []) ->
+ {80, "/"};
+split_port(https, [], []) ->
+ {443, "/"};
+split_port(_, [], Port) ->
+ {list_to_integer(lists:reverse(Port)), "/"};
+split_port(_,[$/ | _] = Path, Port) ->
+ {list_to_integer(lists:reverse(Port)), Path};
+split_port(Scheme, [P | T], Port) ->
+ split_port(Scheme, T, [P | Port]).
+
+%% @spec (Path, Method, Headers, Host, Body) -> Request
+%% Path = iolist()
+%% Method = atom() | string()
+%% Headers = [{atom() | string(), string()}]
+%% Host = string()
+%% Body = iolist()
+-spec format_request(iolist(), atom() | string(), headers(), string(),
+ iolist()) -> iolist().
+format_request(Path, Method, Hdrs, Host, Body) ->
+ FormatedMethod = format_method(Method),
+ [
+ FormatedMethod, " ", Path, " HTTP/1.1\r\n",
+ format_hdrs(add_mandatory_hdrs(FormatedMethod, Hdrs, Host, Body), []),
+ Body
+ ].
+
+format_method(Method) when is_atom(Method) ->
+ string:to_upper(atom_to_list(Method));
+format_method(Method) ->
+ Method.
+
+format_hdrs([{Hdr, Value} | T], Acc) ->
+ NewAcc = [
+ maybe_atom_to_list(Hdr), ":", maybe_atom_to_list(Value), "\r\n" | Acc
+ ],
+ format_hdrs(T, NewAcc);
+format_hdrs([], Acc) ->
+ [Acc, "\r\n"].
+
+add_mandatory_hdrs(Method, Hdrs, Host, Body) ->
+ add_host(add_content_length(Method, Hdrs, Body), Host).
+
+add_content_length("POST", Hdrs, Body) ->
+ add_content_length(Hdrs, Body);
+add_content_length("PUT", Hdrs, Body) ->
+ add_content_length(Hdrs, Body);
+add_content_length(_, Hdrs, _) ->
+ Hdrs.
+
+add_content_length(Hdrs, Body) ->
+ case header_value("content-length", Hdrs) of
+ undefined ->
+ ContentLength = integer_to_list(iolist_size(Body)),
+ [{"Content-Length", ContentLength} | Hdrs];
+ _ -> % We have a content length
+ Hdrs
+ end.
+
+add_host(Hdrs, Host) ->
+ case header_value("host", Hdrs) of
+ undefined ->
+ [{"Host", Host } | Hdrs];
+ _ -> % We have a host
+ Hdrs
+ end.
222 src/lhttpc_manager.erl
@@ -0,0 +1,222 @@
+%%% ----------------------------------------------------------------------------
+%%% 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
+%%% 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 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
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting 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 <oscar@erlang-consulting.com>
+%%% @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}.
+%%% @end
+-module(lhttpc_manager).
+
+-export([start_link/0, connection_count/0, connection_count/1]).
+-export([
+ init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ code_change/3,
+ terminate/2
+ ]).
+
+-behaviour(gen_server).
+
+-record(httpc_man, {destinations = dict:new(), sockets = dict:new()}).
+
+%% @spec () -> Count
+%% Count = integer()
+%% @doc Returns the total number of active connections maintained by the
+%% httpc manager.
+%% @end
+-spec connection_count() -> non_neg_integer().
+connection_count() ->
+ gen_server:call(?MODULE, connection_count).
+
+%% @spec (Destination) -> Count
+%% Destination = {Host, Port, Ssl}
+%% Host = string()
+%% Port = integer()
+%% Ssl = bool()
+%% Count = integer()
+%% @doc Returns the number of active connections to the specific
+%% `Destination' maintained by the httpc manager.
+%% @end
+-spec connection_count({string(), pos_integer(), bool()}) ->
+ non_neg_integer().
+connection_count({Host, Port, Ssl}) ->
+ Destination = {string:to_lower(Host), Port, Ssl},
+ gen_server:call(?MODULE, {connection_count, Destination}).
+
+%% @spec () -> {ok, pid()}
+%% @doc Starts and link to the gen server.
+%% This is normally called by a supervisor.
+%% @end
+-spec start_link() -> {ok, pid()} | {error, allready_started}.
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, nil, []).
+
+%% @hidden
+-spec init(any()) -> {ok, #httpc_man{}}.
+init(_) ->
+ process_flag(priority, high),
+ {ok, #httpc_man{}}.
+
+%% @hidden
+-spec handle_call(any(), any(), #httpc_man{}) ->
+ {reply, any(), #httpc_man{}}.
+handle_call({socket, Pid, Host, Port, Ssl}, _, State) ->
+ {Reply, NewState} = find_socket({Host, Port, Ssl}, Pid, State),
+ {reply, Reply, NewState};
+handle_call(connection_count, _, State) ->
+ {reply, dict:size(State#httpc_man.sockets), State};
+handle_call({connection_count, Destination}, _, State) ->
+ Count = case dict:find(Destination, State#httpc_man.destinations) of
+ {ok, Sockets} -> length(Sockets);
+ error -> 0
+ end,
+ {reply, Count, State};
+handle_call(_, _, State) ->
+ {reply, {error, unknown_request}, State}.
+
+%% @hidden
+-spec handle_cast(any(), #httpc_man{}) -> {noreply, #httpc_man{}}.
+handle_cast({done, Host, Port, Ssl, Socket}, State) ->
+ NewState = store_socket({Host, Port, Ssl}, Socket, State),
+ {noreply, NewState};
+handle_cast(_, State) ->
+ {noreply, State}.
+
+%% @hidden
+-spec handle_info(any(), #httpc_man{}) -> {noreply, #httpc_man{}}.
+handle_info({tcp_closed, Socket}, State) ->
+ {noreply, remove_socket(Socket, State)};
+handle_info({ssl_closed, Socket}, State) ->
+ {noreply, remove_socket(Socket, State)};
+handle_info({timeout, Socket}, State) ->
+ {noreply, remove_socket(Socket, State)};
+handle_info({tcp_error, Socket, _}, State) ->
+ {noreply, remove_socket(Socket, State)};
+handle_info({ssl_error, Socket, _}, State) ->
+ {noreply, remove_socket(Socket, State)};
+handle_info({tcp, Socket, _}, State) ->
+ {noreply, remove_socket(Socket, State)}; % got garbage
+handle_info({ssl, Socket, _}, State) ->
+ {noreply, remove_socket(Socket, State)}; % got garbage
+handle_info(_, State) ->
+ {noreply, State}.
+
+%% @hidden
+-spec terminate(any(), #httpc_man{}) -> ok.
+terminate(_, State) ->
+ close_sockets(State#httpc_man.sockets).
+
+%% @hidden
+-spec code_change(any(), #httpc_man{}, any()) -> #httpc_man{}.
+code_change(_, State, _) ->
+ State.
+
+find_socket({_, _, Ssl} = Destination, Pid, State) ->
+ Destinations = State#httpc_man.destinations,
+ case dict:find(Destination, Destinations) of
+ {ok, [Socket | Sockets]} ->
+ lhttpc_sock:setopts(Socket, [{active, false}], Ssl),
+ case lhttpc_sock:controlling_process(Socket, Pid, Ssl) of
+ ok ->
+ {_, Timer} = dict:fetch(Socket, State#httpc_man.sockets),
+ cancel_timer(Timer, Sockets),
+ NewState = State#httpc_man{
+ destinations = dict:store(Destination, Sockets,
+ Destinations),
+ sockets = dict:erase(Socket,
+ State#httpc_man.sockets)
+ },
+ {{ok, Socket}, NewState};
+ _ -> % Pid has timed out, reuse for someone else
+ lhttpc_sock:setopts(Socket, [{active, true}], Ssl),
+ {no_socket, State}
+ end;
+ {ok, []} ->
+ {no_socket, State};
+ error ->
+ {no_socket, State}
+ end.
+
+remove_socket(Socket, State) ->
+ Destinations = State#httpc_man.destinations,
+ case dict:find(Socket, State#httpc_man.sockets) of
+ {ok, {{_, _, Ssl} = Destination, Timer}} ->
+ cancel_timer(Timer, Socket),
+ lhttpc_sock:close(Socket, Ssl),
+ OldSockets = dict:fetch(Destination, Destinations),
+ NewDestinations = case lists:delete(Socket, OldSockets) of
+ [] -> dict:erase(Destination, Destinations);
+ Sockets -> dict:store(Destination, Sockets, Destinations)
+ end,
+ State#httpc_man{
+ destinations = NewDestinations,
+ sockets = dict:erase(Socket, State#httpc_man.sockets)
+ };
+ error ->
+ State
+ end.
+
+store_socket({_, _, Ssl} = Destination, Socket, State) ->
+ % we want to time out on the socket, should the time be an option?
+ Timer = erlang:send_after(300000, self(), {timeout, Socket}),
+ % the socket might be closed from the other side
+ lhttpc_sock:setopts(Socket, [{active, once}], Ssl),
+ Destinations = State#httpc_man.destinations,
+ Sockets = case dict:find(Destination, Destinations) of
+ {ok, S} ->
+ S;
+ error ->
+ []
+ end,
+ State#httpc_man{
+ destinations = dict:store(Destination, [Socket | Sockets],
+ Destinations),
+ sockets = dict:store(Socket, {Destination, Timer},
+ State#httpc_man.sockets)
+ }.
+
+close_sockets(Sockets) ->
+ lists:foreach(fun({Socket, {{_, _, Ssl}, Timer}}) ->
+ lhttpc_sock:close(Socket, Ssl),
+ erlang:cancel_timer(Timer)
+ end, dict:to_list(Sockets)).
+
+cancel_timer(Timer, Socket) ->
+ case erlang:cancel_timer(Timer) of
+ false ->
+ receive
+ {timeout, Socket} -> ok
+ after
+ 0 -> ok
+ end;
+ _ -> ok
+ end.
159 src/lhttpc_sock.erl
@@ -0,0 +1,159 @@
+%%% ----------------------------------------------------------------------------
+%%% 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
+%%% 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 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
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting 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.
+%%% ----------------------------------------------------------------------------
+
+%%% @private
+%%% @author Oscar Hellström <oscar@erlang-consulting.com>
+%%% @doc
+%%% This module implements wrappers for socket operations.
+%%% Makes it possible to have the same interface to ssl and tcp sockets.
+%%% @end
+-module(lhttpc_sock).
+
+-export([
+ connect/4,
+ read/2,
+ read/3,
+ send/3,
+ controlling_process/3,
+ setopts/3,
+ close/2
+ ]).
+
+-type host() :: string() | {integer(), integer(), integer(), integer()}.
+-type options() :: [atom() | {atom(), any()}].
+
+%% @spec (Host, Port, Options, SslFlag) -> {ok, Socket} | {error, Reason}
+%% Host = string() | ip_address()
+%% Port = integer()
+%% Options = [{atom(), term()} | atom()]
+%% SslFlag = bool()
+%% Socket = port()
+%% Reason = atom()
+%% @doc
+%% Connects to `Host' and `Port'.
+%% Will use the `ssl' module if `SslFlag' is `true' and gen_tcp otherwise.
+%% `Options' are the normal `gen_tcp' or `ssl' Options.
+%% @end
+-spec connect(host(), integer(), options(), bool()) ->
+ {ok, port()} | {error, atom()}.
+connect(Host, Port, Options, true) ->
+ ssl:connect(Host, Port, Options);
+connect(Host, Port, Options, false) ->
+ gen_tcp:connect(Host, Port, Options).
+
+%% @spec (Socket, SslFlag) -> {ok, Data} | {error, Reason}
+%% Socket = port()
+%% Length = integer()
+%% SslFlag = bool()
+%% Data = term()
+%% Reason = atom()
+%% @doc
+%% Reads available bytes from `Socket'.
+%% Will block untill data is available on the socket and return the first
+%% packet.
+%% @end
+-spec read(port(), bool()) -> {ok, any()} | {error, atom()}.
+read(Socket, true) ->
+ ssl:recv(Socket, 0);
+read(Socket, false) ->
+ gen_tcp:recv(Socket, 0).
+
+%% @spec (Socket, Length, SslFlag) -> {ok, Data} | {error, Reason}
+%% Socket = port()
+%% Length = integer()
+%% SslFlag = bool()
+%% Data = term()
+%% Reason = atom()
+%% @doc
+%% Reads `Length' bytes from `Socket'.
+%% Will block untill `Length' bytes is available.
+%% @end
+-spec read(port(), integer(), bool()) -> {ok, any()} | {error, atom()}.
+read(_, 0, _) ->
+ {ok, <<>>};
+read(Socket, Length, true) ->
+ ssl:recv(Socket, Length);
+read(Socket, Length, false) ->
+ gen_tcp:recv(Socket, Length).
+
+%% @spec (Socket, Data, SslFlag) -> ok | {error, Reason}
+%% Socket = port()
+%% Data = iolist()
+%% SslFlag = bool()
+%% Reason = atom()
+%% @doc
+%% Sends data on a socket.
+%% Will use the `ssl' module if `SslFlag' is set to `true', otherwise the
+%% gen_tcp module.
+%% @end
+-spec send(port(), iolist(), bool()) -> ok | {error, atom()}.
+send(Socket, Request, true) ->
+ ssl:send(Socket, Request);
+send(Socket, Request, false) ->
+ gen_tcp:send(Socket, Request).
+
+%% @spec (Socket, Pid, SslFlag) -> ok | {error, Reason}
+%% Socket = port()
+%% Pid = pid()
+%% SslFlag = bool()
+%% Reason = atom()
+%% @doc
+%% Sets the controlling proces for the `Socket'.
+%% @end
+-spec controlling_process(port(), pid(), bool()) ->
+ ok | {error, atom()}.
+controlling_process(Socket, Pid, true) ->
+ ssl:controlling_process(Socket, Pid);
+controlling_process(Socket, Pid, false) ->
+ gen_tcp:controlling_process(Socket, Pid).
+
+%% @spec (Socket, Options, SslFlag) -> ok | {error, Reason}
+%% Socket = port()
+%% Options = [atom() | {atom(), term()}]
+%% SslFlag = bool()
+%% Reason = atom()
+%% @doc
+%% Sets options for a socket. Look in `inet:setopts/2' for more info.
+%% @end
+-spec setopts(port(), options(), bool()) ->
+ ok | {error, atom()}.
+setopts(Socket, Options, true) ->
+ ssl:setopts(Socket, Options);
+setopts(Socket, Options, false) ->
+ inet:setopts(Socket, Options).
+
+%% @spec (Socket, SslFlag) -> ok | {error, Reason}
+%% Socket = port()
+%% SslFlag = bool()
+%% Reason = atom()
+%% @doc
+%% Closes a socket.
+%% @end
+-spec close(port(), bool()) -> ok | {error, atom()}.
+close(Socket, true) ->
+ ssl:close(Socket);
+close(Socket, false) ->
+ gen_tcp:close(Socket).
57 src/lhttpc_sup.erl
@@ -0,0 +1,57 @@
+%%% ----------------------------------------------------------------------------
+%%% 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
+%%% 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 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
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting 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 <oscar@erlang-consulting.com>
+%%% @doc Top supervisor for the lhttpc application.
+%%% This is normally started by the application behaviour implemented in
+%%% {@link lhttpc}.
+%%% @end
+-module(lhttpc_sup).
+-behaviour(supervisor).
+
+-export([start_link/0]).
+-export([init/1]).
+
+-type child() :: {atom(), {atom(), atom(), list(any)},
+ atom(), integer(), atom(), list(atom())}.
+
+%% @spec () -> {ok, pid()} | {error, Reason}
+%% Reason = atom()
+%% @doc Starts and links to the supervisor.
+%% This is normally called from an application behaviour or from another
+%% supervisor.
+%% @end
+-spec start_link() -> {ok, pid()} | {error, atom()}.
+start_link() ->
+ supervisor:start_link(?MODULE, nil).
+
+%% @hidden
+-spec init(any()) -> {ok, {{atom(), integer(), integer()}, [child()]}}.
+init(_) ->
+ LHTTPCManager = {lhttpc_manager, {lhttpc_manager, start_link, []},
+ permanent, 10000, worker, [lhttpc_manager]
+ },
+ {ok, {{one_for_one, 10, 1}, [LHTTPCManager]}}.
28 src/lhttpc_types.hrl
@@ -0,0 +1,28 @@
+%%% ----------------------------------------------------------------------------
+%%% 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
+%%% 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 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
+%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting 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.
+%%% ----------------------------------------------------------------------------
+
+-type header() :: {string() | atom(), string()}.
+-type headers() :: [header()].
1 vsn.mk
@@ -0,0 +1 @@
+VSN=1.0.0

0 comments on commit b8f2f93

Please sign in to comment.
Something went wrong with that request. Please try again.