Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

271 lines (231 sloc) 8.894 kb
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
%% @doc Start and stop the MochiWeb server.
-module(mochiweb).
-author('bob@mochimedia.com').
-export([new_request/1, new_response/1]).
-export([all_loaded/0, all_loaded/1, reload/0]).
reload() ->
[c:l(Module) || Module <- all_loaded()].
all_loaded() ->
all_loaded(filename:dirname(code:which(?MODULE))).
all_loaded(Base) when is_atom(Base) ->
[];
all_loaded(Base) ->
FullBase = Base ++ "/",
F = fun ({_Module, Loaded}, Acc) when is_atom(Loaded) ->
Acc;
({Module, Loaded}, Acc) ->
case lists:prefix(FullBase, Loaded) of
true ->
[Module | Acc];
false ->
Acc
end
end,
lists:foldl(F, [], code:all_loaded()).
%% @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)).
%% @spec new_response({Request, integer(), Headers}) -> MochiWebResponse
%% @doc Return a mochiweb_response data structure.
new_response({Request, Code, Headers}) ->
mochiweb_response:new(Request,
Code,
mochiweb_headers:make(Headers)).
%%
%% 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(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.
Jump to Line
Something went wrong with that request. Please try again.