代码改变世界

mochiweb 源码阅读(二十)

2012-08-25 00:42  rhinovirus  阅读(1972)  评论(1编辑  收藏  举报

  大家好,今天继续上一篇没讲完的mochiweb_request:not_found/1函数:

%% @spec not_found(ExtraHeaders) -> response()
%% @doc Alias for <code>respond({404, [{"Content-Type", "text/plain"}
%% | ExtraHeaders], &lt;&lt;"Not found."&gt;&gt;})</code>.
not_found(ExtraHeaders) ->
    respond({404, [{"Content-Type", "text/plain"} | ExtraHeaders],
             <<"Not found.">>}).

  这个函数就一行代码,调用mochiweb_request:respond/1函数返回404错误,关于这个函数,我们之后还有讲到,到时候再详细看吧。

  回到mochiweb_request:serve_file/3函数,我们看看剩余部分代码:

        RelPath ->
            FullPath = filename:join([DocRoot, RelPath]),
            case filelib:is_dir(FullPath) of
                true ->
                    maybe_redirect(RelPath, FullPath, ExtraHeaders);
                false ->
                    maybe_serve_file(FullPath, ExtraHeaders)
            end

  这里根据mochiweb_util:safe_relative_path/1返回的真实路径RelPath以及DocRoot组合成新的路径:FullPath = filename:join([DocRoot, RelPath]),接着调用函数:filelib:is_dir/1函数,这个函数的erlang doc 地址:http://www.erlang.org/doc/man/filelib.html#is_dir-1,如下图:  

  如果Name指的是目录,则该函数返回true,否则返回false。

  如果为true,则调用mochiweb_request:maybe_redirect/3函数;如果为false,则调用mochiweb_request:maybe_serve_file/2函数

  那么我们先来看下第一个函数:

maybe_redirect([], FullPath, ExtraHeaders) ->
    maybe_serve_file(directory_index(FullPath), ExtraHeaders);

maybe_redirect(RelPath, FullPath, ExtraHeaders) ->
    case string:right(RelPath, 1) of
        "/" ->
            maybe_serve_file(directory_index(FullPath), ExtraHeaders);
        _   ->
            Host = mochiweb_headers:get_value("host", Headers),
            Location = "http://" ++ Host  ++ "/" ++ RelPath ++ "/",
            LocationBin = list_to_binary(Location),
            MoreHeaders = [{"Location", Location},
                           {"Content-Type", "text/html"} | ExtraHeaders],
            Top = <<"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
            "<html><head>"
            "<title>301 Moved Permanently</title>"
            "</head><body>"
            "<h1>Moved Permanently</h1>"
            "<p>The document has moved <a href=\"">>,
            Bottom = <<">here</a>.</p></body></html>\n">>,
            Body = <<Top/binary, LocationBin/binary, Bottom/binary>>,
            respond({301, MoreHeaders, Body})
    end.

  如果RelPath为[],则调用第一个分支:

maybe_redirect([], FullPath, ExtraHeaders) ->
    maybe_serve_file(directory_index(FullPath), ExtraHeaders);

  这里先调用mochiweb_request:directory_index/1函数:

%% This has the same effect as the DirectoryIndex directive in httpd
directory_index(FullPath) ->
    filename:join([FullPath, "index.html"]).

  这个函数就一行代码,调用:filename:join/1拼出目录首页地址,继续回到mochiweb_request:maybe_redirect/3函数第一个分支,看下调用mochiweb_request:maybe_serve_file/2函数

maybe_serve_file(File, ExtraHeaders) ->
    case file:read_file_info(File) of
        {ok, FileInfo} ->
            LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime),
            case get_header_value("if-modified-since") of
                LastModified ->
                    respond({304, ExtraHeaders, ""});
                _ ->
                    case file:open(File, [raw, binary]) of
                        {ok, IoDevice} ->
                            ContentType = mochiweb_util:guess_mime(File),
                            Res = ok({ContentType,
                                      [{"last-modified", LastModified}
                                       | ExtraHeaders],
                                      {file, IoDevice}}),
                            ok = file:close(IoDevice),
                            Res;
                        _ ->
                            not_found(ExtraHeaders)
                    end
            end;
        {error, _} ->
            not_found(ExtraHeaders)
    end.

  这个函数,首先调用file:read_file_info/1函数,erlang doc 地址:http://www.erlang.org/doc/man/file.html#read_file_info-1,如下图:

  大致翻译:

  检索文件信息,如果成功,返回{ok, FileInfo},否则返回{error, Reason}FileInfo是一个file_info记录,在Kernel包含的文件file.hrl中定义。包括下列指令模块调用调用该函数:-include_lib("kernel/include/file.hrl").

  记录定义如下:

-record(file_info,
    {size   :: non_neg_integer(),    % Size of file in bytes.
     type   :: 'device' | 'directory' | 'other' | 'regular' | 'symlink',
     access :: 'read' | 'write' | 'read_write' | 'none',
     atime  :: file:date_time() | integer(), % The local time the file was last read:
                             % {{Year, Mon, Day}, {Hour, Min, Sec}}.
                         % atime, ctime, mtime may also be unix epochs()
     mtime  :: file:date_time() | integer(), % The local time the file was last written.
     ctime  :: file:date_time() | integer(), % The interpretation of this time field
                    % is dependent on operating system.
                    % On Unix it is the last time the file
                    % or the inode was changed.  On Windows,
                    % it is the creation time.
     mode   :: integer(),        % File permissions.  On Windows,
                     % the owner permissions will be
                    % duplicated for group and user.
     links  :: non_neg_integer(),    % Number of links to the file (1 if the
                    % filesystem doesn't support links).
     major_device :: integer(),    % Identifies the file system (Unix),
                     % or the drive number (A: = 0, B: = 1)
                    % (Windows).
     %% The following are Unix specific.
     %% They are set to zero on other operating systems.
     minor_device :: integer(),    % Only valid for devices.
     inode  :: integer(),          % Inode number for file.
     uid    :: integer(),          % User id for owner.
     gid    :: integer()}).            % Group id for owner.

  从上面看,定义的字段还是比较多的,我就不一一说了,大家参照erlang doc 文档理解下。

  继续回到mochiweb_request:maybe_serve_file/2函数,如果正确返回:{ok, FileInfo},则继续正确处理逻辑;如果返回:{error, _},则调用:mochiweb_request:not_found/1函数。

  首先:LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime),这里根据读取到的文件信息中的最后一次写的时间,来生成一个rfc1123 date,erlang doc 地址:http://www.erlang.org/doc/man/httpd_util.html#rfc1123_date-0,如下图:

  大致翻译:rfc1123_date/0返回RFC 1123格式的当前日期。rfc1123_date/1把date格式转为RFC 1123日期格式。

  获取到日期以后,接着调用:mochiweb_request:get_header_value/1函数:

%% @spec get_header_value(K) -> undefined | Value
%% @doc Get the value of a given request header.
get_header_value(K) ->
    mochiweb_headers:get_value(K, Headers).

  这个函数就一行逻辑,调用mochiweb_headers:get_value/2函数,并传递需要的头部属性,以及Headers(该字段值为gb_trees类型)来获取指定头部属性的值:

%% @spec get_value(key(), headers()) -> string() | undefined
%% @doc Return the value of the given header using a case insensitive search.
%%      undefined will be returned for keys that are not present.
get_value(K, T) ->
    case lookup(K, T) of
        {value, {_, V}} ->
            expand(V);
        none ->
            undefined
    end.

  这个函数调用:mochiweb_headers:lookup/2函数,读取对应K的值:

%% @spec lookup(key(), headers()) -> {value, {key(), string()}} | none
%% @doc Return the case preserved key and value for the given header using
%%      a case insensitive search. none will be returned for keys that are
%%      not present.
lookup(K, T) ->
    case gb_trees:lookup(normalize(K), T) of
        {value, {K0, V}} ->
            {value, {K0, expand(V)}};
        none ->
            none
    end.

  其他相关的函数如下:

  mochiweb_headers:normalize/1函数:

normalize(K) when is_list(K) ->
    string:to_lower(K);
normalize(K) when is_atom(K) ->
    normalize(atom_to_list(K));
normalize(K) when is_binary(K) ->
    normalize(binary_to_list(K)).

  mochiweb_headers:expand/1函数:

expand({array, L}) ->
    mochiweb_util:join(lists:reverse(L), ", ");
expand(V) ->
    V.

  这两个也很简单,就不一一说了。

  最后,需要大家先了解下:HTTP的请求头标签 If-Modified-Since,这里有博客园朋友写的一篇文章,大家可以看下:http://www.cnblogs.com/zh2000g/archive/2010/03/22/1692002.html,部分内容:

  

  今天就到这里,下一篇再见。