%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions 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_link/2, start_link/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, format_status/2]).

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

-define(HANDSHAKE_TIMEOUT, 5000).

-record(state, {mod,     %% #mod{}
		manager, %% pid()
		status,  %% accept | busy | blocked
		mfa,     %% {Module, Function, Args} 
		max_keep_alive_request = infinity, %% integer() | infinity
		response_sent = false :: boolean(),
		timeout,   %% infinity | integer() > 0
		timer      :: 'undefined' | reference(), % 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_link(Manager, ConfigDB) ->
    start_link(Manager, ConfigDB, 15000).
start_link(Manager, ConfigDB, AcceptTimeout) ->
    proc_lib:start_link(?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]) ->
    process_flag(trap_exit, true),
    %% 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),
    
    %%Timeout value is in seconds we want it in milliseconds
    KeepAliveTimeOut = 1000 * httpd_util:lookup(ConfigDB, keep_alive_timeout, 150),
    
    case http_transport:negotiate(SocketType, Socket, ?HANDSHAKE_TIMEOUT) of
	{error, Error} ->
	    exit({shutdown, Error}); %% Can be 'normal'.
	ok ->
	    continue_init(Manager, ConfigDB, SocketType, Socket, KeepAliveTimeOut)
    end.

continue_init(Manager, ConfigDB, SocketType, Socket, 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), 
    MaxContentLen = max_content_length(ConfigDB),
    Customize = customize(ConfigDB),

    {_, Status} = httpd_manager:new_connection(Manager),
    
    MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize},
				   {max_version, ?HTTP_MAX_VERSION_STRING}, 
				   {max_method, ?HTTP_MAX_METHOD_STRING},
				   {max_content_length, MaxContentLen},
				   {customize, Customize}
				  ]]}, 

    State = #state{mod                    = Mod, 
		   manager                = Manager, 
		   status                 = Status,
		   timeout                = TimeOut, 
		   max_keep_alive_request = NrOfRequest,
		   mfa                    = MFA},
    
    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)),
     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},
		   mod = #mod{socket_type = SockType, 
			      socket = Socket} = ModData} = State) 
  when (((Proto =:= tcp) orelse 
	 (Proto =:= ssl) orelse 
	 (Proto =:= dummy)) andalso is_binary(Data)) ->

    PROCESSED = (catch Module:Function([Data | Args])),
    NewDataSize = case State#state.byte_limit of
		      undefined ->
			  undefined;
		      _ ->
			  State#state.data + byte_size(Data)
		  end,
    case PROCESSED of
        {ok, 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, {size_error, MaxSize, ErrCode, ErrStr}, Version} ->
	    NewModData =  ModData#mod{http_version = Version},
	    httpd_response:send_status(NewModData, ErrCode, ErrStr),
	    Reason = io_lib:format("~p: ~p max size is ~p~n", 
				   [ErrCode, ErrStr, MaxSize]),
	    error_log(Reason, NewModData),
	    {stop, normal, State#state{response_sent = true, 
				       mod = NewModData}};

	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{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;

handle_info({'EXIT', _, Reason}, State) ->
    {stop, Reason, State};

%% 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(Reason, State) when Reason == normal;
			      Reason == shutdown ->
    do_terminate(State);
terminate({shutdown,_}, State) ->
    do_terminate(State);
terminate(Reason, #state{response_sent = false, mod = ModData} = State) ->
    httpd_response:send_status(ModData, 500, none),
     ReasonStr = 
	lists:flatten(io_lib:format("~s - ~p", 
				    [httpd_util:reason_phrase(500), Reason])),
    error_log(ReasonStr, ModData),
    terminate(Reason, State#state{response_sent = true, mod = ModData});
terminate(_Reason, State) ->
    do_terminate(State).

do_terminate(#state{mod = ModData} = State) ->
    cancel_request_timeout(State),
    httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket).

format_status(normal, [_, State]) ->
    [{data, [{"StateData", State}]}];  
format_status(terminate, [_, State]) ->
    Mod = (State#state.mod),
    case Mod#mod.socket_type of
	ip_comm ->
	    [{data, [{"StateData", State}]}];  
	{essl, _} ->
	    %% Do not print ssl options in superviosr reports
	    [{data, [{"StateData", 
		      State#state{mod = Mod#mod{socket_type = 'TLS'}}}]}]
    end.

%%--------------------------------------------------------------------
%% 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) -> 
    handle_manager_busy(State#state{mod = 
				    ModData#mod{http_version = Version}}),
    {stop, normal, State}; 

handle_http_msg({_, _, Version, {_, _}, _}, 
		#state{status = blocked, mod = ModData} = State) ->
    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) ->        
    case httpd_request:validate(Method, Uri, Version) of
	ok  ->
	    {ok, NewModData} = 
		httpd_request:update_mod_data(ModData, Method, Uri,
					      Version, Headers),
      
	    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}} ->
	    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}}} ->
	    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}}} ->
	    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}};
	{error, {bad_version, Ver}} ->
	    httpd_response:send_status(ModData#mod{http_version = "HTTP/0.9"}, 400, Ver),
	    Reason = io_lib:format("Malformed syntax version: ~p~n", [Ver]),
	    error_log(Reason, ModData),
	    {stop, normal, State#state{response_sent = true}}
    end;
handle_http_msg({ChunkedHeaders, Body}, 
		State = #state{headers = Headers}) ->
    NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders),
    handle_response(State#state{headers = NewHeaders, body = Body});
handle_http_msg(Body, State) ->
    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) ->
    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) ->
    case Headers#http_request_h.'transfer-encoding' of
	"chunked" ->
	    try http_chunk:decode(Body, MaxBodySize, MaxHeaderSize) of
		{Module, Function, Args} ->
		    http_transport:setopts(ModData#mod.socket_type, 
					   ModData#mod.socket, 
					   [{active, once}]),
		    {noreply, State#state{mfa = 
					  {Module, Function, Args}}};
		{ok, {ChunkedHeaders, NewBody}} ->
		    NewHeaders = 
			http_chunk:handle_headers(Headers, ChunkedHeaders),
		    handle_response(State#state{headers = NewHeaders,
						body = NewBody})
	    catch 
		throw:Error ->
		    httpd_response:send_status(ModData, 400, 
					       "Bad input"),
		    Reason = io_lib:format("Chunk decoding failed: ~p~n", 
					   [Error]),
		    error_log(Reason, ModData),
		    {stop, normal, State#state{response_sent = true}}  
	    end;
	Encoding when is_list(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}};
	_ -> 
	    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} ->
			    http_transport:setopts(ModData#mod.socket_type, 
						   ModData#mod.socket, 
						   [{active, once}]),
			    {noreply, State#state{mfa = 
						      {Module, Function, Args}}};
			
			{ok, NewBody} ->
			    handle_response(
			      State#state{headers = Headers,
					  body = NewBody})
		    end;
		false ->
		    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 = list_to_integer(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 ->
    {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) ->
    {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) ->

    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), 
    MaxContentLen = max_content_length(ModData#mod.config_db),
    Customize = customize(ModData#mod.config_db),

    MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize},
				   {max_version, ?HTTP_MAX_VERSION_STRING}, 
				   {max_method, ?HTTP_MAX_METHOD_STRING},
				   {max_content_length, MaxContentLen},
				   {customize, Customize}
				  ]]}, 
    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, _) ->
    {stop, normal, State}.

activate_request_timeout(#state{timeout = Time} = State) ->
    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,  #mod{config_db = ConfigDB}) ->
    Error = lists:flatten(
	      io_lib:format("Error reading request: ~s", [ReasonString])),
    httpd_util:error_log(ConfigDB, Error).


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

max_content_length(ConfigDB) ->    
    httpd_util:lookup(ConfigDB, max_content_length, ?HTTP_MAX_CONTENT_LENGTH).

customize(ConfigDB) ->    
    httpd_util:lookup(ConfigDB, customize, httpd_custom).
