Commits

Oscar Hellström  committed 95dfcd4

Initial commit

  • Participants
  • Tags 1.0.0

Comments (0)

Files changed (14)

+syntax: glob
+
+
+.args
+.depend
+
+*.sw{p,o}
+
+erl_crash.dump
+
+ebin/{*.beam,lhttpc.app}
+test/*.beam
+doc/{edoc-info,*.{png,css,html}}
+cover_report/*
+
+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.
+
+
+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
+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.

File doc/overview.edoc

+@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

File src/lhttpc.app.src

+%%% ----------------------------------------------------------------------------
+%%% 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, []}
+ ]}.
+

File src/lhttpc.erl

+%%% ----------------------------------------------------------------------------
+%%% 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.

File src/lhttpc_client.erl

+%%% ----------------------------------------------------------------------------
+%%% 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.

File src/lhttpc_lib.erl

+%%% ----------------------------------------------------------------------------
+%%% 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.

File src/lhttpc_manager.erl

+%%% ----------------------------------------------------------------------------
+%%% 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.

File src/lhttpc_sock.erl

+%%% ----------------------------------------------------------------------------
+%%% 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).

File src/lhttpc_sup.erl

+%%% ----------------------------------------------------------------------------
+%%% 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]}}.

File src/lhttpc_types.hrl

+%%% ----------------------------------------------------------------------------
+%%% 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()].
+VSN=1.0.0