1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2000-2016. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20%% 21 22-module(httpd_poll). 23-behaviour(gen_server). 24 25 26%% External API 27-export([start/0, start_appup/2, start/3,stop/0,verbosity/1,poll_time/1]). 28 29%% gen_server exports 30-export([init/1, 31 handle_call/3, handle_cast/2, handle_info/2, terminate/2, 32 code_change/3]). 33 34 35-define(default_verbosity,error). 36-define(default_poll_time,60000). %% 60 seconds 37 38 39-record(state,{host = "", port = -1, ptime = -1, tref = none, uris = []}). 40 41 42%% start/0 43%% 44%% Description: Start polling HTTPD with default values 45%% 46start() -> 47 Options = default_options(otp), 48 start("gandalf", 8000, Options). 49 50start_appup(Host, Port) -> 51 Options = default_options(top), 52 start(Host, Port, Options). 53 54%% start/3 55%% 56%% Description: Start polling HTTPD 57%% 58%% Parameters: 59%% Host = string() 60%% Host name of HTTPD 61%% Port = integer() 62%% Port number of HTTPD 63%% Options = [Option] 64%% Option = {poll_time,integer()} | {verbosity,verbosity()} | 65%% {log_file,string()} | {uris,[uri()]} 66%% verbosity() = silence | error | log | debug | trace 67%% uri() = {string(),string} 68%% First part is a descriptive string and the second 69%% part is the actual URI. 70%% 71start(Host,Port,Options) -> 72 gen_server:start({local,httpd_tester},?MODULE,[Host,Port,Options],[]). 73 74stop() -> 75 gen_server:call(httpd_tester,stop). 76 77 78default_options(UriDesc) -> 79 Verbosity = {verbosity,?default_verbosity}, 80 Uris = {uris,uris(UriDesc)}, 81 PollTime = {poll_time,?default_poll_time}, 82 Logging = {log_file,"httpd_poll.log"}, 83 [Verbosity, Uris, PollTime, Logging]. 84 85 86options(Options) -> 87 options(Options, default_options(otp), []). 88 89options([], Defaults, Options) -> 90 Options ++ Defaults; 91options([{Key, _Val} = Opt|Opts], Defaults, Options) -> 92 options(Opts, lists:keydelete(Key, 1, Defaults), [Opt | Options]). 93 94 95verbosity(silence) -> 96 set_verbosity(silence); 97verbosity(error) -> 98 set_verbosity(error); 99verbosity(log) -> 100 set_verbosity(log); 101verbosity(debug) -> 102 set_verbosity(debug); 103verbosity(trace) -> 104 set_verbosity(trace). 105 106set_verbosity(Verbosity) -> 107 gen_server:cast(httpd_tester,{verbosity,Verbosity}). 108 109poll_time(NewTime) -> 110 gen_server:call(httpd_tester,{poll_time,NewTime}). 111 112 113%% ---------------------------------------------------------------------- 114 115 116init([Host, Port, Options0]) -> 117 process_flag(trap_exit,true), 118 Options = options(Options0), 119 put(verbosity,get_verbosity(Options)), 120 log_open(get_log_file(Options)), 121 tstart(), 122 PollTime = get_poll_time(Options), 123 Ref = tcreate(PollTime), 124 log("created"), 125 {ok,#state{host = Host, 126 port = Port, 127 ptime = PollTime, 128 tref = Ref, 129 uris = get_uris(Options)}}. 130 131uris(top) -> 132 [uri_top_index()]; 133 134uris(otp) -> 135 [ 136 uri_top_index(), 137 uri_internal_product1(), 138 uri_internal_product2(), 139 uri_r13b03_test_results(), 140 uri_bjorn1(), 141 uri_bjorn2() 142 ]. 143 144uri_top_index() -> 145 {"top page","/"}. 146 147uri_internal_product1() -> 148 {"product internal page (1)","/product/internal/"}. 149 150uri_internal_product2() -> 151 {"product internal page (2)","/product/internal"}. 152 153uri_r13b03_test_results() -> 154 {"daily build index page", 155 "/product/internal/test/daily/logs.html"}. 156 157uri_bjorn1() -> 158 {"bjorns home page (1)","/~bjorn/"}. 159 160uri_bjorn2() -> 161 {"bjorns home page (2)","/~bjorn"}. 162 163 164handle_call(stop, _From, State) -> 165 vlog("stop request"), 166 {stop, normal, ok, State}; 167 168handle_call({poll_time,NewTime}, _From, State) -> 169 vlog("set new poll time: ~p",[NewTime]), 170 OldTime = State#state.ptime, 171 {stop, normal, OldTime, State#state{ptime = NewTime}}; 172 173handle_call(Request, _From, State) -> 174 vlog("unexpected request(call): ~p",[Request]), 175 {reply, ok, State}. 176 177 178handle_cast({verbosity,Verbosity}, State) -> 179 vlog("set (new) verbosity to: ~p",[Verbosity]), 180 put(verbosity,Verbosity), 181 {noreply, State}; 182 183handle_cast(Message, State) -> 184 vlog("unexpected message(call): ~p",[Message]), 185 {noreply, State}. 186 187 188handle_info(poll_time,State) -> 189 {{Description,Uri},Uris} = get_uri(State#state.uris), 190 vlog("poll time for ~s",[Description]), 191 do_poll(State#state.host,State#state.port,Uri), 192 Ref = tcreate(State#state.ptime), 193 {noreply, State#state{tref = Ref, uris = Uris}}; 194 195handle_info(Info, State) -> 196 vlog("unexpected message(info): ~p",[Info]), 197 {noreply, State}. 198 199 200code_change(_OldVsn, State, _Extra) -> 201 {ok, State}. 202 203 204terminate(_Reason, State) -> 205 tcancel(State#state.tref), 206 log_close(get(log_file)), 207 ok. 208 209 210get_uri([Uri|Uris]) -> 211 {Uri,Uris++[Uri]}. 212 213 214do_poll(Host,Port,Uri) -> 215 (catch poll(create(Host,Port),Uri,"200")). 216 217poll({ok,Socket},Uri,ExpStatus) -> 218 vtrace("poll -> entry with Socket: ~p",[Socket]), 219 put(latest_requested_uri,Uri), 220 Req = "GET " ++ Uri ++ " HTTP/1.0\r\n\r\n", 221 await_poll_response(send(Socket,Req),Socket,ExpStatus); 222poll({error,Reason},_Req,_ExpStatus) -> 223 verror("failed creating socket: ~p",[Reason]), 224 log("failed creating socket: ~p",[Reason]), 225 exit({error,Reason}); 226poll(O,_Req,_ExpStatus) -> 227 verror("unexpected result from socket create: ~p",[O]), 228 log("unexpected result from socket create: ~p",[O]), 229 exit({unexpected_result,O}). 230 231await_poll_response(ok,Socket,ExpStatusCode) -> 232 vtrace("await_poll_response -> awaiting response with status ~s", 233 [ExpStatusCode]), 234 receive 235 {tcp_closed,Socket} -> 236 verror("connection closed when awaiting poll response"), 237 log("connection closed when awaiting reply to GET of '~s'", 238 [get(latest_requested_uri)]), 239 exit(connection_closed); 240 {tcp,Socket,Response} -> 241 vdebug("received response"), 242 validate(ExpStatusCode,Socket,Response) 243 after 10000 -> 244 verror("connection timeout waiting for poll response",[]), 245 log("connection timeout waiting for reply to GET of '~s'", 246 [get(latest_requested_uri)]), 247 exit(connection_timed_out) 248 end; 249await_poll_response(Error,_Socket,_ExpStatusCode) -> 250 verror("failed sending GET request for '~s' for reason: ~p", 251 [get(latest_requested_uri),Error]), 252 log("failed sending GET request for '~s' for reason: ~p", 253 [get(latest_requested_uri),Error]), 254 exit(Error). 255 256 257validate(ExpStatusCode,Socket,Response) -> 258 Sz = sz(Response), 259 vtrace("validate -> Entry with ~p bytes response",[Sz]), 260 Size = trash_the_rest(Socket,Sz), 261 close(Socket), 262 case re:split(Response," ", [{return, list}]) of 263 ["HTTP/1.0",ExpStatusCode|_] -> 264 vlog("response (~p bytes) was ok",[Size]), 265 ok; 266 ["HTTP/1.0",StatusCode|_] -> 267 verror("unexpected response status received: ~s => ~s", 268 [StatusCode,status_to_message(StatusCode)]), 269 log("unexpected result to GET of '~s': ~s => ~s", 270 [get(latest_requested_uri),StatusCode, 271 status_to_message(StatusCode)]), 272 exit({unexpected_response_code,StatusCode,ExpStatusCode}) 273 end. 274 275 276%% ------------------------------------------------------------------ 277 278trash_the_rest(Socket,N) -> 279 receive 280 {tcp, Socket, Trash} -> 281 vtrace("trash_the_rest -> trash ~p bytes",[sz(Trash)]), 282 trash_the_rest(Socket,add(N,sz(Trash))); 283 {tcp_closed, Socket} -> 284 vdebug("socket closed after receiving ~p bytes",[N]), 285 N 286 after 10000 -> 287 verror("connection timeout waiting for message"), 288 exit(connection_timed_out) 289 end. 290 291 292add(N1, N2) when is_integer(N1) andalso is_integer(N2) -> 293 N1 + N2; 294add(N1, _N2) when is_integer(N1) -> 295 N1; 296add(_N1, N2) when is_integer(N2) -> 297 N2. 298 299sz(L) when is_list(L) -> 300 length(lists:flatten(L)); 301sz(B) when is_binary(B) -> 302 size(B); 303sz(O) -> 304 {unknown_size,O}. 305 306 307%% -------------------------------------------------------------- 308%% 309%% Status code to printable string 310%% 311 312status_to_message(L) when is_list(L) -> 313 case (catch list_to_integer(L)) of 314 I when is_integer(I) -> 315 status_to_message(I); 316 _ -> 317 io_lib:format("UNKNOWN STATUS CODE: '~p'",[L]) 318 end; 319status_to_message(100) -> "Section 10.1.1: Continue"; 320status_to_message(101) -> "Section 10.1.2: Switching Protocols"; 321status_to_message(200) -> "Section 10.2.1: OK"; 322status_to_message(201) -> "Section 10.2.2: Created"; 323status_to_message(202) -> "Section 10.2.3: Accepted"; 324status_to_message(203) -> "Section 10.2.4: Non-Authoritative Information"; 325status_to_message(204) -> "Section 10.2.5: No Content"; 326status_to_message(205) -> "Section 10.2.6: Reset Content"; 327status_to_message(206) -> "Section 10.2.7: Partial Content"; 328status_to_message(300) -> "Section 10.3.1: Multiple Choices"; 329status_to_message(301) -> "Section 10.3.2: Moved Permanently"; 330status_to_message(302) -> "Section 10.3.3: Found"; 331status_to_message(303) -> "Section 10.3.4: See Other"; 332status_to_message(304) -> "Section 10.3.5: Not Modified"; 333status_to_message(305) -> "Section 10.3.6: Use Proxy"; 334status_to_message(307) -> "Section 10.3.8: Temporary Redirect"; 335status_to_message(400) -> "Section 10.4.1: Bad Request"; 336status_to_message(401) -> "Section 10.4.2: Unauthorized"; 337status_to_message(402) -> "Section 10.4.3: Peyment Required"; 338status_to_message(403) -> "Section 10.4.4: Forbidden"; 339status_to_message(404) -> "Section 10.4.5: Not Found"; 340status_to_message(405) -> "Section 10.4.6: Method Not Allowed"; 341status_to_message(406) -> "Section 10.4.7: Not Acceptable"; 342status_to_message(407) -> "Section 10.4.8: Proxy Authentication Required"; 343status_to_message(408) -> "Section 10.4.9: Request Time-Out"; 344status_to_message(409) -> "Section 10.4.10: Conflict"; 345status_to_message(410) -> "Section 10.4.11: Gone"; 346status_to_message(411) -> "Section 10.4.12: Length Required"; 347status_to_message(412) -> "Section 10.4.13: Precondition Failed"; 348status_to_message(413) -> "Section 10.4.14: Request Entity Too Large"; 349status_to_message(414) -> "Section 10.4.15: Request-URI Too Large"; 350status_to_message(415) -> "Section 10.4.16: Unsupported Media Type"; 351status_to_message(416) -> "Section 10.4.17: Requested range not satisfiable"; 352status_to_message(417) -> "Section 10.4.18: Expectation Failed"; 353status_to_message(500) -> "Section 10.5.1: Internal Server Error"; 354status_to_message(501) -> "Section 10.5.2: Not Implemented"; 355status_to_message(502) -> "Section 10.5.3: Bad Gatteway"; 356status_to_message(503) -> "Section 10.5.4: Service Unavailable"; 357status_to_message(504) -> "Section 10.5.5: Gateway Time-out"; 358status_to_message(505) -> "Section 10.5.6: HTTP Version not supported"; 359status_to_message(Code) -> io_lib:format("Unknown status code: ~p",[Code]). 360 361 362%% ---------------------------------------------------------------- 363 364create(Host,Port) -> 365 vtrace("create -> ~n\tHost: ~s~n\tPort: ~p",[Host,Port]), 366 case gen_tcp:connect(Host,Port,[{packet,0},{reuseaddr,true}]) of 367 {ok,Socket} -> 368 {ok,Socket}; 369 {error,{enfile,_}} -> 370 {error,enfile}; 371 Error -> 372 Error 373 end. 374 375close(Socket) -> 376 gen_tcp:close(Socket). 377 378 379send(Socket,Data) -> 380 vtrace("send -> send ~p bytes of data",[length(Data)]), 381 gen_tcp:send(Socket,Data). 382 383 384%% ---------------------------------------------------------------- 385 386tstart() -> 387 timer:start(). 388 389tcreate(Time) -> 390 {ok,Ref} = timer:send_after(Time,poll_time), 391 Ref. 392 393tcancel(Ref) -> 394 timer:cancel(Ref). 395 396%% ---------------------------------------------------------------- 397 398log_open(undefined) -> 399 ok; 400log_open(FileName) -> 401 put(log_file,fopen(FileName)). 402 403log_close(undefined) -> 404 ok; 405log_close(Fd) -> 406 fclose(Fd). 407 408log(F) -> 409 log(F,[]). 410 411log(F,A) -> 412 {{Year,Month,Day},{Hour,Min,Sec}} = local_time(), 413 fwrite(get(log_file), 414 "~w.~w.~w ~w.~w.~w " ++ F ++ "~n", 415 [Year,Month,Day,Hour,Min,Sec] ++ A). 416 417%% ---------------------------------------------------------------- 418 419fopen(Name) -> 420 {ok,Fd} = file:open(Name,[write]), 421 Fd. 422 423fclose(Fd) -> 424 file:close(Fd). 425 426fwrite(undefined,_F,_A) -> 427 ok; 428fwrite(Fd,F,A) -> 429 io:format(Fd,F,A). 430 431 432%% ---------------------------------------------------------------- 433 434get_poll_time(Opts) -> 435 get_option(poll_time,Opts,?default_poll_time). 436 437get_log_file(Opts) -> 438 get_option(log_file,Opts). 439 440get_uris(Opts) -> 441 get_option(uris,Opts,[]). 442 443get_verbosity(Opts) -> 444 get_option(verbosity,Opts,?default_verbosity). 445 446get_option(Opt,Opts) -> 447 get_option(Opt,Opts,undefined). 448 449get_option(Opt,Opts,Default) -> 450 case lists:keysearch(Opt,1,Opts) of 451 {value,{Opt,Value}} -> 452 Value; 453 false -> 454 Default 455 end. 456 457%% ---------------------------------------------------------------- 458 459%% sleep(T) -> receive after T -> ok end. 460 461%% ---------------------------------------------------------------- 462 463%% vtrace(F) -> vprint(get(verbosity),trace,F,[]). 464vtrace(F,A) -> vprint(get(verbosity),trace,F,A). 465 466vdebug(F) -> vprint(get(verbosity),debug,F,[]). 467vdebug(F,A) -> vprint(get(verbosity),debug,F,A). 468 469vlog(F) -> vprint(get(verbosity),log,F,[]). 470vlog(F,A) -> vprint(get(verbosity),log,F,A). 471 472verror(F) -> vprint(get(verbosity),error,F,[]). 473verror(F,A) -> vprint(get(verbosity),error,F,A). 474 475vprint(trace, Severity, F, A) -> vprint(Severity,F,A); 476vprint(debug, trace, _F, _A) -> ok; 477vprint(debug, Severity, F, A) -> vprint(Severity,F,A); 478vprint(log, log, F, A) -> vprint(log,F,A); 479vprint(log, error, F, A) -> vprint(log,F,A); 480vprint(error, error, F, A) -> vprint(error,F,A); 481vprint(_Verbosity,_Severity,_F,_A) -> ok. 482 483vprint(Severity,F,A) -> 484 {{Year,Month,Day},{Hour,Min,Sec}} = local_time(), 485 io:format("~w.~w.~w ~w.~w.~w " ++ image_of(Severity) ++ F ++ "~n", 486 [Year,Month,Day,Hour,Min,Sec] ++ A). 487 488image_of(error) -> "ERR: "; 489image_of(log) -> "LOG: "; 490image_of(debug) -> "DBG: "; 491image_of(trace) -> "TRC: ". 492 493local_time() -> calendar:local_time(). 494 495 496