mochiweb 源码阅读(十九)
2012-08-23 00:10 rhinovirus 阅读(1796) 评论(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], <<"Not found.">>})</code>. not_found(ExtraHeaders) -> respond({404, [{"Content-Type", "text/plain"} | ExtraHeaders], <<"Not found.">>}).
好了,这一篇就到这里,晚安。
本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名rhinovirus(包含链接http://www.cnblogs.com/rhinovirus/),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系。