%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2000-2010. 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%
%%
%%
-module(httpd_poll).
-behaviour(gen_server).
%% External API
-export([start/0, start_appup/2, start/3,stop/0,verbosity/1,poll_time/1]).
%% gen_server exports
-export([init/1,
handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
-define(default_verbosity,error).
-define(default_poll_time,60000). %% 60 seconds
-record(state,{host = "", port = -1, ptime = -1, tref = none, uris = []}).
%% start/0
%%
%% Description: Start polling HTTPD with default values
%%
start() ->
Options = default_options(otp),
start("gandalf", 8000, Options).
start_appup(Host, Port) ->
Options = default_options(top),
start(Host, Port, Options).
%% start/3
%%
%% Description: Start polling HTTPD
%%
%% Parameters:
%% Host = string()
%% Host name of HTTPD
%% Port = integer()
%% Port number of HTTPD
%% Options = [Option]
%% Option = {poll_time,integer()} | {verbosity,verbosity()} |
%% {log_file,string()} | {uris,[uri()]}
%% verbosity() = silence | error | log | debug | trace
%% uri() = {string(),string}
%% First part is a descriptive string and the second
%% part is the actual URI.
%%
start(Host,Port,Options) ->
gen_server:start({local,httpd_tester},?MODULE,[Host,Port,Options],[]).
stop() ->
gen_server:call(httpd_tester,stop).
default_options(UriDesc) ->
Verbosity = {verbosity,?default_verbosity},
Uris = {uris,uris(UriDesc)},
PollTime = {poll_time,?default_poll_time},
Logging = {log_file,"httpd_poll.log"},
[Verbosity, Uris, PollTime, Logging].
options(Options) ->
options(Options, default_options(otp), []).
options([], Defaults, Options) ->
Options ++ Defaults;
options([{Key, _Val} = Opt|Opts], Defaults, Options) ->
options(Opts, lists:keydelete(Key, 1, Defaults), [Opt | Options]).
verbosity(silence) ->
set_verbosity(silence);
verbosity(error) ->
set_verbosity(error);
verbosity(log) ->
set_verbosity(log);
verbosity(debug) ->
set_verbosity(debug);
verbosity(trace) ->
set_verbosity(trace).
set_verbosity(Verbosity) ->
gen_server:cast(httpd_tester,{verbosity,Verbosity}).
poll_time(NewTime) ->
gen_server:call(httpd_tester,{poll_time,NewTime}).
%% ----------------------------------------------------------------------
init([Host, Port, Options0]) ->
process_flag(trap_exit,true),
Options = options(Options0),
put(verbosity,get_verbosity(Options)),
log_open(get_log_file(Options)),
tstart(),
PollTime = get_poll_time(Options),
Ref = tcreate(PollTime),
log("created"),
{ok,#state{host = Host,
port = Port,
ptime = PollTime,
tref = Ref,
uris = get_uris(Options)}}.
uris(top) ->
[uri_top_index()];
uris(otp) ->
[
uri_top_index(),
uri_internal_product1(),
uri_internal_product2(),
uri_r13b03_test_results(),
uri_bjorn1(),
uri_bjorn2()
].
uri_top_index() ->
{"top page","/"}.
uri_internal_product1() ->
{"product internal page (1)","/product/internal/"}.
uri_internal_product2() ->
{"product internal page (2)","/product/internal"}.
uri_r13b03_test_results() ->
{"daily build index page",
"/product/internal/test/daily/logs.html"}.
uri_bjorn1() ->
{"bjorns home page (1)","/~bjorn/"}.
uri_bjorn2() ->
{"bjorns home page (2)","/~bjorn"}.
handle_call(stop, _From, State) ->
vlog("stop request"),
{stop, normal, ok, State};
handle_call({poll_time,NewTime}, _From, State) ->
vlog("set new poll time: ~p",[NewTime]),
OldTime = State#state.ptime,
{stop, normal, OldTime, State#state{ptime = NewTime}};
handle_call(Request, _From, State) ->
vlog("unexpected request(call): ~p",[Request]),
{reply, ok, State}.
handle_cast({verbosity,Verbosity}, State) ->
vlog("set (new) verbosity to: ~p",[Verbosity]),
put(verbosity,Verbosity),
{noreply, State};
handle_cast(Message, State) ->
vlog("unexpected message(call): ~p",[Message]),
{noreply, State}.
handle_info(poll_time,State) ->
{{Description,Uri},Uris} = get_uri(State#state.uris),
vlog("poll time for ~s",[Description]),
do_poll(State#state.host,State#state.port,Uri),
Ref = tcreate(State#state.ptime),
{noreply, State#state{tref = Ref, uris = Uris}};
handle_info(Info, State) ->
vlog("unexpected message(info): ~p",[Info]),
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, State) ->
tcancel(State#state.tref),
log_close(get(log_file)),
ok.
get_uri([Uri|Uris]) ->
{Uri,Uris++[Uri]}.
do_poll(Host,Port,Uri) ->
(catch poll(create(Host,Port),Uri,"200")).
poll({ok,Socket},Uri,ExpStatus) ->
vtrace("poll -> entry with Socket: ~p",[Socket]),
put(latest_requested_uri,Uri),
Req = "GET " ++ Uri ++ " HTTP/1.0\r\n\r\n",
await_poll_response(send(Socket,Req),Socket,ExpStatus);
poll({error,Reason},_Req,_ExpStatus) ->
verror("failed creating socket: ~p",[Reason]),
log("failed creating socket: ~p",[Reason]),
exit({error,Reason});
poll(O,_Req,_ExpStatus) ->
verror("unexpected result from socket create: ~p",[O]),
log("unexpected result from socket create: ~p",[O]),
exit({unexpected_result,O}).
await_poll_response(ok,Socket,ExpStatusCode) ->
vtrace("await_poll_response -> awaiting response with status ~s",
[ExpStatusCode]),
receive
{tcp_closed,Socket} ->
verror("connection closed when awaiting poll response"),
log("connection closed when awaiting reply to GET of '~s'",
[get(latest_requested_uri)]),
exit(connection_closed);
{tcp,Socket,Response} ->
vdebug("received response"),
validate(ExpStatusCode,Socket,Response)
after 10000 ->
verror("connection timeout waiting for poll response",[]),
log("connection timeout waiting for reply to GET of '~s'",
[get(latest_requested_uri)]),
exit(connection_timed_out)
end;
await_poll_response(Error,_Socket,_ExpStatusCode) ->
verror("failed sending GET request for '~s' for reason: ~p",
[get(latest_requested_uri),Error]),
log("failed sending GET request for '~s' for reason: ~p",
[get(latest_requested_uri),Error]),
exit(Error).
validate(ExpStatusCode,Socket,Response) ->
Sz = sz(Response),
vtrace("validate -> Entry with ~p bytes response",[Sz]),
Size = trash_the_rest(Socket,Sz),
close(Socket),
case inets_regexp:split(Response," ") of
{ok,["HTTP/1.0",ExpStatusCode|_]} ->
vlog("response (~p bytes) was ok",[Size]),
ok;
{ok,["HTTP/1.0",StatusCode|_]} ->
verror("unexpected response status received: ~s => ~s",
[StatusCode,status_to_message(StatusCode)]),
log("unexpected result to GET of '~s': ~s => ~s",
[get(latest_requested_uri),StatusCode,
status_to_message(StatusCode)]),
exit({unexpected_response_code,StatusCode,ExpStatusCode})
end.
%% ------------------------------------------------------------------
trash_the_rest(Socket,N) ->
receive
{tcp, Socket, Trash} ->
vtrace("trash_the_rest -> trash ~p bytes",[sz(Trash)]),
trash_the_rest(Socket,add(N,sz(Trash)));
{tcp_closed, Socket} ->
vdebug("socket closed after receiving ~p bytes",[N]),
N
after 10000 ->
verror("connection timeout waiting for message"),
exit(connection_timed_out)
end.
add(N1, N2) when is_integer(N1) andalso is_integer(N2) ->
N1 + N2;
add(N1, _N2) when is_integer(N1) ->
N1;
add(_N1, N2) when is_integer(N2) ->
N2.
sz(L) when is_list(L) ->
length(lists:flatten(L));
sz(B) when is_binary(B) ->
size(B);
sz(O) ->
{unknown_size,O}.
%% --------------------------------------------------------------
%%
%% Status code to printable string
%%
status_to_message(L) when is_list(L) ->
case (catch list_to_integer(L)) of
I when is_integer(I) ->
status_to_message(I);
_ ->
io_lib:format("UNKNOWN STATUS CODE: '~p'",[L])
end;
status_to_message(100) -> "Section 10.1.1: Continue";
status_to_message(101) -> "Section 10.1.2: Switching Protocols";
status_to_message(200) -> "Section 10.2.1: OK";
status_to_message(201) -> "Section 10.2.2: Created";
status_to_message(202) -> "Section 10.2.3: Accepted";
status_to_message(203) -> "Section 10.2.4: Non-Authoritative Information";
status_to_message(204) -> "Section 10.2.5: No Content";
status_to_message(205) -> "Section 10.2.6: Reset Content";
status_to_message(206) -> "Section 10.2.7: Partial Content";
status_to_message(300) -> "Section 10.3.1: Multiple Choices";
status_to_message(301) -> "Section 10.3.2: Moved Permanently";
status_to_message(302) -> "Section 10.3.3: Found";
status_to_message(303) -> "Section 10.3.4: See Other";
status_to_message(304) -> "Section 10.3.5: Not Modified";
status_to_message(305) -> "Section 10.3.6: Use Proxy";
status_to_message(307) -> "Section 10.3.8: Temporary Redirect";
status_to_message(400) -> "Section 10.4.1: Bad Request";
status_to_message(401) -> "Section 10.4.2: Unauthorized";
status_to_message(402) -> "Section 10.4.3: Peyment Required";
status_to_message(403) -> "Section 10.4.4: Forbidden";
status_to_message(404) -> "Section 10.4.5: Not Found";
status_to_message(405) -> "Section 10.4.6: Method Not Allowed";
status_to_message(406) -> "Section 10.4.7: Not Acceptable";
status_to_message(407) -> "Section 10.4.8: Proxy Authentication Required";
status_to_message(408) -> "Section 10.4.9: Request Time-Out";
status_to_message(409) -> "Section 10.4.10: Conflict";
status_to_message(410) -> "Section 10.4.11: Gone";
status_to_message(411) -> "Section 10.4.12: Length Required";
status_to_message(412) -> "Section 10.4.13: Precondition Failed";
status_to_message(413) -> "Section 10.4.14: Request Entity Too Large";
status_to_message(414) -> "Section 10.4.15: Request-URI Too Large";
status_to_message(415) -> "Section 10.4.16: Unsupported Media Type";
status_to_message(416) -> "Section 10.4.17: Requested range not satisfiable";
status_to_message(417) -> "Section 10.4.18: Expectation Failed";
status_to_message(500) -> "Section 10.5.1: Internal Server Error";
status_to_message(501) -> "Section 10.5.2: Not Implemented";
status_to_message(502) -> "Section 10.5.3: Bad Gatteway";
status_to_message(503) -> "Section 10.5.4: Service Unavailable";
status_to_message(504) -> "Section 10.5.5: Gateway Time-out";
status_to_message(505) -> "Section 10.5.6: HTTP Version not supported";
status_to_message(Code) -> io_lib:format("Unknown status code: ~p",[Code]).
%% ----------------------------------------------------------------
create(Host,Port) ->
vtrace("create -> ~n\tHost: ~s~n\tPort: ~p",[Host,Port]),
case gen_tcp:connect(Host,Port,[{packet,0},{reuseaddr,true}]) of
{ok,Socket} ->
{ok,Socket};
{error,{enfile,_}} ->
{error,enfile};
Error ->
Error
end.
close(Socket) ->
gen_tcp:close(Socket).
send(Socket,Data) ->
vtrace("send -> send ~p bytes of data",[length(Data)]),
gen_tcp:send(Socket,Data).
%% ----------------------------------------------------------------
tstart() ->
timer:start().
tcreate(Time) ->
{ok,Ref} = timer:send_after(Time,poll_time),
Ref.
tcancel(Ref) ->
timer:cancel(Ref).
%% ----------------------------------------------------------------
log_open(undefined) ->
ok;
log_open(FileName) ->
put(log_file,fopen(FileName)).
log_close(undefined) ->
ok;
log_close(Fd) ->
fclose(Fd).
log(F) ->
log(F,[]).
log(F,A) ->
{{Year,Month,Day},{Hour,Min,Sec}} = local_time(),
fwrite(get(log_file),
"~w.~w.~w ~w.~w.~w " ++ F ++ "~n",
[Year,Month,Day,Hour,Min,Sec] ++ A).
%% ----------------------------------------------------------------
fopen(Name) ->
{ok,Fd} = file:open(Name,[write]),
Fd.
fclose(Fd) ->
file:close(Fd).
fwrite(undefined,_F,_A) ->
ok;
fwrite(Fd,F,A) ->
io:format(Fd,F,A).
%% ----------------------------------------------------------------
get_poll_time(Opts) ->
get_option(poll_time,Opts,?default_poll_time).
get_log_file(Opts) ->
get_option(log_file,Opts).
get_uris(Opts) ->
get_option(uris,Opts,[]).
get_verbosity(Opts) ->
get_option(verbosity,Opts,?default_verbosity).
get_option(Opt,Opts) ->
get_option(Opt,Opts,undefined).
get_option(Opt,Opts,Default) ->
case lists:keysearch(Opt,1,Opts) of
{value,{Opt,Value}} ->
Value;
false ->
Default
end.
%% ----------------------------------------------------------------
%% sleep(T) -> receive after T -> ok end.
%% ----------------------------------------------------------------
%% vtrace(F) -> vprint(get(verbosity),trace,F,[]).
vtrace(F,A) -> vprint(get(verbosity),trace,F,A).
vdebug(F) -> vprint(get(verbosity),debug,F,[]).
vdebug(F,A) -> vprint(get(verbosity),debug,F,A).
vlog(F) -> vprint(get(verbosity),log,F,[]).
vlog(F,A) -> vprint(get(verbosity),log,F,A).
verror(F) -> vprint(get(verbosity),error,F,[]).
verror(F,A) -> vprint(get(verbosity),error,F,A).
vprint(trace, Severity, F, A) -> vprint(Severity,F,A);
vprint(debug, trace, _F, _A) -> ok;
vprint(debug, Severity, F, A) -> vprint(Severity,F,A);
vprint(log, log, F, A) -> vprint(log,F,A);
vprint(log, error, F, A) -> vprint(log,F,A);
vprint(error, error, F, A) -> vprint(error,F,A);
vprint(_Verbosity,_Severity,_F,_A) -> ok.
vprint(Severity,F,A) ->
{{Year,Month,Day},{Hour,Min,Sec}} = local_time(),
io:format("~w.~w.~w ~w.~w.~w " ++ image_of(Severity) ++ F ++ "~n",
[Year,Month,Day,Hour,Min,Sec] ++ A).
image_of(error) -> "ERR: ";
image_of(log) -> "LOG: ";
image_of(debug) -> "DBG: ";
image_of(trace) -> "TRC: ".
local_time() -> calendar:local_time().