大家好,这两天简单测试了下 erlang-mysql-driver,erlang-mysql-driver 是 MySQL 的 Erlang 语言驱动程序。代码可通过 SVN 获取:
svn checkout http://erlang-mysql-driver.googlecode.com/svn/trunk/ erlang-mysql-driver-read-only
以后再分享给大家,今天继续来看mochiweb源码,依然是看下面几行:
{Protocol, _, http_eoh} when Protocol == http orelse Protocol == ssl -> Req = new_request(Socket, Request, Headers), call_body(Body, Req), ?MODULE:after_response(Body, Req);
上一篇,我们看完了:Req = new_request(Socket, Request, Headers),这一行,今天继续往下,看下:call_body(Body, Req),这行,这里调用:mochiweb_http:call_body/2函数:
call_body({M, F, A}, Req) -> erlang:apply(M, F, [Req | A]); call_body({M, F}, Req) -> M:F(Req); call_body(Body, Req) -> Body(Req).
这里我们来回忆下Body的定义,结合下上文,回忆下:
mochiweb_http:parse_options/1函数:
parse_options(Options) -> {loop, HttpLoop} = proplists:lookup(loop, Options), Loop = {?MODULE, loop, [HttpLoop]}, Options1 = [{loop, Loop} | proplists:delete(loop, Options)], mochilists:set_defaults(?DEFAULTS, Options1).
这里HttpLoop就是Body,而这个参数是获取配置文件中loop的值,也就是在mochiweb_example_web:start/1函数定义:
start(Options) -> {DocRoot, Options1} = get_option(docroot, Options), Loop = fun (Req) -> ?MODULE:loop(Req, DocRoot) end, Profile_fun = fun (Proplist) -> io:format("Proplist = ~p~n", [Proplist]) end, mochiweb_http:start([{name, ?MODULE}, {loop, Loop}, {profile_fun, Profile_fun} | Options1]).
我们可以看到,这个匿名函数,接受一个参数Req,然后调用?MODULE:loop/2函数,也就是:mochiweb_example_web:loop/2函数,注意:第二个参数的值为:DocRoot,这种用法通常称之为:闭包。大家可以查看《Erlang/OTP并发编程实战》第二章,第2.7.2小节,关于匿名fun函数中闭包的内容。
不知道大家能不能看出接下来mochiweb_http:call_body/2函数会调用第几个分支了吗?
答案是,第三个分支,假如我们希望函数调用第二个分支,大家知道怎么去修改吗?
正确答案如下:
start(Options) -> {DocRoot, Options1} = get_option(docroot, Options), Loop = fun (Req) -> ?MODULE:loop(Req, DocRoot) end, Profile_fun = fun (Proplist) -> io:format("Proplist = ~p~n", [Proplist]) end, mochiweb_http:start([{name, ?MODULE}, {loop, {?MODULE, Loop}}, {profile_fun, Profile_fun} | Options1]).
我修改loop配置项值为:{loop, {?MODULE, Loop}},那么它会匹配第二分支:
call_body({M, F}, Req) ->
M:F(Req);
当然这里并不能简单的这样修改,因为这种调用函数的方法,会破坏闭包。
DocRoot这个参数的值,定义如下:
P = mochiweb_example_deps:local_path(["priv", "www"])
值为:P = "/home/administrator/workplace/mochiweb_example/priv/www",最后我们来看下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.
我们来依次看下这个函数的逻辑:
"/" ++ Path = Req:get(path),这一行,首先Req是调用mochiweb_request:new/5返回的实例,接着用实例去调用调用get/1函数,匹配的分支如下:
get(path) -> case erlang:get(?SAVE_PATH) of undefined -> {Path0, _, _} = mochiweb_util:urlsplit_path(RawPath), Path = mochiweb_util:unquote(Path0), put(?SAVE_PATH, Path), Path; Cached -> Cached end;
这个函数,首先调用erlang:get/1,同样,查找erlang doc,地址:http://www.erlang.org/doc/man/erlang.html#get-1,如下图:
很简单,我就不说了,大家如果不明白,还可以参考《Erlang程序设计》第5章,5.1.18小节关于进程字典的用法。
有个细节,注意下,不知道大家知道为什么erlang:get/1没有用get/1,而put/2却没有加erlang模块名吗?这里我把erlang模块名去了,保存,提示如下信息:
原因是:erlang:get/1已经自动导入了,而该模块下又定义了get/1函数,所以如果简写就会有问题,而如果我们给put/2函数加上erlang模块名,是没有问题的。
?SAVE_PATH定义如下:
-define(SAVE_PATH, mochiweb_request_path).
这个函数,首先判断:mochiweb_request_path这个原子是否存在于进程字典中,如果不存在,则处理RawPath相关逻辑后,存入进程字典,并返回该值;如果存在,则返回缓存的值。
这个RawPath,为该模块的实例保存的值,接下来我们看下这两个函数:
mochiweb_util:urlsplit_path/1函数:
%% @spec urlsplit_path(Url) -> {Path, Query, Fragment} %% @doc Return a 3-tuple, does not expand % escapes. Only supports HTTP style %% paths. urlsplit_path(Path) -> urlsplit_path(Path, []). urlsplit_path("", Acc) -> {lists:reverse(Acc), "", ""}; urlsplit_path("?" ++ Rest, Acc) -> {Query, Fragment} = urlsplit_query(Rest), {lists:reverse(Acc), Query, Fragment}; urlsplit_path("#" ++ Rest, Acc) -> {lists:reverse(Acc), "", Rest}; urlsplit_path([C | Rest], Acc) -> urlsplit_path(Rest, [C | Acc]).
测试用例代码如下:
urlsplit_path_test() -> {"/foo/bar", "", ""} = urlsplit_path("/foo/bar"), {"/foo", "baz", ""} = urlsplit_path("/foo?baz"), {"/foo", "", "bar?baz"} = urlsplit_path("/foo#bar?baz"), {"/foo", "", "bar?baz#wibble"} = urlsplit_path("/foo#bar?baz#wibble"), {"/foo", "bar", "baz"} = urlsplit_path("/foo?bar#baz"), {"/foo", "bar?baz", "baz"} = urlsplit_path("/foo?bar?baz#baz"), ok.
mochiweb_util:unquote/1函数:
%% @spec unquote(string() | binary()) -> string() %% @doc Unquote a URL encoded string. unquote(Binary) when is_binary(Binary) -> unquote(binary_to_list(Binary)); unquote(String) -> qs_revdecode(lists:reverse(String)).
测试用例代码如下:
unquote_test() -> ?assertEqual("foo bar", unquote("foo+bar")), ?assertEqual("foo bar", unquote("foo%20bar")), ?assertEqual("foo\r\n", unquote("foo%0D%0A")), ?assertEqual("foo\r\n", unquote(<<"foo%0D%0A">>)), ok.
关于这两个函数,我不打算详细解释,大家看下测试用例就明白了。
好了,这一篇就到这里,我们下一篇再见,谢谢。