大家好,下了一天雨,十分涼爽,繼續來看mochiweb源碼,這一篇,我們來消化下上一篇留下的問題。
首先是mochiweb_socket_server:handle_cast/2關於{accepted, Pid, Timing}消息的處理:
handle_cast({accepted, Pid, Timing}, State=#mochiweb_socket_server{active_sockets=ActiveSockets}) -> State1 = State#mochiweb_socket_server{active_sockets=1 + ActiveSockets}, case State#mochiweb_socket_server.profile_fun of undefined -> undefined; F when is_function(F) -> catch F([{timing, Timing} | state_to_proplist(State1)]) end, {noreply, recycle_acceptor(Pid, State1)};
這個分支比較簡單,首先,修改了State#mochiweb_socket_server記錄active_sockets字段的值,增加1;
接着判斷是否定義了State#mochiweb_socket_server.profile_fun字段,如果之前的配置文件中存在該選項,且該選項是函數,則調用該函數,傳遞類似這樣的參數值:[{name, Name}, {port, Port}, {active_sockets, ActiveSockets}, {timing, Timing}]。
而mochiweb_socket_server:state_to_proplist/1函數代碼如下,一眼就能看明白:
state_to_proplist(#mochiweb_socket_server{name=Name, port=Port, active_sockets=ActiveSockets}) -> [{name, Name}, {port, Port}, {active_sockets, ActiveSockets}].
關於這個配置項,我們可以從mochiweb_http:start/1函數的注釋上了解到,如下:
%% @spec start(Options) -> ServerRet %% Options = [option()] %% Option = {name, atom()} | {ip, string() | tuple()} | {backlog, integer()} %% | {nodelay, boolean()} | {acceptor_pool_size, integer()} %% | {ssl, boolean()} | {profile_fun, undefined | (Props) -> ok} %% | {link, false} %% @doc Start a mochiweb server. %% profile_fun is used to profile accept timing. %% After each accept, if defined, profile_fun is called with a proplist of a subset of the mochiweb_socket_server state and timing information. %% The proplist is as follows: [{name, Name}, {port, Port}, {active_sockets, ActiveSockets}, {timing, Timing}]. %% @end start(Options) -> mochiweb_socket_server:start(parse_options(Options)).
這里我們修改下mochiweb_example_web:start/1,增加profile_fun選項來測試下這個功能,代碼如下:
start(Options) -> {DocRoot, Options1} = get_option(docroot, Options), Loop = fun (Req) -> ?MODULE:loop(Req, DocRoot) end, Profile_fun = fun (Proplist) -> io:format("Proplist = ~p~n", [Proplist]) end, mochiweb_http:start([{name, ?MODULE}, {loop, Loop}, {profile_fun, Profile_fun} | Options1]).
重新編譯並啟動,然后用瀏覽器訪問:http://127.0.0.1:8080/,接着我們就能看到如下打印結果了:
最后該分支返回如下結果:{noreply, recycle_acceptor(Pid, State1)};
這里我們主要看下:mochiweb_socket_server:recycle_acceptor/2函數:
recycle_acceptor(Pid, State=#mochiweb_socket_server{ acceptor_pool=Pool, listen=Listen, loop=Loop, active_sockets=ActiveSockets}) -> case sets:is_element(Pid, Pool) of true -> Acceptor = mochiweb_acceptor:start_link(self(), Listen, Loop), Pool1 = sets:add_element(Acceptor, sets:del_element(Pid, Pool)), State#mochiweb_socket_server{acceptor_pool=Pool1}; false -> State#mochiweb_socket_server{active_sockets=ActiveSockets - 1} end.
可以看到,這個函數最后返回State#mochiweb_socket_server記錄;
首先,調用sets:is_element/2判斷Pid是否在Pool中存在;如果存在,則調用mochiweb_acceptor:start_link/3生成一個新的acceptor進程,並從Pool中移除Pid,添加新的acceptor進程,最后修改State#mochiweb_socket_server.acceptor_pool字段的值。
如果不存在,則修改State#mochiweb_socket_server.active_sockets的值減少1。
注意:這里可以看出每當有個客戶端連接上來,Pool池就會把當前負責的acceptor進程移除,同時添加一個新的acceptor進程,也就是保證Pool池,始終和啟動時擁有的數量一致。
第一個問題解決了,接下來是第二個問題,調用mochiweb_http:loop/2函數,完整代碼如下:
loop(Socket, Body) -> ok = mochiweb_socket:setopts(Socket, [{packet, http}]), request(Socket, Body).
從上下文,我們可以知道這里的Body是個匿名函數,它就是mochiweb_example_web:start/1定義的Loop變量,大家可以翻看前一篇文章,回憶下。
這個函數,首先調用mochiweb_socket:setopts/2修改Socket的配置項,該函數完整代碼如下:
setopts({ssl, Socket}, Opts) -> ssl:setopts(Socket, Opts); setopts(Socket, Opts) -> inet:setopts(Socket, Opts).
同樣是分SSL協議和非SSL協議來調用不同系統函數設置。
erlang doc 地址:http://www.erlang.org/doc/man/ssl.html#setopts-2,http://www.erlang.org/doc/man/inet.html#setopts-2
關於{packet, http}選項,大家可以查看上面第二個地址的文檔,如下圖:
最后調用函數mochiweb_http:request/2:
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.
好了,這一篇就到這里,這個函數我們留到下一篇繼續跟大家分享。
晚安。