代码改变世界

mochiweb 源码阅读(十九)

2012-08-23 00:10  rhinovirus  阅读(1794)  评论(0编辑  收藏  举报

  大家好,好几天没跟新了,今天继续来和大家分享mochiweb源码,上一篇,我们看到了mochiweb_request:get(path)分支,这一篇我们依然回到mochiweb_example_web:loop/2继续往下:

loop(Req, DocRoot) ->
    "/" ++ Path = Req:get(path),
    try
        case Req:get(method) of
            Method when Method =:= 'GET'; Method =:= 'HEAD' ->
                case Path of
                    _ ->
                        Req:serve_file(Path, DocRoot)
                end;
            'POST' ->
                case Path of
                    _ ->
                        Req:not_found()
                end;
            _ ->
                Req:respond({501, [], []})
        end
    catch
        Type:What ->
            Report = ["web request failed",
                      {path, Path},
                      {type, Type}, {what, What},
                      {trace, erlang:get_stacktrace()}],
            error_logger:error_report(Report),
            %% NOTE: mustache templates need \ because they are not awesome.
            Req:respond({500, [{"Content-Type", "text/plain"}],
                         "request failed, sorry\n"})
    end.

  在调用mochiweb_request:get(path)分支获取了Path以后,调用mochiweb_request:get(method)分支,我们看下这个函数的具体实现:

get(method) ->
    Method;

  这里仅仅是返回Method变量的值,表示请求的类型,如果Method的值为 'GET' 或者 'HEAD'时,无论Path的值是什么,都调用:mochiweb_request:serve_file(Path, DocRoot)函数;如果Method的值为'POST',无论Path的值是什么,都调用:mochiweb_request:not_found/0函数;其他值,则调用:mochiweb_request:respond({501, [], []})函数,返回501错误码。

  那么接下来,我们先看下函数:mochiweb_request:serve_file/2

%% @spec serve_file(Path, DocRoot) -> Response
%% @doc Serve a file relative to DocRoot.
serve_file(Path, DocRoot) ->
    serve_file(Path, DocRoot, []).

%% @spec serve_file(Path, DocRoot, ExtraHeaders) -> Response
%% @doc Serve a file relative to DocRoot.
serve_file(Path, DocRoot, ExtraHeaders) ->
    case mochiweb_util:safe_relative_path(Path) of
        undefined ->
            not_found(ExtraHeaders);
        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
    end.

  该函数调用mochiweb_request:serve_file/3函数,并传递最后一个参数为[];而mochiweb_request:serve_file/3函数紧接着调用mochiweb_util:safe_relative_path/1函数,代码如下:

%% @spec safe_relative_path(string()) -> string() | undefined
%% @doc Return the reduced version of a relative path or undefined if it
%%      is not safe. safe relative paths can be joined with an absolute path
%%      and will result in a subdirectory of the absolute path.
safe_relative_path("/" ++ _) ->
    undefined;
safe_relative_path(P) ->
    safe_relative_path(P, []).

safe_relative_path("", Acc) ->
    case Acc of
        [] ->
            "";
        _ ->
            string:join(lists:reverse(Acc), "/")
    end;
safe_relative_path(P, Acc) ->
    case partition(P, "/") of
        {"", "/", _} ->
            %% /foo or foo//bar
            undefined;
        {"..", _, _} when Acc =:= [] ->
            undefined;
        {"..", _, Rest} ->
            safe_relative_path(Rest, tl(Acc));
        {Part, "/", ""} ->
            safe_relative_path("", ["", Part | Acc]);
        {Part, _, Rest} ->
            safe_relative_path(Rest, [Part | Acc])
    end.

  测试代码:

safe_relative_path_test() ->
    "foo" = safe_relative_path("foo"),
    "foo/" = safe_relative_path("foo/"),
    "foo" = safe_relative_path("foo/bar/.."),
    "bar" = safe_relative_path("foo/../bar"),
    "bar/" = safe_relative_path("foo/../bar/"),
    "" = safe_relative_path("foo/.."),
    "" = safe_relative_path("foo/../"),
    undefined = safe_relative_path("/foo"),
    undefined = safe_relative_path("../foo"),
    undefined = safe_relative_path("foo/../.."),
    undefined = safe_relative_path("foo//"),
    ok.

  我们来看下mochiweb_util:safe_relative_path/1函数具体逻辑:

safe_relative_path("/" ++ _) ->
    undefined;

  如果传入的参数是:"/" ++ _ 组合而成,返回undefined,例如:undefined= safe_relative_path("/foo");

  否则调用mochiweb_util:safe_relative_path/2函数,并传递第二个参数为:[],代码如下:

safe_relative_path(P) ->
    safe_relative_path(P, []).

  mochiweb_util:safe_relative_path/2函数也是两个分支:

  首先如果传递的第一个参数为:"",则调用第一个分支,否则调用第二个分支。

safe_relative_path("", Acc) ->
    case Acc of
        [] ->
            "";
        _ ->
            string:join(lists:reverse(Acc), "/")
    end;

  如果Acc为:"",则返回:"",否则返回:string:join(lists:reverse(Acc), "/"),这个就不细说了,相信大家都能看懂;

  第二个分支,首先调用函数:mochiweb_util:partition/2,第一个参数为P,第二个参数为:"/",然后根据返回值的不同分别处理:

  返回:{"", "/", _},则直接返回:undefined;

  返回:{"..", _, _} ,并且 Acc =:= [] ,也直接返回:undefined;

  剩下三种情况都是递归调用自身,只不过是参数的处理不同:

        {"..", _, Rest} ->
            safe_relative_path(Rest, tl(Acc));
        {Part, "/", ""} ->
            safe_relative_path("", ["", Part | Acc]);
        {Part, _, Rest} ->
            safe_relative_path(Rest, [Part | Acc])

  这里我们只关注内置函数:tl/1,erlang doc 地址如下:http://www.erlang.org/doc/man/erlang.html#tl-1,如下图:

  返回列表List1的尾部,减去第一个元素;失败:badarg如果列表是空列表[ ]。

  看下mochiweb_util:partition/2代码:

%% @spec partition(String, Sep) -> {String, [], []} | {Prefix, Sep, Postfix}
%% @doc Inspired by Python 2.5's str.partition:
%%      partition("foo/bar", "/") = {"foo", "/", "bar"},
%%      partition("foo", "/") = {"foo", "", ""}.
partition(String, Sep) ->
    case partition(String, Sep, []) of
        undefined ->
            {String, "", ""};
        Result ->
            Result
    end.

partition("", _Sep, _Acc) ->
    undefined;
partition(S, Sep, Acc) ->
    case partition2(S, Sep) of
        undefined ->
            [C | Rest] = S,
            partition(Rest, Sep, [C | Acc]);
        Rest ->
            {lists:reverse(Acc), Sep, Rest}
    end.

partition2(Rest, "") ->
    Rest;
partition2([C | R1], [C | R2]) ->
    partition2(R1, R2);
partition2(_S, _Sep) ->
    undefined.

  测试代码如下:

partition_test() ->
    {"foo", "", ""} = partition("foo", "/"),
    {"foo", "/", "bar"} = partition("foo/bar", "/"),
    {"foo", "/", ""} = partition("foo/", "/"),
    {"", "/", "bar"} = partition("/bar", "/"),
    {"f", "oo/ba", "r"} = partition("foo/bar", "oo/ba"),
    ok.

  这个函数就不详细讲了,没有什么复杂的东西,就是路径处理的逻辑。

  回到mochiweb_request:serve_file/3函数,如果mochiweb_util:safe_relative_path/1函数返回:undefined,则调用:mochiweb_request:not_found/1函数,这里:ExtraHeaders = [],该函数代码如下:

%% @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.">>}).

  好了,这一篇就到这里,晚安。