New option directory_indices, and improve logging (thanks to Brian Cully)(EJAB-932)

SVN Revision: 2180
This commit is contained in:
Badlop 2009-06-16 13:45:51 +00:00
parent 1bcc001615
commit c4e80abeac
1 changed files with 72 additions and 40 deletions

View File

@ -52,10 +52,16 @@
-define(STRING2LOWER, httpd_util).
-endif.
-record(state, {host, docroot, accesslog, accesslogfd}).
-record(state, {host, docroot, accesslog, accesslogfd, directory_indices}).
-define(PROCNAME, ejabberd_mod_http_fileserver).
%% Response is {DataSize, Code, [{HeaderKey, HeaderValue}], Data}
-define(HTTP_ERR_FILE_NOT_FOUND, {-1, 404, [], "Not found"}).
-define(HTTP_ERR_FORBIDDEN, {-1, 403, [], "Forbidden"}).
-compile(export_all).
%%====================================================================
%% gen_mod callbacks
%%====================================================================
@ -100,11 +106,12 @@ start_link(Host, Opts) ->
%%--------------------------------------------------------------------
init([Host, Opts]) ->
try initialize(Host, Opts) of
{DocRoot, AccessLog, AccessLogFD} ->
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices} ->
{ok, #state{host = Host,
accesslog = AccessLog,
accesslogfd = AccessLogFD,
docroot = DocRoot}}
docroot = DocRoot,
directory_indices = DirectoryIndices}}
catch
throw:Reason ->
{stop, Reason}
@ -118,7 +125,8 @@ initialize(Host, Opts) ->
check_docroot_is_readable(DRInfo, DocRoot),
AccessLog = gen_mod:get_opt(accesslog, Opts, undefined),
AccessLogFD = try_open_log(AccessLog, Host),
{DocRoot, AccessLog, AccessLogFD}.
DirectoryIndices = gen_mod:get_opt(directory_indices, Opts, []),
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices}.
check_docroot_defined(DocRoot, Host) ->
case DocRoot of
@ -168,7 +176,7 @@ try_open_log(FN, Host) ->
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({serve, LocalPath}, _From, State) ->
Reply = serve(LocalPath, State#state.docroot),
Reply = serve(LocalPath, State#state.docroot, State#state.directory_indices),
{reply, Reply, State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
@ -179,8 +187,8 @@ handle_call(_Request, _From, State) ->
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({add_to_log, Code, Request}, State) ->
add_to_log(State#state.accesslogfd, Code, Request),
handle_cast({add_to_log, FileSize, Code, Request}, State) ->
add_to_log(State#state.accesslogfd, FileSize, Code, Request),
{noreply, State};
handle_cast(reopen_log, State) ->
FD2 = reopen_log(State#state.accesslog, State#state.accesslogfd),
@ -227,34 +235,46 @@ code_change(_OldVsn, State, _Extra) ->
process(LocalPath, Request) ->
?DEBUG("Requested ~p", [LocalPath]),
try gen_server:call(get_proc_name(Request#request.host), {serve, LocalPath}) of
Result ->
{Code, _, _} = Result,
add_to_log(Code, Request),
Result
{FileSize, Code, Headers, Contents} ->
add_to_log(FileSize, Code, Request),
{Code, Headers, Contents}
catch
exit:{noproc, _} ->
ejabberd_web:error(not_found)
end.
serve(LocalPath, DocRoot) ->
serve(LocalPath, DocRoot, DirectoryIndices) ->
FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
case file:read_file(FileName) of
{ok, FileContents} ->
?DEBUG("Delivering content.", []),
{200,
[{"Server", "ejabberd"},
{"Last-Modified", last_modified(FileName)},
{"Content-Type", content_type(FileName)}],
FileContents};
{error, Error} ->
?DEBUG("Delivering error: ~p", [Error]),
case Error of
eacces -> {403, [], "Forbidden"};
enoent -> {404, [], "Not found"};
_Else -> {404, [], atom_to_list(Error)}
end
case file:read_file_info(FileName) of
{error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND;
{error, eacces} -> ?HTTP_ERR_FORBIDDEN;
{ok, #file_info{type = directory}} -> serve_index(FileName, DirectoryIndices);
{ok, FileInfo} -> serve_file(FileInfo, FileName)
end.
%% Troll through the directory indices attempting to find one which
%% works, if none can be found, return a 404.
serve_index(_FileName, []) ->
?HTTP_ERR_FILE_NOT_FOUND;
serve_index(FileName, [Index | T]) ->
IndexFileName = filename:join([FileName] ++ [Index]),
case file:read_file_info(IndexFileName) of
{error, _Error} -> serve_index(FileName, T);
{ok, #file_info{type = directory}} -> serve_index(FileName, T);
{ok, FileInfo} -> serve_file(FileInfo, IndexFileName)
end.
%% Assume the file exists if we got this far and attempt to read it in
%% and serve it up.
serve_file(FileInfo, FileName) ->
?DEBUG("Delivering: ~s", [FileName]),
{ok, FileContents} = file:read_file(FileName),
{FileInfo#file_info.size,
200, [{"Server", "ejabberd"},
{"Last-Modified", last_modified(FileInfo)},
{"Content-Type", content_type(FileName)}],
FileContents}.
%%----------------------------------------------------------------------
%% Log file
%%----------------------------------------------------------------------
@ -279,16 +299,15 @@ reopen_log(FN, FD) ->
reopen_log(Host) ->
gen_server:cast(get_proc_name(Host), reopen_log).
add_to_log(Code, Request) ->
add_to_log(FileSize, Code, Request) ->
gen_server:cast(get_proc_name(Request#request.host),
{add_to_log, Code, Request}).
{add_to_log, FileSize, Code, Request}).
add_to_log(undefined, _Code, _Request) ->
add_to_log(undefined, _FileSize, _Code, _Request) ->
ok;
add_to_log(File, Code, Request) ->
add_to_log(File, FileSize, Code, Request) ->
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
%% TODO: This IP address conversion supports only IPv4, not IPv6
IP = join(tuple_to_list(element(1, Request#request.ip)), "."),
IP = ip_to_string(element(1, Request#request.ip)),
Path = join(Request#request.path, "/"),
Query = case join(lists:map(fun(E) -> lists:concat([element(1, E), "=", element(2, E)]) end,
Request#request.q), "&") of
@ -297,6 +316,8 @@ add_to_log(File, Code, Request) ->
String ->
[$? | String]
end,
UserAgent = find_header('User-Agent', Request#request.headers, "-"),
Referer = find_header('Referer', Request#request.headers, "-"),
%% Pseudo Combined Apache log format:
%% 127.0.0.1 - - [28/Mar/2007:18:41:55 +0200] "GET / HTTP/1.1" 302 303 "-" "tsung"
%% TODO some fields are harcoded/missing:
@ -304,12 +325,16 @@ add_to_log(File, Code, Request) ->
%% Month should be 3*letter, not integer 1..12
%% Missing time zone = (`+' | `-') 4*digit
%% Missing protocol version: HTTP/1.1
%% Missing size of the object, not including response headers. If no content: "-"
%% Missing Referer HTTP request header
%% Missing User-Agent HTTP request header.
%% For reference: http://httpd.apache.org/docs/2.2/logs.html
io:format(File, "~s - - [~p/~p/~p:~p:~p:~p] \"~s /~s~s\" ~p -1 \"-\" \"-\"~n",
[IP, Day, Month, Year, Hour, Minute, Second, Request#request.method, Path, Query, Code]).
io:format(File, "~s - - [~p/~p/~p:~p:~p:~p] \"~s /~s~s\" ~p ~p ~p ~p~n",
[IP, Day, Month, Year, Hour, Minute, Second, Request#request.method, Path, Query, Code,
FileSize, Referer, UserAgent]).
find_header(Header, Headers, Default) ->
case lists:keysearch(Header, 1, Headers) of
{value, {_, Value}} -> Value;
false -> Default
end.
%%----------------------------------------------------------------------
%% Utilities
@ -340,7 +365,14 @@ content_type(Filename) ->
_Else -> "application/octet-stream"
end.
last_modified(FileName) ->
{ok, FileInfo} = file:read_file_info(FileName),
last_modified(FileInfo) ->
Then = FileInfo#file_info.mtime,
httpd_util:rfc1123_date(Then).
%% Convert IP address tuple to string representation. Accepts either
%% IPv4 or IPv6 address tuples.
ip_to_string(Address) when size(Address) == 4 ->
join(tuple_to_list(Address), ".");
ip_to_string(Address) when size(Address) == 8 ->
Parts = lists:map(fun (Int) -> io_lib:format("~.16B", [Int]) end, tuple_to_list(Address)),
?STRING2LOWER:to_lower(lists:flatten(join(Parts, ":"))).