cowboy源码分析(三)

上接 cowboy源码分析(二)

我们接着分析cowboy_protocol.erl的request/7模块

 

-module(cowboy_protocol).

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

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

 

%%----省略若干行


request(B, State=#state{transport=Transport}, M, P, Q, Version, Headers) -> case lists:keyfind(<<"host">>, 1, Headers) of false when Version =:= 'HTTP/1.1' -> error_terminate(400, State); false -> request(B, State, M, P, Q, Version, Headers, <<>>, default_port(Transport:name())); {_, RawHost} -> try parse_host(RawHost, false, <<>>) of {Host, undefined} -> request(B, State, M, P, Q, Version, Headers, Host, default_port(Transport:name())); {Host, Port} -> request(B, State, M, P, Q, Version, Headers, Host, Port) catch _:_ -> error_terminate(400, State) end end. -spec default_port(atom()) -> 80 | 443. default_port(ssl) -> 443; default_port(_) -> 80. %% Same code as cow_http:parse_fullhost/1, but inline because we %% really want this to go fast. parse_host(<< $[, Rest/bits >>, false, <<>>) -> parse_host(Rest, true, << $[ >>); parse_host(<<>>, false, Acc) -> {Acc, undefined}; parse_host(<< $:, Rest/bits >>, false, Acc) -> {Acc, list_to_integer(binary_to_list(Rest))}; parse_host(<< $], Rest/bits >>, true, Acc) -> parse_host(Rest, false, << Acc/binary, $] >>); parse_host(<< C, Rest/bits >>, E, Acc) -> case C of ?INLINE_LOWERCASE(parse_host, Rest, E, Acc) end. %% End of request parsing. %% %% We create the Req object and start handling the request. request(Buffer, State=#state{socket=Socket, transport=Transport, req_keepalive=ReqKeepalive, max_keepalive=MaxKeepalive, compress=Compress, onresponse=OnResponse}, Method, Path, Query, Version, Headers, Host, Port) -> case Transport:peername(Socket) of {ok, Peer} -> Req = cowboy_req:new(Socket, Transport, Peer, Method, Path, Query, Version, Headers, Host, Port, Buffer, ReqKeepalive < MaxKeepalive, Compress, OnResponse), onrequest(Req, State); {error, _} -> %% Couldn't read the peer address; connection is gone. terminate(State) end. %% Call the global onrequest callback. The callback can send a reply, %% in which case we consider the request handled and move on to the next %% one. Note that since we haven't dispatched yet, we don't know the %% handler, host_info, path_info or bindings yet. -spec onrequest(cowboy_req:req(), #state{}) -> ok. onrequest(Req, State=#state{onrequest=undefined}) -> execute(Req, State); onrequest(Req, State=#state{onrequest=OnRequest}) -> Req2 = OnRequest(Req), case cowboy_req:get(resp_state, Req2) of waiting -> execute(Req2, State); _ -> next_request(Req2, State, ok) end. -spec execute(cowboy_req:req(), #state{}) -> ok. execute(Req, State=#state{middlewares=Middlewares, env=Env}) -> execute(Req, State, Env, Middlewares). -spec execute(cowboy_req:req(), #state{}, cowboy_middleware:env(), [module()]) -> ok. execute(Req, State, Env, []) -> next_request(Req, State, get_value(result, Env, ok)); execute(Req, State, Env, [Middleware|Tail]) -> case Middleware:execute(Req, Env) of {ok, Req2, Env2} -> execute(Req2, State, Env2, Tail); {suspend, Module, Function, Args} -> erlang:hibernate(?MODULE, resume, [State, Env, Tail, Module, Function, Args]); {halt, Req2} -> next_request(Req2, State, ok); {error, Code, Req2} -> error_terminate(Code, Req2, State) end. -spec resume(#state{}, cowboy_middleware:env(), [module()], module(), module(), [any()]) -> ok. resume(State, Env, Tail, Module, Function, Args) -> case apply(Module, Function, Args) of {ok, Req2, Env2} -> execute(Req2, State, Env2, Tail); {suspend, Module2, Function2, Args2} -> erlang:hibernate(?MODULE, resume, [State, Env, Tail, Module2, Function2, Args2]); {halt, Req2} -> next_request(Req2, State, ok); {error, Code, Req2} -> error_terminate(Code, Req2, State) end. -spec next_request(cowboy_req:req(), #state{}, any()) -> ok. next_request(Req, State=#state{req_keepalive=Keepalive, timeout=Timeout}, HandlerRes) -> cowboy_req:ensure_response(Req, 204), %% If we are going to close the connection, %% we do not want to attempt to skip the body. case cowboy_req:get(connection, Req) of close -> terminate(State); _ -> %% Skip the body if it is reasonably sized. Close otherwise. Buffer = case cowboy_req:body(Req) of {ok, _, Req2} -> cowboy_req:get(buffer, Req2); _ -> close end, %% Flush the resp_sent message before moving on. if HandlerRes =:= ok, Buffer =/= close -> receive {cowboy_req, resp_sent} -> ok after 0 -> ok end, ?MODULE:parse_request(Buffer, State#state{req_keepalive=Keepalive + 1, until=until(Timeout)}, 0); true -> terminate(State) end end. -spec error_terminate(cowboy:http_status(), #state{}) -> ok. error_terminate(Status, State=#state{socket=Socket, transport=Transport, compress=Compress, onresponse=OnResponse}) -> error_terminate(Status, cowboy_req:new(Socket, Transport, undefined, <<"GET">>, <<>>, <<>>, 'HTTP/1.1', [], <<>>, undefined, <<>>, false, Compress, OnResponse), State). -spec error_terminate(cowboy:http_status(), cowboy_req:req(), #state{}) -> ok. error_terminate(Status, Req, State) -> _ = cowboy_req:reply(Status, Req), terminate(State). -spec terminate(#state{}) -> ok. terminate(#state{socket=Socket, transport=Transport}) -> Transport:close(Socket), ok.

 

首先进入request/7函数,取得服务器host和port  =》

=》进入request/9,调用cowboy_req:new/14新建一个Req(等等我们再额外分析cowboy_req:new/14)=》

=》后面进入onrequest/2,判断onrequest参数 =》

=》直接进入execute/2 ,取得Middlewares,和Env =》

=》然后是execute/4 ,对Middlewares的列表模块(['cowboy_router',  'cowboy_handler'])分别执行Middleware:execute =》

先执行cowboy_router:execute/2 =》

-module(cowboy_router).
-behaviour(cowboy_middleware).

-export([compile/1]).
-export([execute/2]).

%%----省略若干行

-spec execute(Req, Env)
    -> {ok, Req, Env} | {error, 400 | 404, Req}
    when Req::cowboy_req:req(), Env::cowboy_middleware:env().
execute(Req, Env) ->
    {_, Dispatch} = lists:keyfind(dispatch, 1, Env),
    [Host, Path] = cowboy_req:get([host, path], Req),
    case match(Dispatch, Host, Path) of
        {ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} ->
            Req2 = cowboy_req:set_bindings(HostInfo, PathInfo, Bindings, Req),
            {ok, Req2, [{handler, Handler}, {handler_opts, HandlerOpts}|Env]};
        {error, notfound, host} ->
            {error, 400, Req};
        {error, badrequest, path} ->
            {error, 400, Req};
        {error, notfound, path} ->
            {error, 404, Req}
    end.

%%---省略若干行

这个函数主要是想取得Dispatch(这个就是开始cowboy_router:compile执行取得的路径路由信息,具体参见cowboy源码分析(一)),Host(主机), Path(相对路径) =》

=》 然后就是match函数,match函数这里就不详细的导读了,我们直接看结果,取得Handler(请求的句柄), HandlerOpts(句柄参数), Bindings(变量绑定,这个暂时没找到例子),

HostInfo 服务器信息, PathInfo路径信息, 存入Req;把Handler信息存入Env =》返回Req,和ENV,这个函数执行完成了,

 

下面就是请求的执行函数,cowboy_handler:execute/2,

 对于这个函数的解析和上面cowboy_req:new/14的说明我们放在下一节说明

未完待续~~

posted @ 2016-07-20 10:50  土豆008  阅读(523)  评论(0编辑  收藏  举报