You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2014/02/12 07:22:08 UTC
[36/50] [abbrv] mochiweb commit: updated refs/heads/import-master to
3a54dbf
Upgrade to Mochiweb 1.4.1.
Modifications from upstream
1) Makefile is replaced by Makefile.am
2) mochiweb.app.in is custom to us.
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@1040473 13f79535-47bb-0310-9956-ffa450edef68
Project: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/commit/964f4656
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/tree/964f4656
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/diff/964f4656
Branch: refs/heads/import-master
Commit: 964f4656d8270bd9ec327089b65f6f35600b37b3
Parents: a89afc6
Author: Robert Newson <rn...@apache.org>
Authored: Tue Nov 30 11:57:08 2010 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Tue Nov 30 11:57:08 2010 +0000
----------------------------------------------------------------------
Makefile.am | 2 +-
mochijson2.erl | 51 +++++++++-
mochinum.erl | 77 +++++++++-----
mochiweb.app.in | 2 +-
mochiweb.app.src | 2 +-
mochiweb_acceptor.erl | 2 +-
mochiweb_html.erl | 219 ++++++++++++++++++++++++++++++++++++++--
mochiweb_http.erl | 53 ++++++----
mochiweb_request.erl | 9 +-
mochiweb_request_tests.erl | 63 ++++++++++++
mochiweb_socket_server.erl | 104 +++++++++++++++++--
mochiweb_util.erl | 2 +-
12 files changed, 516 insertions(+), 70 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/Makefile.am
----------------------------------------------------------------------
diff --git a/Makefile.am b/Makefile.am
index b622ff0..e14a529 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -14,7 +14,7 @@ if USE_NATIVE_MOCHIJSON
MOCHIJSON_ERLC_FLAGS=+native
endif
-mochiwebebindir = $(localerlanglibdir)/mochiweb-7c2bc2/ebin
+mochiwebebindir = $(localerlanglibdir)/mochiweb-1.4.1/ebin
mochiweb_file_collection = \
mochifmt.erl \
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/mochijson2.erl
----------------------------------------------------------------------
diff --git a/mochijson2.erl b/mochijson2.erl
index 64cabc8..bdf6d77 100644
--- a/mochijson2.erl
+++ b/mochijson2.erl
@@ -4,6 +4,38 @@
%% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works
%% with binaries as strings, arrays as lists (without an {array, _})
%% wrapper and it only knows how to decode UTF-8 (and ASCII).
+%%
+%% JSON terms are decoded as follows (javascript -> erlang):
+%% <ul>
+%% <li>{"key": "value"} ->
+%% {struct, [{<<"key">>, <<"value">>}]}</li>
+%% <li>["array", 123, 12.34, true, false, null] ->
+%% [<<"array">>, 123, 12.34, true, false, null]
+%% </li>
+%% </ul>
+%% <ul>
+%% <li>Strings in JSON decode to UTF-8 binaries in Erlang</li>
+%% <li>Objects decode to {struct, PropList}</li>
+%% <li>Numbers decode to integer or float</li>
+%% <li>true, false, null decode to their respective terms.</li>
+%% </ul>
+%% The encoder will accept the same format that the decoder will produce,
+%% but will also allow additional cases for leniency:
+%% <ul>
+%% <li>atoms other than true, false, null will be considered UTF-8
+%% strings (even as a proplist key)
+%% </li>
+%% <li>{json, IoList} will insert IoList directly into the output
+%% with no validation
+%% </li>
+%% <li>{array, Array} will be encoded as Array
+%% (legacy mochijson style)
+%% </li>
+%% <li>A non-empty raw proplist will be encoded as an object as long
+%% as the first pair does not have an atom key of json, struct,
+%% or array
+%% </li>
+%% </ul>
-module(mochijson2).
-author('bob@mochimedia.com').
@@ -101,10 +133,16 @@ json_encode(F, _State) when is_float(F) ->
mochinum:digits(F);
json_encode(S, State) when is_binary(S); is_atom(S) ->
json_encode_string(S, State);
-json_encode(Array, State) when is_list(Array) ->
- json_encode_array(Array, State);
+json_encode([{K, _}|_] = Props, State) when (K =/= struct andalso
+ K =/= array andalso
+ K =/= json) ->
+ json_encode_proplist(Props, State);
json_encode({struct, Props}, State) when is_list(Props) ->
json_encode_proplist(Props, State);
+json_encode(Array, State) when is_list(Array) ->
+ json_encode_array(Array, State);
+json_encode({array, Array}, State) when is_list(Array) ->
+ json_encode_array(Array, State);
json_encode({json, IoList}, _State) ->
IoList;
json_encode(Bad, #encoder{handler=null}) ->
@@ -732,6 +770,15 @@ key_encode_test() ->
?assertEqual(
<<"{\"foo\":1}">>,
iolist_to_binary(encode({struct, [{"foo", 1}]}))),
+ ?assertEqual(
+ <<"{\"foo\":1}">>,
+ iolist_to_binary(encode([{foo, 1}]))),
+ ?assertEqual(
+ <<"{\"foo\":1}">>,
+ iolist_to_binary(encode([{<<"foo">>, 1}]))),
+ ?assertEqual(
+ <<"{\"foo\":1}">>,
+ iolist_to_binary(encode([{"foo", 1}]))),
?assertEqual(
<<"{\"\\ud834\\udd20\":1}">>,
iolist_to_binary(
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/mochinum.erl
----------------------------------------------------------------------
diff --git a/mochinum.erl b/mochinum.erl
index a7e2bfb..3c96b13 100644
--- a/mochinum.erl
+++ b/mochinum.erl
@@ -29,11 +29,10 @@ digits(N) when is_integer(N) ->
digits(0.0) ->
"0.0";
digits(Float) ->
- {Frac, Exp} = frexp(Float),
- Exp1 = Exp - 53,
- Frac1 = trunc(abs(Frac) * (1 bsl 53)),
- [Place | Digits] = digits1(Float, Exp1, Frac1),
- R = insert_decimal(Place, [$0 + D || D <- Digits]),
+ {Frac1, Exp1} = frexp_int(Float),
+ [Place0 | Digits0] = digits1(Float, Exp1, Frac1),
+ {Place, Digits} = transform_digits(Place0, Digits0),
+ R = insert_decimal(Place, Digits),
case Float < 0 of
true ->
[$- | R];
@@ -64,7 +63,6 @@ int_pow(X, N) when N > 0 ->
int_ceil(X) ->
T = trunc(X),
case (X - T) of
- Neg when Neg < 0 -> T;
Pos when Pos > 0 -> T + 1;
_ -> T
end.
@@ -228,6 +226,20 @@ log2floor(Int, N) ->
log2floor(Int bsr 1, 1 + N).
+transform_digits(Place, [0 | Rest]) ->
+ transform_digits(Place, Rest);
+transform_digits(Place, Digits) ->
+ {Place, [$0 + D || D <- Digits]}.
+
+
+frexp_int(F) ->
+ case unpack(F) of
+ {_Sign, 0, Frac} ->
+ {Frac, ?MIN_EXP};
+ {_Sign, Exp, Frac} ->
+ {Frac + (1 bsl 52), Exp - 53 - ?FLOAT_BIAS}
+ end.
+
%%
%% Tests
%%
@@ -235,21 +247,21 @@ log2floor(Int, N) ->
-ifdef(TEST).
int_ceil_test() ->
- 1 = int_ceil(0.0001),
- 0 = int_ceil(0.0),
- 1 = int_ceil(0.99),
- 1 = int_ceil(1.0),
- -1 = int_ceil(-1.5),
- -2 = int_ceil(-2.0),
+ ?assertEqual(1, int_ceil(0.0001)),
+ ?assertEqual(0, int_ceil(0.0)),
+ ?assertEqual(1, int_ceil(0.99)),
+ ?assertEqual(1, int_ceil(1.0)),
+ ?assertEqual(-1, int_ceil(-1.5)),
+ ?assertEqual(-2, int_ceil(-2.0)),
ok.
int_pow_test() ->
- 1 = int_pow(1, 1),
- 1 = int_pow(1, 0),
- 1 = int_pow(10, 0),
- 10 = int_pow(10, 1),
- 100 = int_pow(10, 2),
- 1000 = int_pow(10, 3),
+ ?assertEqual(1, int_pow(1, 1)),
+ ?assertEqual(1, int_pow(1, 0)),
+ ?assertEqual(1, int_pow(10, 0)),
+ ?assertEqual(10, int_pow(10, 1)),
+ ?assertEqual(100, int_pow(10, 2)),
+ ?assertEqual(1000, int_pow(10, 3)),
ok.
digits_test() ->
@@ -274,9 +286,9 @@ digits_test() ->
?assertEqual("4503599627370496.0",
digits(4503599627370496.0)),
%% small denormalized number
- %% 4.94065645841246544177e-324
+ %% 4.94065645841246544177e-324 =:= 5.0e-324
<<SmallDenorm/float>> = <<0,0,0,0,0,0,0,1>>,
- ?assertEqual("4.9406564584124654e-324",
+ ?assertEqual("5.0e-324",
digits(SmallDenorm)),
?assertEqual(SmallDenorm,
list_to_float(digits(SmallDenorm))),
@@ -301,31 +313,42 @@ digits_test() ->
digits(LargeNorm)),
?assertEqual(LargeNorm,
list_to_float(digits(LargeNorm))),
+ %% issue #10 - mochinum:frexp(math:pow(2, -1074)).
+ ?assertEqual("5.0e-324",
+ digits(math:pow(2, -1074))),
ok.
frexp_test() ->
%% zero
- {0.0, 0} = frexp(0.0),
+ ?assertEqual({0.0, 0}, frexp(0.0)),
%% one
- {0.5, 1} = frexp(1.0),
+ ?assertEqual({0.5, 1}, frexp(1.0)),
%% negative one
- {-0.5, 1} = frexp(-1.0),
+ ?assertEqual({-0.5, 1}, frexp(-1.0)),
%% small denormalized number
%% 4.94065645841246544177e-324
<<SmallDenorm/float>> = <<0,0,0,0,0,0,0,1>>,
- {0.5, -1073} = frexp(SmallDenorm),
+ ?assertEqual({0.5, -1073}, frexp(SmallDenorm)),
%% large denormalized number
%% 2.22507385850720088902e-308
<<BigDenorm/float>> = <<0,15,255,255,255,255,255,255>>,
- {0.99999999999999978, -1022} = frexp(BigDenorm),
+ ?assertEqual(
+ {0.99999999999999978, -1022},
+ frexp(BigDenorm)),
%% small normalized number
%% 2.22507385850720138309e-308
<<SmallNorm/float>> = <<0,16,0,0,0,0,0,0>>,
- {0.5, -1021} = frexp(SmallNorm),
+ ?assertEqual({0.5, -1021}, frexp(SmallNorm)),
%% large normalized number
%% 1.79769313486231570815e+308
<<LargeNorm/float>> = <<127,239,255,255,255,255,255,255>>,
- {0.99999999999999989, 1024} = frexp(LargeNorm),
+ ?assertEqual(
+ {0.99999999999999989, 1024},
+ frexp(LargeNorm)),
+ %% issue #10 - mochinum:frexp(math:pow(2, -1074)).
+ ?assertEqual(
+ {0.5, -1073},
+ frexp(math:pow(2, -1074))),
ok.
-endif.
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/mochiweb.app.in
----------------------------------------------------------------------
diff --git a/mochiweb.app.in b/mochiweb.app.in
index c6a2630..6a4a314 100644
--- a/mochiweb.app.in
+++ b/mochiweb.app.in
@@ -1,6 +1,6 @@
{application, mochiweb,
[{description, "MochiMedia Web Server"},
- {vsn, "7c2bc2"},
+ {vsn, "1.4.1"},
{modules, [
mochihex,
mochijson,
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/mochiweb.app.src
----------------------------------------------------------------------
diff --git a/mochiweb.app.src b/mochiweb.app.src
index a1c95aa..37a21fb 100644
--- a/mochiweb.app.src
+++ b/mochiweb.app.src
@@ -1,7 +1,7 @@
%% This is generated from src/mochiweb.app.src
{application, mochiweb,
[{description, "MochiMedia Web Server"},
- {vsn, "7c2bc2"},
+ {vsn, "1.4.1"},
{modules, []},
{registered, []},
{mod, {mochiweb_app, []}},
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/mochiweb_acceptor.erl
----------------------------------------------------------------------
diff --git a/mochiweb_acceptor.erl b/mochiweb_acceptor.erl
index 79d172c..20a9b4b 100644
--- a/mochiweb_acceptor.erl
+++ b/mochiweb_acceptor.erl
@@ -22,7 +22,7 @@ init(Server, Listen, Loop) ->
{error, closed} ->
exit(normal);
{error, timeout} ->
- exit(normal);
+ init(Server, Listen, Loop);
{error, esslaccept} ->
exit(normal);
Other ->
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/mochiweb_html.erl
----------------------------------------------------------------------
diff --git a/mochiweb_html.erl b/mochiweb_html.erl
index a15c359..0f281db 100644
--- a/mochiweb_html.erl
+++ b/mochiweb_html.erl
@@ -131,6 +131,11 @@ to_html([], Acc) ->
lists:reverse(Acc);
to_html([{'=', Content} | Rest], Acc) ->
to_html(Rest, [Content | Acc]);
+to_html([{pi, Bin} | Rest], Acc) ->
+ Open = [<<"<?">>,
+ Bin,
+ <<"?>">>],
+ to_html(Rest, [Open | Acc]);
to_html([{pi, Tag, Attrs} | Rest], Acc) ->
Open = [<<"<?">>,
Tag,
@@ -216,6 +221,9 @@ to_tokens([{Tag0, [T0={'=', _C0} | R1]} | Rest], Acc) ->
to_tokens([{Tag0, [T0={comment, _C0} | R1]} | Rest], Acc) ->
%% Allow {comment, iolist()}
to_tokens([{Tag0, R1} | Rest], [T0 | Acc]);
+to_tokens([{Tag0, [T0={pi, _S0} | R1]} | Rest], Acc) ->
+ %% Allow {pi, binary()}
+ to_tokens([{Tag0, R1} | Rest], [T0 | Acc]);
to_tokens([{Tag0, [T0={pi, _S0, _A0} | R1]} | Rest], Acc) ->
%% Allow {pi, binary(), list()}
to_tokens([{Tag0, R1} | Rest], [T0 | Acc]);
@@ -290,6 +298,9 @@ tokenize(B, S=#decoder{offset=O}) ->
tokenize_doctype(B, ?ADV_COL(S, 10));
<<_:O/binary, "<![CDATA[", _/binary>> ->
tokenize_cdata(B, ?ADV_COL(S, 9));
+ <<_:O/binary, "<?php", _/binary>> ->
+ {Body, S1} = raw_qgt(B, ?ADV_COL(S, 2)),
+ {{pi, Body}, S1};
<<_:O/binary, "<?", _/binary>> ->
{Tag, S1} = tokenize_literal(B, ?ADV_COL(S, 2)),
{Attrs, S2} = tokenize_attributes(B, S1),
@@ -309,7 +320,7 @@ tokenize(B, S=#decoder{offset=O}) ->
{Tag, S1} = tokenize_literal(B, ?INC_COL(S)),
{Attrs, S2} = tokenize_attributes(B, S1),
{S3, HasSlash} = find_gt(B, S2),
- Singleton = HasSlash orelse is_singleton(norm(binary_to_list(Tag))),
+ Singleton = HasSlash orelse is_singleton(Tag),
{{start_tag, Tag, Attrs, Singleton}, S3};
_ ->
tokenize_data(B, S)
@@ -333,6 +344,8 @@ tree([{start_tag, Tag, Attrs, true} | Rest], S) ->
tree(Rest, append_stack_child(norm({Tag, Attrs}), S));
tree([{start_tag, Tag, Attrs, false} | Rest], S) ->
tree(Rest, stack(norm({Tag, Attrs}), S));
+tree([T={pi, _Raw} | Rest], S) ->
+ tree(Rest, append_stack_child(T, S));
tree([T={pi, _Tag, _Attrs} | Rest], S) ->
tree(Rest, append_stack_child(T, S));
tree([T={comment, _Comment} | Rest], S) ->
@@ -367,6 +380,10 @@ stack(T1, Stack) ->
append_stack_child(StartTag, [{Name, Attrs, Acc} | Stack]) ->
[{Name, Attrs, [StartTag | Acc]} | Stack].
+destack(<<"br">>, Stack) ->
+ %% This is an ugly hack to make dumb_br_test() pass,
+ %% this makes it such that br can never have children.
+ Stack;
destack(TagName, Stack) when is_list(Stack) ->
F = fun (X) ->
case X of
@@ -387,8 +404,8 @@ destack(TagName, Stack) when is_list(Stack) ->
{_, []} ->
%% Actually was a singleton
Stack;
- {Pre, [{T1, A1, []} | Post1]} ->
- [{T0, A0, [{T1, A1, lists:reverse(Pre)} | Post1]}
+ {Pre, [{T1, A1, Acc1} | Post1]} ->
+ [{T0, A0, [{T1, A1, Acc1 ++ lists:reverse(Pre)} | Post1]}
| Post0]
end;
_ ->
@@ -459,10 +476,51 @@ tokenize_attr_value(Attr, B, S) ->
case B of
<<_:O/binary, "=", _/binary>> ->
S2 = skip_whitespace(B, ?INC_COL(S1)),
- tokenize_word_or_literal(B, S2);
+ tokenize_quoted_or_unquoted_attr_value(B, S2);
_ ->
{Attr, S1}
end.
+
+tokenize_quoted_or_unquoted_attr_value(B, S=#decoder{offset=O}) ->
+ case B of
+ <<_:O/binary>> ->
+ { [], S };
+ <<_:O/binary, Q, _/binary>> when Q =:= ?QUOTE orelse
+ Q =:= ?SQUOTE ->
+ tokenize_quoted_attr_value(B, ?INC_COL(S), [], Q);
+ <<_:O/binary, _/binary>> ->
+ tokenize_unquoted_attr_value(B, S, [])
+ end.
+
+tokenize_quoted_attr_value(B, S=#decoder{offset=O}, Acc, Q) ->
+ case B of
+ <<_:O/binary>> ->
+ { iolist_to_binary(lists:reverse(Acc)), S };
+ <<_:O/binary, $&, _/binary>> ->
+ {{data, Data, false}, S1} = tokenize_charref(B, ?INC_COL(S)),
+ tokenize_quoted_attr_value(B, S1, [Data|Acc], Q);
+ <<_:O/binary, Q, _/binary>> ->
+ { iolist_to_binary(lists:reverse(Acc)), ?INC_COL(S) };
+ <<_:O/binary, $\n, _/binary>> ->
+ { iolist_to_binary(lists:reverse(Acc)), ?INC_LINE(S) };
+ <<_:O/binary, C, _/binary>> ->
+ tokenize_quoted_attr_value(B, ?INC_COL(S), [C|Acc], Q)
+ end.
+
+tokenize_unquoted_attr_value(B, S=#decoder{offset=O}, Acc) ->
+ case B of
+ <<_:O/binary>> ->
+ { iolist_to_binary(lists:reverse(Acc)), S };
+ <<_:O/binary, $&, _/binary>> ->
+ {{data, Data, false}, S1} = tokenize_charref(B, ?INC_COL(S)),
+ tokenize_unquoted_attr_value(B, S1, [Data|Acc]);
+ <<_:O/binary, $/, $>, _/binary>> ->
+ { iolist_to_binary(lists:reverse(Acc)), S };
+ <<_:O/binary, C, _/binary>> when ?PROBABLE_CLOSE(C) ->
+ { iolist_to_binary(lists:reverse(Acc)), S };
+ <<_:O/binary, C, _/binary>> ->
+ tokenize_unquoted_attr_value(B, ?INC_COL(S), [C|Acc])
+ end.
skip_whitespace(B, S=#decoder{offset=O}) ->
case B of
@@ -472,8 +530,17 @@ skip_whitespace(B, S=#decoder{offset=O}) ->
S
end.
-tokenize_literal(Bin, S) ->
- tokenize_literal(Bin, S, []).
+tokenize_literal(Bin, S=#decoder{offset=O}) ->
+ case Bin of
+ <<_:O/binary, C, _/binary>> when C =:= $>
+ orelse C =:= $/
+ orelse C =:= $= ->
+ %% Handle case where tokenize_literal would consume
+ %% 0 chars. http://github.com/mochi/mochiweb/pull/13
+ {[C], ?INC_COL(S)};
+ _ ->
+ tokenize_literal(Bin, S, [])
+ end.
tokenize_literal(Bin, S=#decoder{offset=O}, Acc) ->
case Bin of
@@ -486,13 +553,33 @@ tokenize_literal(Bin, S=#decoder{offset=O}, Acc) ->
orelse C =:= $=) ->
tokenize_literal(Bin, ?INC_COL(S), [C | Acc]);
_ ->
- {iolist_to_binary(lists:reverse(Acc)), S}
+ {iolist_to_binary(string:to_lower(lists:reverse(Acc))), S}
+ end.
+
+raw_qgt(Bin, S=#decoder{offset=O}) ->
+ raw_qgt(Bin, S, O).
+
+raw_qgt(Bin, S=#decoder{offset=O}, Start) ->
+ case Bin of
+ <<_:O/binary, "?>", _/binary>> ->
+ Len = O - Start,
+ <<_:Start/binary, Raw:Len/binary, _/binary>> = Bin,
+ {Raw, ?ADV_COL(S, 2)};
+ <<_:O/binary, C, _/binary>> ->
+ raw_qgt(Bin, ?INC_CHAR(S, C), Start);
+ <<_:O/binary>> ->
+ <<_:Start/binary, Raw/binary>> = Bin,
+ {Raw, S}
end.
find_qgt(Bin, S=#decoder{offset=O}) ->
case Bin of
<<_:O/binary, "?>", _/binary>> ->
?ADV_COL(S, 2);
+ <<_:O/binary, ">", _/binary>> ->
+ ?ADV_COL(S, 1);
+ <<_:O/binary, "/>", _/binary>> ->
+ ?ADV_COL(S, 2);
%% tokenize_attributes takes care of this state:
%% <<_:O/binary, C, _/binary>> ->
%% find_qgt(Bin, ?INC_CHAR(S, C));
@@ -570,7 +657,7 @@ tokenize_word_or_literal(Bin, S=#decoder{offset=O}) ->
tokenize_word(Bin, ?INC_COL(S), C);
<<_:O/binary, C, _/binary>> when not ?IS_WHITESPACE(C) ->
%% Sanity check for whitespace
- tokenize_literal(Bin, S, [])
+ tokenize_literal(Bin, S)
end.
tokenize_word(Bin, S, Quote) ->
@@ -880,6 +967,15 @@ parse_test() ->
{<<"br">>, [], []},
<<"bar">>]}]},
parse(<<"<html><link>foo<br>bar</link></html>">>)),
+ %% Case insensitive tags
+ ?assertEqual(
+ {<<"html">>, [],
+ [{<<"head">>, [], [<<"foo">>,
+ {<<"br">>, [], []},
+ <<"BAR">>]},
+ {<<"body">>, [{<<"class">>, <<"">>}, {<<"bgcolor">>, <<"#Aa01fF">>}], []}
+ ]},
+ parse(<<"<html><Head>foo<bR>BAR</head><body Class=\"\" bgcolor=\"#Aa01fF\"></BODY></html>">>)),
ok.
exhaustive_is_singleton_test() ->
@@ -1056,6 +1152,113 @@ doctype_test() ->
mochiweb_html:parse("<html>"
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"
"<head></head></body></html>")),
+ %% http://github.com/mochi/mochiweb/pull/13
+ ?assertEqual(
+ {<<"html">>,[],[{<<"head">>,[],[]}]},
+ mochiweb_html:parse("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"/>"
+ "<html>"
+ "<head></head></body></html>")),
+ ok.
+
+dumb_br_test() ->
+ %% http://code.google.com/p/mochiweb/issues/detail?id=71
+ ?assertEqual(
+ {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>]},
+ mochiweb_html:parse("<div><br/><br/>z</br/></br/></div>")),
+ ?assertEqual(
+ {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>]},
+ mochiweb_html:parse("<div><br><br>z</br/></br/></div>")),
+ ?assertEqual(
+ {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>, {<<"br">>, [], []}, {<<"br">>, [], []}]},
+ mochiweb_html:parse("<div><br><br>z<br/><br/></div>")),
+ ?assertEqual(
+ {<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>]},
+ mochiweb_html:parse("<div><br><br>z</br></br></div>")).
+
+
+php_test() ->
+ %% http://code.google.com/p/mochiweb/issues/detail?id=71
+ ?assertEqual(
+ [{pi, <<"php\n">>}],
+ mochiweb_html:tokens(
+ "<?php\n?>")),
+ ?assertEqual(
+ {<<"div">>, [], [{pi, <<"php\n">>}]},
+ mochiweb_html:parse(
+ "<div><?php\n?></div>")),
+ ok.
+
+parse_unquoted_attr_test() ->
+ D0 = <<"<html><img src=/images/icon.png/></html>">>,
+ ?assertEqual(
+ {<<"html">>,[],[
+ { <<"img">>, [ { <<"src">>, <<"/images/icon.png">> } ], [] }
+ ]},
+ mochiweb_html:parse(D0)),
+
+ D1 = <<"<html><img src=/images/icon.png></img></html>">>,
+ ?assertEqual(
+ {<<"html">>,[],[
+ { <<"img">>, [ { <<"src">>, <<"/images/icon.png">> } ], [] }
+ ]},
+ mochiweb_html:parse(D1)),
+
+ D2 = <<"<html><img src=/images/icon>.png width=100></img></html>">>,
+ ?assertEqual(
+ {<<"html">>,[],[
+ { <<"img">>, [ { <<"src">>, <<"/images/icon>.png">> }, { <<"width">>, <<"100">> } ], [] }
+ ]},
+ mochiweb_html:parse(D2)),
+ ok.
+
+parse_quoted_attr_test() ->
+ D0 = <<"<html><img src='/images/icon.png'></html>">>,
+ ?assertEqual(
+ {<<"html">>,[],[
+ { <<"img">>, [ { <<"src">>, <<"/images/icon.png">> } ], [] }
+ ]},
+ mochiweb_html:parse(D0)),
+
+ D1 = <<"<html><img src=\"/images/icon.png'></html>">>,
+ ?assertEqual(
+ {<<"html">>,[],[
+ { <<"img">>, [ { <<"src">>, <<"/images/icon.png'></html>">> } ], [] }
+ ]},
+ mochiweb_html:parse(D1)),
+
+ D2 = <<"<html><img src=\"/images/icon>.png\"></html>">>,
+ ?assertEqual(
+ {<<"html">>,[],[
+ { <<"img">>, [ { <<"src">>, <<"/images/icon>.png">> } ], [] }
+ ]},
+ mochiweb_html:parse(D2)),
+ ok.
+
+parse_missing_attr_name_test() ->
+ D0 = <<"<html =black></html>">>,
+ ?assertEqual(
+ {<<"html">>, [ { <<"=">>, <<"=">> }, { <<"black">>, <<"black">> } ], [] },
+ mochiweb_html:parse(D0)),
ok.
+parse_broken_pi_test() ->
+ D0 = <<"<html><?xml:namespace prefix = o ns = \"urn:schemas-microsoft-com:office:office\" /></html>">>,
+ ?assertEqual(
+ {<<"html">>, [], [
+ { pi, <<"xml:namespace">>, [ { <<"prefix">>, <<"o">> },
+ { <<"ns">>, <<"urn:schemas-microsoft-com:office:office">> } ] }
+ ] },
+ mochiweb_html:parse(D0)),
+ ok.
+
+parse_funny_singletons_test() ->
+ D0 = <<"<html><input><input>x</input></input></html>">>,
+ ?assertEqual(
+ {<<"html">>, [], [
+ { <<"input">>, [], [] },
+ { <<"input">>, [], [ <<"x">> ] }
+ ] },
+ mochiweb_html:parse(D0)),
+ ok.
+
-endif.
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/mochiweb_http.erl
----------------------------------------------------------------------
diff --git a/mochiweb_http.erl b/mochiweb_http.erl
index ab0af7e..23a4752 100644
--- a/mochiweb_http.erl
+++ b/mochiweb_http.erl
@@ -35,6 +35,16 @@ start() ->
start([{ip, "127.0.0.1"},
{loop, {?MODULE, default_body}}]).
+%% @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}
+%% @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)).
@@ -90,22 +100,23 @@ loop(Socket, Body) ->
request(Socket, Body).
request(Socket, Body) ->
- case mochiweb_socket:recv(Socket, 0, ?REQUEST_RECV_TIMEOUT) of
- {ok, {http_request, Method, Path, Version}} ->
+ mochiweb_socket:setopts(Socket, [{active, once}]),
+ receive
+ {Protocol, _, {http_request, Method, Path, Version}} when Protocol == http orelse Protocol == ssl ->
mochiweb_socket:setopts(Socket, [{packet, httph}]),
headers(Socket, {Method, Path, Version}, [], Body, 0);
- {error, {http_error, "\r\n"}} ->
+ {Protocol, _, {http_error, "\r\n"}} when Protocol == http orelse Protocol == ssl ->
request(Socket, Body);
- {error, {http_error, "\n"}} ->
+ {Protocol, _, {http_error, "\n"}} when Protocol == http orelse Protocol == ssl ->
request(Socket, Body);
- {error, closed} ->
- mochiweb_socket:close(Socket),
- exit(normal);
- {error, timeout} ->
+ {tcp_closed, _} ->
mochiweb_socket:close(Socket),
exit(normal);
_Other ->
handle_invalid_request(Socket)
+ after ?REQUEST_RECV_TIMEOUT ->
+ mochiweb_socket:close(Socket),
+ exit(normal)
end.
reentry(Body) ->
@@ -118,21 +129,23 @@ headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) ->
mochiweb_socket:setopts(Socket, [{packet, raw}]),
handle_invalid_request(Socket, Request, Headers);
headers(Socket, Request, Headers, Body, HeaderCount) ->
- case mochiweb_socket:recv(Socket, 0, ?HEADERS_RECV_TIMEOUT) of
- {ok, http_eoh} ->
- mochiweb_socket:setopts(Socket, [{packet, raw}]),
- Req = mochiweb:new_request({Socket, Request,
- lists:reverse(Headers)}),
+ mochiweb_socket:setopts(Socket, [{active, once}]),
+ receive
+ {Protocol, _, http_eoh} when Protocol == http orelse Protocol == ssl ->
+ Req = new_request(Socket, Request, Headers),
call_body(Body, Req),
?MODULE:after_response(Body, Req);
- {ok, {http_header, _, Name, _, Value}} ->
+ {Protocol, _, {http_header, _, Name, _, Value}} when Protocol == http orelse Protocol == ssl ->
headers(Socket, Request, [{Name, Value} | Headers], Body,
1 + HeaderCount);
- {error, closed} ->
+ {tcp_closed, _} ->
mochiweb_socket:close(Socket),
exit(normal);
_Other ->
handle_invalid_request(Socket, Request, Headers)
+ after ?HEADERS_RECV_TIMEOUT ->
+ mochiweb_socket:close(Socket),
+ exit(normal)
end.
call_body({M, F}, Req) ->
@@ -144,13 +157,15 @@ handle_invalid_request(Socket) ->
handle_invalid_request(Socket, {'GET', {abs_path, "/"}, {0,9}}, []).
handle_invalid_request(Socket, Request, RevHeaders) ->
- mochiweb_socket:setopts(Socket, [{packet, raw}]),
- Req = mochiweb:new_request({Socket, Request,
- lists:reverse(RevHeaders)}),
+ Req = new_request(Socket, Request, RevHeaders),
Req:respond({400, [], []}),
mochiweb_socket:close(Socket),
exit(normal).
+new_request(Socket, Request, RevHeaders) ->
+ mochiweb_socket:setopts(Socket, [{packet, raw}]),
+ mochiweb:new_request({Socket, Request, lists:reverse(RevHeaders)}).
+
after_response(Body, Req) ->
Socket = Req:get(socket),
case Req:should_close() of
@@ -162,6 +177,8 @@ after_response(Body, Req) ->
?MODULE:loop(Socket, Body)
end.
+parse_range_request("bytes=0-") ->
+ undefined;
parse_range_request(RawRange) when is_list(RawRange) ->
try
"bytes=" ++ RangeString = RawRange,
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/mochiweb_request.erl
----------------------------------------------------------------------
diff --git a/mochiweb_request.erl b/mochiweb_request.erl
index ffe4e9e..8225778 100644
--- a/mochiweb_request.erl
+++ b/mochiweb_request.erl
@@ -40,8 +40,8 @@
%% @type response(). A mochiweb_response parameterized module instance.
%% @type ioheaders() = headers() | [{key(), value()}].
-% 10 second default idle timeout
--define(IDLE_TIMEOUT, 10000).
+% 5 minute default idle timeout
+-define(IDLE_TIMEOUT, 300000).
% Maximum recv_body() length of 1MB
-define(MAX_RECV_BODY, (1024*1024)).
@@ -382,8 +382,8 @@ ok({ContentType, ResponseHeaders, Body}) ->
%% @doc Return true if the connection must be closed. If false, using
%% Keep-Alive should be safe.
should_close() ->
- ForceClose = erlang:get(mochiweb_request_force_close) =/= undefined,
- DidNotRecv = erlang:get(mochiweb_request_recv) =:= undefined,
+ ForceClose = erlang:get(?SAVE_FORCE_CLOSE) =/= undefined,
+ DidNotRecv = erlang:get(?SAVE_RECV) =:= undefined,
ForceClose orelse Version < {1, 0}
%% Connection: close
orelse get_header_value("connection") =:= "close"
@@ -405,6 +405,7 @@ cleanup() ->
?SAVE_PATH,
?SAVE_RECV,
?SAVE_BODY,
+ ?SAVE_BODY_LENGTH,
?SAVE_POST,
?SAVE_COOKIE,
?SAVE_FORCE_CLOSE]],
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/mochiweb_request_tests.erl
----------------------------------------------------------------------
diff --git a/mochiweb_request_tests.erl b/mochiweb_request_tests.erl
new file mode 100644
index 0000000..b61a583
--- /dev/null
+++ b/mochiweb_request_tests.erl
@@ -0,0 +1,63 @@
+-module(mochiweb_request_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+-ifdef(TEST).
+
+accepts_content_type_test() ->
+ Req1 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "multipart/related"}])),
+ ?assertEqual(true, Req1:accepts_content_type("multipart/related")),
+
+ Req2 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "text/html"}])),
+ ?assertEqual(false, Req2:accepts_content_type("multipart/related")),
+
+ Req3 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "text/html, multipart/*"}])),
+ ?assertEqual(true, Req3:accepts_content_type("multipart/related")),
+
+ Req4 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "text/html, multipart/*; q=0.0"}])),
+ ?assertEqual(false, Req4:accepts_content_type("multipart/related")),
+
+ Req5 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "text/html, multipart/*; q=0"}])),
+ ?assertEqual(false, Req5:accepts_content_type("multipart/related")),
+
+ Req6 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "text/html, */*; q=0.0"}])),
+ ?assertEqual(false, Req6:accepts_content_type("multipart/related")),
+
+ Req7 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "multipart/*; q=0.0, */*"}])),
+ ?assertEqual(false, Req7:accepts_content_type("multipart/related")),
+
+ Req8 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "*/*; q=0.0, multipart/*"}])),
+ ?assertEqual(true, Req8:accepts_content_type("multipart/related")),
+
+ Req9 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "*/*; q=0.0, multipart/related"}])),
+ ?assertEqual(true, Req9:accepts_content_type("multipart/related")),
+
+ Req10 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "text/html; level=1"}])),
+ ?assertEqual(true, Req10:accepts_content_type("text/html;level=1")),
+
+ Req11 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "text/html; level=1, text/html"}])),
+ ?assertEqual(true, Req11:accepts_content_type("text/html")),
+
+ Req12 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "text/html; level=1; q=0.0, text/html"}])),
+ ?assertEqual(false, Req12:accepts_content_type("text/html;level=1")),
+
+ Req13 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "text/html; level=1; q=0.0, text/html"}])),
+ ?assertEqual(false, Req13:accepts_content_type("text/html; level=1")),
+
+ Req14 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Accept", "text/html;level=1;q=0.1, text/html"}])),
+ ?assertEqual(true, Req14:accepts_content_type("text/html; level=1")).
+
+-endif.
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/mochiweb_socket_server.erl
----------------------------------------------------------------------
diff --git a/mochiweb_socket_server.erl b/mochiweb_socket_server.erl
index 1aae09a..ff0d8f3 100644
--- a/mochiweb_socket_server.erl
+++ b/mochiweb_socket_server.erl
@@ -12,7 +12,7 @@
-export([start/1, stop/1]).
-export([init/1, handle_call/3, handle_cast/2, terminate/2, code_change/3,
handle_info/2]).
--export([get/2]).
+-export([get/2, set/3]).
-record(mochiweb_socket_server,
{port,
@@ -28,7 +28,10 @@
acceptor_pool_size=16,
ssl=false,
ssl_opts=[{ssl_imp, new}],
- acceptor_pool=sets:new()}).
+ acceptor_pool=sets:new(),
+ profile_fun=undefined}).
+
+-define(is_old_state(State), not is_record(State, mochiweb_socket_server)).
start(State=#mochiweb_socket_server{}) ->
start_server(State);
@@ -38,6 +41,12 @@ start(Options) ->
get(Name, Property) ->
gen_server:call(Name, {get, Property}).
+set(Name, profile_fun, Fun) ->
+ gen_server:cast(Name, {set, profile_fun, Fun});
+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) ->
@@ -102,7 +111,10 @@ parse_options([{ssl, Ssl} | Rest], State) when is_boolean(Ssl) ->
parse_options(Rest, State#mochiweb_socket_server{ssl=Ssl});
parse_options([{ssl_opts, SslOpts} | Rest], State) when is_list(SslOpts) ->
SslOpts1 = [{ssl_imp, new} | proplists:delete(ssl_imp, SslOpts)],
- parse_options(Rest, State#mochiweb_socket_server{ssl_opts=SslOpts1}).
+ parse_options(Rest, State#mochiweb_socket_server{ssl_opts=SslOpts1});
+parse_options([{profile_fun, ProfileFun} | Rest], State) when is_function(ProfileFun) ->
+ parse_options(Rest, State#mochiweb_socket_server{profile_fun=ProfileFun}).
+
start_server(State=#mochiweb_socket_server{ssl=Ssl, name=Name}) ->
case Ssl of
@@ -123,7 +135,7 @@ start_server(State=#mochiweb_socket_server{ssl=Ssl, name=Name}) ->
ensure_int(N) when is_integer(N) ->
N;
ensure_int(S) when is_list(S) ->
- integer_to_list(S).
+ list_to_integer(S).
ipv6_supported() ->
case (catch inet:getaddr("localhost", inet6)) of
@@ -157,7 +169,7 @@ init(State=#mochiweb_socket_server{ip=Ip, port=Port, backlog=Backlog, nodelay=No
{stop, eacces} ->
case Port < 1024 of
true ->
- case fdsrv:start() of
+ case catch fdsrv:start() of
{ok, _} ->
case fdsrv:bind_socket(tcp, Port) of
{ok, Fd} ->
@@ -203,6 +215,28 @@ do_get(port, #mochiweb_socket_server{port=Port}) ->
do_get(active_sockets, #mochiweb_socket_server{active_sockets=ActiveSockets}) ->
ActiveSockets.
+
+state_to_proplist(#mochiweb_socket_server{name=Name,
+ port=Port,
+ active_sockets=ActiveSockets}) ->
+ [{name, Name}, {port, Port}, {active_sockets, ActiveSockets}].
+
+upgrade_state(State = #mochiweb_socket_server{}) ->
+ State;
+upgrade_state({mochiweb_socket_server, Port, Loop, Name,
+ Max, IP, Listen, NoDelay, Backlog, ActiveSockets,
+ AcceptorPoolSize, SSL, SSL_opts,
+ AcceptorPool}) ->
+ #mochiweb_socket_server{port=Port, loop=Loop, name=Name, max=Max, ip=IP,
+ listen=Listen, nodelay=NoDelay, backlog=Backlog,
+ active_sockets=ActiveSockets,
+ acceptor_pool_size=AcceptorPoolSize,
+ ssl=SSL,
+ ssl_opts=SSL_opts,
+ acceptor_pool=AcceptorPool}.
+
+handle_call(Req, From, State) when ?is_old_state(State) ->
+ handle_call(Req, From, upgrade_state(State));
handle_call({get, Property}, _From, State) ->
Res = do_get(Property, State),
{reply, Res, State};
@@ -210,13 +244,33 @@ handle_call(_Message, _From, State) ->
Res = error,
{reply, Res, State}.
-handle_cast({accepted, Pid, _Timing},
+
+handle_cast(Req, State) when ?is_old_state(State) ->
+ handle_cast(Req, upgrade_state(State));
+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)};
+handle_cast({set, profile_fun, ProfileFun}, State) ->
+ State1 = case ProfileFun of
+ ProfileFun when is_function(ProfileFun); ProfileFun =:= undefined ->
+ State#mochiweb_socket_server{profile_fun=ProfileFun};
+ _ ->
+ State
+ end,
+ {noreply, State1};
handle_cast(stop, State) ->
{stop, normal, State}.
+
+terminate(Reason, State) when ?is_old_state(State) ->
+ terminate(Reason, upgrade_state(State));
terminate(_Reason, #mochiweb_socket_server{listen=Listen, port=Port}) ->
mochiweb_socket:close(Listen),
case Port < 1024 of
@@ -244,6 +298,8 @@ recycle_acceptor(Pid, State=#mochiweb_socket_server{
State#mochiweb_socket_server{active_sockets=ActiveSockets - 1}
end.
+handle_info(Msg, State) when ?is_old_state(State) ->
+ handle_info(Msg, upgrade_state(State));
handle_info({'EXIT', Pid, normal}, State) ->
{noreply, recycle_acceptor(Pid, State)};
handle_info({'EXIT', Pid, Reason},
@@ -258,6 +314,20 @@ handle_info({'EXIT', Pid, Reason},
ok
end,
{noreply, recycle_acceptor(Pid, State)};
+
+% this is what release_handler needs to get a list of modules,
+% since our supervisor modules list is set to 'dynamic'
+% see sasl-2.1.9.2/src/release_handler_1.erl get_dynamic_mods
+handle_info({From, Tag, get_modules}, State = #mochiweb_socket_server{name={local,Mod}}) ->
+ From ! {element(2,Tag), [Mod]},
+ {noreply, State};
+
+% If for some reason we can't get the module name, send empty list to avoid release_handler timeout:
+handle_info({From, Tag, get_modules}, State) ->
+ error_logger:info_msg("mochiweb_socket_server replying to dynamic modules request as '[]'~n",[]),
+ From ! {element(2,Tag), []},
+ {noreply, State};
+
handle_info(Info, State) ->
error_logger:info_report([{'INFO', Info}, {'State', State}]),
{noreply, State}.
@@ -269,4 +339,26 @@ handle_info(Info, State) ->
%%
-include_lib("eunit/include/eunit.hrl").
-ifdef(TEST).
+
+upgrade_state_test() ->
+ OldState = {mochiweb_socket_server,
+ port, loop, name,
+ max, ip, listen,
+ nodelay, backlog,
+ active_sockets,
+ acceptor_pool_size,
+ ssl, ssl_opts, acceptor_pool},
+ State = upgrade_state(OldState),
+ CmpState = #mochiweb_socket_server{port=port, loop=loop,
+ name=name, max=max, ip=ip,
+ listen=listen, nodelay=nodelay,
+ backlog=backlog,
+ active_sockets=active_sockets,
+ acceptor_pool_size=acceptor_pool_size,
+ ssl=ssl, ssl_opts=ssl_opts,
+ acceptor_pool=acceptor_pool,
+ profile_fun=undefined},
+ ?assertEqual(CmpState, State).
+
-endif.
+
http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/964f4656/mochiweb_util.erl
----------------------------------------------------------------------
diff --git a/mochiweb_util.erl b/mochiweb_util.erl
index 62ff0d0..3b50fe7 100644
--- a/mochiweb_util.erl
+++ b/mochiweb_util.erl
@@ -620,7 +620,7 @@ cmd_port_test_spool(Port, Acc) ->
cmd_port_test_spool(Port, ["\n", Data | Acc]);
{Port, Unknown} ->
throw({unknown, Unknown})
- after 100 ->
+ after 1000 ->
throw(timeout)
end.