diff --git a/src/fusco.erl b/src/fusco.erl index ee7e2cc..d21b64e 100644 --- a/src/fusco.erl +++ b/src/fusco.erl @@ -67,7 +67,8 @@ host_header, out_timestamp, in_timestamp, - on_connect + on_connect, + recv_timeout = 'infinity' :: timeout() }). %%============================================================================== @@ -188,12 +189,7 @@ request(Client, Path, Method, Hdrs, Body, Timeout) -> %%------------------------------------------------------------------------------ -spec request(pid(), string(), method(), headers(), iodata(), integer(), pos_timeout()) -> result(). request(Client, Path, Method, Hdrs, Body, SendRetry, Timeout) when is_binary(Path) -> - try - gen_server:call(Client, {request, Path, Method, Hdrs, Body, SendRetry}, Timeout) - catch - exit:{timeout, _} -> - {error, timeout} - end; + gen_server:call(Client, {request, Path, Method, Hdrs, Body, SendRetry, Timeout}, infinity); request(_, _, _, _, _, _, _) -> {error, badarg}. @@ -250,7 +246,7 @@ handle_call(connect, _From, #client_state{socket = undefined} = State) -> end; handle_call(connect, _From, State) -> {reply, ok, State}; -handle_call({request, Path, Method, Hdrs, Body, SendRetry}, From, +handle_call({request, Path, Method, Hdrs, Body, SendRetry, Timeout}, From, State = #client_state{host_header = Host, use_cookies = UseCookies}) -> Cookies = delete_expired_cookies(State), @@ -260,7 +256,8 @@ handle_call({request, Path, Method, Hdrs, Body, SendRetry}, From, request = Request, requester = From, connection_header = ConHeader, - attempts = SendRetry + 1}). + attempts = SendRetry + 1, + recv_timeout = Timeout}). %%-------------------------------------------------------------------- %% @private @@ -341,11 +338,13 @@ send_request(#client_state{socket = undefined} = State) -> {reply, Error, NewState} end; send_request(#client_state{socket = Socket, ssl = Ssl, request = Request, - attempts = Attempts} = State) -> + attempts = Attempts, recv_timeout = RecvTimeout} = State) -> Out = os:timestamp(), + %If we have a timeout set then we need to ensure a timeout on sending too + fusco_sock:setopts(Socket, [{send_timeout, RecvTimeout}, {send_timeout_close, true}], Ssl), case fusco_sock:send(Socket, Request, Ssl) of ok -> - read_response(State#client_state{out_timestamp = Out}); + read_response(State#client_state{out_timestamp = Out}); {error, closed} -> fusco_sock:close(Socket, Ssl), send_request(State#client_state{socket = undefined, attempts = Attempts - 1}); @@ -365,10 +364,10 @@ request_first_destination(#client_state{host = Host, port = Port, ssl = Ssl}) -> %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -read_proxy_connect_response(State) -> +read_proxy_connect_response(#client_state{recv_timeout = RecvTimeout} = State) -> Socket = State#client_state.socket, ProxyIsSsl = (State#client_state.proxy)#fusco_url.is_ssl, - case fusco_protocol:recv(Socket, ProxyIsSsl) of + case fusco_protocol:recv(Socket, ProxyIsSsl, RecvTimeout) of #response{status_code = <<$1,_,_>>} -> %% RFC 2616, section 10.1: %% A client MUST be prepared to accept one or more @@ -406,8 +405,9 @@ read_proxy_connect_response(State) -> -spec read_response(#client_state{}) -> {any(), socket()} | no_return(). read_response(#client_state{socket = Socket, ssl = Ssl, use_cookies = UseCookies, connection_header = ConHdr, cookies = Cookies, - requester = From, out_timestamp = Out, attempts = Attempts} = State) -> - case fusco_protocol:recv(Socket, Ssl) of + requester = From, out_timestamp = Out, attempts = Attempts, + recv_timeout = RecvTimeout} = State) -> + case fusco_protocol:recv(Socket, Ssl, RecvTimeout) of #response{status_code = <<$1,_,_>>} -> %% RFC 2616, section 10.1: %% A client MUST be prepared to accept one or more diff --git a/src/fusco_protocol.erl b/src/fusco_protocol.erl index 4e52a4e..d30baf3 100644 --- a/src/fusco_protocol.erl +++ b/src/fusco_protocol.erl @@ -17,283 +17,292 @@ %% Latency is here defined as the time from the start of packet transmission to the start of packet reception %% API --export([recv/2, +-export([recv/2, recv/3, decode_cookie/1]). %% TEST --export([decode_header_value/5, - decode_header/3]). +-export([decode_header_value/5, decode_header_value/6, + decode_header/3, decode_header/4]). %% TODO handle partial downloads recv(Socket, Ssl) -> - case fusco_sock:recv(Socket, Ssl) of + recv(Socket, Ssl, infinity). + +recv(Socket, Ssl, Timeout) -> + case fusco_sock:recv(Socket, Ssl, Timeout) of {ok, Data} -> decode_status_line(<< Data/binary >>, - ?RECEPTION(Data, #response{socket = Socket, ssl = Ssl})); + ?RECEPTION(Data, #response{socket = Socket, ssl = Ssl}), Timeout); {error, Reason} -> {error, Reason} end. -decode_status_line(<<"HTTP/1.0\s",C1,C2,C3,$\s,Rest/bits>>, Response) -> +decode_status_line(<<"HTTP/1.0\s",C1,C2,C3,$\s,Rest/bits>>, Response, Timeout) -> decode_reason_phrase(Rest, <<>>, Response#response{version = {1,0}, - status_code = <>}); -decode_status_line(<<"HTTP/1.1\s",C1,C2,C3,$\s,Rest/bits>>, Response) -> + status_code = <>}, Timeout); +decode_status_line(<<"HTTP/1.1\s",C1,C2,C3,$\s,Rest/bits>>, Response, Timeout) -> decode_reason_phrase(Rest, <<>>, Response#response{version = {1,1}, - status_code = <>}); -decode_status_line(Bin, Response = #response{size = Size}) when Size < 13 -> - case fusco_sock:recv(Response#response.socket, Response#response.ssl) of + status_code = <>}, Timeout); +decode_status_line(Bin, Response = #response{size = Size}, Timeout) when Size < 13 -> + case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of {ok, Data} -> - decode_status_line(<>, ?SIZE(Data, Response)); + decode_status_line(<>, ?SIZE(Data, Response), Timeout); {error, Reason} -> {error, Reason} end; -decode_status_line(_, _) -> +decode_status_line(_, _, _) -> {error, status_line}. -decode_reason_phrase(<<>>, Acc, Response) -> - case fusco_sock:recv(Response#response.socket, Response#response.ssl) of +decode_reason_phrase(<<>>, Acc, Response, Timeout) -> + case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of {ok, Data} -> - decode_reason_phrase(Data, Acc, ?SIZE(Data, Response)); + decode_reason_phrase(Data, Acc, ?SIZE(Data, Response), Timeout); {error, Reason} -> {error, Reason} end; -decode_reason_phrase(<<$\r>>, Acc, Response) -> - case fusco_sock:recv(Response#response.socket, Response#response.ssl) of +decode_reason_phrase(<<$\r>>, Acc, Response, Timeout) -> + case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of {ok, Data} -> - decode_reason_phrase(<<$\r, Data/binary>>, Acc, ?SIZE(Data, Response)); + decode_reason_phrase(<<$\r, Data/binary>>, Acc, ?SIZE(Data, Response), Timeout); {error, Reason} -> {error, Reason} end; -decode_reason_phrase(<<$\n, Rest/bits>>, Acc, Response) -> - decode_header(Rest, <<>>, Response#response{reason = Acc}); -decode_reason_phrase(<<$\r,$\n, Rest/bits>>, Acc, Response) -> - decode_header(Rest, <<>>, Response#response{reason = Acc}); -decode_reason_phrase(<>, Acc, Response) -> - decode_reason_phrase(Rest, <>, Response). - -decode_header(<<>>, Acc, Response) -> - case fusco_sock:recv(Response#response.socket, Response#response.ssl) of +decode_reason_phrase(<<$\n, Rest/bits>>, Acc, Response, Timeout) -> + decode_header(Rest, <<>>, Response#response{reason = Acc}, Timeout); +decode_reason_phrase(<<$\r,$\n, Rest/bits>>, Acc, Response, Timeout) -> + decode_header(Rest, <<>>, Response#response{reason = Acc}, Timeout); +decode_reason_phrase(<>, Acc, Response, Timeout) -> + decode_reason_phrase(Rest, <>, Response, Timeout). + +decode_header(Data, Acc, Response) -> + decode_header(Data, Acc, Response, infinity). + +decode_header(<<>>, Acc, Response, Timeout) -> + case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of {ok, Data} -> - decode_header(Data, Acc, ?SIZE(Data, Response)); + decode_header(Data, Acc, ?SIZE(Data, Response), Timeout); {error, closed} -> case Acc of <<>> -> - decode_body(<<>>, Response); + decode_body(<<>>, Response, Timeout); _ -> {error, closed} end; {error, Reason} -> {error, Reason} end; -decode_header(<<$\r>>, Acc, Response) -> - case fusco_sock:recv(Response#response.socket, Response#response.ssl) of +decode_header(<<$\r>>, Acc, Response, Timeout) -> + case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of {ok, Data} -> - decode_header(<<$\r, Data/binary>>, Acc, ?SIZE(Data, Response)); + decode_header(<<$\r, Data/binary>>, Acc, ?SIZE(Data, Response), Timeout); {error, Reason} -> {error, Reason} end; -decode_header(<<$\s, Rest/bits>>, Acc, Response) -> - decode_header(Rest, Acc, Response); -decode_header(<<$:, Rest/bits>>, Header, Response) -> - decode_header_value_ws(Rest, Header, Response); -decode_header(<<$\n, Rest/bits>>, <<>>, Response) -> - decode_body(Rest, Response); -decode_header(<<$\r, $\n, Rest/bits>>, <<>>, Response) -> - decode_body(Rest, Response); -decode_header(<<$\r, $\n, _Rest/bits>>, _, _Response) -> +decode_header(<<$\s, Rest/bits>>, Acc, Response, Timeout) -> + decode_header(Rest, Acc, Response, Timeout); +decode_header(<<$:, Rest/bits>>, Header, Response, Timeout) -> + decode_header_value_ws(Rest, Header, Response, Timeout); +decode_header(<<$\n, Rest/bits>>, <<>>, Response, Timeout) -> + decode_body(Rest, Response, Timeout); +decode_header(<<$\r, $\n, Rest/bits>>, <<>>, Response, Timeout) -> + decode_body(Rest, Response, Timeout); +decode_header(<<$\r, $\n, _Rest/bits>>, _, _Response, _Timeout) -> {error, header}; -decode_header(<<$A, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$B, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$C, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$D, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$E, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$F, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$G, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$H, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$I, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$J, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$K, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$L, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$M, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$N, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$O, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$P, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$Q, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$R, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$S, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$T, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$U, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$V, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$W, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$X, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$Y, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<<$Z, Rest/bits>>, Header, Response) -> - decode_header(Rest, <
>, Response); -decode_header(<>, Header, Response) -> - decode_header(Rest, <
>, Response). - -decode_header_value_ws(<<$\s, Rest/bits>>, H, S) -> - decode_header_value_ws(Rest, H, S); -decode_header_value_ws(<<$\t, Rest/bits>>, H, S) -> - decode_header_value_ws(Rest, H, S); -decode_header_value_ws(Rest, <<"connection">> = H, S) -> - decode_header_value_lc(Rest, H, <<>>, <<>>, S); -decode_header_value_ws(Rest, <<"transfer-encoding">> = H, S) -> - decode_header_value_lc(Rest, H, <<>>, <<>>, S); -decode_header_value_ws(Rest, H, S) -> - decode_header_value(Rest, H, <<>>, <<>>, S). - -decode_header_value(<<>>, H, V, T, Response) -> - case fusco_sock:recv(Response#response.socket, Response#response.ssl) of +decode_header(<<$A, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$B, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$C, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$D, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$E, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$F, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$G, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$H, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$I, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$J, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$K, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$L, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$M, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$N, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$O, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$P, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$Q, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$R, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$S, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$T, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$U, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$V, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$W, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$X, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$Y, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<<$Z, Rest/bits>>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout); +decode_header(<>, Header, Response, Timeout) -> + decode_header(Rest, <
>, Response, Timeout). + +decode_header_value_ws(<<$\s, Rest/bits>>, H, S, Timeout) -> + decode_header_value_ws(Rest, H, S, Timeout); +decode_header_value_ws(<<$\t, Rest/bits>>, H, S, Timeout) -> + decode_header_value_ws(Rest, H, S, Timeout); +decode_header_value_ws(Rest, <<"connection">> = H, S, Timeout) -> + decode_header_value_lc(Rest, H, <<>>, <<>>, S, Timeout); +decode_header_value_ws(Rest, <<"transfer-encoding">> = H, S, Timeout) -> + decode_header_value_lc(Rest, H, <<>>, <<>>, S, Timeout); +decode_header_value_ws(Rest, H, S, Timeout) -> + decode_header_value(Rest, H, <<>>, <<>>, S, Timeout). + +decode_header_value(Data, H, V, T, Response) -> + decode_header_value(Data, H, V, T, Response, infinity). + +decode_header_value(<<>>, H, V, T, Response, Timeout) -> + case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of {ok, Data} -> - decode_header_value(Data, H, V, T, ?SIZE(Data, Response)); + decode_header_value(Data, H, V, T, ?SIZE(Data, Response), Timeout); {error, Reason} -> {error, Reason} end; -decode_header_value(<<$\r>>, H, V, T, Response) -> - case fusco_sock:recv(Response#response.socket, Response#response.ssl) of +decode_header_value(<<$\r>>, H, V, T, Response, Timeout) -> + case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of {ok, Data} -> - decode_header_value(<<$\r, Data/binary>>, H, V, T, ?SIZE(Data, Response)); + decode_header_value(<<$\r, Data/binary>>, H, V, T, ?SIZE(Data, Response), Timeout); {error, Reason} -> {error, Reason} end; -decode_header_value(<<$\n, Rest/bits>>, <<"content-length">> = H, V, _T, Response) -> +decode_header_value(<<$\n, Rest/bits>>, <<"content-length">> = H, V, _T, Response, Timeout) -> decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers], - content_length = binary_to_integer(V)}); -decode_header_value(<<$\n, Rest/bits>>, <<"set-cookie">> = H, V, _T, Response) -> + content_length = binary_to_integer(V)}, Timeout); +decode_header_value(<<$\n, Rest/bits>>, <<"set-cookie">> = H, V, _T, Response, Timeout) -> decode_header(Rest, <<>>, Response#response{cookies = [decode_cookie(V) | Response#response.cookies], - headers = [{H, V} | Response#response.headers]}); -decode_header_value(<<$\n, Rest/bits>>, H, V, _T, Response) -> - decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers]}); -decode_header_value(<<$\r, $\n, Rest/bits>>, <<"set-cookie">> = H, V, _T, Response) -> + headers = [{H, V} | Response#response.headers]}, Timeout); +decode_header_value(<<$\n, Rest/bits>>, H, V, _T, Response, Timeout) -> + decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers]}, Timeout); +decode_header_value(<<$\r, $\n, Rest/bits>>, <<"set-cookie">> = H, V, _T, Response, Timeout) -> decode_header(Rest, <<>>, Response#response{cookies = [decode_cookie(V) | Response#response.cookies], - headers = [{H, V} | Response#response.headers]}); -decode_header_value(<<$\r,$\n, Rest/bits>>, <<"content-length">> = H, V, _T, Response) -> + headers = [{H, V} | Response#response.headers]}, Timeout); +decode_header_value(<<$\r,$\n, Rest/bits>>, <<"content-length">> = H, V, _T, Response, Timeout) -> decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers], - content_length = binary_to_integer(V)}); -decode_header_value(<<$\r, $\n, Rest/bits>>, H, V, _T, Response) -> - decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers]}); -decode_header_value(<<$\s, Rest/bits>>, H, V, T, Response) -> - decode_header_value(Rest, H, V, <>, Response); -decode_header_value(<<$\t, Rest/bits>>, H, V, T, Response) -> - decode_header_value(Rest, H, V, <>, Response); -decode_header_value(<>, H, V, <<>>, Response) -> - decode_header_value(Rest, H, <>, <<>>, Response); -decode_header_value(<>, H, V, T, Response) -> - decode_header_value(Rest, H, <>, <<>>, Response). - -decode_header_value_lc(<<>>, H, V, T, Response) -> - case fusco_sock:recv(Response#response.socket, Response#response.ssl) of + content_length = binary_to_integer(V)}, Timeout); +decode_header_value(<<$\r, $\n, Rest/bits>>, H, V, _T, Response, Timeout) -> + decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers]}, Timeout); +decode_header_value(<<$\s, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value(Rest, H, V, <>, Response, Timeout); +decode_header_value(<<$\t, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value(Rest, H, V, <>, Response, Timeout); +decode_header_value(<>, H, V, <<>>, Response, Timeout) -> + decode_header_value(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value(<>, H, V, T, Response, Timeout) -> + decode_header_value(Rest, H, <>, <<>>, Response, Timeout). + +decode_header_value_lc(<<>>, H, V, T, Response, Timeout) -> + case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of {ok, Data} -> - decode_header_value_lc(Data, H, V, T, ?SIZE(Data, Response)); + decode_header_value_lc(Data, H, V, T, ?SIZE(Data, Response), Timeout); {error, Reason} -> {error, Reason} end; -decode_header_value_lc(<<$\r>>, H, V, T, Response) -> - case fusco_sock:recv(Response#response.socket, Response#response.ssl) of +decode_header_value_lc(<<$\r>>, H, V, T, Response, Timeout) -> + case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of {ok, Data} -> - decode_header_value_lc(<<$\r, Data/binary>>, H, V, T, ?SIZE(Data, Response)); + decode_header_value_lc(<<$\r, Data/binary>>, H, V, T, ?SIZE(Data, Response), Timeout); {error, Reason} -> {error, Reason} end; -decode_header_value_lc(<<$\n, Rest/bits>>, <<"transfer-encoding">> = H, V, _T, Response) -> +decode_header_value_lc(<<$\n, Rest/bits>>, <<"transfer-encoding">> = H, V, _T, Response, Timeout) -> decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers], - transfer_encoding = V}); -decode_header_value_lc(<<$\n, Rest/bits>>, H, V, _T, Response) -> + transfer_encoding = V}, Timeout); +decode_header_value_lc(<<$\n, Rest/bits>>, H, V, _T, Response, Timeout) -> decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers], - connection = V}); -decode_header_value_lc(<<$\r, $\n, Rest/bits>>, <<"transfer-encoding">> = H, V, _T, Response) -> + connection = V}, Timeout); +decode_header_value_lc(<<$\r, $\n, Rest/bits>>, <<"transfer-encoding">> = H, V, _T, Response, Timeout) -> decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers], - transfer_encoding = V}); -decode_header_value_lc(<<$\r, $\n, Rest/bits>>, H, V, _T, Response) -> + transfer_encoding = V}, Timeout); +decode_header_value_lc(<<$\r, $\n, Rest/bits>>, H, V, _T, Response, Timeout) -> decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers], - connection = V}); -decode_header_value_lc(<<$\s, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, V, <>, Response); -decode_header_value_lc(<<$\t, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, V, <>, Response); -decode_header_value_lc(<<$A, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$B, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$C, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$D, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$E, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$F, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$G, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$H, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$I, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$J, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$K, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$L, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$M, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$N, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$O, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$P, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$Q, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$R, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$S, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$T, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$U, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$V, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$W, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$X, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$Y, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<<$Z, Rest/bits>>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response); -decode_header_value_lc(<>, H, V, T, Response) -> - decode_header_value_lc(Rest, H, <>, <<>>, Response). + connection = V}, Timeout); +decode_header_value_lc(<<$\s, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, V, <>, Response, Timeout); +decode_header_value_lc(<<$\t, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, V, <>, Response, Timeout); +decode_header_value_lc(<<$A, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$B, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$C, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$D, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$E, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$F, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$G, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$H, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$I, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$J, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$K, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$L, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$M, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$N, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$O, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$P, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$Q, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$R, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$S, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$T, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$U, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$V, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$W, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$X, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$Y, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<<$Z, Rest/bits>>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout); +decode_header_value_lc(<>, H, V, T, Response, Timeout) -> + decode_header_value_lc(Rest, H, <>, <<>>, Response, Timeout). %% RFC 6265 %% TODO decode cookie values, this only accepts 'a=b' @@ -417,68 +426,68 @@ decode_cookie_av_value(<>, Co, AV, Value) -> decode_body(<<>>, Response = #response{status_code = <<$1, _, _>>, - transfer_encoding = TE}) + transfer_encoding = TE}, _Timeout) when TE =/= <<"chunked">> -> return(<<>>, Response); -decode_body(<<$\r, $\n, Rest/bits>>, Response) -> - decode_body(Rest, Response); +decode_body(<<$\r, $\n, Rest/bits>>, Response, Timeout) -> + decode_body(Rest, Response, Timeout); decode_body(Rest, Response = #response{status_code = <<$1, _, _>>, - transfer_encoding = TE}) + transfer_encoding = TE}, Timeout) when TE =/= <<"chunked">> -> decode_status_line(Rest, #response{socket = Response#response.socket, ssl = Response#response.ssl, - in_timestamp = Response#response.in_timestamp}); -decode_body(Rest, Response = #response{transfer_encoding = <<"chunked">>}) -> - decode_chunked_body(Rest, <<>>, <<>>, Response); -decode_body(Rest, Response) -> + in_timestamp = Response#response.in_timestamp}, Timeout); +decode_body(Rest, Response = #response{transfer_encoding = <<"chunked">>}, Timeout) -> + decode_chunked_body(Rest, <<>>, <<>>, Response, Timeout); +decode_body(Rest, Response, Timeout) -> case byte_size(Rest) >= Response#response.content_length of true -> return(Rest, Response); false -> - case fusco_sock:recv(Response#response.socket, Response#response.ssl) of + case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of {ok, Data} -> - decode_body(<>, ?SIZE(Data, Response)); + decode_body(<>, ?SIZE(Data, Response), Timeout); _ -> %% NOTE: Return what we have so far return(Rest, Response) end end. -download_chunked_body(Rest, Acc, Size, Response) -> - case fusco_sock:recv(Response#response.socket, Response#response.ssl) of +download_chunked_body(Rest, Acc, Size, Response, Timeout) -> + case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of {ok, Data} -> decode_chunked_body(<>, Acc, Size, - ?SIZE(Data, Response)); + ?SIZE(Data, Response), Timeout); _ -> return(Acc, Response) end. -decode_chunked_body(<<$0,$\r,$\n,$\r,$\n>>, Acc, _, Response) -> +decode_chunked_body(<<$0,$\r,$\n,$\r,$\n>>, Acc, _, Response, _Timeout) -> return(Acc, Response); -decode_chunked_body(<<$0, Rest/bits>> = R, Acc, Size, Response) +decode_chunked_body(<<$0, Rest/bits>> = R, Acc, Size, Response, Timeout) when is_binary(Size), byte_size(Rest) < 4 -> - download_chunked_body(R, Acc, Size, Response); -decode_chunked_body(<<$\r>> = R, Acc, Size, Response) when is_binary(Size) -> - download_chunked_body(R, Acc, Size, Response); -decode_chunked_body(<<$\r,$\n, Rest/bits>>, Acc, <<>>, Response) -> - decode_chunked_body(Rest, Acc, <<>>, Response); -decode_chunked_body(<<$\r,$\n, Rest/bits>>, Acc, Size, Response) when is_binary(Size) -> + download_chunked_body(R, Acc, Size, Response, Timeout); +decode_chunked_body(<<$\r>> = R, Acc, Size, Response, Timeout) when is_binary(Size) -> + download_chunked_body(R, Acc, Size, Response, Timeout); +decode_chunked_body(<<$\r,$\n, Rest/bits>>, Acc, <<>>, Response, Timeout) -> + decode_chunked_body(Rest, Acc, <<>>, Response, Timeout); +decode_chunked_body(<<$\r,$\n, Rest/bits>>, Acc, Size, Response, Timeout) when is_binary(Size) -> IntSize = erlang:binary_to_integer(Size, 16), - decode_chunked_body(Rest, Acc, IntSize, Response); -decode_chunked_body(<>, Acc, Size, Response) when is_binary(Size) -> - decode_chunked_body(Rest, Acc, <>, Response); -decode_chunked_body(<<>> = R, Acc, Size, Response) when is_binary(Size) -> - download_chunked_body(R, Acc, Size, Response); -decode_chunked_body(Rest, Acc, Size, Response) when is_integer(Size) -> + decode_chunked_body(Rest, Acc, IntSize, Response, Timeout); +decode_chunked_body(<>, Acc, Size, Response, Timeout) when is_binary(Size) -> + decode_chunked_body(Rest, Acc, <>, Response, Timeout); +decode_chunked_body(<<>> = R, Acc, Size, Response, Timeout) when is_binary(Size) -> + download_chunked_body(R, Acc, Size, Response, Timeout); +decode_chunked_body(Rest, Acc, Size, Response, Timeout) when is_integer(Size) -> case byte_size(Rest) of S when S == Size -> - decode_chunked_body(<<>>, <>, <<>>, Response); + decode_chunked_body(<<>>, <>, <<>>, Response, Timeout); S when S < Size -> - download_chunked_body(Rest, Acc, Size, Response); + download_chunked_body(Rest, Acc, Size, Response, Timeout); S when S > Size -> Current = binary:part(Rest, 0, Size), Next = binary:part(Rest, Size, S - Size), - decode_chunked_body(Next, <>, <<>>, Response) + decode_chunked_body(Next, <>, <<>>, Response, Timeout) end. return(Body, Response) -> diff --git a/src/fusco_sock.erl b/src/fusco_sock.erl index 70f2fbc..411b692 100644 --- a/src/fusco_sock.erl +++ b/src/fusco_sock.erl @@ -13,7 +13,8 @@ recv/2, recv/3, send/3, - close/2]). + close/2, + setopts/3]). -include("fusco_types.hrl"). @@ -102,3 +103,21 @@ close(Socket, true) -> ssl:close(Socket); close(Socket, false) -> gen_tcp:close(Socket). + +%%------------------------------------------------------------------------------ +%% @spec (Socket, Opts, SslFlag) -> ok | {error, Reason} +%% Socket = socket() +%% Opts = list() +%% SslFlag = boolean() +%% Reason = atom() +%% @doc +%% Sets options for a socket. +%% Will use the `ssl' module if `SslFlag' is set to `true', otherwise the +%% inets module. +%% @end +%%------------------------------------------------------------------------------ +-spec setopts(socket(), list(), boolean()) -> ok | {error, atom()}. +setopts(Socket, Opts, true) -> + ssl:setopts(Socket, Opts); +setopts(Socket, Opts, false) -> + inet:setopts(Socket, Opts). diff --git a/test/fusco_tests.erl b/test/fusco_tests.erl index fb8756d..b008b72 100644 --- a/test/fusco_tests.erl +++ b/test/fusco_tests.erl @@ -57,7 +57,8 @@ tcp_test_() -> ?_test(pre_1_1_server_keep_alive()), ?_test(post_100_continue()), ?_test(request_timeout()), - ?_test(trailing_space_header()) + ?_test(trailing_space_header()), + ?_test(closed_after_timeout()) ]} }. @@ -162,6 +163,26 @@ invalid_options() -> ?assertError({bad_option, {foo, bar}}, fusco:start(URL, [{foo, bar}, bad_option])). +closed_after_timeout() -> + {ok, _, _, Port} = webserver:start(gen_tcp, [fun webserver_utils:no_response/5, stay_open]), + URL = url(Port), + {ok, Client} = fusco:start(URL, []), + fusco:request(Client, <<"/slow">>, "GET", [], [], 50), + fusco:disconnect(Client), + wait_for_exit(10, Client), + ?assertEqual(false,erlang:is_process_alive(Client)). + +wait_for_exit(0, _) -> + ok; +wait_for_exit(N, Proc) -> + timer:sleep(50), + case is_process_alive(Proc) of + false -> + ok; + true -> + wait_for_exit(N - 1, Proc) + end. + url(Port) -> url(inet, Port). diff --git a/test/webserver_utils.erl b/test/webserver_utils.erl index a9fae66..9480788 100644 --- a/test/webserver_utils.erl +++ b/test/webserver_utils.erl @@ -83,3 +83,6 @@ trailing_space_header(Module, Socket, _, _, _) -> "Content-Length: 14 \r\n\r\n" ?DEFAULT_STRING ). + +no_response(_, _, _, _, _) -> + ok.