Anonymous avatar Anonymous committed 9d05069

Better, and more central, connection handling.

Comments (0)

Files changed (2)

src/lhttpc_client.erl

 -include("lhttpc_types.hrl").
 
 -record(client_state, {
-        host,
-        port,
-        ssl,
-        request,
+        host :: string(),
+        port = 80 :: integer(),
+        ssl = false :: true | false,
+        request :: iolist(),
+        request_headers :: headers(),
         socket,
-        connect_timeout,
-        attempts
+        connect_timeout = infinity :: timeout(),
+        attempts :: integer()
     }).
 
+-define(CONNECTION_HDR(HDRS, DEFAULT),
+    string:to_lower(lhttpc_lib:header_value("connection", HDRS, DEFAULT))).
+
 -spec request(pid(), string(), string() | atom(), headers(),
         iolist(), [option()]) -> no_return().
 %% @spec (From, URL, Method, Hdrs, Body, Options) -> ok
         port = Port,
         ssl = Ssl,
         request = Request,
+        request_headers = Hdrs,
         socket = Socket,
         connect_timeout = proplists:get_value(connect_timeout, Options,
             infinity),
             read_response(State, Vsn, Status, [Header | Hdrs], Body);
         {ok, http_eoh} ->
             lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl),
-            {NewBody, NewHdrs, NewSocket} = read_body(Vsn, Hdrs, Ssl, Socket),
-            {{Status, NewHdrs, NewBody}, NewSocket};
+            {NewBody, NewHdrs} = read_body(Vsn, Hdrs, Ssl, Socket),
+            Response = {Status, NewHdrs, NewBody},
+            RequestHdrs = State#client_state.request_headers,
+            NewSocket = maybe_close_socket(Socket, Ssl, Vsn, RequestHdrs,
+                NewHdrs),
+            {Response, NewSocket};
         {error, closed} ->
             % Either we only noticed that the socket was closed after we
             % sent the request, the server closed it just after we put
             erlang:error(Reason)
     end.
 
+maybe_close_socket(Socket, Ssl, {1, Minor}, ReqHdrs, RespHdrs) when Minor >= 1->
+    ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"),
+    ServerConnection = ?CONNECTION_HDR(RespHdrs, "keep-alive"),
+    if
+        ClientConnection =:= "close"; ServerConnection =:= "close" ->
+            lhttpc_sock:close(Socket, Ssl),
+            undefined;
+        ClientConnection =/= "close", ServerConnection =/= "close" ->
+            Socket
+    end;
+maybe_close_socket(Socket, Ssl, {0, _}, ReqHdrs, RespHdrs) ->
+    ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"),
+    ServerConnection = ?CONNECTION_HDR(RespHdrs, "close"),
+    if
+        ClientConnection =:= "close"; ServerConnection =/= "keep-alive" ->
+            lhttpc_sock:close(Socket, Ssl),
+            undefined;
+        ClientConnection =/= "close", ServerConnection =:= "keep-alive" ->
+            Socket
+    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
     % * 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).
+    %   closed (AFAIK, 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
 read_length(Hdrs, Ssl, Socket, Length) ->
     case lhttpc_sock:recv(Socket, Length, Ssl) of
         {ok, Data} ->
-            NewSocket = case lhttpc_lib:header_value("connection", Hdrs) of
-                "close" ->
-                    lhttpc_sock:close(Socket, Ssl),
-                    undefined;
-                _ ->
-                    Socket
-            end,
-            {Data, Hdrs, NewSocket};
+            {Data, Hdrs};
         {error, Reason} ->
             erlang:error(Reason)
     end.
                 0 ->
                     Body = list_to_binary(lists:reverse(Chunks)),
                     lhttpc_sock:setopts(Socket, [{packet, httph}], Ssl),
-                    {Body, read_trailers(Socket, Ssl, Hdrs), Socket};
+                    {Body, read_trailers(Socket, Ssl, Hdrs)};
                 Size ->
                     Chunk = read_chunk(Socket, Ssl, Size),
                     read_chunked_body(Socket, Ssl, Hdrs, [Chunk | Chunks])
             erlang:error({bad_trailer, Data})
     end.
 
-read_infinite_body(Socket, {1, 1}, Hdrs, Ssl) ->
-    case lhttpc_lib:header_value("connection", Hdrs) of
+read_infinite_body(Socket, {1, Minor}, Hdrs, Ssl) when Minor >= 1 ->
+    HdrValue = lhttpc_lib:header_value("connection", Hdrs, "keep-alive"),
+    case string:to_lower(HdrValue) of
         "close" -> read_until_closed(Socket, <<>>, Hdrs, Ssl);
         _       -> erlang:error(no_content_length)
     end;
 read_infinite_body(Socket, _, Hdrs, Ssl) ->
-    case lhttpc_lib:header_value("KeepAlive", Hdrs) of
-        undefined -> read_until_closed(Socket, <<>>, Hdrs, Ssl);
-        _         -> erlang:error(no_content_length)
+    HdrValue = lhttpc_lib:header_value("connection", Hdrs, "close"),
+    case string:to_lower(HdrValue) of
+        "close" -> read_until_closed(Socket, <<>>, Hdrs, Ssl);
+        _       -> erlang:error(no_content_length)
     end.
 
 read_until_closed(Socket, Acc, Hdrs, Ssl) ->
             NewAcc = <<Acc/binary, Body/binary>>,
             read_until_closed(Socket, NewAcc, Hdrs, Ssl);
         {error, closed} ->
-            lhttpc_sock:close(Socket, Ssl),
-            {Acc, Hdrs, undefined}; % The socket has been closed
+            {Acc, Hdrs};
         {error, Reason} ->
             erlang:error(Reason)
     end.

src/lhttpc_lib.erl

 %%% @end
 -module(lhttpc_lib).
 
--export([parse_url/1, format_request/5, header_value/2]).
+-export([parse_url/1, format_request/5, header_value/2, header_value/3]).
 -export([maybe_atom_to_list/1]).
 
 -include("lhttpc_types.hrl").
 %% Value = term()
 %% @doc
 %% Returns the value associated with the `Header' in `Headers'.
-%% `Header' must be a lowercase string, since every hader is mangled to
+%% `Header' must be a lowercase string, since every header is mangled to
 %% check the match.
 %% @end
--spec header_value(string(), [{string(), any()}]) -> undefined | string().
+-spec header_value(string(), [{string(), Value}]) -> undefined | Value.
 header_value(Hdr, Hdrs) ->
     header_value(Hdr, Hdrs, undefined).
 
+%% @spec header_value(Header, Headers, Default) -> Default | term()
+%% Header = string()
+%% Headers = [{string(), term()}]
+%% Value = term()
+%% Default = term()
+%% @doc
+%% Returns the value associated with the `Header' in `Headers'.
+%% `Header' must be a lowercase string, since every header is mangled to
+%% check the match.  If no match is found, `Default' is returned.
+%% @end
+-spec header_value(string(), [{string(), Value}], Default) ->
+    Default | Value.
 header_value(Hdr, [{Hdr, Value} | _], _) ->
     Value;
 header_value(Hdr, [{ThisHdr, Value}| Hdrs], Default) ->
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.