cowboy源码分析(二)

接 cowboy源码分析(一)

 

下面我们重点看看cowboy_protocol.erl代码

-module(cowboy_protocol).

%% API.
-export([start_link/4]).

%% Internal.
-export([init/4]).
-export([parse_request/3]).
-export([resume/6]).

-type opts() :: [{compress, boolean()}
    | {env, cowboy_middleware:env()}
    | {max_empty_lines, non_neg_integer()}
    | {max_header_name_length, non_neg_integer()}
    | {max_header_value_length, non_neg_integer()}
    | {max_headers, non_neg_integer()}
    | {max_keepalive, non_neg_integer()}
    | {max_request_line_length, non_neg_integer()}
    | {middlewares, [module()]}
    | {onrequest, cowboy:onrequest_fun()}
    | {onresponse, cowboy:onresponse_fun()}
    | {timeout, timeout()}].
-export_type([opts/0]).

-record(state, {
    socket :: inet:socket(),
    transport :: module(),
    middlewares :: [module()],
    compress :: boolean(),
    env :: cowboy_middleware:env(),
    onrequest :: undefined | cowboy:onrequest_fun(),
    onresponse = undefined :: undefined | cowboy:onresponse_fun(),
    max_empty_lines :: non_neg_integer(),
    req_keepalive = 1 :: non_neg_integer(),
    max_keepalive :: non_neg_integer(),
    max_request_line_length :: non_neg_integer(),
    max_header_name_length :: non_neg_integer(),
    max_header_value_length :: non_neg_integer(),
    max_headers :: non_neg_integer(),
    timeout :: timeout(),
    until :: non_neg_integer() | infinity
}).

-include_lib("cowlib/include/cow_inline.hrl").

%% API.

-spec start_link(ranch:ref(), inet:socket(), module(), opts()) -> {ok, pid()}.
start_link(Ref, Socket, Transport, Opts) ->
    Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
    {ok, Pid}.

%% Internal.

%% Faster alternative to proplists:get_value/3.
get_value(Key, Opts, Default) ->
    case lists:keyfind(Key, 1, Opts) of
        {_, Value} -> Value;
        _ -> Default
    end.

-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok.
init(Ref, Socket, Transport, Opts) ->
    Compress = get_value(compress, Opts, false),
    MaxEmptyLines = get_value(max_empty_lines, Opts, 5),
    MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64),
    MaxHeaderValueLength = get_value(max_header_value_length, Opts, 4096),
    MaxHeaders = get_value(max_headers, Opts, 100),
    MaxKeepalive = get_value(max_keepalive, Opts, 100),
    MaxRequestLineLength = get_value(max_request_line_length, Opts, 4096),
    Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]),
    Env = [{listener, Ref}|get_value(env, Opts, [])],
    OnRequest = get_value(onrequest, Opts, undefined),
    OnResponse = get_value(onresponse, Opts, undefined),
    Timeout = get_value(timeout, Opts, 5000),
    ok = ranch:accept_ack(Ref),
    wait_request(<<>>, #state{socket=Socket, transport=Transport,
        middlewares=Middlewares, compress=Compress, env=Env,
        max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive,
        max_request_line_length=MaxRequestLineLength,
        max_header_name_length=MaxHeaderNameLength,
        max_header_value_length=MaxHeaderValueLength, max_headers=MaxHeaders,
        onrequest=OnRequest, onresponse=OnResponse,
        timeout=Timeout, until=until(Timeout)}, 0).

-spec until(timeout()) -> non_neg_integer() | infinity.
until(infinity) ->
    infinity;
until(Timeout) ->
    {Me, S, Mi} = os:timestamp(),
    Me * 1000000000 + S * 1000 + Mi div 1000 + Timeout.

%% Request parsing.
%%
%% The next set of functions is the request parsing code. All of it
%% runs using a single binary match context. This optimization ends
%% right after the header parsing is finished and the code becomes
%% more interesting past that point.

-spec recv(inet:socket(), module(), non_neg_integer() | infinity)
    -> {ok, binary()} | {error, closed | timeout | atom()}.
recv(Socket, Transport, infinity) ->
    Transport:recv(Socket, 0, infinity);
recv(Socket, Transport, Until) ->
    {Me, S, Mi} = os:timestamp(),
    Now = Me * 1000000000 + S * 1000 + Mi div 1000,
    Timeout = Until - Now,
    if    Timeout < 0 ->
            {error, timeout};
        true ->
            Transport:recv(Socket, 0, Timeout)
    end.

-spec wait_request(binary(), #state{}, non_neg_integer()) -> ok.
wait_request(Buffer, State=#state{socket=Socket, transport=Transport,
        until=Until}, ReqEmpty) ->
    case recv(Socket, Transport, Until) of
        {ok, Data} ->
            parse_request(<< Buffer/binary, Data/binary >>, State, ReqEmpty);
        {error, _} ->
            terminate(State)
    end.

-spec parse_request(binary(), #state{}, non_neg_integer()) -> ok.
%% Empty lines must be using \r\n.
parse_request(<< $\n, _/binary >>, State, _) ->
    error_terminate(400, State);
%% We limit the length of the Request-line to MaxLength to avoid endlessly
%% reading from the socket and eventually crashing.
parse_request(Buffer, State=#state{max_request_line_length=MaxLength,
        max_empty_lines=MaxEmpty}, ReqEmpty) ->
    case match_eol(Buffer, 0) of
        nomatch when byte_size(Buffer) > MaxLength ->
            error_terminate(414, State);
        nomatch ->
            wait_request(Buffer, State, ReqEmpty);
        1 when ReqEmpty =:= MaxEmpty ->
            error_terminate(400, State);
        1 ->
            << _:16, Rest/binary >> = Buffer,
            parse_request(Rest, State, ReqEmpty + 1);
        _ ->
            parse_method(Buffer, State, <<>>)
    end.

match_eol(<< $\n, _/bits >>, N) ->
    N;
match_eol(<< _, Rest/bits >>, N) ->
    match_eol(Rest, N + 1);
match_eol(_, _) ->
    nomatch.

parse_method(<< C, Rest/bits >>, State, SoFar) ->
    case C of
        $\r -> error_terminate(400, State);
        $\s -> parse_uri(Rest, State, SoFar);
        _ -> parse_method(Rest, State, << SoFar/binary, C >>)
    end.

parse_uri(<< $\r, _/bits >>, State, _) ->
    error_terminate(400, State);
parse_uri(<< "* ", Rest/bits >>, State, Method) ->
    parse_version(Rest, State, Method, <<"*">>, <<>>);
parse_uri(<< "http://", Rest/bits >>, State, Method) ->
    parse_uri_skip_host(Rest, State, Method);
parse_uri(<< "https://", Rest/bits >>, State, Method) ->
    parse_uri_skip_host(Rest, State, Method);
parse_uri(<< "HTTP://", Rest/bits >>, State, Method) ->
    parse_uri_skip_host(Rest, State, Method);
parse_uri(<< "HTTPS://", Rest/bits >>, State, Method) ->
    parse_uri_skip_host(Rest, State, Method);
parse_uri(Buffer, State, Method) ->
    parse_uri_path(Buffer, State, Method, <<>>).

parse_uri_skip_host(<< C, Rest/bits >>, State, Method) ->
    case C of
        $\r -> error_terminate(400, State);
        $/ -> parse_uri_path(Rest, State, Method, <<"/">>);
        $\s -> parse_version(Rest, State, Method, <<"/">>, <<>>);
        $? -> parse_uri_query(Rest, State, Method, <<"/">>, <<>>);
        $# -> skip_uri_fragment(Rest, State, Method, <<"/">>, <<>>);
        _ -> parse_uri_skip_host(Rest, State, Method)
    end.

parse_uri_path(<< C, Rest/bits >>, State, Method, SoFar) ->
    case C of
        $\r -> error_terminate(400, State);
        $\s -> parse_version(Rest, State, Method, SoFar, <<>>);
        $? -> parse_uri_query(Rest, State, Method, SoFar, <<>>);
        $# -> skip_uri_fragment(Rest, State, Method, SoFar, <<>>);
        _ -> parse_uri_path(Rest, State, Method, << SoFar/binary, C >>)
    end.

parse_uri_query(<< C, Rest/bits >>, S, M, P, SoFar) ->
    case C of
        $\r -> error_terminate(400, S);
        $\s -> parse_version(Rest, S, M, P, SoFar);
        $# -> skip_uri_fragment(Rest, S, M, P, SoFar);
        _ -> parse_uri_query(Rest, S, M, P, << SoFar/binary, C >>)
    end.

skip_uri_fragment(<< C, Rest/bits >>, S, M, P, Q) ->
    case C of
        $\r -> error_terminate(400, S);
        $\s -> parse_version(Rest, S, M, P, Q);
        _ -> skip_uri_fragment(Rest, S, M, P, Q)
    end.

parse_version(<< "HTTP/1.1\r\n", Rest/bits >>, S, M, P, Q) ->
    parse_header(Rest, S, M, P, Q, 'HTTP/1.1', []);
parse_version(<< "HTTP/1.0\r\n", Rest/bits >>, S, M, P, Q) ->
    parse_header(Rest, S, M, P, Q, 'HTTP/1.0', []);
parse_version(_, State, _, _, _) ->
    error_terminate(505, State).

%% Stop receiving data if we have more than allowed number of headers.
wait_header(_, State=#state{max_headers=MaxHeaders}, _, _, _, _, Headers)
        when length(Headers) >= MaxHeaders ->
    error_terminate(400, State);
wait_header(Buffer, State=#state{socket=Socket, transport=Transport,
        until=Until}, M, P, Q, V, H) ->
    case recv(Socket, Transport, Until) of
        {ok, Data} ->
            parse_header(<< Buffer/binary, Data/binary >>,
                State, M, P, Q, V, H);
        {error, timeout} ->
            error_terminate(408, State);
        {error, _} ->
            terminate(State)
    end.

parse_header(<< $\r, $\n, Rest/bits >>, S, M, P, Q, V, Headers) ->
    request(Rest, S, M, P, Q, V, lists:reverse(Headers));         %这里就是url处理完后的入口
parse_header(Buffer, State=#state{max_header_name_length=MaxLength},
        M, P, Q, V, H) ->
    case match_colon(Buffer, 0) of
        nomatch when byte_size(Buffer) > MaxLength ->
            error_terminate(400, State);
        nomatch ->
            wait_header(Buffer, State, M, P, Q, V, H);
        _ ->
            parse_hd_name(Buffer, State, M, P, Q, V, H, <<>>)
    end.

%%省略若干行

 

在这里我们省略的一些代码,看看cowboy_protocol的主要流程

注意标红色的部分

 

首先是start_link/4启动init -》

-》init 初始化参数,找不到的设置为默认 -》

-》设置超时时间,进入cowboy_protocol:wait_request -》

-》接收数据,recv -》

-》解析接收数据,parse_method 首先是http的方法(get/put等) -》

-》 parse_uri 解析请求的url (/或者/test.html),其中parse_uri_query 解析请求url的?后面的参数 -》

-》 parse_version 解析http的版本(http 1.1/1.0等) -》

-》 parse_header 解析剩下的头部

 

解析完成的头部信息如下(例子)

  M:<<"GET">>,
  P:<<"/">>,
  Q:<<>>,
  V:'HTTP/1.1',
  Headers:[{<<"if-modified-since">>,
  <<"Tue, 22 Mar 2016 09:51:57 GMT">>},
  {<<"if-none-match">>,
  <<"\"3936977058\"">>},
  {<<"accept-language">>,
  <<"zh-CN,zh;q=0.8">>},
  {<<"accept-encoding">>,
  <<"gzip, deflate, sdch">>},
  {<<"user-agent">>,
  <<"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36">>},
  {<<"upgrade-insecure-requests">>,
  <<"1">>},
  {<<"accept">>,
  <<"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8">>},
  {<<"cache-control">>,
  <<"max-age=0">>},
  {<<"connection">>,
  <<"keep-alive">>},
  {<<"host">>,
  <<"192.168.80.146:8080">>}]

 

对照上面的流程就能很清晰了

头部完成后进入request/7函数。

 现在就先在这,未完待续~~

posted @ 2016-07-19 16:19  土豆008  阅读(481)  评论(0编辑  收藏  举报