%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1997-2012. All Rights Reserved.
%% 
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%% Description: Implements a request handler process for the HTTP server.  
%% 

-module(httpd_request_handler).

-behaviour(gen_server).

%% Application internal API
-export([start/2, start/3, socket_ownership_transfered/3]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).

-include("httpd.hrl").
-include("http_internal.hrl").
-include("httpd_internal.hrl").

-record(state, {mod,     %% #mod{}
		manager, %% pid()
		status,  %% accept | busy | blocked
		mfa,     %% {Module, Function, Args} 
		max_keep_alive_request = infinity, %% integer() | infinity
		response_sent = false, %% true | false 
		timeout,  %% infinity | integer() > 0
		timer,     %% ref() - Request timer
		headers,  %% #http_request_h{}
		body,      %% binary()
		data,      %% The total data received in bits, checked after 10s
		byte_limit  %% Bit limit per second before kick out
	       }).

%%====================================================================
%% Application internal API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start() -> {ok, Pid} | ignore | {error,Error}
%% Description: Starts a httpd-request handler process. Intended to be
%% called by the httpd acceptor process.
%%--------------------------------------------------------------------
start(Manager, ConfigDB) ->
    start(Manager, ConfigDB, 15000).
start(Manager, ConfigDB, AcceptTimeout) ->
    proc_lib:start(?MODULE, init, [[Manager, ConfigDB,AcceptTimeout]]).


%%--------------------------------------------------------------------
%% socket_ownership_transfered(Pid, SocketType, Socket) -> void()
%%
%% Pid = pid()
%% SocketType = ip_comm | ssl 
%% Socket = socket()
%%
%% Description: Send a message to the request handler process
%% confirming that the socket ownership has now sucssesfully been
%% transfered to it. Intended to be called by the httpd acceptor
%% process.
%%--------------------------------------------------------------------
socket_ownership_transfered(Pid, SocketType, Socket) ->
    Pid ! {socket_ownership_transfered, SocketType, Socket}.

%%--------------------------------------------------------------------
%% Function: init(Args) -> _
%%
%% Description: Initiates the server. Obs special init that uses 
%% gen_server:enter_loop/3. This is used instead of the normal
%% gen_server callback init, as a more complex init than the
%% gen_server provides is needed. 
%%--------------------------------------------------------------------
init([Manager, ConfigDB, AcceptTimeout]) ->
    ?hdrd("initiate", 
	  [{manager, Manager}, {cdb, ConfigDB}, {timeout, AcceptTimeout}]),
    %% Make sure this process terminates if the httpd manager process
    %% should die!
    link(Manager), 
    %% At this point the function httpd_request_handler:start/2 will return.
    proc_lib:init_ack({ok, self()}),
    
    {SocketType, Socket} = await_socket_ownership_transfer(AcceptTimeout),
    ?hdrd("socket ownership transfered", 
	  [{socket_type, SocketType}, {socket, Socket}]),

    TimeOut = httpd_util:lookup(ConfigDB, keep_alive_timeout, 150000),
    Then = erlang:now(),
    
    ?hdrd("negotiate", []),
    case http_transport:negotiate(SocketType, Socket, TimeOut) of
	{error, Error} ->
	    ?hdrd("negotiation failed", [{error, Error}]),
	    exit(Error); %% Can be 'normal'.
	ok ->
	    ?hdrt("negotiation successfull", []),
	    NewTimeout = TimeOut - timer:now_diff(now(),Then) div 1000,
	    continue_init(Manager, ConfigDB, SocketType, Socket, NewTimeout)
    end.

continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) ->
    ?hdrt("continue init", [{timeout, TimeOut}]),
    Resolve = http_transport:resolve(),
    
    Peername = httpd_socket:peername(SocketType, Socket),
    InitData = #init_data{peername = Peername, resolve = Resolve},
    Mod = #mod{config_db = ConfigDB,
               socket_type = SocketType,
               socket = Socket,
               init_data = InitData},
    
    MaxHeaderSize = max_header_size(ConfigDB), 
    MaxURISize    = max_uri_size(ConfigDB), 
    NrOfRequest   = max_keep_alive_request(ConfigDB), 
    
    {_, Status} = httpd_manager:new_connection(Manager),
    
    MFA = {httpd_request, parse, [{MaxURISize, MaxHeaderSize}]}, 

    State = #state{mod                    = Mod, 
		   manager                = Manager, 
		   status                 = Status,
		   timeout                = TimeOut, 
		   max_keep_alive_request = NrOfRequest,
		   mfa                    = MFA},
    
    ?hdrt("activate request timeout", []),
    
    ?hdrt("set socket options (binary, packet & active)", []),
    http_transport:setopts(SocketType, Socket, 
			   [binary, {packet, 0}, {active, once}]),
    NewState =  data_receive_counter(activate_request_timeout(State), httpd_util:lookup(ConfigDB, minimum_bytes_per_second, false)),
    ?hdrt("init done", []),
    gen_server:enter_loop(?MODULE, [], NewState).


%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(Request, From, #state{mod = ModData} = State) ->
    Error = 
	lists:flatten(
	  io_lib:format("Unexpected request: "
			"~n~p"
			"~nto request handler (~p) from ~p"
			"~n", [Request, self(), From])),
    error_log(Error, ModData),
    {stop, {call_api_violation, Request, From}, State}.

%%--------------------------------------------------------------------
%% handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(Msg, #state{mod = ModData} = State) ->
    Error = 
	lists:flatten(
	  io_lib:format("Unexpected message: "
			"~n~p"
			"~nto request handler (~p)"
			"~n", [Msg, self()])),
    error_log(Error, ModData),
    {noreply, State}.

%%--------------------------------------------------------------------
%% handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({Proto, Socket, Data}, 
	    #state{mfa = {Module, Function, Args} = MFA,
		   mod = #mod{socket_type = SockType, 
			      socket = Socket} = ModData} = State) 
  when (((Proto =:= tcp) orelse 
	 (Proto =:= ssl) orelse 
	 (Proto =:= dummy)) andalso is_binary(Data)) ->

    ?hdrd("received data", 
	  [{data, Data}, {proto, Proto}, 
	   {socket, Socket}, {socket_type, SockType}, {mfa, MFA}]),
    
%%     case (catch Module:Function([Data | Args])) of
    PROCESSED = (catch Module:Function([Data | Args])),
    NewDataSize = case State#state.byte_limit of
		      undefined ->
			  undefined;
		      _ ->
			  State#state.data + byte_size(Data)
		  end,
    ?hdrt("data processed", [{processing_result, PROCESSED}]),
    case PROCESSED of
        {ok, Result} ->
	    ?hdrd("data processed", [{result, Result}]),
	    NewState = case NewDataSize of
			   undefined ->
			       cancel_request_timeout(State);
			   _ ->
			       set_new_data_size(cancel_request_timeout(State), NewDataSize)
		       end,
            handle_http_msg(Result, NewState); 

	{error, {uri_too_long, MaxSize}, Version} ->
	    ?hdrv("uri too long", [{max_size, MaxSize}, {version, Version}]),
	    NewModData =  ModData#mod{http_version = Version},
	    httpd_response:send_status(NewModData, 414, "URI too long"),
	    Reason = io_lib:format("Uri too long, max size is ~p~n", 
				   [MaxSize]),
	    error_log(Reason, NewModData),
	    {stop, normal, State#state{response_sent = true, 
				       mod = NewModData}};
	{error, {header_too_long, MaxSize}, Version} ->
	    ?hdrv("header too long", 
		  [{max_size, MaxSize}, {version, Version}]),
	    NewModData =  ModData#mod{http_version = Version},
	    httpd_response:send_status(NewModData, 413, "Header too long"),
	    Reason = io_lib:format("Header too long, max size is ~p~n", 
				   [MaxSize]),
	    error_log(Reason, NewModData),
	    {stop, normal, State#state{response_sent = true, 
				       mod = NewModData}};
	NewMFA ->
	    ?hdrd("data processed - reactivate socket", [{new_mfa, NewMFA}]),
	    http_transport:setopts(SockType, Socket, [{active, once}]),
	    case NewDataSize of
		undefined ->
		    {noreply, State#state{mfa = NewMFA}};
		_ ->
		    {noreply, State#state{mfa = NewMFA, data = NewDataSize}}
	    end
    end;

%% Error cases
handle_info({tcp_closed, _}, State) ->
    {stop, normal, State};
handle_info({ssl_closed, _}, State) ->
    {stop, normal, State};
handle_info({tcp_error, _, _} = Reason, State) ->
    {stop, Reason, State};
handle_info({ssl_error, _, _} = Reason, State) ->
    {stop, Reason, State};

%% Timeouts
handle_info(timeout, #state{mod = ModData, mfa = {_, parse, _}} = State) ->
    error_log("No request received on keep-alive connection "
	      "before server side timeout", ModData),
    %% No response should be sent!
    {stop, normal, State#state{response_sent = true}}; 
handle_info(timeout, #state{mod = ModData} = State) ->
    httpd_response:send_status(ModData, 408, "Request timeout"),
    error_log("The client did not send the whole request before the "
	      "server side timeout", ModData),
    {stop, normal, State#state{response_sent = true}};
handle_info(check_data_first, #state{data = Data, byte_limit = Byte_Limit} = State) ->
    case Data >= (Byte_Limit*3) of 
	true ->
	    erlang:send_after(1000, self(), check_data),
	    {noreply, State#state{data = 0}};
	_ ->
	    {stop, normal, State#state{response_sent = true}}
    end;
handle_info(check_data, #state{data = Data, byte_limit = Byte_Limit} = State) ->
    case Data >= Byte_Limit of 
	true ->
	    erlang:send_after(1000, self(), check_data),
	    {noreply, State#state{data = 0}};
	_ ->
	    {stop, normal, State#state{response_sent = true}}
    end;
%% Default case
handle_info(Info, #state{mod = ModData} = State) ->
    Error = lists:flatten(
	      io_lib:format("Unexpected info: "
			    "~n~p"
			    "~nto request handler (~p)"
			    "~n", [Info, self()])),
    error_log(Error, ModData),
    {noreply, State}.


%%--------------------------------------------------------------------
%% terminate(Reason, State) -> void()
%%
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(normal, State) ->
    do_terminate(State);
terminate(Reason, #state{response_sent = false, mod = ModData} = State) ->
    httpd_response:send_status(ModData, 500, none),
    error_log(httpd_util:reason_phrase(500), ModData),
    terminate(Reason, State#state{response_sent = true, mod = ModData});
terminate(_Reason, State) ->
    do_terminate(State).

do_terminate(#state{mod = ModData, manager = Manager} = State) ->
    catch httpd_manager:done_connection(Manager),
    cancel_request_timeout(State),
    %% receive after 5000 -> ok end, 
    httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket).


%%--------------------------------------------------------------------
%% code_change(OldVsn, State, Extra) -> {ok, NewState}
%%
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
set_new_data_size(State, NewData) ->
    State#state{data = NewData}.
await_socket_ownership_transfer(AcceptTimeout) ->
    receive
	{socket_ownership_transfered, SocketType, Socket} ->
	    {SocketType, Socket}
    after AcceptTimeout ->
	    exit(accept_socket_timeout)
    end.

handle_http_msg({_, _, Version, {_, _}, _}, 
		#state{status = busy, mod = ModData} = State) -> 
    ?hdrt("handle http msg when manager busy", [{mod, ModData}]),
    handle_manager_busy(State#state{mod = 
				    ModData#mod{http_version = Version}}),
    {stop, normal, State}; 

handle_http_msg({_, _, Version, {_, _}, _}, 
		#state{status = blocked, mod = ModData} = State) ->
    ?hdrt("handle http msg when manager blocket", [{mod, ModData}]),
    handle_manager_blocked(State#state{mod = 
				       ModData#mod{http_version = Version}}),
    {stop, normal, State}; 

handle_http_msg({Method, Uri, Version, {RecordHeaders, Headers}, Body},
		#state{status = accept, mod = ModData} = State) ->        
    ?hdrt("handle http msg when manager accepting", 
	  [{method, Method}, {mod, ModData}]),
    case httpd_request:validate(Method, Uri, Version) of
	ok  ->
	    ?hdrt("request validated", []),
	    {ok, NewModData} = 
		httpd_request:update_mod_data(ModData, Method, Uri,
					      Version, Headers),
      
	    ?hdrt("new mod data", [{mod, NewModData}]),
	    case is_host_specified_if_required(NewModData#mod.absolute_uri,
					       RecordHeaders, Version) of
		true ->
		    handle_body(State#state{headers = RecordHeaders,
					    body = Body,
					    mod = NewModData});
		false ->
		    httpd_response:send_status(ModData#mod{http_version = 
							   Version}, 
					       400, none),
		    {stop, normal, State#state{response_sent = true}}
	    end;
	{error, {not_supported, What}} ->
	    ?hdrd("validation failed: not supported", [{what, What}]),
	    httpd_response:send_status(ModData#mod{http_version = Version},
				       501, {Method, Uri, Version}),
	    Reason = io_lib:format("Not supported: ~p~n", [What]),
	    error_log(Reason, ModData),
	    {stop, normal, State#state{response_sent = true}};
	{error, {bad_request, {forbidden, URI}}} ->
	    ?hdrd("validation failed: bad request - forbidden", 
		  [{uri, URI}]),
	    httpd_response:send_status(ModData#mod{http_version = Version},
				       403, URI),
	    Reason = io_lib:format("Forbidden URI: ~p~n", [URI]),
	    error_log(Reason, ModData),
	    {stop, normal, State#state{response_sent = true}};
	{error, {bad_request, {malformed_syntax, URI}}} ->
	    ?hdrd("validation failed: bad request - malformed syntax", 
		  [{uri, URI}]),
	    httpd_response:send_status(ModData#mod{http_version = Version},
				       400, URI),
	    Reason = io_lib:format("Malformed syntax in URI: ~p~n", [URI]),
	    error_log(Reason, ModData),
	    {stop, normal, State#state{response_sent = true}}
    end;
handle_http_msg({ChunkedHeaders, Body}, 
		State = #state{headers = Headers}) ->
    ?hdrt("handle http msg", 
	  [{chunked_headers, ChunkedHeaders}, {body, Body}]),
    NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders),
    handle_response(State#state{headers = NewHeaders, body = Body});
handle_http_msg(Body, State) ->
    ?hdrt("handle http msg", [{body, Body}]),
    handle_response(State#state{body = Body}).

handle_manager_busy(#state{mod = #mod{config_db = ConfigDB}} = State) ->
    MaxClients = httpd_util:lookup(ConfigDB, max_clients, 150),
    Reason = io_lib:format("heavy load (>~w processes)", [MaxClients]),
    reject_connection(State, lists:flatten(Reason)).

handle_manager_blocked(State) ->
    Reason = "Server maintenance performed, try again later",
    reject_connection(State, Reason).

reject_connection(#state{mod = ModData} = State, Reason) ->
    httpd_response:send_status(ModData, 503, Reason),
    {stop, normal, State#state{response_sent = true}}. 

is_host_specified_if_required(nohost, #http_request_h{host = undefined}, 
			      "HTTP/1.1") ->
    false;
is_host_specified_if_required(_, _, _) ->
    true.

handle_body(#state{mod = #mod{config_db = ConfigDB}} = State) ->
    ?hdrt("handle body", []),    
    MaxHeaderSize = max_header_size(ConfigDB), 
    MaxBodySize   = max_body_size(ConfigDB), 
   
    case handle_expect(State, MaxBodySize) of 
	ok ->
	    handle_body(State, MaxHeaderSize, MaxBodySize);
	Other ->
	    Other
    
    end.
	
handle_body(#state{headers = Headers, body = Body, mod = ModData} = State,
	    MaxHeaderSize, MaxBodySize) ->
    ?hdrt("handle body", [{headers, Headers}, {body, Body}]),    
    case Headers#http_request_h.'transfer-encoding' of
	"chunked" ->
	    ?hdrt("chunked - attempt decode", []),    
	    case http_chunk:decode(Body, MaxBodySize, MaxHeaderSize) of
		{Module, Function, Args} ->
		    ?hdrt("chunk decoded", 
			  [{module, Module}, 
			   {function, Function}, 
			   {args, Args}]),    
		    http_transport:setopts(ModData#mod.socket_type, 
					   ModData#mod.socket, 
					   [{active, once}]),
		    {noreply, State#state{mfa = 
					  {Module, Function, Args}}};
		{ok, {ChunkedHeaders, NewBody}} ->
		    ?hdrt("chunk decoded", 
			  [{chunked_headers, ChunkedHeaders}, 
			   {new_body, NewBody}]),    
		    NewHeaders = 
			http_chunk:handle_headers(Headers, ChunkedHeaders),
		    ?hdrt("chunked - headers handled", 
			  [{new_headers, NewHeaders}]),    
		    handle_response(State#state{headers = NewHeaders,
						body = NewBody})
	    end;
	Encoding when is_list(Encoding) ->
	    ?hdrt("not chunked - encoding", [{encoding, Encoding}]),    
	    httpd_response:send_status(ModData, 501, 
				       "Unknown Transfer-Encoding"),
	    Reason = io_lib:format("Unknown Transfer-Encoding: ~p~n", 
				   [Encoding]),
	    error_log(Reason, ModData),
	    {stop, normal, State#state{response_sent = true}};
	_ -> 
	    ?hdrt("not chunked", []),    
	    Length = 
		list_to_integer(Headers#http_request_h.'content-length'),
	    case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of
		true ->
		    case httpd_request:whole_body(Body, Length) of 
			{Module, Function, Args} ->
			    ?hdrt("whole body", 
				  [{module, Module}, 
				   {function, Function}, 
				   {args, Args}]),    
			    http_transport:setopts(ModData#mod.socket_type, 
						   ModData#mod.socket, 
						   [{active, once}]),
			    {noreply, State#state{mfa = 
						  {Module, Function, Args}}};
			
			{ok, NewBody} ->
			    ?hdrt("whole body", 
				  [{new_body, NewBody}]),    
			    handle_response(
			      State#state{headers = Headers,
					  body = NewBody})
		    end;
		false ->
		    ?hdrd("body too long", 
			  [{length, Length}, {max_body_size, MaxBodySize}]),
		    httpd_response:send_status(ModData, 413, "Body too long"),
		    error_log("Body too long", ModData),
		    {stop, normal,  State#state{response_sent = true}}
	    end
    end.

handle_expect(#state{headers = Headers, mod = 
		     #mod{config_db = ConfigDB} = ModData} = State, 
	      MaxBodySize) ->
    Length = Headers#http_request_h.'content-length',
    case expect(Headers, ModData#mod.http_version, ConfigDB) of
	continue when (MaxBodySize > Length) orelse (MaxBodySize =:= nolimit) ->
	    httpd_response:send_status(ModData, 100, ""),
	    ok;
	continue when MaxBodySize < Length ->
	    httpd_response:send_status(ModData, 413, "Body too long"),
	    error_log("Body too long", ModData),
	    {stop, normal, State#state{response_sent = true}};
	{break, Value} ->
	    httpd_response:send_status(ModData, 417, 
				       "Unexpected expect value"),
	    Reason = io_lib:format("Unexpected expect value: ~p~n", [Value]),
	    error_log(Reason, ModData),
	    {stop, normal,  State#state{response_sent = true}};
	no_expect_header ->
	    ok;
	http_1_0_expect_header ->
	    httpd_response:send_status(ModData, 400, 
				       "Only HTTP/1.1 Clients "
				       "may use the Expect Header"),
	    error_log("Client with lower version than 1.1 tried to send"
		      "an expect header", ModData),
	    {stop, normal, State#state{response_sent = true}}
    end.

expect(Headers, "HTTP/1.1", _) ->
    case Headers#http_request_h.expect of
	"100-continue" ->
	    continue; 
	undefined ->
	    no_expect_header;
	Other ->
	    {break, Other}
    end;
expect(Headers, _, ConfigDB) ->
    case Headers#http_request_h.expect of
	undefined ->
	    no_expect_header;
	_ ->
	    case httpd_util:lookup(ConfigDB, expect, continue) of
		continue->
		    no_expect_header;
		_ ->
		    http_1_0_expect_header
	    end
    end.

handle_response(#state{body    = Body, 
		       mod     = ModData, 
		       headers = Headers,
		       max_keep_alive_request = Max} = State) when Max > 0 ->
    ?hdrt("handle response", 
	  [{body, Body}, {mod, ModData}, {headers, Headers}, {max, Max}]),    
    {NewBody, Data} = httpd_request:body_data(Headers, Body),
    ok = httpd_response:generate_and_send_response(
	   ModData#mod{entity_body = NewBody}),
    handle_next_request(State#state{response_sent = true}, Data);

handle_response(#state{body    = Body, 
		       headers = Headers, 
		       mod     = ModData} = State) ->
    ?hdrt("handle response", 
	  [{body, Body}, {mod, ModData}, {headers, Headers}]),    
    {NewBody, _} = httpd_request:body_data(Headers, Body),
    ok = httpd_response:generate_and_send_response(
	   ModData#mod{entity_body = NewBody}),
    {stop, normal, State#state{response_sent = true}}.

handle_next_request(#state{mod = #mod{connection = true} = ModData,
			   max_keep_alive_request = Max} = State, Data) ->
    ?hdrt("handle next request", [{max, Max}]),    

    NewModData = #mod{socket_type = ModData#mod.socket_type, 
 		      socket      = ModData#mod.socket, 
 		      config_db   = ModData#mod.config_db, 
 		      init_data   = ModData#mod.init_data},
    MaxHeaderSize = max_header_size(ModData#mod.config_db), 
    MaxURISize    = max_uri_size(ModData#mod.config_db), 

    MFA = {httpd_request, parse, [{MaxURISize, MaxHeaderSize}]}, 
    TmpState = State#state{mod                    = NewModData,
			   mfa                    = MFA,
			   max_keep_alive_request = decrease(Max),
			   headers                = undefined, 
			   body                   = undefined,
			   response_sent          = false},
    
    NewState = activate_request_timeout(TmpState),

    case Data of
	<<>> ->
	    http_transport:setopts(ModData#mod.socket_type,
				   ModData#mod.socket, [{active, once}]),
	    {noreply, NewState};
	_ ->
	    handle_info({dummy, ModData#mod.socket, Data}, NewState)
    end;

handle_next_request(State, _) ->
    ?hdrt("handle next request - stop", []),    
    {stop, normal, State}.

activate_request_timeout(#state{timeout = Time} = State) ->
    ?hdrt("activate request timeout", [{time, Time}]),    
    Ref = erlang:send_after(Time, self(), timeout),
    State#state{timer = Ref}.
data_receive_counter(State, Byte_limit) ->
    case Byte_limit of
	false ->
	    State#state{data = 0};
	Nr ->
	    erlang:send_after(3000, self(), check_data_first),
	    State#state{data = 0, byte_limit = Nr}
    end.
cancel_request_timeout(#state{timer = undefined} = State) ->
    State;
cancel_request_timeout(#state{timer = Timer} = State) ->
    erlang:cancel_timer(Timer),
    receive 
	timeout ->
	    ok
    after 0 ->
	    ok
    end,
    State#state{timer = undefined}.

decrease(N) when is_integer(N) ->
    N-1;
decrease(N) ->
    N.

error_log(ReasonString, Info) ->
    Error = lists:flatten(
	      io_lib:format("Error reading request: ~s", [ReasonString])),
    error_log(mod_log, Info, Error),
    error_log(mod_disk_log, Info, Error).

error_log(Mod, #mod{config_db = ConfigDB} = Info, String) ->
    Modules = httpd_util:lookup(ConfigDB, modules,
				[mod_get, mod_head, mod_log]),
    case lists:member(Mod, Modules) of
	true ->
	    Mod:error_log(Info, String);
	_ ->
	    ok
    end.


%%--------------------------------------------------------------------
%% Config access wrapper functions
%%--------------------------------------------------------------------

max_header_size(ConfigDB) ->
    httpd_util:lookup(ConfigDB, max_header_size, ?HTTP_MAX_HEADER_SIZE).

max_uri_size(ConfigDB) ->
    httpd_util:lookup(ConfigDB, max_uri_size, ?HTTP_MAX_URI_SIZE).

max_body_size(ConfigDB) ->
    httpd_util:lookup(ConfigDB, max_body_size, nolimit).

max_keep_alive_request(ConfigDB) ->
    httpd_util:lookup(ConfigDB, max_keep_alive_request, infinity).

