代码改变世界

mochiweb 源码阅读(十七)

2012-08-11 02:37  rhinovirus  阅读(2040)  评论(2编辑  收藏  举报

  大家好,今天周五,继续来和大家一起分享mochiweb源码。最近我也是在看百度连城翻译的《Erlang/OTP并发编程实战》,其中第11章:为缓存添加HTTP接口,有这本书的朋友可以翻看下,这里介绍的内容,有助于大家理解mochiweb源码。

  上一篇,我们讲到了mochiweb_http:headers/5函数,当所有协议头解析完毕后会有一个空行,它标志着报文头部的结束,剩下的便是消息正文。

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

  代码中第一个分支就是当读取到空行时,继续处理消息正文,第二个分支则是解析每一个协议头。

  接下来我们首先看下:mochiweb_http:new_request/3函数:

new_request(Socket, Request, RevHeaders) ->
    ok = mochiweb_socket:setopts(Socket, [{packet, raw}]),
    mochiweb:new_request({Socket, Request, lists:reverse(RevHeaders)}).

  这个函数首先修改socket选项{packet, raw},不设置消息打包规则,如果{packet, 1|2|4},则表示每一个包都会带上一个N(1,2或4)字节长的长度计数。

  该函数接着调用mochiweb:new_request/1函数:

%% @spec new_request({Socket, Request, Headers}) -> MochiWebRequest
%% @doc Return a mochiweb_request data structure.
new_request({Socket, {Method, {abs_path, Uri}, Version}, Headers}) ->
    mochiweb_request:new(Socket,
                         Method,
                         Uri,
                         Version,
                         mochiweb_headers:make(Headers));
% this case probably doesn't "exist".
new_request({Socket, {Method, {absoluteURI, _Protocol, _Host, _Port, Uri},
                      Version}, Headers}) ->
    mochiweb_request:new(Socket,
                         Method,
                         Uri,
                         Version,
                         mochiweb_headers:make(Headers));
%% Request-URI is "*"
%% From http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
new_request({Socket, {Method, '*'=Uri, Version}, Headers}) ->
    mochiweb_request:new(Socket,
                         Method,
                         Uri,
                         Version,
                         mochiweb_headers:make(Headers)).

  这个函数一共有三个分支,但是每一个都是匹配对应的参数,紧接着调用mochiweb_request:new/5函数,但是这里注意,我在mochiweb_request模块导出的函数列表中,并没有发现对应的函数,那么这究竟是怎么回事呢?

  这种用法称之为:参数化的Module(Parameterized Module),下面是关于这个用法的相关文章:

  http://mryufeng.iteye.com/blog/477376

  http://erlangdisplay.iteye.com/blog/315401

  http://www.erlangsir.com/2011/04/01/%E5%8F%82%E6%95%B0%E5%8C%96%E7%9A%84module/

  http://www.cnblogs.com/me-sa/archive/2012/02/16/Erlang0037.html

  这里注意mochiweb_request模块定义:

-module(mochiweb_request, [Socket, Method, RawPath, Version, Headers]).

  关于这个用法,希望大家认真看下上面几篇文章,就能够明白了。

  这里我们还需要看下:mochiweb_headers:make/1函数:

%% @spec make(headers() | [{key(), value()}]) -> headers()
%% @doc Construct a headers() from the given list.
make(L) when is_list(L) ->
    from_list(L);
%% assume a non-list is already mochiweb_headers.
make(T) ->
    T.

  如果L为列表,则调用函数mochiweb_headers:from_list/1函数:

%% @spec from_list([{key(), value()}]) -> headers()
%% @doc Construct a headers() from the given list.
from_list(List) ->
    lists:foldl(fun ({K, V}, T) -> insert(K, V, T) end, empty(), List).

  mochiweb_headers:empty/0函数

%% @type headers().
%% @type key() = atom() | binary() | string().
%% @type value() = atom() | binary() | string() | integer().

%% @spec empty() -> headers()
%% @doc Create an empty headers structure.
empty() ->
    gb_trees:empty().

  mochiweb_headers:insert/3函数

%% @spec insert(key(), value(), headers()) -> headers()
%% @doc Insert the pair into the headers, merging with any pre-existing key.
%%      A merge is done with Value = V0 ++ ", " ++ V1.
insert(K, V, T) ->
    K1 = normalize(K),
    V1 = any_to_list(V),
    try gb_trees:insert(K1, {K, V1}, T)
    catch
        error:{key_exists, _} ->
            {K0, V0} = gb_trees:get(K1, T),
            V2 = merge(K1, V1, V0),
            gb_trees:update(K1, {K0, V2}, T)
    end.

  mochiweb_headers:normalize/1函数

normalize(K) when is_list(K) ->
    string:to_lower(K);
normalize(K) when is_atom(K) ->
    normalize(atom_to_list(K));
normalize(K) when is_binary(K) ->
    normalize(binary_to_list(K)).

  mochiweb_headers:any_to_list/1函数

any_to_list(V) when is_list(V) ->
    V;
any_to_list(V) when is_atom(V) ->
    atom_to_list(V);
any_to_list(V) when is_binary(V) ->
    binary_to_list(V);
any_to_list(V) when is_integer(V) ->
    integer_to_list(V).

  这几个函数都比较简单,我们重点看下:gb_trees这个模块的作用,erlang doc地址:http://www.erlang.org/doc/man/gb_trees.html,也可以参考坚强2002同学的这篇文章:http://www.cnblogs.com/me-sa/archive/2012/06/23/erlang-gb_trees.html

  这里注意:

  delete/2delete_any/2函数的区别是,如果key不在树中,调用delete/2会导致崩溃,而delete_any/2什么也不做返回新树

  enter/3insert/3函数的区别是,如果key在树中存在,调用insert/3会导致崩溃,则enter/3则跟新树中的值。

  get/2调用该函数根据Key检索中存储的值假设,关键是目前在树中否则崩溃

  最后得到的数据如下:

< Headers = [{'Host',"127.0.0.1:8080"},
             {'User-Agent',"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1"},
             {'Accept',"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
             {'Accept-Language',"zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3"},
             {'Accept-Encoding',"gzip, deflate"},
             {'Connection',"keep-alive"},
             {'If-Modified-Since',"Mon, 09 Jul 2012 15:17:42 GMT"},
             {'Cache-Control',"max-age=0"}]

< H = {8,
       {"host",
        {'Host',"127.0.0.1:8080"},
        {"accept",
         {'Accept',"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
         nil,
         {"accept-language",
          {'Accept-Language',"zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3"},
          {"accept-encoding",{'Accept-Encoding',"gzip, deflate"},nil,nil},
          {"connection",
           {'Connection',"keep-alive"},
           {"cache-control",{'Cache-Control',"max-age=0"},nil,nil},
           nil}}},
        {"user-agent",
         {'User-Agent',"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1"},
         {"if-modified-since",
          {'If-Modified-Since',"Mon, 09 Jul 2012 15:17:42 GMT"},
          nil,nil},
         nil}}}

  好了,这一篇就到这里,晚安。