代码改变世界

mochiweb 源码阅读(十六)

2012-08-06 23:57  rhinovirus  阅读(1747)  评论(0编辑  收藏  举报

  大家好,几天没跟新这个系列了,忙着考驾照,今天继续来跟大家分享mochiweb源码,上一篇我们讲到了mochiweb_http:handle_invalid_request/1函数,这一篇,我们来看下,正确的逻辑处理,再讲这之前,还有个地方跟大家提下,就是如果mochiweb_http:request/2函数,在使用receive读取消息时,如果发生超时,则是简单的关闭套接字以及退出进程,代码如下:

    after ?REQUEST_RECV_TIMEOUT ->
        mochiweb_socket:close(Socket),
        exit(normal)
    end.

  宏定义如下:

-define(REQUEST_RECV_TIMEOUT, 300000).   %% timeout waiting for request line

  好了,现在我们可以回到正确逻辑了:

        {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);

  这个分支也只有2行代码,很简单,首先调用mochiweb_socket:setopts/2设置Socket选项,这里是:{packet, httph},当设置这个选项时,返回的数据包会是什么样的呢?通过查阅erlang doc,地址:http://www.erlang.org/doc/man/inet.html#setopts-2,如下图:

  大致翻译如下:

    这两类往往是没有必要,socket内部的第一行被读取后,将自动从http/http_bin切换到httph/httph_bin。无论如何,有些场合可能他们是有用的,例如从区块解析数据报。

  关于这个选项,还有如下一段描述:

  大致翻译如下:

    超文本传输协议。数据包返回的格式根据上述HttpPacket。一个包可以是一个请求,响应,一个头或端头标记。无效行返回HttpError

    确认请求的方法和报头域返回原子。其他返回作为字符串。

    当预计一个HttpRequest或者一个HttpResponsehttp协议类型应该只用于第一行。

    下面的调用应该使用httph得到HttpHeaderhttp_eoh返回结束的标志头并开始任何以下消息体。

    该变种http_bin和httph_bin将返回字符串(httpstring)为二进制文件代替列表。

  好了,现在我们已经大概知道了http | httph | http_bin | httph_bin这四个选项在什么情况下使用,回到mochiweb_http:request/2函数,设置完{packet, httph}后,紧接着调用mochiweb_http:headers/5函数:

headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) ->
    %% Too many headers sent, bad request.
    ok = mochiweb_socket:setopts(Socket, [{packet, raw}]),
    handle_invalid_request(Socket, Request, Headers);
headers(Socket, Request, Headers, Body, HeaderCount) ->
    ok = mochiweb_socket:setopts(Socket, [{active, once}]),
    receive
        {Protocol, _, http_eoh} when Protocol == http orelse Protocol == ssl ->
            Req = new_request(Socket, Request, Headers),
            call_body(Body, Req),
            ?MODULE:after_response(Body, Req);
        {Protocol, _, {http_header, _, Name, _, Value}} when Protocol == http orelse Protocol == ssl ->
            headers(Socket, Request, [{Name, Value} | Headers], Body,
                    1 + HeaderCount);
        {tcp_closed, _} ->
            mochiweb_socket:close(Socket),
            exit(normal);
        _Other ->
            handle_invalid_request(Socket, Request, Headers)
    after ?HEADERS_RECV_TIMEOUT ->
        mochiweb_socket:close(Socket),
        exit(normal)
    end.

  这个函数有两个分支,我们先看第一个,当HeaderCount为?MAX_HEADERS时,调用第一个分支,注释:Too many headers sent, bad request,发送太多的头部,坏的请求。

宏定义如下:

-define(MAX_HEADERS, 1000).

  其实这也很好理解,递归调用mochiweb_http:headers/5函数来解析http_header,直到解析到http_eoh为止,如果HeaderCount等于?MAX_HEADERS,则会走第一个分支,从这一行我们知道这个参数会在整个调用过程递增:

        {Protocol, _, {http_header, _, Name, _, Value}} when Protocol == http orelse Protocol == ssl ->
            headers(Socket, Request, [{Name, Value} | Headers], Body,
                    1 + HeaderCount);

  第一个分支的逻辑,修改Socket选项{packet, raw},然后调用函数:mochiweb_http:handle_invalid_request/3处理为无效请求。

  第二个分支和前一篇介绍的mochiweb_http:request/2函数,有很多相同的地方,依然是修改Socket选项为{active, once},接着使用receive读取一条消息:

  消息格式说明:

  {http, Socket, HttpPacket}

  HttpPacket = HttpHeader | http_eoh

  HttpHeader = {http_header, integer(), HttpField, Reserved=term(), Value=HttpString}

  只要不是返回{http, Socket, http_eoh}就递归调用mochiweb_http:headers/5函数继续解析头部,并且保存{Name, Value}到Headers列表头部:

headers(Socket, Request, [{Name, Value} | Headers], Body,
                    1 + HeaderCount);

  而当解析得到{http, Socket, http_eoh}时,处理如下:

        {Protocol, _, http_eoh} when Protocol == http orelse Protocol == ssl ->
            Req = new_request(Socket, Request, Headers),
            call_body(Body, Req),
            ?MODULE:after_response(Body, Req);

  这里就三行代码,首先调用mochiweb_http:new_request/3获取Req对象,接着调用mochiweb_http:call_body/2函数,最后调用该模块下的?MODULE:after_response/2函数。

  好了,这一篇就到这里,下一篇我们将继续从这三个函数来和大家分享。

  最后,谢谢大家的支持,晚安。