上篇主要梳理的是mochiweb整体连接的处理机制,这篇主要梳理mochiweb如何和外界进行交互
接下来我们回到mochiweb_acceptor文件中init/4函数,我们已经走完了do_accept/2函数路径,接下来继续往下看,这里为了方便查看,再贴一遍mochiweb_acceptor:init/4代码
init(Server, Listen, Loop, Opts) ->
case catch do_accept(Server, Listen) of
{ok, Socket} ->
call_loop(Loop, Socket, Opts);
{error, Err} when Err =:= closed orelse
Err =:= esslaccept orelse
Err =:= timeout ->
exit(normal);
Other ->
%% Mitigate out of file descriptor scenario by sleeping for a
%% short time to slow error rate
case Other of
{error, emfile} ->
receive
after ?EMFILE_SLEEP_MSEC ->
ok
end;
_ ->
ok
end,
error_logger:error_report(
[{application, mochiweb},
"Accept failed error",
lists:flatten(io_lib:format("~p", [Other]))]),
exit({error, accept_failed})
end.
执行完do_accept/2若执行正常则执行call_loop/3,我们现在从call_loop/3开始分析
call_loop({M, F}, Socket, Opts) ->
M:F(Socket, Opts);
call_loop({M, F, [A1]}, Socket, Opts) ->
M:F(Socket, Opts, A1);
call_loop({M, F, A}, Socket, Opts) ->
erlang:apply(M, F, [Socket, Opts | A]);
call_loop(Loop, Socket, Opts) ->
Loop(Socket, Opts).
这里可以看到call_loop四次重载仅仅存在第一个参数格式的不同或者为空,可以查看源码阅读2得知Loop应为{mochiweb_http,loop,[{keepalive,loop}]}由此可知会调用call_loop第二次重载,带入参数,则会执行mochiweb_http:loop(Socket,Opts,[{keepalive,loop}],接下来查看mochiweb_http:loop/3
loop(Socket, Opts, Body) ->
ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, http}])),
request(Socket, Opts, Body).
request(Socket, Opts, Body) ->
ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{active, once}])),
receive
{Protocol, _, {http_request, Method, Path, Version}} when Protocol == http orelse Protocol == ssl ->
ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, httph}])),
headers(Socket, Opts, {Method, Path, Version}, [], Body, 0);
{Protocol, _, {http_error, "\r\n"}} when Protocol == http orelse Protocol == ssl ->
request(Socket, Opts, Body);
{Protocol, _, {http_error, "\n"}} when Protocol == http orelse Protocol == ssl ->
request(Socket, Opts, Body);
{tcp_closed, _} ->
mochiweb_socket:close(Socket),
exit(normal);
{ssl_closed, _} ->
mochiweb_socket:close(Socket),
exit(normal)
after ?REQUEST_RECV_TIMEOUT ->
mochiweb_socket:close(Socket),
exit(normal)
end.
loop/2首先调用mochiweb_socket:setopts/2针对是否使用ssl协议,采取不同的处理,修改Socket相关配置为{packet,http},设定http协议打包配置,然后调用mochiweb_http:request/3,在request/3中再次调用setopts/2修改Socket相关配置为{active,noce},{active,noce}相关介绍可查看《Erlang程序设计(第二版)》第十七章套接字编程,混合消息接收(部分阻塞式)内容request/3针对不同接收信息执行不同处理,超时或退出信息执行Socket关闭操作,http_error信息递归调用request/3,http_request信息则调用mochiweb_http:headers/6解析http头信息
headers(Socket, Opts, Request, Headers, _Body, ?MAX_HEADERS) ->
%% Too many headers sent, bad request.
ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, raw}])),
handle_invalid_request(Socket, Opts, Request, Headers);
headers(Socket, Opts, Request, Headers, Body, HeaderCount) ->
ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{active, once}])),
receive
{Protocol, _, http_eoh} when Protocol == http orelse Protocol == ssl ->
Req = new_request(Socket, Opts, Request, Headers),
call_body(Body, Req),
?MODULE:after_response(Body, Req);
{Protocol, _, {http_header, _, Name, _, Value}} when Protocol == http orelse Protocol == ssl ->
headers(Socket, Opts, Request, [{Name, Value} | Headers], Body,
1 + HeaderCount);
{tcp_closed, _} ->
mochiweb_socket:close(Socket),
exit(normal);
{tcp_error, _, emsgsize} = Other ->
handle_invalid_msg_request(Other, Socket, Opts, Request, Headers)
after ?HEADERS_RECV_TIMEOUT ->
mochiweb_socket:close(Socket),
exit(normal)
end.
从上可知headers分为两个分支,若HeaderCount==?MAX_HEADERS时进入第一个分支,由注释可知,头文件信息过多,判定为无效连接headers第二分支则是循环解析http协议头,提取相关Header元素,直到解析完毕(http_eoh),然后调用mochiweb_http:new_request/4创建一个request对象,并调用mochiweb_http:call_body/2,最后在调用mochiweb_http:after_response/2
这里补充一下这里获取的消息格式(HttpRequest,HttpResponse在http协议中只应用与第一行-request/3已处理)
{http,Socket,HttpPacket}
HttpPacekt=HttpHeader|http_eoh
HttpHeader={http_header,integer(),HttpField,Reserved::term(),Value::HttpString}
首先我们一起看一下mochiweb_http:new_request/4
new_request(Socket, Opts, Request, RevHeaders) ->
ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, raw}])),
mochiweb:new_request({Socket, Opts, Request, lists:reverse(RevHeaders)}).
转入mochiweb:new_request/1
new_request({Socket, {Method, HttpUri, Version}, Headers}) ->
new_request({Socket, [], {Method, HttpUri, Version}, Headers});
new_request({Socket, Opts, {Method, HttpUri, Version}, Headers}) ->
mochiweb_request:new(Socket,
Opts,
Method,
uri(HttpUri),
Version,
mochiweb_headers:make(Headers)).
这里mochiweb_headers:make/1主要作用是存储headers中相关元素,使用到gb_trees(General balanced trees)模块,可自行查看手册
接下来继续跟踪mochiweb_request:new/6
接下来继续跟踪mochiweb_request:new/6
%% @spec new(Socket, Method, RawPath, Version, headers()) -> request()
%% @doc Create a new request instance.
new(Socket, Method, RawPath, Version, Headers) ->
new(Socket, [], Method, RawPath, Version, Headers).
%% @spec new(Socket, Opts, Method, RawPath, Version, headers()) -> request()
%% @doc Create a new request instance.
new(Socket, Opts, Method, RawPath, Version, Headers) ->
{?MODULE, [Socket, Opts, Method, RawPath, Version, Headers]}.
从注释可知,这里new/6函数是创建一个request实例并返回
Req={mochiweb_request,[Socket,Opts,Method,RawPath,Version,Headers]}
然后回到mochiweb_http:headers/6继续执行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).
call_body传入的第一个参数从上文可知为[{keepalive,loop}],然后直接调用keepalive:loop(Req),keepalive属于自定义模块,自定义loop可以针对不同的情况对请求进行处理响应,有兴趣的可以自行了解一下最后查看mochiweb_http:after_response/2,终于到要到最后了,我已经头晕目眩了,不知道还有没有人坚持看到这里
after_response(Body, Req) ->
Socket = Req:get(socket),
case Req:should_close() of
true ->
mochiweb_socket:close(Socket),
exit(normal);
false ->
Req:cleanup(),
erlang:garbage_collect(),
?MODULE:loop(Socket, mochiweb_request:get(opts, Req), Body)
end.
Req:get(socket)实际上会调用mochiweb_request/2,这种用法称为参数化的Module(Parameterized Module),这里不做详细梳理这里可以做个测试:
通过报错信息可以得知Req:get(1)调用的是request:get/2,通过debuger可以发现实际执行的是request:get(1,[1,2,3]),此外还有一点需要注意的是,默认参数必须为元组即模块实例{?MODULE,[....]},这样针对不同模块实例可产生多个对象