Skip to content

Commit

Permalink
Merge branch 'trunk' into btree_node_and_doc_caching
Browse files Browse the repository at this point in the history
  • Loading branch information
fdmanana committed Nov 12, 2010
2 parents 039058e + 0fdfbae commit 3291157
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 39 deletions.
17 changes: 5 additions & 12 deletions src/couchdb/couch_httpd_db.erl
Expand Up @@ -580,7 +580,7 @@ db_doc_req(#httpd{method='DELETE'}=Req, Db, DocId) ->
{[{<<"_rev">>, ?l2b(Rev)},{<<"_deleted">>,true}]}))
end;

db_doc_req(#httpd{method='GET'}=Req, Db, DocId) ->
db_doc_req(#httpd{method = 'GET', mochi_req = MochiReq} = Req, Db, DocId) ->
#doc_query_args{
rev = Rev,
open_revs = Revs,
Expand All @@ -599,11 +599,7 @@ db_doc_req(#httpd{method='GET'}=Req, Db, DocId) ->
send_doc(Req, Doc, Options);
_ ->
{ok, Results} = couch_db:open_doc_revs(Db, DocId, Revs, Options),
AcceptedTypes = case couch_httpd:header_value(Req, "Accept") of
undefined -> [];
AcceptHeader -> string:tokens(AcceptHeader, ", ")
end,
case lists:member("multipart/mixed", AcceptedTypes) of
case MochiReq:accepts_content_type("multipart/mixed") of
false ->
{ok, Resp} = start_json_response(Req, 200),
send_chunk(Resp, "["),
Expand Down Expand Up @@ -745,14 +741,11 @@ send_doc(Req, Doc, Options) ->

send_doc_efficiently(Req, #doc{atts=[]}=Doc, Headers, Options) ->
send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options));
send_doc_efficiently(Req, #doc{atts=Atts}=Doc, Headers, Options) ->
send_doc_efficiently(#httpd{mochi_req = MochiReq} = Req,
#doc{atts = Atts} = Doc, Headers, Options) ->
case lists:member(attachments, Options) of
true ->
AcceptedTypes = case couch_httpd:header_value(Req, "Accept") of
undefined -> [];
AcceptHeader -> string:tokens(AcceptHeader, ", ")
end,
case lists:member("multipart/related", AcceptedTypes) of
case MochiReq:accepts_content_type("multipart/related") of
false ->
send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options));
true ->
Expand Down
53 changes: 53 additions & 0 deletions src/mochiweb/mochiweb_request.erl
Expand Up @@ -21,6 +21,7 @@
-export([parse_cookie/0, get_cookie_value/1]).
-export([serve_file/2, serve_file/3]).
-export([accepted_encodings/1]).
-export([accepts_content_type/1]).

-define(SAVE_QS, mochiweb_request_qs).
-define(SAVE_PATH, mochiweb_request_path).
Expand Down Expand Up @@ -707,6 +708,58 @@ accepted_encodings(SupportedEncodings) ->
)
end.

%% @spec accepts_content_type(string() | binary()) -> boolean() | bad_accept_header
%%
%% @doc Determines whether a request accepts a given media type by analyzing its
%% "Accept" header.
%%
%% Examples
%%
%% 1) For a missing "Accept" header:
%% accepts_content_type("application/json") -> true
%%
%% 2) For an "Accept" header with value "text/plain, application/*":
%% accepts_content_type("application/json") -> true
%%
%% 3) For an "Accept" header with value "text/plain, */*; q=0.0":
%% accepts_content_type("application/json") -> false
%%
%% 4) For an "Accept" header with value "text/plain; q=0.5, */*; q=0.1":
%% accepts_content_type("application/json") -> true
%%
%% 5) For an "Accept" header with value "text/*; q=0.0, */*":
%% accepts_content_type("text/plain") -> false
%%
accepts_content_type(ContentType) when is_binary(ContentType) ->
accepts_content_type(binary_to_list(ContentType));
accepts_content_type(ContentType1) ->
ContentType = re:replace(ContentType1, "\\s", "", [global, {return, list}]),
AcceptHeader = case get_header_value("Accept") of
undefined ->
"*/*";
Value ->
Value
end,
case mochiweb_util:parse_qvalues(AcceptHeader) of
invalid_qvalue_string ->
bad_accept_header;
QList ->
[MainType, _SubType] = string:tokens(ContentType, "/"),
SuperType = MainType ++ "/*",
lists:any(
fun({"*/*", Q}) when Q > 0.0 ->
true;
({Type, Q}) when Q > 0.0 ->
Type =:= ContentType orelse Type =:= SuperType;
(_) ->
false
end,
QList
) andalso
(not lists:member({ContentType, 0.0}, QList)) andalso
(not lists:member({SuperType, 0.0}, QList))
end.

%%
%% Tests
%%
Expand Down
79 changes: 56 additions & 23 deletions src/mochiweb/mochiweb_util.erl
Expand Up @@ -414,15 +414,16 @@ shell_quote([C | Rest], Acc) ->
shell_quote(Rest, [C | Acc]).

%% @spec parse_qvalues(string()) -> [qvalue()] | invalid_qvalue_string
%% @type qvalue() = {encoding(), float()}.
%% @type qvalue() = {media_type() | encoding() , float()}.
%% @type media_type() = string().
%% @type encoding() = string().
%%
%% @doc Parses a list (given as a string) of elements with Q values associated
%% to them. Elements are separated by commas and each element is separated
%% from its Q value by a semicolon. Q values are optional but when missing
%% the value of an element is considered as 1.0. A Q value is always in the
%% range [0.0, 1.0]. A Q value list is used for example as the value of the
%% HTTP "Accept-Encoding" header.
%% HTTP "Accept" and "Accept-Encoding" headers.
%%
%% Q values are described in section 2.9 of the RFC 2616 (HTTP 1.1).
%%
Expand All @@ -433,29 +434,12 @@ shell_quote([C | Rest], Acc) ->
%%
parse_qvalues(QValuesStr) ->
try
{ok, Re} = re:compile("^\\s*q\\s*=\\s*((?:0|1)(?:\\.\\d{1,3})?)\\s*$"),
lists:map(
fun(Pair) ->
case string:tokens(Pair, ";") of
[Enc] ->
{string:strip(Enc), 1.0};
[Enc, QStr] ->
case re:run(QStr, Re, [{capture, [1], list}]) of
{match, [Q]} ->
QVal = case Q of
"0" ->
0.0;
"1" ->
1.0;
Else ->
list_to_float(Else)
end,
case QVal < 0.0 orelse QVal > 1.0 of
false ->
{string:strip(Enc), QVal}
end
end
end
[Type | Params] = string:tokens(Pair, ";"),
NormParams = normalize_media_params(Params),
{Q, NonQParams} = extract_q(NormParams),
{string:join([string:strip(Type) | NonQParams], ";"), Q}
end,
string:tokens(string:to_lower(QValuesStr), ",")
)
Expand All @@ -464,6 +448,46 @@ parse_qvalues(QValuesStr) ->
invalid_qvalue_string
end.

normalize_media_params(Params) ->
{ok, Re} = re:compile("\\s"),
normalize_media_params(Re, Params, []).

normalize_media_params(_Re, [], Acc) ->
lists:reverse(Acc);
normalize_media_params(Re, [Param | Rest], Acc) ->
NormParam = re:replace(Param, Re, "", [global, {return, list}]),
normalize_media_params(Re, Rest, [NormParam | Acc]).

extract_q(NormParams) ->
{ok, KVRe} = re:compile("^([^=]+)=([^=]+)$"),
{ok, QRe} = re:compile("^((?:0|1)(?:\\.\\d{1,3})?)$"),
extract_q(KVRe, QRe, NormParams, []).

extract_q(_KVRe, _QRe, [], Acc) ->
{1.0, lists:reverse(Acc)};
extract_q(KVRe, QRe, [Param | Rest], Acc) ->
case re:run(Param, KVRe, [{capture, [1, 2], list}]) of
{match, [Name, Value]} ->
case Name of
"q" ->
{match, [Q]} = re:run(Value, QRe, [{capture, [1], list}]),
QVal = case Q of
"0" ->
0.0;
"1" ->
1.0;
Else ->
list_to_float(Else)
end,
case QVal < 0.0 orelse QVal > 1.0 of
false ->
{QVal, lists:reverse(Acc) ++ Rest}
end;
_ ->
extract_q(KVRe, QRe, Rest, [Param | Acc])
end
end.

%% @spec pick_accepted_encodings([qvalue()], [encoding()], encoding()) ->
%% [encoding()]
%%
Expand Down Expand Up @@ -822,11 +846,20 @@ parse_qvalues_test() ->
),
[{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 1.0}, {"identity", 1.0}] =
parse_qvalues("gzip; q=0.5,deflate,identity, identity "),
[{"text/html;level=1", 1.0}, {"text/plain", 0.5}] =
parse_qvalues("text/html;level=1, text/plain;q=0.5"),
[{"text/html;level=1", 0.3}, {"text/plain", 1.0}] =
parse_qvalues("text/html;level=1;q=0.3, text/plain"),
[{"text/html;level=1", 0.3}, {"text/plain", 1.0}] =
parse_qvalues("text/html; level = 1; q = 0.3, text/plain"),
[{"text/html;level=1", 0.3}, {"text/plain", 1.0}] =
parse_qvalues("text/html;q=0.3;level=1, text/plain"),
invalid_qvalue_string = parse_qvalues("gzip; q=1.1, deflate"),
invalid_qvalue_string = parse_qvalues("gzip; q=0.5, deflate;q=2"),
invalid_qvalue_string = parse_qvalues("gzip, deflate;q=AB"),
invalid_qvalue_string = parse_qvalues("gzip; q=2.1, deflate"),
invalid_qvalue_string = parse_qvalues("gzip; q=0.1234, deflate"),
invalid_qvalue_string = parse_qvalues("text/html;level=1;q=0.3, text/html;level"),
ok.

pick_accepted_encodings_test() ->
Expand Down
12 changes: 8 additions & 4 deletions test/etap/140-attachment-comp.t
Expand Up @@ -301,7 +301,8 @@ test_get_1st_png_att_with_accept_encoding_deflate() ->
test_get_doc_with_1st_text_att() ->
{ok, {{_, Code, _}, _Headers, Body}} = http:request(
get,
{db_url() ++ "/testdoc1?attachments=true", []},
{db_url() ++ "/testdoc1?attachments=true",
[{"Accept", "application/json"}]},
[],
[{sync, true}]),
etap:is(Code, 200, "HTTP response code is 200"),
Expand Down Expand Up @@ -367,7 +368,8 @@ test_1st_text_att_stub() ->
test_get_doc_with_1st_png_att() ->
{ok, {{_, Code, _}, _Headers, Body}} = http:request(
get,
{db_url() ++ "/testdoc2?attachments=true", []},
{db_url() ++ "/testdoc2?attachments=true",
[{"Accept", "application/json"}]},
[],
[{sync, true}]),
etap:is(Code, 200, "HTTP response code is 200"),
Expand Down Expand Up @@ -492,7 +494,8 @@ test_get_2nd_png_att_with_accept_encoding_gzip() ->
test_get_doc_with_2nd_text_att() ->
{ok, {{_, Code, _}, _Headers, Body}} = http:request(
get,
{db_url() ++ "/testdoc3?attachments=true", []},
{db_url() ++ "/testdoc3?attachments=true",
[{"Accept", "application/json"}]},
[],
[{sync, true}]),
etap:is(Code, 200, "HTTP response code is 200"),
Expand Down Expand Up @@ -554,7 +557,8 @@ test_2nd_text_att_stub() ->
test_get_doc_with_2nd_png_att() ->
{ok, {{_, Code, _}, _Headers, Body}} = http:request(
get,
{db_url() ++ "/testdoc4?attachments=true", []},
{db_url() ++ "/testdoc4?attachments=true",
[{"Accept", "application/json"}]},
[],
[{sync, true}]),
etap:is(Code, 200, "HTTP response code is 200"),
Expand Down

0 comments on commit 3291157

Please sign in to comment.