大家好,这一篇我们接着上一篇最后提到的mochiweb_http:request/2继续来和大家分享mochiweb源码:
request(Socket, Body) -> ok = mochiweb_socket:setopts(Socket, [{active, once}]), receive {Protocol, _, {http_request, Method, Path, Version}} when Protocol == http orelse Protocol == ssl -> ok = mochiweb_socket:setopts(Socket, [{packet, httph}]), headers(Socket, {Method, Path, Version}, [], Body, 0); {Protocol, _, {http_error, "\r\n"}} when Protocol == http orelse Protocol == ssl -> request(Socket, Body); {Protocol, _, {http_error, "\n"}} when Protocol == http orelse Protocol == ssl -> request(Socket, Body); {tcp_closed, _} -> mochiweb_socket:close(Socket), exit(normal); {ssl_closed, _} -> mochiweb_socket:close(Socket), exit(normal); _Other -> handle_invalid_request(Socket) after ?REQUEST_RECV_TIMEOUT -> mochiweb_socket:close(Socket), exit(normal) end.
ok = mochiweb_socket:setopts(Socket, [{active, once}]),首先依然调用上一篇提到的mochiweb_socket:setopts/2函数,来修改Socket配置项,我们在这一行之前增加获取原有配置项的代码,来查看下之前的配置:
Default_Opts = inet:getopts(Socket, [active]), io:format("Default opts: ~p~n", [Default_Opts]),
输出如下:
Default opts: {ok,[{active,false}]}
关于{active, once}这个选项,大家可以参阅《Erlang程序设计》第十四章第14.2.3节混合型模式(半阻塞),下面这段部分摘抄:
在这个模式下,套接字是主动的但是仅仅针对一个消息。在控制进程发过一个消息后,必须显示地调用函数inet:setopts来把它重新激活以便接受下一个消息。在次之前,系统会处于阻塞状态。
也可以参考erlang doc,地址:,如下图:
好了,关于这个选项相信大家应该已经明白,我们继续往下看:
receive
after ?REQUEST_RECV_TIMEOUT
end.
我们从上面知道,如果使用{active, once}选项,一条来自Socket数据消息将被发送到进程。为了得到更多的消息,必须再次调用setopts/2{ active,once}选项。
那么这里就是从控制进程中读取一条消息,而这条消息的格式,根据之前调用的mochiweb_http:loop/2函数:
loop(Socket, Body) -> ok = mochiweb_socket:setopts(Socket, [{packet, http}]), request(Socket, Body).
{packet, http}这个选项将决定返回的消息的格式,如下图:
超文本传输协议。返回数据包的格式HttpPacket在中说明。被动模式的套接字将通过gen_tcp:recv返回{ok, HttpPacket},而{ active,once}的套接字将发送如{http, Socket, HttpPacket}的消息。
而我们从能够知道如下消息格式:
HttpPacket = HttpRequest | HttpResponse | HttpHeader | http_eoh | HttpError
HttpRequest = {http_request, HttpMethod, HttpUri, HttpVersion}
也就是最后,读取到的消息应该满足如下格式:
{http, Socket, {http_request, HttpMethod, HttpUri, HttpVersion}}
那么接下来就可以看下读取消息以后,根据得到的消息的不同格式不同处理:
{Protocol, _, {http_request, Method, Path, Version}} when Protocol == http orelse Protocol == ssl -> ok = mochiweb_socket:setopts(Socket, [{packet, httph}]), headers(Socket, {Method, Path, Version}, [], Body, 0);
这部分就是正确返回,也就是正常的逻辑处理,这部分我们最后来看:
{Protocol, _, {http_error, "\r\n"}} when Protocol == http orelse Protocol == ssl -> request(Socket, Body); {Protocol, _, {http_error, "\n"}} when Protocol == http orelse Protocol == ssl -> request(Socket, Body);
这两种情况,读取返回的是http_error错误,递归调用,继续读取;
{tcp_closed, _} -> mochiweb_socket:close(Socket), exit(normal); {ssl_closed, _} -> mochiweb_socket:close(Socket), exit(normal);
而这种是读取到套接字关闭的处理,调用mochiweb_socket:close/1,关闭套接字,完整代码如下:
close({ssl, Socket}) -> ssl:close(Socket);close(Socket) -> gen_tcp:close(Socket).
下面这部分是其他特殊情况的处理:
_Other -> handle_invalid_request(Socket)
调用mochiweb_http:handle_invalid_request/1函数,从函数命名来看,这个函数是处理无效请求的handle:
-spec handle_invalid_request(term()) -> no_return().handle_invalid_request(Socket) -> handle_invalid_request(Socket, { 'GET', {abs_path, "/"}, {0,9}}, []), exit(normal).-spec handle_invalid_request(term(), term(), term()) -> no_return().handle_invalid_request(Socket, Request, RevHeaders) -> Req = new_request(Socket, Request, RevHeaders), Req:respond({ 400, [], []}), mochiweb_socket:close(Socket), exit(normal).
第一个函数,会简单构造后2个参数,紧接着调用mochiweb_http:handle_invalid_request/3函数,接着调用exit(normal),退出当前进程。
第二个函数,先调用函数mochiweb_http:new_request/3,我们暂且理解这个函数为处理下一个请求,之后我们讲到正确逻辑的时候还会提到,这里也先跳过。接着调用Req:respond/1返回400错误码,再接着就是调用mochiweb_socket:close/1关闭套接字,最后调用exit(normal),退出当前进程。
好了,这一篇就到这里,咱们下一篇继续。
晚安。