change stop from cast to call
Project: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/commit/8e3f3dd3 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/tree/8e3f3dd3 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/diff/8e3f3dd3 Branch: refs/heads/1843-feature-bigcouch Commit: 8e3f3dd36b9668d9422f9adef14b509c39f6bb44 Parents: 525d843 Author: Bob Ippolito <[email protected]> Authored: Wed May 29 15:24:22 2013 -0700 Committer: Bob Ippolito <[email protected]> Committed: Wed May 29 15:24:22 2013 -0700 ---------------------------------------------------------------------- CHANGES.md | 2 + src/mochiweb.erl | 205 ------------------------------------ src/mochiweb_socket_server.erl | 16 ++- test/mochiweb_tests.erl | 199 ++++++++++++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 215 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/8e3f3dd3/CHANGES.md ---------------------------------------------------------------------- diff --git a/CHANGES.md b/CHANGES.md index 065f19f..06a8b5f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ Version 2.7.0 released XXXX-XX-XX +* `mochiweb_socket_server:stop/1` is now a synchronous + call instead of an asynchronous cast * `mochiweb_html:parse_tokens/1` (and `parse/1`) will now create a html element to wrap documents that have a HTML5 doctype (`<!doctype html>`) but no html element http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/8e3f3dd3/src/mochiweb.erl ---------------------------------------------------------------------- diff --git a/src/mochiweb.erl b/src/mochiweb.erl index 250beb5..f597c73 100644 --- a/src/mochiweb.erl +++ b/src/mochiweb.erl @@ -74,208 +74,3 @@ ensure_started(App) -> {error, {already_started, App}} -> ok end. - -%% -%% Tests -%% --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - --record(treq, {path, body= <<>>, xreply= <<>>}). - -ssl_cert_opts() -> - EbinDir = filename:dirname(code:which(?MODULE)), - CertDir = filename:join([EbinDir, "..", "support", "test-materials"]), - CertFile = filename:join(CertDir, "test_ssl_cert.pem"), - KeyFile = filename:join(CertDir, "test_ssl_key.pem"), - [{certfile, CertFile}, {keyfile, KeyFile}]. - -with_server(Transport, ServerFun, ClientFun) -> - ServerOpts0 = [{ip, "127.0.0.1"}, {port, 0}, {loop, ServerFun}], - ServerOpts = case Transport of - plain -> - ServerOpts0; - ssl -> - ServerOpts0 ++ [{ssl, true}, {ssl_opts, ssl_cert_opts()}] - end, - {ok, Server} = mochiweb_http:start_link(ServerOpts), - Port = mochiweb_socket_server:get(Server, port), - Res = (catch ClientFun(Transport, Port)), - mochiweb_http:stop(Server), - Res. - -request_test() -> - R = mochiweb_request:new(z, z, "/foo/bar/baz%20wibble+quux?qs=2", z, []), - "/foo/bar/baz wibble quux" = R:get(path), - ok. - --define(LARGE_TIMEOUT, 60). - -single_http_GET_test() -> - do_GET(plain, 1). - -single_https_GET_test() -> - do_GET(ssl, 1). - -multiple_http_GET_test() -> - do_GET(plain, 3). - -multiple_https_GET_test() -> - do_GET(ssl, 3). - -hundred_http_GET_test_() -> % note the underscore - {timeout, ?LARGE_TIMEOUT, - fun() -> ?assertEqual(ok, do_GET(plain,100)) end}. - -hundred_https_GET_test_() -> % note the underscore - {timeout, ?LARGE_TIMEOUT, - fun() -> ?assertEqual(ok, do_GET(ssl,100)) end}. - -single_128_http_POST_test() -> - do_POST(plain, 128, 1). - -single_128_https_POST_test() -> - do_POST(ssl, 128, 1). - -single_2k_http_POST_test() -> - do_POST(plain, 2048, 1). - -single_2k_https_POST_test() -> - do_POST(ssl, 2048, 1). - -single_100k_http_POST_test() -> - do_POST(plain, 102400, 1). - -single_100k_https_POST_test() -> - do_POST(ssl, 102400, 1). - -multiple_100k_http_POST_test() -> - do_POST(plain, 102400, 3). - -multiple_100K_https_POST_test() -> - do_POST(ssl, 102400, 3). - -hundred_128_http_POST_test_() -> % note the underscore - {timeout, ?LARGE_TIMEOUT, - fun() -> ?assertEqual(ok, do_POST(plain, 128, 100)) end}. - -hundred_128_https_POST_test_() -> % note the underscore - {timeout, ?LARGE_TIMEOUT, - fun() -> ?assertEqual(ok, do_POST(ssl, 128, 100)) end}. - -do_GET(Transport, Times) -> - PathPrefix = "/whatever/", - ReplyPrefix = "You requested: ", - ServerFun = fun (Req) -> - Reply = ReplyPrefix ++ Req:get(path), - Req:ok({"text/plain", Reply}) - end, - TestReqs = [begin - Path = PathPrefix ++ integer_to_list(N), - ExpectedReply = list_to_binary(ReplyPrefix ++ Path), - #treq{path=Path, xreply=ExpectedReply} - end || N <- lists:seq(1, Times)], - ClientFun = new_client_fun('GET', TestReqs), - ok = with_server(Transport, ServerFun, ClientFun), - ok. - -do_POST(Transport, Size, Times) -> - ServerFun = fun (Req) -> - Body = Req:recv_body(), - Headers = [{"Content-Type", "application/octet-stream"}], - Req:respond({201, Headers, Body}) - end, - TestReqs = [begin - Path = "/stuff/" ++ integer_to_list(N), - Body = crypto:rand_bytes(Size), - #treq{path=Path, body=Body, xreply=Body} - end || N <- lists:seq(1, Times)], - ClientFun = new_client_fun('POST', TestReqs), - ok = with_server(Transport, ServerFun, ClientFun), - ok. - -new_client_fun(Method, TestReqs) -> - fun (Transport, Port) -> - client_request(Transport, Port, Method, TestReqs) - end. - -client_request(Transport, Port, Method, TestReqs) -> - Opts = [binary, {active, false}, {packet, http}], - SockFun = case Transport of - plain -> - {ok, Socket} = gen_tcp:connect("127.0.0.1", Port, Opts), - fun (recv) -> - gen_tcp:recv(Socket, 0); - ({recv, Length}) -> - gen_tcp:recv(Socket, Length); - ({send, Data}) -> - gen_tcp:send(Socket, Data); - ({setopts, L}) -> - inet:setopts(Socket, L) - end; - ssl -> - {ok, Socket} = ssl:connect("127.0.0.1", Port, [{ssl_imp, new} | Opts]), - fun (recv) -> - ssl:recv(Socket, 0); - ({recv, Length}) -> - ssl:recv(Socket, Length); - ({send, Data}) -> - ssl:send(Socket, Data); - ({setopts, L}) -> - ssl:setopts(Socket, L) - end - end, - client_request(SockFun, Method, TestReqs). - -client_request(SockFun, _Method, []) -> - {the_end, {error, closed}} = {the_end, SockFun(recv)}, - ok; -client_request(SockFun, Method, - [#treq{path=Path, body=Body, xreply=ExReply} | Rest]) -> - Request = [atom_to_list(Method), " ", Path, " HTTP/1.1\r\n", - client_headers(Body, Rest =:= []), - "\r\n", - Body], - ok = SockFun({send, Request}), - case Method of - 'GET' -> - {ok, {http_response, {1,1}, 200, "OK"}} = SockFun(recv); - 'POST' -> - {ok, {http_response, {1,1}, 201, "Created"}} = SockFun(recv) - end, - ok = SockFun({setopts, [{packet, httph}]}), - {ok, {http_header, _, 'Server', _, "MochiWeb" ++ _}} = SockFun(recv), - {ok, {http_header, _, 'Date', _, _}} = SockFun(recv), - {ok, {http_header, _, 'Content-Type', _, _}} = SockFun(recv), - {ok, {http_header, _, 'Content-Length', _, ConLenStr}} = SockFun(recv), - ContentLength = list_to_integer(ConLenStr), - {ok, http_eoh} = SockFun(recv), - ok = SockFun({setopts, [{packet, raw}]}), - {payload, ExReply} = {payload, drain_reply(SockFun, ContentLength, <<>>)}, - ok = SockFun({setopts, [{packet, http}]}), - client_request(SockFun, Method, Rest). - -client_headers(Body, IsLastRequest) -> - ["Host: localhost\r\n", - case Body of - <<>> -> - ""; - _ -> - ["Content-Type: application/octet-stream\r\n", - "Content-Length: ", integer_to_list(byte_size(Body)), "\r\n"] - end, - case IsLastRequest of - true -> - "Connection: close\r\n"; - false -> - "" - end]. - -drain_reply(_SockFun, 0, Acc) -> - Acc; -drain_reply(SockFun, Length, Acc) -> - Sz = erlang:min(Length, 1024), - {ok, B} = SockFun({recv, Sz}), - drain_reply(SockFun, Length - Sz, <<Acc/bytes, B/bytes>>). - --endif. http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/8e3f3dd3/src/mochiweb_socket_server.erl ---------------------------------------------------------------------- diff --git a/src/mochiweb_socket_server.erl b/src/mochiweb_socket_server.erl index e6e9fc9..3c8a8b1 100644 --- a/src/mochiweb_socket_server.erl +++ b/src/mochiweb_socket_server.erl @@ -59,13 +59,9 @@ set(Name, Property, _Value) -> error_logger:info_msg("?MODULE:set for ~p with ~p not implemented~n", [Name, Property]). -stop(Name) when is_atom(Name) -> - gen_server:cast(Name, stop); -stop(Pid) when is_pid(Pid) -> - gen_server:cast(Pid, stop); -stop({local, Name}) -> - stop(Name); -stop({global, Name}) -> +stop(Name) when is_atom(Name) orelse is_pid(Name) -> + gen_server:call(Name, stop); +stop({Scope, Name}) when Scope =:= local orelse Scope =:= global -> stop(Name); stop(Options) -> State = parse_options(Options), @@ -235,6 +231,8 @@ handle_call(Req, From, State) when ?is_old_state(State) -> handle_call({get, Property}, _From, State) -> Res = do_get(Property, State), {reply, Res, State}; +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; handle_call(_Message, _From, State) -> Res = error, {reply, Res, State}. @@ -259,9 +257,7 @@ handle_cast({set, profile_fun, ProfileFun}, State) -> _ -> State end, - {noreply, State1}; -handle_cast(stop, State) -> - {stop, normal, State}. + {noreply, State1}. terminate(Reason, State) when ?is_old_state(State) -> http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/8e3f3dd3/test/mochiweb_tests.erl ---------------------------------------------------------------------- diff --git a/test/mochiweb_tests.erl b/test/mochiweb_tests.erl new file mode 100644 index 0000000..15cb06a --- /dev/null +++ b/test/mochiweb_tests.erl @@ -0,0 +1,199 @@ +-module(mochiweb_tests). +-include_lib("eunit/include/eunit.hrl"). + +-record(treq, {path, body= <<>>, xreply= <<>>}). + +ssl_cert_opts() -> + EbinDir = filename:dirname(code:which(?MODULE)), + CertDir = filename:join([EbinDir, "..", "support", "test-materials"]), + CertFile = filename:join(CertDir, "test_ssl_cert.pem"), + KeyFile = filename:join(CertDir, "test_ssl_key.pem"), + [{certfile, CertFile}, {keyfile, KeyFile}]. + +with_server(Transport, ServerFun, ClientFun) -> + ServerOpts0 = [{ip, "127.0.0.1"}, {port, 0}, {loop, ServerFun}], + ServerOpts = case Transport of + plain -> + ServerOpts0; + ssl -> + ServerOpts0 ++ [{ssl, true}, {ssl_opts, ssl_cert_opts()}] + end, + {ok, Server} = mochiweb_http:start_link(ServerOpts), + Port = mochiweb_socket_server:get(Server, port), + Res = (catch ClientFun(Transport, Port)), + mochiweb_http:stop(Server), + Res. + +request_test() -> + R = mochiweb_request:new(z, z, "/foo/bar/baz%20wibble+quux?qs=2", z, []), + "/foo/bar/baz wibble quux" = R:get(path), + ok. + +-define(LARGE_TIMEOUT, 60). + +single_http_GET_test() -> + do_GET(plain, 1). + +single_https_GET_test() -> + do_GET(ssl, 1). + +multiple_http_GET_test() -> + do_GET(plain, 3). + +multiple_https_GET_test() -> + do_GET(ssl, 3). + +hundred_http_GET_test_() -> % note the underscore + {timeout, ?LARGE_TIMEOUT, + fun() -> ?assertEqual(ok, do_GET(plain,100)) end}. + +hundred_https_GET_test_() -> % note the underscore + {timeout, ?LARGE_TIMEOUT, + fun() -> ?assertEqual(ok, do_GET(ssl,100)) end}. + +single_128_http_POST_test() -> + do_POST(plain, 128, 1). + +single_128_https_POST_test() -> + do_POST(ssl, 128, 1). + +single_2k_http_POST_test() -> + do_POST(plain, 2048, 1). + +single_2k_https_POST_test() -> + do_POST(ssl, 2048, 1). + +single_100k_http_POST_test() -> + do_POST(plain, 102400, 1). + +single_100k_https_POST_test() -> + do_POST(ssl, 102400, 1). + +multiple_100k_http_POST_test() -> + do_POST(plain, 102400, 3). + +multiple_100K_https_POST_test() -> + do_POST(ssl, 102400, 3). + +hundred_128_http_POST_test_() -> % note the underscore + {timeout, ?LARGE_TIMEOUT, + fun() -> ?assertEqual(ok, do_POST(plain, 128, 100)) end}. + +hundred_128_https_POST_test_() -> % note the underscore + {timeout, ?LARGE_TIMEOUT, + fun() -> ?assertEqual(ok, do_POST(ssl, 128, 100)) end}. + +do_GET(Transport, Times) -> + PathPrefix = "/whatever/", + ReplyPrefix = "You requested: ", + ServerFun = fun (Req) -> + Reply = ReplyPrefix ++ Req:get(path), + Req:ok({"text/plain", Reply}) + end, + TestReqs = [begin + Path = PathPrefix ++ integer_to_list(N), + ExpectedReply = list_to_binary(ReplyPrefix ++ Path), + #treq{path=Path, xreply=ExpectedReply} + end || N <- lists:seq(1, Times)], + ClientFun = new_client_fun('GET', TestReqs), + ok = with_server(Transport, ServerFun, ClientFun), + ok. + +do_POST(Transport, Size, Times) -> + ServerFun = fun (Req) -> + Body = Req:recv_body(), + Headers = [{"Content-Type", "application/octet-stream"}], + Req:respond({201, Headers, Body}) + end, + TestReqs = [begin + Path = "/stuff/" ++ integer_to_list(N), + Body = crypto:rand_bytes(Size), + #treq{path=Path, body=Body, xreply=Body} + end || N <- lists:seq(1, Times)], + ClientFun = new_client_fun('POST', TestReqs), + ok = with_server(Transport, ServerFun, ClientFun), + ok. + +new_client_fun(Method, TestReqs) -> + fun (Transport, Port) -> + client_request(Transport, Port, Method, TestReqs) + end. + +client_request(Transport, Port, Method, TestReqs) -> + Opts = [binary, {active, false}, {packet, http}], + SockFun = case Transport of + plain -> + {ok, Socket} = gen_tcp:connect("127.0.0.1", Port, Opts), + fun (recv) -> + gen_tcp:recv(Socket, 0); + ({recv, Length}) -> + gen_tcp:recv(Socket, Length); + ({send, Data}) -> + gen_tcp:send(Socket, Data); + ({setopts, L}) -> + inet:setopts(Socket, L) + end; + ssl -> + {ok, Socket} = ssl:connect("127.0.0.1", Port, [{ssl_imp, new} | Opts]), + fun (recv) -> + ssl:recv(Socket, 0); + ({recv, Length}) -> + ssl:recv(Socket, Length); + ({send, Data}) -> + ssl:send(Socket, Data); + ({setopts, L}) -> + ssl:setopts(Socket, L) + end + end, + client_request(SockFun, Method, TestReqs). + +client_request(SockFun, _Method, []) -> + {the_end, {error, closed}} = {the_end, SockFun(recv)}, + ok; +client_request(SockFun, Method, + [#treq{path=Path, body=Body, xreply=ExReply} | Rest]) -> + Request = [atom_to_list(Method), " ", Path, " HTTP/1.1\r\n", + client_headers(Body, Rest =:= []), + "\r\n", + Body], + ok = SockFun({send, Request}), + case Method of + 'GET' -> + {ok, {http_response, {1,1}, 200, "OK"}} = SockFun(recv); + 'POST' -> + {ok, {http_response, {1,1}, 201, "Created"}} = SockFun(recv) + end, + ok = SockFun({setopts, [{packet, httph}]}), + {ok, {http_header, _, 'Server', _, "MochiWeb" ++ _}} = SockFun(recv), + {ok, {http_header, _, 'Date', _, _}} = SockFun(recv), + {ok, {http_header, _, 'Content-Type', _, _}} = SockFun(recv), + {ok, {http_header, _, 'Content-Length', _, ConLenStr}} = SockFun(recv), + ContentLength = list_to_integer(ConLenStr), + {ok, http_eoh} = SockFun(recv), + ok = SockFun({setopts, [{packet, raw}]}), + {payload, ExReply} = {payload, drain_reply(SockFun, ContentLength, <<>>)}, + ok = SockFun({setopts, [{packet, http}]}), + client_request(SockFun, Method, Rest). + +client_headers(Body, IsLastRequest) -> + ["Host: localhost\r\n", + case Body of + <<>> -> + ""; + _ -> + ["Content-Type: application/octet-stream\r\n", + "Content-Length: ", integer_to_list(byte_size(Body)), "\r\n"] + end, + case IsLastRequest of + true -> + "Connection: close\r\n"; + false -> + "" + end]. + +drain_reply(_SockFun, 0, Acc) -> + Acc; +drain_reply(SockFun, Length, Acc) -> + Sz = erlang:min(Length, 1024), + {ok, B} = SockFun({recv, Sz}), + drain_reply(SockFun, Length - Sz, <<Acc/bytes, B/bytes>>).
