You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by jc...@apache.org on 2009/03/05 07:14:39 UTC
svn commit: r750332 [2/2] - in /couchdb/branches/rep_security: ./ bin/
etc/couchdb/ etc/default/ etc/init/ share/ share/server/ share/www/
share/www/script/ share/www/script/test/ share/www/style/ src/couchdb/
src/ibrowse/ src/mochiweb/ test/
Modified: couchdb/branches/rep_security/src/couchdb/couch_httpd_db.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_httpd_db.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_httpd_db.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_httpd_db.erl Thu Mar 5 06:14:36 2009
@@ -102,6 +102,7 @@
send_method_not_allowed(Req, "POST");
db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) ->
+ couch_stats_collector:increment({httpd, bulk_requests}),
{JsonProps} = couch_httpd:json_body(Req),
DocsArray = proplists:get_value(<<"docs">>, JsonProps),
case couch_httpd:header_value(Req, "X-Couch-Full-Commit", "false") of
@@ -202,46 +203,48 @@
} = QueryArgs = couch_httpd_view:parse_view_query(Req),
{ok, Info} = couch_db:get_db_info(Db),
- TotalRowCount = proplists:get_value(doc_count, Info),
-
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
- TotalRowCount, #view_fold_helper_funs{
- reduce_count = fun couch_db:enum_docs_since_reduce_to_count/1
- }),
- StartKey2 = case StartKey of
- nil -> 0;
- <<>> -> 100000000000;
- {} -> 100000000000;
- StartKey when is_integer(StartKey) -> StartKey
- end,
- {ok, FoldResult} = couch_db:enum_docs_since(Db, StartKey2, Dir,
- fun(DocInfo, Offset, Acc) ->
- #doc_info{
- id=Id,
- rev=Rev,
- update_seq=UpdateSeq,
- deleted=Deleted,
- conflict_revs=ConflictRevs,
- deleted_conflict_revs=DelConflictRevs
- } = DocInfo,
- Json = {
- [{<<"rev">>, couch_doc:rev_to_str(Rev)}] ++
- case ConflictRevs of
- [] -> [];
- _ -> [{<<"conflicts">>, couch_doc:rev_to_strs(ConflictRevs)}]
- end ++
- case DelConflictRevs of
- [] -> [];
- _ -> [{<<"deleted_conflicts">>, couch_doc:rev_to_strs(DelConflictRevs)}]
- end ++
- case Deleted of
- true -> [{<<"deleted">>, true}];
- false -> []
- end
- },
- FoldlFun({{UpdateSeq, Id}, Json}, Offset, Acc)
- end, {Limit, SkipCount, undefined, []}),
- couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
+ CurrentEtag = couch_httpd:make_etag(proplists:get_value(update_seq, Info)),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ TotalRowCount = proplists:get_value(doc_count, Info),
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+ TotalRowCount, #view_fold_helper_funs{
+ reduce_count = fun couch_db:enum_docs_since_reduce_to_count/1
+ }),
+ StartKey2 = case StartKey of
+ nil -> 0;
+ <<>> -> 100000000000;
+ {} -> 100000000000;
+ StartKey when is_integer(StartKey) -> StartKey
+ end,
+ {ok, FoldResult} = couch_db:enum_docs_since(Db, StartKey2, Dir,
+ fun(DocInfo, Offset, Acc) ->
+ #doc_info{
+ id=Id,
+ rev=Rev,
+ update_seq=UpdateSeq,
+ deleted=Deleted,
+ conflict_revs=ConflictRevs,
+ deleted_conflict_revs=DelConflictRevs
+ } = DocInfo,
+ Json = {
+ [{<<"rev">>, couch_doc:rev_to_str(Rev)}] ++
+ case ConflictRevs of
+ [] -> [];
+ _ -> [{<<"conflicts">>, couch_doc:rev_to_strs(ConflictRevs)}]
+ end ++
+ case DelConflictRevs of
+ [] -> [];
+ _ -> [{<<"deleted_conflicts">>, couch_doc:rev_to_strs(DelConflictRevs)}]
+ end ++
+ case Deleted of
+ true -> [{<<"deleted">>, true}];
+ false -> []
+ end
+ },
+ FoldlFun({{UpdateSeq, Id}, Json}, Offset, Acc)
+ end, {Limit, SkipCount, undefined, []}),
+ couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
+ end);
db_req(#httpd{path_parts=[_,<<"_all_docs_by_seq">>]}=Req, _Db) ->
send_method_not_allowed(Req, "GET,HEAD");
@@ -276,9 +279,8 @@
PathFront = "/" ++ couch_httpd:quote(binary_to_list(DbName)) ++ "/",
RawSplit = regexp:split(MochiReq:get(raw_path),"_design%2F"),
{ok, [PathFront|PathTail]} = RawSplit,
- RedirectTo = couch_httpd:absolute_uri(Req, PathFront ++ "_design/" ++
- mochiweb_util:join(PathTail, "%2F")),
- couch_httpd:send_response(Req, 301, [{"Location", RedirectTo}], <<>>);
+ couch_httpd:send_redirect(Req, PathFront ++ "_design/" ++
+ mochiweb_util:join(PathTail, "_design%2F"));
db_req(#httpd{path_parts=[_DbName,<<"_design">>,Name]}=Req, Db) ->
db_doc_req(Req, Db, <<"_design/",Name/binary>>);
@@ -303,77 +305,81 @@
direction = Dir
} = QueryArgs = couch_httpd_view:parse_view_query(Req, Keys),
{ok, Info} = couch_db:get_db_info(Db),
- TotalRowCount = proplists:get_value(doc_count, Info),
- StartId = if is_binary(StartKey) -> StartKey;
- true -> StartDocId
- end,
- FoldAccInit = {Limit, SkipCount, undefined, []},
+ CurrentEtag = couch_httpd:make_etag(proplists:get_value(update_seq, Info)),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
- PassedEndFun =
- case Dir of
- fwd ->
- fun(ViewKey, _ViewId) ->
- couch_db_updater:less_docid(EndKey, ViewKey)
- end;
- rev->
- fun(ViewKey, _ViewId) ->
- couch_db_updater:less_docid(ViewKey, EndKey)
- end
- end,
-
- case Keys of
- nil ->
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
- TotalRowCount, #view_fold_helper_funs{
- reduce_count = fun couch_db:enum_docs_reduce_to_count/1,
- passed_end = PassedEndFun
- }),
- AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
- case couch_doc:to_doc_info(FullDocInfo) of
- #doc_info{deleted=false, rev=Rev} ->
- FoldlFun({{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}}, Offset, Acc);
- #doc_info{deleted=true} ->
- {ok, Acc}
- end
+ TotalRowCount = proplists:get_value(doc_count, Info),
+ StartId = if is_binary(StartKey) -> StartKey;
+ true -> StartDocId
end,
- {ok, FoldResult} = couch_db:enum_docs(Db, StartId, Dir,
- AdapterFun, FoldAccInit),
- couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
- _ ->
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db,
- TotalRowCount, #view_fold_helper_funs{
- reduce_count = fun(Offset) -> Offset end
- }),
- KeyFoldFun = case Dir of
+ FoldAccInit = {Limit, SkipCount, undefined, []},
+
+ PassedEndFun =
+ case Dir of
fwd ->
- fun lists:foldl/3;
- rev ->
- fun lists:foldr/3
+ fun(ViewKey, _ViewId) ->
+ couch_db_updater:less_docid(EndKey, ViewKey)
+ end;
+ rev->
+ fun(ViewKey, _ViewId) ->
+ couch_db_updater:less_docid(ViewKey, EndKey)
+ end
end,
- {ok, FoldResult} = KeyFoldFun(
- fun(Key, {ok, FoldAcc}) ->
- DocInfo = (catch couch_db:get_doc_info(Db, Key)),
- Doc = case DocInfo of
- {ok, #doc_info{id=Id, rev=Rev, deleted=false}} = DocInfo ->
- {{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}};
- {ok, #doc_info{id=Id, rev=Rev, deleted=true}} = DocInfo ->
- {{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}, {deleted, true}]}};
- not_found ->
- {{Key, error}, not_found};
- _ ->
- ?LOG_ERROR("Invalid DocInfo: ~p", [DocInfo]),
- throw({error, invalid_doc_info})
- end,
- Acc = (catch FoldlFun(Doc, 0, FoldAcc)),
- case Acc of
- {stop, Acc2} ->
- {ok, Acc2};
- _ ->
- Acc
+
+ case Keys of
+ nil ->
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+ TotalRowCount, #view_fold_helper_funs{
+ reduce_count = fun couch_db:enum_docs_reduce_to_count/1,
+ passed_end = PassedEndFun
+ }),
+ AdapterFun = fun(#full_doc_info{id=Id}=FullDocInfo, Offset, Acc) ->
+ case couch_doc:to_doc_info(FullDocInfo) of
+ #doc_info{deleted=false, rev=Rev} ->
+ FoldlFun({{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}}, Offset, Acc);
+ #doc_info{deleted=true} ->
+ {ok, Acc}
end
- end, {ok, FoldAccInit}, Keys),
- couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
- end.
+ end,
+ {ok, FoldResult} = couch_db:enum_docs(Db, StartId, Dir,
+ AdapterFun, FoldAccInit),
+ couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult});
+ _ ->
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db,
+ TotalRowCount, #view_fold_helper_funs{
+ reduce_count = fun(Offset) -> Offset end
+ }),
+ KeyFoldFun = case Dir of
+ fwd ->
+ fun lists:foldl/3;
+ rev ->
+ fun lists:foldr/3
+ end,
+ {ok, FoldResult} = KeyFoldFun(
+ fun(Key, {ok, FoldAcc}) ->
+ DocInfo = (catch couch_db:get_doc_info(Db, Key)),
+ Doc = case DocInfo of
+ {ok, #doc_info{id=Id, rev=Rev, deleted=false}} = DocInfo ->
+ {{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}]}};
+ {ok, #doc_info{id=Id, rev=Rev, deleted=true}} = DocInfo ->
+ {{Id, Id}, {[{rev, couch_doc:rev_to_str(Rev)}, {deleted, true}]}};
+ not_found ->
+ {{Key, error}, not_found};
+ _ ->
+ ?LOG_ERROR("Invalid DocInfo: ~p", [DocInfo]),
+ throw({error, invalid_doc_info})
+ end,
+ Acc = (catch FoldlFun(Doc, 0, FoldAcc)),
+ case Acc of
+ {stop, Acc2} ->
+ {ok, Acc2};
+ _ ->
+ Acc
+ end
+ end, {ok, FoldAccInit}, Keys),
+ couch_httpd_view:finish_view_fold(Req, TotalRowCount, {ok, FoldResult})
+ end
+ end).
@@ -400,7 +406,7 @@
[] -> [{"Etag", DiskEtag}]; % output etag only when we have no meta
_ -> []
end,
- send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options))
+ send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options))
end);
_ ->
{ok, Results} = couch_db:open_doc_revs(Db, DocId, Revs, Options),
@@ -595,18 +601,31 @@
db_attachment_req(#httpd{method=Method}=Req, Db, DocId, FileNameParts)
when (Method == 'PUT') or (Method == 'DELETE') ->
- FileName = list_to_binary(mochiweb_util:join(lists:map(fun binary_to_list/1, FileNameParts),"/")),
+ FileName = list_to_binary(mochiweb_util:join(lists:map(fun binary_to_list/1,
+ FileNameParts),"/")),
NewAttachment = case Method of
'DELETE' ->
[];
_ ->
+ % see couch_db:doc_flush_binaries for usage of this structure
[{FileName, {
- list_to_binary(couch_httpd:header_value(Req,"Content-Type")),
+ case couch_httpd:header_value(Req,"Content-Type") of
+ undefined ->
+ % We could throw an error here or guess by the FileName.
+ % Currently, just giving it a default.
+ <<"application/octet-stream">>;
+ CType ->
+ list_to_binary(CType)
+ end,
case couch_httpd:header_value(Req,"Content-Length") of
undefined ->
- throw({bad_request, "Attachment uploads must be fixed length"});
+ {fun(MaxChunkSize, ChunkFun, InitState) ->
+ couch_httpd:recv_chunked(Req, MaxChunkSize,
+ ChunkFun, InitState)
+ end, undefined};
Length ->
- {fun() -> couch_httpd:recv(Req, 0) end, list_to_integer(Length)}
+ {fun() -> couch_httpd:recv(Req, 0) end,
+ list_to_integer(Length)}
end
}}]
end,
@@ -677,7 +696,6 @@
undefined -> undefined;
Value -> couch_doc:parse_rev(string:strip(Value, both, $"))
end,
-
case {ExplicitRev, Etag} of
{undefined, undefined} -> missing_rev;
{_, undefined} -> ExplicitRev;
Modified: couchdb/branches/rep_security/src/couchdb/couch_httpd_external.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_httpd_external.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_httpd_external.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_httpd_external.erl Thu Mar 5 06:14:36 2009
@@ -106,6 +106,8 @@
Args;
{<<"code">>, Value} ->
Args#extern_resp_args{code=Value};
+ {<<"stop">>, true} ->
+ Args#extern_resp_args{stop=true};
{<<"json">>, Value} ->
Args#extern_resp_args{
data=?JSON_ENCODE(Value),
Modified: couchdb/branches/rep_security/src/couchdb/couch_httpd_misc_handlers.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_httpd_misc_handlers.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_httpd_misc_handlers.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_httpd_misc_handlers.erl Thu Mar 5 06:14:36 2009
@@ -14,7 +14,7 @@
-export([handle_welcome_req/2,handle_favicon_req/2,handle_utils_dir_req/2,
handle_all_dbs_req/1,handle_replicate_req/1,handle_restart_req/1,
- handle_uuids_req/1,handle_config_req/1,handle_stats_req/1,
+ handle_uuids_req/1,handle_config_req/1,
handle_task_status_req/1]).
-export([increment_update_seq_req/2]).
@@ -50,8 +50,7 @@
couch_httpd:serve_file(Req, RelativePath, DocumentRoot);
{_ActionKey, "", _RelativePath} ->
% GET /_utils
- Headers = [{"Location", couch_httpd:absolute_uri(Req, "/_utils/")}],
- couch_httpd:send_response(Req, 301, Headers, <<>>)
+ couch_httpd:send_redirect(Req, "/_utils/")
end;
handle_utils_dir_req(Req, _) ->
send_method_not_allowed(Req, "GET,HEAD").
@@ -64,13 +63,6 @@
send_method_not_allowed(Req, "GET,HEAD").
-handle_stats_req(#httpd{method='GET'}=Req) ->
- ok = couch_httpd:verify_is_server_admin(Req),
- send_json(Req, {couch_server:get_stats() ++ couch_file_stats:get_stats()});
-handle_stats_req(Req) ->
- send_method_not_allowed(Req, "GET,HEAD").
-
-
handle_task_status_req(#httpd{method='GET'}=Req) ->
ok = couch_httpd:verify_is_server_admin(Req),
% convert the list of prop lists to a list of json objects
@@ -111,14 +103,18 @@
send_method_not_allowed(Req, "POST").
-handle_uuids_req(#httpd{method='POST'}=Req) ->
+handle_uuids_req(#httpd{method='GET'}=Req) ->
Count = list_to_integer(couch_httpd:qs_value(Req, "count", "1")),
+ CacheBustingHeaders = [{"Date", httpd_util:rfc1123_date()},
+ {"Cache-Control", "no-cache"},
+ {"Expires", "Fri, 01 Jan 1990 00:00:00 GMT"}, % Past date, ON PURPOSE!
+ {"Pragma", "no-cache"}],
% generate the uuids
UUIDs = [ couch_util:new_uuid() || _ <- lists:seq(1,Count)],
% send a JSON response
- send_json(Req, {[{<<"uuids">>, UUIDs}]});
+ send_json(Req, 200, CacheBustingHeaders, {[{<<"uuids">>, UUIDs}]});
handle_uuids_req(Req) ->
- send_method_not_allowed(Req, "POST").
+ send_method_not_allowed(Req, "GET").
% Config request handler
Modified: couchdb/branches/rep_security/src/couchdb/couch_httpd_show.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_httpd_show.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_httpd_show.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_httpd_show.erl Thu Mar 5 06:14:36 2009
@@ -19,19 +19,23 @@
-import(couch_httpd,
[send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
- start_json_response/2,send_chunk/2,end_json_response/1,
+ start_json_response/2,send_chunk/2,
start_chunked_response/3, send_error/4]).
handle_doc_show_req(#httpd{
method='GET',
- path_parts=[_, _, DesignName, ShowName, Docid]
+ path_parts=[_, _, DesignName, ShowName, DocId]
}=Req, Db) ->
DesignId = <<"_design/", DesignName/binary>>,
#doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]),
- Doc = couch_httpd_db:couch_doc_open(Db, Docid, nil, []),
- send_doc_show_response(Lang, ShowSrc, Doc, Req, Db);
+ Doc = try couch_httpd_db:couch_doc_open(Db, DocId, nil, []) of
+ FoundDoc -> FoundDoc
+ catch
+ _ -> nil
+ end,
+ send_doc_show_response(Lang, ShowSrc, DocId, Doc, Req, Db);
handle_doc_show_req(#httpd{
method='GET',
@@ -41,7 +45,7 @@
#doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, nil, []),
Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]),
- send_doc_show_response(Lang, ShowSrc, nil, Req, Db);
+ send_doc_show_response(Lang, ShowSrc, nil, nil, Req, Db);
handle_doc_show_req(#httpd{method='GET'}=Req, _Db) ->
send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>);
@@ -56,6 +60,9 @@
ListSrc = get_nested_json_value({Props}, [<<"lists">>, ListName]),
send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db);
+handle_view_list_req(#httpd{method='GET'}=Req, _Db) ->
+ send_error(Req, 404, <<"list_error">>, <<"Invalid path.">>);
+
handle_view_list_req(Req, _Db) ->
send_method_not_allowed(Req, "GET,HEAD").
@@ -71,30 +78,29 @@
throw({not_found, json_mismatch}).
send_view_list_response(Lang, ListSrc, ViewName, DesignId, Req, Db) ->
- % TODO add etags when we get view etags
#view_query_args{
stale = Stale,
reduce = Reduce
- } = QueryArgs = couch_httpd_view:parse_view_query(Req),
+ } = QueryArgs = couch_httpd_view:parse_view_query(Req, nil, nil, true),
case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
- {ok, View} ->
- output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs);
+ {ok, View, Group} ->
+ output_map_list(Req, Lang, ListSrc, View, Group, Db, QueryArgs);
{not_found, _Reason} ->
case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
- {ok, ReduceView} ->
+ {ok, ReduceView, Group} ->
case Reduce of
false ->
MapView = couch_view:extract_map_view(ReduceView),
- output_map_list(Req, Lang, ListSrc, MapView, Db, QueryArgs);
+ output_map_list(Req, Lang, ListSrc, MapView, Group, Db, QueryArgs);
_ ->
- throw({not_implemented, reduce_view_lists})
+ output_reduce_list(Req, Lang, ListSrc, ReduceView, Group, Db, QueryArgs)
end;
{not_found, Reason} ->
throw({not_found, Reason})
end
end.
-
-output_map_list(Req, Lang, ListSrc, View, Db, QueryArgs) ->
+
+output_map_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs) ->
#view_query_args{
limit = Limit,
direction = Dir,
@@ -104,71 +110,158 @@
} = QueryArgs,
{ok, RowCount} = couch_view:get_row_count(View),
Start = {StartKey, StartDocId},
+ Headers = MReq:get(headers),
+ Hlist = mochiweb_headers:to_list(Headers),
+ Accept = proplists:get_value('Accept', Hlist),
+ CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept}),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ % get the os process here
+ % pass it into the view fold with closures
+ {ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
+
+ StartListRespFun = fun(Req2, _Etag, TotalViewCount, Offset) ->
+ ExternalResp = couch_query_servers:render_list_head(QueryServer,
+ Req2, Db, TotalViewCount, Offset),
+ JsonResp = apply_etag(ExternalResp, CurrentEtag),
+ #extern_resp_args{
+ code = Code,
+ data = BeginBody,
+ ctype = CType,
+ headers = ExtHeaders
+ } = couch_httpd_external:parse_external_response(JsonResp),
+ JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
+ {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
+ {ok, Resp, binary_to_list(BeginBody)}
+ end,
+
+ SendListRowFun = fun(Resp, Db2, {{Key, DocId}, Value},
+ RowFront, _IncludeDocs) ->
+ JsonResp = couch_query_servers:render_list_row(QueryServer,
+ Req, Db2, {{Key, DocId}, Value}),
+ #extern_resp_args{
+ stop = StopIter,
+ data = RowBody
+ } = couch_httpd_external:parse_external_response(JsonResp),
+ case StopIter of
+ true -> stop;
+ _ ->
+ RowFront2 = case RowFront of
+ nil -> [];
+ _ -> RowFront
+ end,
+ Chunk = RowFront2 ++ binary_to_list(RowBody),
+ case Chunk of
+ [] -> {ok, Resp};
+ _ -> send_chunk(Resp, Chunk)
+ end
+ end
+ end,
+
+ FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount,
+ #view_fold_helper_funs{
+ reduce_count = fun couch_view:reduce_to_count/1,
+ start_response = StartListRespFun,
+ send_row = SendListRowFun
+ }),
+ FoldAccInit = {Limit, SkipCount, undefined, []},
+ FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+ finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, RowCount)
+ end).
+
+output_reduce_list(#httpd{mochi_req=MReq}=Req, Lang, ListSrc, View, Group, Db, QueryArgs) ->
+ #view_query_args{
+ limit = Limit,
+ direction = Dir,
+ skip = SkipCount,
+ start_key = StartKey,
+ start_docid = StartDocId,
+ end_key = EndKey,
+ end_docid = EndDocId,
+ group_level = GroupLevel
+ } = QueryArgs,
% get the os process here
% pass it into the view fold with closures
{ok, QueryServer} = couch_query_servers:start_view_list(Lang, ListSrc),
-
- StartListRespFun = fun(Req2, Code, TotalViewCount, Offset) ->
- JsonResp = couch_query_servers:render_list_head(QueryServer,
- Req2, Db, TotalViewCount, Offset),
- #extern_resp_args{
- code = Code,
- data = BeginBody,
- ctype = CType,
- headers = Headers
- } = couch_httpd_external:parse_external_response(JsonResp),
- JsonHeaders = couch_httpd_external:default_or_content_type(CType, Headers),
- {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
- {ok, Resp, binary_to_list(BeginBody)}
- end,
+ Headers = MReq:get(headers),
+ Hlist = mochiweb_headers:to_list(Headers),
+ Accept = proplists:get_value('Accept', Hlist),
+ CurrentEtag = couch_httpd_view:view_group_etag(Group, {Lang, ListSrc, Accept}),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ StartListRespFun = fun(Req2, _Etag, _, _) ->
+ JsonResp = couch_query_servers:render_reduce_head(QueryServer,
+ Req2, Db),
+ JsonResp2 = apply_etag(JsonResp, CurrentEtag),
+ #extern_resp_args{
+ code = Code,
+ data = BeginBody,
+ ctype = CType,
+ headers = ExtHeaders
+ } = couch_httpd_external:parse_external_response(JsonResp2),
+ JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
+ {ok, Resp} = start_chunked_response(Req, Code, JsonHeaders),
+ {ok, Resp, binary_to_list(BeginBody)}
+ end,
- SendListRowFun = fun(Resp, Db2, {{Key, DocId}, Value},
- RowFront, _IncludeDocs) ->
- JsonResp = couch_query_servers:render_list_row(QueryServer,
- Req, Db2, {{Key, DocId}, Value}),
- #extern_resp_args{
- data = RowBody
- } = couch_httpd_external:parse_external_response(JsonResp),
- RowFront2 = case RowFront of
- nil -> [];
- _ -> RowFront
+ SendListRowFun = fun(Resp, {Key, Value}, RowFront) ->
+ JsonResp = couch_query_servers:render_reduce_row(QueryServer,
+ Req, Db, {Key, Value}),
+ #extern_resp_args{
+ stop = StopIter,
+ data = RowBody
+ } = couch_httpd_external:parse_external_response(JsonResp),
+ RowFront2 = case RowFront of
+ nil -> [];
+ _ -> RowFront
+ end,
+ case StopIter of
+ true -> stop;
+ _ ->
+ Chunk = RowFront2 ++ binary_to_list(RowBody),
+ case Chunk of
+ [] -> {ok, Resp};
+ _ -> send_chunk(Resp, Chunk)
+ end
+ end
end,
- send_chunk(Resp, RowFront2 ++ binary_to_list(RowBody))
- end,
- FoldlFun = couch_httpd_view:make_view_fold_fun(Req, QueryArgs, Db, RowCount,
- #view_fold_helper_funs{
- reduce_count = fun couch_view:reduce_to_count/1,
- start_response = StartListRespFun,
- send_row = SendListRowFun
- }),
- FoldAccInit = {Limit, SkipCount, undefined, []},
- FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
- finish_view_list(Req, Db, QueryServer, RowCount, FoldResult, StartListRespFun).
+ {ok, GroupRowsFun, RespFun} = couch_httpd_view:make_reduce_fold_funs(Req,
+ GroupLevel, QueryArgs, CurrentEtag,
+ #reduce_fold_helper_funs{
+ start_response = StartListRespFun,
+ send_row = SendListRowFun
+ }),
+ FoldAccInit = {Limit, SkipCount, undefined, []},
+ FoldResult = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId},
+ {EndKey, EndDocId}, GroupRowsFun, RespFun,
+ FoldAccInit),
+ finish_list(Req, Db, QueryServer, CurrentEtag, FoldResult, StartListRespFun, null)
+ end).
-finish_view_list(Req, Db, QueryServer, TotalRows,
- FoldResult, StartListRespFun) ->
+finish_list(Req, Db, QueryServer, Etag, FoldResult, StartListRespFun, TotalRows) ->
case FoldResult of
- {ok, {_, _, undefined, _}} ->
- {ok, Resp, BeginBody} = StartListRespFun(Req, 200, TotalRows, null),
+ {ok, Acc} ->
JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
#extern_resp_args{
data = Tail
} = couch_httpd_external:parse_external_response(JsonTail),
- send_chunk(Resp, BeginBody ++ Tail),
- send_chunk(Resp, []);
- {ok, {_, _, Resp, _AccRevRows}} ->
- JsonTail = couch_query_servers:render_list_tail(QueryServer, Req, Db),
- #extern_resp_args{
- data = Tail
- } = couch_httpd_external:parse_external_response(JsonTail),
- send_chunk(Resp, Tail),
+ {Resp, BeginBody} = case Acc of
+ {_, _, undefined, _} ->
+ {ok, Resp2, BeginBody2} = StartListRespFun(Req, Etag, TotalRows, null),
+ {Resp2, BeginBody2};
+ {_, _, Resp2, _} ->
+ {Resp2, ""}
+ end,
+ Chunk = BeginBody ++ binary_to_list(Tail),
+ case Chunk of
+ [] -> ok;
+ _ -> send_chunk(Resp, Chunk)
+ end,
send_chunk(Resp, []);
Error ->
throw(Error)
end.
-send_doc_show_response(Lang, ShowSrc, nil, #httpd{mochi_req=MReq}=Req, Db) ->
+send_doc_show_response(Lang, ShowSrc, DocId, nil, #httpd{mochi_req=MReq}=Req, Db) ->
% compute etag with no doc
Headers = MReq:get(headers),
Hlist = mochiweb_headers:to_list(Headers),
@@ -176,12 +269,12 @@
CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, nil, Accept}),
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc,
- nil, Req, Db),
+ DocId, nil, Req, Db),
JsonResp = apply_etag(ExternalResp, CurrentEtag),
couch_httpd_external:send_external_response(Req, JsonResp)
end);
-send_doc_show_response(Lang, ShowSrc, #doc{revs=Revs}=Doc, #httpd{mochi_req=MReq}=Req, Db) ->
+send_doc_show_response(Lang, ShowSrc, DocId, #doc{revs=Revs}=Doc, #httpd{mochi_req=MReq}=Req, Db) ->
% calculate the etag
Headers = MReq:get(headers),
Hlist = mochiweb_headers:to_list(Headers),
@@ -190,7 +283,7 @@
% We know our etag now
couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc,
- Doc, Req, Db),
+ DocId, Doc, Req, Db),
JsonResp = apply_etag(ExternalResp, CurrentEtag),
couch_httpd_external:send_external_response(Req, JsonResp)
end).
Modified: couchdb/branches/rep_security/src/couchdb/couch_httpd_view.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_httpd_view.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_httpd_view.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_httpd_view.erl Thu Mar 5 06:14:36 2009
@@ -15,12 +15,12 @@
-export([handle_view_req/2,handle_temp_view_req/2]).
--export([parse_view_query/1,parse_view_query/2,make_view_fold_fun/5,
- finish_view_fold/3, view_row_obj/3]).
+-export([parse_view_query/1,parse_view_query/2,parse_view_query/4,make_view_fold_fun/6,
+ finish_view_fold/3, view_row_obj/3, view_group_etag/1, view_group_etag/2, make_reduce_fold_funs/5]).
-import(couch_httpd,
- [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
- start_json_response/2,send_chunk/2,end_json_response/1]).
+ [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,send_chunk/2,
+ start_json_response/2, start_json_response/3, end_json_response/1]).
design_doc_view(Req, Db, Id, ViewName, Keys) ->
#view_query_args{
@@ -28,24 +28,26 @@
reduce = Reduce
} = QueryArgs = parse_view_query(Req, Keys),
DesignId = <<"_design/", Id/binary>>,
- case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
- {ok, View} ->
- output_map_view(Req, View, Db, QueryArgs, Keys);
+ Result = case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of
+ {ok, View, Group} ->
+ output_map_view(Req, View, Group, Db, QueryArgs, Keys);
{not_found, Reason} ->
case couch_view:get_reduce_view(Db, DesignId, ViewName, Stale) of
- {ok, ReduceView} ->
+ {ok, ReduceView, Group} ->
parse_view_query(Req, Keys, true), % just for validation
case Reduce of
false ->
MapView = couch_view:extract_map_view(ReduceView),
- output_map_view(Req, MapView, Db, QueryArgs, Keys);
+ output_map_view(Req, MapView, Group, Db, QueryArgs, Keys);
_ ->
- output_reduce_view(Req, ReduceView, QueryArgs, Keys)
+ output_reduce_view(Req, ReduceView, Group, QueryArgs, Keys)
end;
_ ->
throw({not_found, Reason})
end
- end.
+ end,
+ couch_stats_collector:increment({httpd, view_reads}),
+ Result.
handle_view_req(#httpd{method='GET',path_parts=[_,_, Id, ViewName]}=Req, Db) ->
design_doc_view(Req, Db, Id, ViewName, nil);
@@ -60,7 +62,7 @@
handle_temp_view_req(#httpd{method='POST'}=Req, Db) ->
QueryArgs = parse_view_query(Req),
-
+ couch_stats_collector:increment({httpd, temporary_view_reads}),
case couch_httpd:primary_header_value(Req, "content-type") of
undefined -> ok;
"application/json" -> ok;
@@ -73,19 +75,19 @@
Keys = proplists:get_value(<<"keys">>, Props, nil),
case proplists:get_value(<<"reduce">>, Props, null) of
null ->
- {ok, View} = couch_view:get_temp_map_view(Db, Language,
+ {ok, View, Group} = couch_view:get_temp_map_view(Db, Language,
DesignOptions, MapSrc),
- output_map_view(Req, View, Db, QueryArgs, Keys);
+ output_map_view(Req, View, Group, Db, QueryArgs, Keys);
RedSrc ->
- {ok, View} = couch_view:get_temp_reduce_view(Db, Language,
+ {ok, View, Group} = couch_view:get_temp_reduce_view(Db, Language,
DesignOptions, MapSrc, RedSrc),
- output_reduce_view(Req, View, QueryArgs, Keys)
+ output_reduce_view(Req, View, Group, QueryArgs, Keys)
end;
handle_temp_view_req(Req, _Db) ->
send_method_not_allowed(Req, "POST").
-output_map_view(Req, View, Db, QueryArgs, nil) ->
+output_map_view(Req, View, Group, Db, QueryArgs, nil) ->
#view_query_args{
limit = Limit,
direction = Dir,
@@ -93,38 +95,44 @@
start_key = StartKey,
start_docid = StartDocId
} = QueryArgs,
- {ok, RowCount} = couch_view:get_row_count(View),
- Start = {StartKey, StartDocId},
- FoldlFun = make_view_fold_fun(Req, QueryArgs, Db, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}),
- FoldAccInit = {Limit, SkipCount, undefined, []},
- FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
- finish_view_fold(Req, RowCount, FoldResult);
+ CurrentEtag = view_group_etag(Group),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ {ok, RowCount} = couch_view:get_row_count(View),
+ Start = {StartKey, StartDocId},
+ FoldlFun = make_view_fold_fun(Req, QueryArgs, CurrentEtag, Db, RowCount, #view_fold_helper_funs{reduce_count=fun couch_view:reduce_to_count/1}),
+ FoldAccInit = {Limit, SkipCount, undefined, []},
+ FoldResult = couch_view:fold(View, Start, Dir, FoldlFun, FoldAccInit),
+ finish_view_fold(Req, RowCount, FoldResult)
+ end);
-output_map_view(Req, View, Db, QueryArgs, Keys) ->
+output_map_view(Req, View, Group, Db, QueryArgs, Keys) ->
#view_query_args{
limit = Limit,
direction = Dir,
skip = SkipCount,
start_docid = StartDocId
} = QueryArgs,
- {ok, RowCount} = couch_view:get_row_count(View),
- FoldAccInit = {Limit, SkipCount, undefined, []},
- FoldResult = lists:foldl(
- fun(Key, {ok, FoldAcc}) ->
- Start = {Key, StartDocId},
- FoldlFun = make_view_fold_fun(Req,
- QueryArgs#view_query_args{
- start_key = Key,
- end_key = Key
- }, Db, RowCount,
- #view_fold_helper_funs{
- reduce_count = fun couch_view:reduce_to_count/1
- }),
- couch_view:fold(View, Start, Dir, FoldlFun, FoldAcc)
- end, {ok, FoldAccInit}, Keys),
- finish_view_fold(Req, RowCount, FoldResult).
+ CurrentEtag = view_group_etag(Group, Keys),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ {ok, RowCount} = couch_view:get_row_count(View),
+ FoldAccInit = {Limit, SkipCount, undefined, []},
+ FoldResult = lists:foldl(
+ fun(Key, {ok, FoldAcc}) ->
+ Start = {Key, StartDocId},
+ FoldlFun = make_view_fold_fun(Req,
+ QueryArgs#view_query_args{
+ start_key = Key,
+ end_key = Key
+ }, CurrentEtag, Db, RowCount,
+ #view_fold_helper_funs{
+ reduce_count = fun couch_view:reduce_to_count/1
+ }),
+ couch_view:fold(View, Start, Dir, FoldlFun, FoldAcc)
+ end, {ok, FoldAccInit}, Keys),
+ finish_view_fold(Req, RowCount, FoldResult)
+ end).
-output_reduce_view(Req, View, QueryArgs, nil) ->
+output_reduce_view(Req, View, Group, QueryArgs, nil) ->
#view_query_args{
start_key = StartKey,
end_key = EndKey,
@@ -135,15 +143,16 @@
end_docid = EndDocId,
group_level = GroupLevel
} = QueryArgs,
- {ok, Resp} = start_json_response(Req, 200),
- {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
- send_chunk(Resp, "{\"rows\":["),
- {ok, _} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId},
- {EndKey, EndDocId}, GroupRowsFun, RespFun, {"", Skip, Limit}),
- send_chunk(Resp, "]}"),
- end_json_response(Resp);
+ CurrentEtag = view_group_etag(Group),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, #reduce_fold_helper_funs{}),
+ FoldAccInit = {Limit, Skip, undefined, []},
+ {ok, {_, _, Resp, _}} = couch_view:fold_reduce(View, Dir, {StartKey, StartDocId},
+ {EndKey, EndDocId}, GroupRowsFun, RespFun, FoldAccInit),
+ finish_reduce_fold(Req, Resp)
+ end);
-output_reduce_view(Req, View, QueryArgs, Keys) ->
+output_reduce_view(Req, View, Group, QueryArgs, Keys) ->
#view_query_args{
limit = Limit,
skip = Skip,
@@ -152,21 +161,27 @@
end_docid = EndDocId,
group_level = GroupLevel
} = QueryArgs,
- {ok, Resp} = start_json_response(Req, 200),
- {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Resp, GroupLevel),
- send_chunk(Resp, "{\"rows\":["),
- lists:foldl(
- fun(Key, AccSeparator) ->
- {ok, {NewAcc, _, _}} = couch_view:fold_reduce(View, Dir, {Key, StartDocId},
- {Key, EndDocId}, GroupRowsFun, RespFun,
- {AccSeparator, Skip, Limit}),
- NewAcc % Switch to comma
- end,
- "", Keys), % Start with no comma
- send_chunk(Resp, "]}"),
- end_json_response(Resp).
+ CurrentEtag = view_group_etag(Group),
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ {ok, GroupRowsFun, RespFun} = make_reduce_fold_funs(Req, GroupLevel, QueryArgs, CurrentEtag, #reduce_fold_helper_funs{}),
+ {Resp, _} = lists:foldl(
+ fun(Key, {Resp, AccSeparator}) ->
+ FoldAccInit = {Limit, Skip, Resp, AccSeparator},
+ {_, {_, _, Resp2, NewAcc}} = couch_view:fold_reduce(View, Dir, {Key, StartDocId},
+ {Key, EndDocId}, GroupRowsFun, RespFun, FoldAccInit),
+ % Switch to comma
+ {Resp2, NewAcc}
+ end,
+ {undefined, []}, Keys), % Start with no comma
+ finish_reduce_fold(Req, Resp)
+ end).
-make_reduce_fold_funs(Resp, GroupLevel) ->
+make_reduce_fold_funs(Req, GroupLevel, _QueryArgs, Etag, HelperFuns) ->
+ #reduce_fold_helper_funs{
+ start_response = StartRespFun,
+ send_row = SendRowFun
+ } = apply_default_helper_funs(HelperFuns),
+
GroupRowsFun =
fun({_Key1,_}, {_Key2,_}) when GroupLevel == 0 ->
true;
@@ -176,31 +191,47 @@
({Key1,_}, {Key2,_}) ->
Key1 == Key2
end,
- RespFun = fun(_Key, _Red, {AccSeparator,AccSkip,AccLimit}) when AccSkip > 0 ->
- {ok, {AccSeparator,AccSkip-1,AccLimit}};
- (_Key, _Red, {AccSeparator,0,AccLimit}) when AccLimit == 0 ->
- {stop, {AccSeparator,0,AccLimit}};
- (_Key, Red, {AccSeparator,0,AccLimit}) when GroupLevel == 0 ->
- Json = ?JSON_ENCODE({[{key, null}, {value, Red}]}),
- send_chunk(Resp, AccSeparator ++ Json),
- {ok, {",",0,AccLimit-1}};
- (Key, Red, {AccSeparator,0,AccLimit})
+ RespFun = fun(_Key, _Red, {AccLimit, AccSkip, Resp, AccSeparator}) when AccSkip > 0 ->
+ {ok, {AccLimit, AccSkip - 1, Resp, AccSeparator}};
+ (_Key, _Red, {0, 0, Resp, AccSeparator}) ->
+ {stop, {0, 0, Resp, AccSeparator}};
+ (_Key, Red, {AccLimit, 0, Resp, AccSeparator}) when GroupLevel == 0 ->
+ {ok, Resp2, RowSep} = case Resp of
+ undefined -> StartRespFun(Req, Etag, null, null);
+ _ -> {ok, Resp, nil}
+ end,
+ RowResult = case SendRowFun(Resp2, {null, Red}, RowSep) of
+ stop -> stop;
+ _ -> ok
+ end,
+ {RowResult, {AccLimit - 1, 0, Resp2, AccSeparator}};
+ (Key, Red, {AccLimit, 0, Resp, AccSeparator})
when is_integer(GroupLevel)
andalso is_list(Key) ->
- Json = ?JSON_ENCODE(
- {[{key, lists:sublist(Key, GroupLevel)},{value, Red}]}),
- send_chunk(Resp, AccSeparator ++ Json),
- {ok, {",",0,AccLimit-1}};
- (Key, Red, {AccSeparator,0,AccLimit}) ->
- Json = ?JSON_ENCODE({[{key, Key}, {value, Red}]}),
- send_chunk(Resp, AccSeparator ++ Json),
- {ok, {",",0,AccLimit-1}}
+ {ok, Resp2, RowSep} = case Resp of
+ undefined -> StartRespFun(Req, Etag, null, null);
+ _ -> {ok, Resp, nil}
+ end,
+ RowResult = case SendRowFun(Resp2, {lists:sublist(Key, GroupLevel), Red}, RowSep) of
+ stop -> stop;
+ _ -> ok
+ end,
+ {RowResult, {AccLimit - 1, 0, Resp2, AccSeparator}};
+ (Key, Red, {AccLimit, 0, Resp, AccSeparator}) ->
+ {ok, Resp2, RowSep} = case Resp of
+ undefined -> StartRespFun(Req, Etag, null, null);
+ _ -> {ok, Resp, nil}
+ end,
+ RowResult = case SendRowFun(Resp2, {Key, Red}, RowSep) of
+ stop -> stop;
+ _ -> ok
+ end,
+ {RowResult, {AccLimit - 1, 0, Resp2, AccSeparator}}
end,
{ok, GroupRowsFun, RespFun}.
-
reverse_key_default(nil) -> {};
reverse_key_default({}) -> nil;
reverse_key_default(Key) -> Key.
@@ -210,6 +241,8 @@
parse_view_query(Req, Keys) ->
parse_view_query(Req, Keys, nil).
parse_view_query(Req, Keys, IsReduce) ->
+ parse_view_query(Req, Keys, IsReduce, false).
+parse_view_query(Req, Keys, IsReduce, IgnoreExtra) ->
QueryList = couch_httpd:qs(Req),
#view_query_args{
group_level = GroupLevel
@@ -328,10 +361,18 @@
Msg1 = "Bad URL query value for 'include_docs' expected \"true\" or \"false\".",
throw({query_parse_error, Msg1})
end;
+ {"format", _} ->
+ % we just ignore format, so that JS can have it
+ Args;
_ -> % unknown key
- Msg = lists:flatten(io_lib:format(
- "Bad URL query key:~s", [Key])),
- throw({query_parse_error, Msg})
+ case IgnoreExtra of
+ true ->
+ Args;
+ false ->
+ Msg = lists:flatten(io_lib:format(
+ "Bad URL query key:~s", [Key])),
+ throw({query_parse_error, Msg})
+ end
end
end, #view_query_args{}, QueryList),
case IsReduce of
@@ -371,7 +412,7 @@
end
end.
-make_view_fold_fun(Req, QueryArgs, Db,
+make_view_fold_fun(Req, QueryArgs, Etag, Db,
TotalViewCount, HelperFuns) ->
#view_query_args{
end_key = EndKey,
@@ -404,15 +445,19 @@
{ok, {AccLimit, AccSkip - 1, Resp, AccRevRows}};
{_, _, _, undefined} ->
Offset = ReduceCountFun(OffsetReds),
- {ok, Resp2, BeginBody} = StartRespFun(Req, 200,
+ {ok, Resp2, BeginBody} = StartRespFun(Req, Etag,
TotalViewCount, Offset),
- SendRowFun(Resp2, Db,
- {{Key, DocId}, Value}, BeginBody, IncludeDocs),
- {ok, {AccLimit - 1, 0, Resp2, AccRevRows}};
+ case SendRowFun(Resp2, Db,
+ {{Key, DocId}, Value}, BeginBody, IncludeDocs) of
+ stop -> {stop, {AccLimit - 1, 0, Resp2, AccRevRows}};
+ _ -> {ok, {AccLimit - 1, 0, Resp2, AccRevRows}}
+ end;
{_, AccLimit, _, Resp} when (AccLimit > 0) ->
- SendRowFun(Resp, Db,
- {{Key, DocId}, Value}, nil, IncludeDocs),
- {ok, {AccLimit - 1, 0, Resp, AccRevRows}}
+ case SendRowFun(Resp, Db,
+ {{Key, DocId}, Value}, nil, IncludeDocs) of
+ stop -> {stop, {AccLimit - 1, 0, Resp, AccRevRows}};
+ _ -> {ok, {AccLimit - 1, 0, Resp, AccRevRows}}
+ end
end
end.
@@ -442,6 +487,25 @@
send_row = SendRow2
}.
+apply_default_helper_funs(#reduce_fold_helper_funs{
+ start_response = StartResp,
+ send_row = SendRow
+}=Helpers) ->
+ StartResp2 = case StartResp of
+ undefined -> fun json_reduce_start_resp/4;
+ _ -> StartResp
+ end,
+
+ SendRow2 = case SendRow of
+ undefined -> fun send_json_reduce_row/3;
+ _ -> SendRow
+ end,
+
+ Helpers#reduce_fold_helper_funs{
+ start_response = StartResp2,
+ send_row = SendRow2
+ }.
+
make_passed_end_fun(Dir, EndKey, EndDocId) ->
case Dir of
fwd ->
@@ -454,8 +518,8 @@
end
end.
-json_view_start_resp(Req, Code, TotalViewCount, Offset) ->
- {ok, Resp} = couch_httpd:start_json_response(Req, Code),
+json_view_start_resp(Req, Etag, TotalViewCount, Offset) ->
+ {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
BeginBody = io_lib:format("{\"total_rows\":~w,\"offset\":~w,\"rows\":[\r\n",
[TotalViewCount, Offset]),
{ok, Resp, BeginBody}.
@@ -467,7 +531,28 @@
_ -> RowFront
end,
send_chunk(Resp, RowFront2 ++ ?JSON_ENCODE(JsonObj)).
+
+json_reduce_start_resp(Req, Etag, _, _) ->
+ {ok, Resp} = start_json_response(Req, 200, [{"Etag", Etag}]),
+ BeginBody = "{\"rows\":[\r\n",
+ {ok, Resp, BeginBody}.
+
+send_json_reduce_row(Resp, {Key, Value}, RowFront) ->
+ RowFront2 = case RowFront of
+ nil -> ",\r\n";
+ _ -> RowFront
+ end,
+ send_chunk(Resp, RowFront2 ++ ?JSON_ENCODE({[{key, Key}, {value, Value}]})).
+
+view_group_etag(Group) ->
+ view_group_etag(Group, nil).
+view_group_etag(#group{sig=Sig,current_seq=CurrentSeq}, Extra) ->
+ % This is not as granular as it could be.
+ % If there are updates to the db that do not effect the view index,
+ % they will change the Etag. For more granular Etags we'd need to keep
+ % track of the last Db seq that caused an index change.
+ couch_httpd:make_etag({Sig, CurrentSeq, Extra}).
view_row_obj(Db, {{Key, DocId}, Value}, IncludeDocs) ->
case DocId of
@@ -520,3 +605,14 @@
Error ->
throw(Error)
end.
+
+finish_reduce_fold(Req, Resp) ->
+ case Resp of
+ undefined ->
+ send_json(Req, 200, {[
+ {rows, []}
+ ]});
+ Resp ->
+ send_chunk(Resp, "\r\n]}"),
+ end_json_response(Resp)
+ end.
Modified: couchdb/branches/rep_security/src/couchdb/couch_query_servers.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_query_servers.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_query_servers.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_query_servers.erl Thu Mar 5 06:14:36 2009
@@ -18,7 +18,9 @@
-export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2,code_change/3,stop/0]).
-export([start_doc_map/2, map_docs/2, stop_doc_map/1]).
-export([reduce/3, rereduce/3,validate_doc_update/5]).
--export([render_doc_show/5,start_view_list/2,render_list_head/5, render_list_row/4, render_list_tail/3]).
+-export([render_doc_show/6,start_view_list/2,render_list_head/5,
+ render_list_row/4, render_list_tail/3, render_reduce_head/3,
+ render_reduce_row/4]).
% -export([test/0]).
-include("couch_db.hrl").
@@ -122,14 +124,18 @@
after
ok = ret_os_process(Lang, Pid)
end.
+append_docid(DocId, JsonReqIn) ->
+ [{<<"docId">>, DocId} | JsonReqIn].
-render_doc_show(Lang, ShowSrc, Doc, Req, Db) ->
+render_doc_show(Lang, ShowSrc, DocId, Doc, Req, Db) ->
Pid = get_os_process(Lang),
- JsonDoc = case Doc of
- nil -> null;
- _ -> couch_doc:to_json_obj(Doc, [revs])
+ {JsonReqIn} = couch_httpd_external:json_req_obj(Req, Db),
+
+ {JsonReq, JsonDoc} = case {DocId, Doc} of
+ {nil, nil} -> {{JsonReqIn}, null};
+ {DocId, nil} -> {{append_docid(DocId, JsonReqIn)}, null};
+ _ -> {{append_docid(DocId, JsonReqIn)}, couch_doc:to_json_obj(Doc, [revs])}
end,
- JsonReq = couch_httpd_external:json_req_obj(Req, Db),
try couch_os_process:prompt(Pid,
[<<"show_doc">>, ShowSrc, JsonDoc, JsonReq]) of
FormResp ->
@@ -160,6 +166,16 @@
JsonResp.
+render_reduce_head({_Lang, Pid}, Req, Db) ->
+ Head = {[]},
+ JsonReq = couch_httpd_external:json_req_obj(Req, Db),
+ couch_os_process:prompt(Pid, [<<"list_begin">>, Head, JsonReq]).
+
+render_reduce_row({_Lang, Pid}, Req, Db, {Key, Value}) ->
+ JsonRow = {[{key, Key}, {value, Value}]},
+ JsonReq = couch_httpd_external:json_req_obj(Req, Db),
+ couch_os_process:prompt(Pid, [<<"list_row">>, JsonRow, JsonReq]).
+
init([]) ->
Modified: couchdb/branches/rep_security/src/couchdb/couch_rep.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_rep.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_rep.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_rep.erl Thu Mar 5 06:14:36 2009
@@ -203,10 +203,12 @@
_ ->
iolist_to_binary(?JSON_ENCODE(JsonBody))
end,
- Options = [
+ Options = case Action of
+ get -> [];
+ _ -> [{transfer_encoding, {chunked, 65535}}]
+ end ++ [
{content_type, "application/json; charset=utf-8"},
- {max_pipeline_size, 101},
- {transfer_encoding, {chunked, 65535}}
+ {max_pipeline_size, 101}
],
case ibrowse:send_req(Url, Headers, Action, Body, Options) of
{ok, Status, ResponseHeaders, ResponseBody} ->
Modified: couchdb/branches/rep_security/src/couchdb/couch_server.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_server.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_server.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_server.erl Thu Mar 5 06:14:36 2009
@@ -182,7 +182,9 @@
maybe_close_lru_db(#server{dbs_open=NumOpen}=Server) ->
% must free up the lru db.
case try_close_lru(now()) of
- ok -> {ok, Server#server{dbs_open=NumOpen-1}};
+ ok ->
+ couch_stats_collector:decrement({couchdb, open_databases}),
+ {ok, Server#server{dbs_open=NumOpen - 1}};
Error -> Error
end.
@@ -195,15 +197,15 @@
true ->
[{_, DbName}] = ets:lookup(couch_dbs_by_lru, LruTime),
[{_, {MainPid, LruTime}}] = ets:lookup(couch_dbs_by_name, DbName),
- case couch_db:num_refs(MainPid) of
- 0 ->
+ case couch_db:is_idle(MainPid) of
+ true ->
exit(MainPid, kill),
receive {'EXIT', MainPid, _Reason} -> ok end,
true = ets:delete(couch_dbs_by_lru, LruTime),
true = ets:delete(couch_dbs_by_name, DbName),
true = ets:delete(couch_dbs_by_pid, MainPid),
ok;
- _NumRefs ->
+ false ->
% this still has referrers. Go ahead and give it a current lru time
% and try the next one in the table.
NewLruTime = now(),
@@ -235,6 +237,7 @@
true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
DbsOpen = Server2#server.dbs_open + 1,
+ couch_stats_collector:increment({couchdb, open_databases}),
{reply, {ok, MainPid},
Server2#server{dbs_open=DbsOpen}};
Error ->
@@ -270,6 +273,7 @@
true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
DbsOpen = Server2#server.dbs_open + 1,
+ couch_stats_collector:increment({couchdb, open_databases}),
couch_db_update_notifier:notify({created, DbName}),
{reply, {ok, MainPid},
Server2#server{dbs_open=DbsOpen}};
@@ -299,6 +303,7 @@
true = ets:delete(couch_dbs_by_name, DbName),
true = ets:delete(couch_dbs_by_pid, Pid),
true = ets:delete(couch_dbs_by_lru, LruTime),
+ couch_stats_collector:decrement({couchdb, open_databases}),
Server#server{dbs_open=Server#server.dbs_open - 1}
end,
case file:delete(FullFilepath) of
@@ -328,6 +333,7 @@
true = ets:delete(couch_dbs_by_pid, Pid),
true = ets:delete(couch_dbs_by_name, DbName),
true = ets:delete(couch_dbs_by_lru, LruTime),
- {noreply, Server#server{dbs_open=DbsOpen-1}};
+ couch_stats_collector:decrement({couchdb, open_databases}),
+ {noreply, Server#server{dbs_open=DbsOpen - 1}};
handle_info(Info, _Server) ->
exit({unknown_message, Info}).
Modified: couchdb/branches/rep_security/src/couchdb/couch_server_sup.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_server_sup.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_server_sup.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_server_sup.erl Thu Mar 5 06:14:36 2009
@@ -145,12 +145,6 @@
brutal_kill,
supervisor,
[couch_server]},
- {couch_file_stats,
- {couch_file_stats, start_link, []},
- permanent,
- brutal_kill,
- supervisor,
- [couch_file_stats]},
{couch_db_update_event,
{gen_event, start_link, [{local, couch_db_update}]},
permanent,
@@ -158,7 +152,6 @@
supervisor,
dynamic}]}).
-
start_secondary_services() ->
DaemonChildSpecs = [
begin
@@ -173,7 +166,7 @@
end
|| {Name, SpecStr}
<- couch_config:get("daemons"), SpecStr /= ""],
-
+
supervisor:start_link({local, couch_secondary_services}, couch_server_sup,
{{one_for_one, 10, 3600}, DaemonChildSpecs}).
Modified: couchdb/branches/rep_security/src/couchdb/couch_stream.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_stream.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_stream.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_stream.erl Thu Mar 5 06:14:36 2009
@@ -136,6 +136,7 @@
{reply, {Pos, BytesRemaining}, Stream};
handle_call({set_min_buffer, MinBuffer}, _From, Stream) ->
{reply, ok, Stream#write_stream{min_alloc = MinBuffer}};
+% set next_alloc if we need more room
handle_call({ensure_buffer, BufferSizeRequested}, _From, Stream) ->
#write_stream{bytes_remaining = BytesRemainingInCurrentBuffer} = Stream,
case BytesRemainingInCurrentBuffer < BufferSizeRequested of
Modified: couchdb/branches/rep_security/src/couchdb/couch_view.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/couchdb/couch_view.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/couchdb/couch_view.erl (original)
+++ couchdb/branches/rep_security/src/couchdb/couch_view.erl Thu Mar 5 06:14:36 2009
@@ -61,14 +61,19 @@
{ok, Count}.
get_temp_reduce_view(Db, Type, DesignOptions, MapSrc, RedSrc) ->
- {ok, #group{views=[View]}} = get_temp_group(Db, Type, DesignOptions, MapSrc, RedSrc),
- {ok, {temp_reduce, View}}.
+ {ok, #group{views=[View]}=Group} = get_temp_group(Db, Type, DesignOptions, MapSrc, RedSrc),
+ {ok, {temp_reduce, View}, Group}.
get_reduce_view(Db, GroupId, Name, Update) ->
case get_group(Db, GroupId, Update) of
- {ok, #group{views=Views,def_lang=Lang}} ->
- get_reduce_view0(Name, Lang, Views);
+ {ok, #group{views=Views,def_lang=Lang}=Group} ->
+ case get_reduce_view0(Name, Lang, Views) of
+ {ok, View} ->
+ {ok, View, Group};
+ Else ->
+ Else
+ end;
Error ->
Error
end.
@@ -137,13 +142,18 @@
get_temp_map_view(Db, Type, DesignOptions, Src) ->
- {ok, #group{views=[View]}} = get_temp_group(Db, Type, DesignOptions, Src, []),
- {ok, View}.
+ {ok, #group{views=[View]}=Group} = get_temp_group(Db, Type, DesignOptions, Src, []),
+ {ok, View, Group}.
get_map_view(Db, GroupId, Name, Stale) ->
case get_group(Db, GroupId, Stale) of
- {ok, #group{views=Views}} ->
- get_map_view0(Name, Views);
+ {ok, #group{views=Views}=Group} ->
+ case get_map_view0(Name, Views) of
+ {ok, View} ->
+ {ok, View, Group};
+ Else ->
+ Else
+ end;
Error ->
Error
end.
@@ -194,10 +204,10 @@
init([]) ->
% read configuration settings and register for configuration changes
- RootDir = couch_config:get("couchdb", "database_dir"),
+ RootDir = couch_config:get("couchdb", "view_index_dir"),
Self = self(),
ok = couch_config:register(
- fun("couchdb", "database_dir")->
+ fun("couchdb", "view_index_dir")->
exit(Self, config_change)
end),
Modified: couchdb/branches/rep_security/src/ibrowse/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/ibrowse/Makefile.am?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/ibrowse/Makefile.am (original)
+++ couchdb/branches/rep_security/src/ibrowse/Makefile.am Thu Mar 5 06:14:36 2009
@@ -38,7 +38,8 @@
EXTRA_DIST = \
$(ibrowse_file_collection) \
- $(ibrowseebin_static_file)
+ $(ibrowseebin_static_file) \
+ ibrowse.hrl
CLEANFILES = \
$(ibrowseebin_make_generated_file_list)
Modified: couchdb/branches/rep_security/src/mochiweb/Makefile.am
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/Makefile.am?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/Makefile.am (original)
+++ couchdb/branches/rep_security/src/mochiweb/Makefile.am Thu Mar 5 06:14:36 2009
@@ -10,7 +10,7 @@
## License for the specific language governing permissions and limitations under
## the License.
-mochiwebebindir = $(localerlanglibdir)/mochiweb-r82/ebin
+mochiwebebindir = $(localerlanglibdir)/mochiweb-r97/ebin
mochiweb_file_collection = \
mochifmt.erl \
Modified: couchdb/branches/rep_security/src/mochiweb/mochijson.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochijson.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochijson.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochijson.erl Thu Mar 5 06:14:36 2009
@@ -478,10 +478,6 @@
true = equiv(E, decode(encode(E))),
test_one(Rest, 1+N).
-e2j_test_vec(unicode) ->
- [
- {"foo" ++ [500] ++ "bar", [$", $f, $o, $o, 500, $b, $a, $r, $"]}
- ];
e2j_test_vec(utf8) ->
[
{1, "1"},
Modified: couchdb/branches/rep_security/src/mochiweb/mochijson2.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochijson2.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochijson2.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochijson2.erl Thu Mar 5 06:14:36 2009
@@ -70,7 +70,10 @@
%% @spec decode(iolist()) -> json_term()
%% @doc Decode the given iolist to Erlang terms.
decode(S) ->
- json_decode(S, #decoder{}).
+ try json_decode(S, #decoder{})
+ catch
+ _:_ -> throw({invalid_json, S})
+ end.
test() ->
test_all().
@@ -129,13 +132,81 @@
lists:reverse([$\} | Acc1]).
json_encode_string(A, _State) when is_atom(A) ->
- json_encode_string_unicode(xmerl_ucs:from_utf8(atom_to_list(A)), [?Q]);
+ L = atom_to_list(A),
+ case json_string_is_safe(L) of
+ true ->
+ [?Q, L, ?Q];
+ false ->
+ json_encode_string_unicode(xmerl_ucs:from_utf8(L), [?Q])
+ end;
json_encode_string(B, _State) when is_binary(B) ->
- json_encode_string_unicode(xmerl_ucs:from_utf8(B), [?Q]);
+ case json_bin_is_safe(B) of
+ true ->
+ [?Q, B, ?Q];
+ false ->
+ json_encode_string_unicode(xmerl_ucs:from_utf8(B), [?Q])
+ end;
json_encode_string(I, _State) when is_integer(I) ->
- json_encode_string_unicode(integer_to_list(I), [?Q]);
+ [?Q, integer_to_list(I), ?Q];
json_encode_string(L, _State) when is_list(L) ->
- json_encode_string_unicode(L, [?Q]).
+ case json_string_is_safe(L) of
+ true ->
+ [?Q, L, ?Q];
+ false ->
+ json_encode_string_unicode(L, [?Q])
+ end.
+
+json_string_is_safe([]) ->
+ true;
+json_string_is_safe([C | Rest]) ->
+ case C of
+ ?Q ->
+ false;
+ $\\ ->
+ false;
+ $\b ->
+ false;
+ $\f ->
+ false;
+ $\n ->
+ false;
+ $\r ->
+ false;
+ $\t ->
+ false;
+ C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+ false;
+ C when C < 16#7f ->
+ json_string_is_safe(Rest);
+ _ ->
+ false
+ end.
+
+json_bin_is_safe(<<>>) ->
+ true;
+json_bin_is_safe(<<C, Rest/binary>>) ->
+ case C of
+ ?Q ->
+ false;
+ $\\ ->
+ false;
+ $\b ->
+ false;
+ $\f ->
+ false;
+ $\n ->
+ false;
+ $\r ->
+ false;
+ $\t ->
+ false;
+ C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+ false;
+ C when C < 16#7f ->
+ json_bin_is_safe(Rest);
+ _ ->
+ false
+ end.
json_encode_string_unicode([], Acc) ->
lists:reverse([$\" | Acc]);
@@ -257,8 +328,28 @@
decode_array(B, S1#decoder{state=any}, Acc)
end.
-tokenize_string(B, S) ->
- tokenize_string(B, S, []).
+tokenize_string(B, S=#decoder{offset=O}) ->
+ case tokenize_string_fast(B, O) of
+ {escape, O1} ->
+ Length = O1 - O,
+ S1 = ?ADV_COL(S, Length),
+ <<_:O/binary, Head:Length/binary, _/binary>> = B,
+ tokenize_string(B, S1, lists:reverse(binary_to_list(Head)));
+ O1 ->
+ Length = O1 - O,
+ <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B,
+ {{const, String}, ?ADV_COL(S, Length + 1)}
+ end.
+
+tokenize_string_fast(B, O) ->
+ case B of
+ <<_:O/binary, ?Q, _/binary>> ->
+ O;
+ <<_:O/binary, C, _/binary>> when C =/= $\\ ->
+ tokenize_string_fast(B, 1 + O);
+ _ ->
+ {escape, O}
+ end.
tokenize_string(B, S=#decoder{offset=O}, Acc) ->
case B of
Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb.app
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb.app?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb.app (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb.app Thu Mar 5 06:14:36 2009
@@ -21,7 +21,10 @@
mochiweb_socket_server,
mochiweb_sup,
mochiweb_util,
- reloader
+ reloader,
+ mochifmt,
+ mochifmt_std,
+ mochifmt_records
]},
{registered, []},
{mod, {mochiweb_app, []}},
Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb_headers.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb_headers.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb_headers.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb_headers.erl Thu Mar 5 06:14:36 2009
@@ -6,7 +6,7 @@
-module(mochiweb_headers).
-author('bob@mochimedia.com').
-export([empty/0, from_list/1, insert/3, enter/3, get_value/2, lookup/2]).
--export([get_primary_value/2]).
+-export([delete_any/2, get_primary_value/2]).
-export([default/3, enter_from_list/2, default_from_list/2]).
-export([to_list/1, make/1]).
-export([test/0]).
@@ -35,6 +35,8 @@
H3),
"application/x-www-form-urlencoded" = ?MODULE:get_primary_value(
"content-type", H4),
+ H4 = ?MODULE:delete_any("nonexistent-header", H4),
+ H3 = ?MODULE:delete_any("content-type", H4),
ok.
%% @spec empty() -> headers()
@@ -145,6 +147,12 @@
gb_trees:update(K1, {K0, V2}, T)
end.
+%% @spec delete_any(key(), headers()) -> headers()
+%% @doc Delete the header corresponding to key if it is present.
+delete_any(K, T) ->
+ K1 = normalize(K),
+ gb_trees:delete_any(K1, T).
+
%% Internal API
expand({array, L}) ->
Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb_html.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb_html.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb_html.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb_html.erl Thu Mar 5 06:14:36 2009
@@ -129,7 +129,9 @@
test() ->
test_destack(),
test_tokens(),
+ test_tokens2(),
test_parse(),
+ test_parse2(),
test_parse_tokens(),
test_escape(),
test_escape_attr(),
@@ -426,6 +428,34 @@
Expect = parse(D0),
ok.
+test_tokens2() ->
+ D0 = <<"<channel><title>from __future__ import *</title><link>http://bob.pythonmac.org</link><description>Bob's Rants</description></channel>">>,
+ Expect = [{start_tag,<<"channel">>,[],false},
+ {start_tag,<<"title">>,[],false},
+ {data,<<"from __future__ import *">>,false},
+ {end_tag,<<"title">>},
+ {start_tag,<<"link">>,[],true},
+ {data,<<"http://bob.pythonmac.org">>,false},
+ {end_tag,<<"link">>},
+ {start_tag,<<"description">>,[],false},
+ {data,<<"Bob's Rants">>,false},
+ {end_tag,<<"description">>},
+ {end_tag,<<"channel">>}],
+ Expect = tokens(D0),
+ ok.
+
+test_parse2() ->
+ D0 = <<"<channel><title>from __future__ import *</title><link>http://bob.pythonmac.org<br>foo</link><description>Bob's Rants</description></channel>">>,
+ Expect = {<<"channel">>,[],
+ [{<<"title">>,[],[<<"from __future__ import *">>]},
+ {<<"link">>,[],[
+ <<"http://bob.pythonmac.org">>,
+ {<<"br">>,[],[]},
+ <<"foo">>]},
+ {<<"description">>,[],[<<"Bob's Rants">>]}]},
+ Expect = parse(D0),
+ ok.
+
test_parse_tokens() ->
D0 = [{doctype,[<<"HTML">>,<<"PUBLIC">>,<<"-//W3C//DTD HTML 4.01 Transitional//EN">>]},
{data,<<"\n">>,true},
@@ -562,8 +592,23 @@
end,
case lists:splitwith(F, Stack) of
{_, []} ->
- %% No match, no state change
- Stack;
+ %% If we're parsing something like XML we might find
+ %% a <link>tag</link> that is normally a singleton
+ %% in HTML but isn't here
+ case {is_singleton(TagName), Stack} of
+ {true, [{T0, A0, Acc0} | Post0]} ->
+ case lists:splitwith(F, Acc0) of
+ {_, []} ->
+ %% Actually was a singleton
+ Stack;
+ {Pre, [{T1, A1, []} | Post1]} ->
+ [{T0, A0, [{T1, A1, lists:reverse(Pre)} | Post1]}
+ | Post0]
+ end;
+ _ ->
+ %% No match, no state change
+ Stack
+ end;
{_Pre, [_T]} ->
%% Unfurl the whole stack, we're done
destack(Stack);
Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb_http.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb_http.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb_http.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb_http.erl Thu Mar 5 06:14:36 2009
@@ -10,6 +10,7 @@
-define(IDLE_TIMEOUT, 30000).
+-define(MAX_HEADERS, 1000).
-define(DEFAULTS, [{name, ?MODULE},
{port, 8888}]).
@@ -37,7 +38,7 @@
stop(Name) ->
mochiweb_socket_server:stop(Name).
-
+
start() ->
start([{ip, "127.0.0.1"},
{loop, {?MODULE, default_body}}]).
@@ -80,12 +81,12 @@
{body, Req:recv_body()},
Req:dump()]]),
Req:ok({"text/html", [], frm(Body)});
-default_body(Req, 'POST', _Path) ->
+default_body(Req, 'POST', _Path) ->
Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()},
{parse_cookie, Req:parse_cookie()},
{parse_post, Req:parse_post()},
Req:dump()]]),
- Req:ok({"text/html", [], frm(Body)});
+ Req:ok({"text/html", [], frm(Body)});
default_body(Req, _Method, _Path) ->
Req:respond({501, [], []}).
@@ -99,7 +100,7 @@
request(Socket, Body) ->
case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of
{ok, {http_request, Method, Path, Version}} ->
- headers(Socket, {Method, Path, Version}, [], Body);
+ headers(Socket, {Method, Path, Version}, [], Body, 0);
{error, {http_error, "\r\n"}} ->
request(Socket, Body);
{error, {http_error, "\n"}} ->
@@ -109,7 +110,15 @@
exit(normal)
end.
-headers(Socket, Request, Headers, Body) ->
+headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) ->
+ %% Too many headers sent, bad request.
+ inet:setopts(Socket, [{packet, raw}]),
+ Req = mochiweb:new_request({Socket, Request,
+ lists:reverse(Headers)}),
+ Req:respond({400, [], []}),
+ gen_tcp:close(Socket),
+ exit(normal);
+headers(Socket, Request, Headers, Body, HeaderCount) ->
case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of
{ok, http_eoh} ->
inet:setopts(Socket, [{packet, raw}]),
@@ -125,7 +134,8 @@
?MODULE:loop(Socket, Body)
end;
{ok, {http_header, _, Name, _, Value}} ->
- headers(Socket, Request, [{Name, Value} | Headers], Body);
+ headers(Socket, Request, [{Name, Value} | Headers], Body,
+ 1 + HeaderCount);
_Other ->
gen_tcp:close(Socket),
exit(normal)
Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb_request.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb_request.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb_request.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb_request.erl Thu Mar 5 06:14:36 2009
@@ -12,7 +12,7 @@
-define(READ_SIZE, 8192).
-export([get_header_value/1, get_primary_header_value/1, get/1, dump/0]).
--export([send/1, recv/1, recv/2, recv_body/0, recv_body/1]).
+-export([send/1, recv/1, recv/2, recv_body/0, recv_body/1, stream_body/3]).
-export([start_response/1, start_response_length/1, start_raw_response/1]).
-export([respond/1, ok/1]).
-export([not_found/0, not_found/1]).
@@ -171,28 +171,54 @@
%% @doc Receive the body of the HTTP request (defined by Content-Length).
%% Will receive up to MaxBody bytes.
recv_body(MaxBody) ->
+ % we could use a sane constant for max chunk size
+ Body = stream_body(?MAX_RECV_BODY, fun
+ ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) ->
+ iolist_to_binary(lists:reverse(BinAcc));
+ ({Length, Bin}, {LengthAcc, BinAcc}) ->
+ NewLength = Length + LengthAcc,
+ if NewLength > MaxBody ->
+ exit({body_too_large, chunked});
+ true ->
+ {NewLength, [Bin | BinAcc]}
+ end
+ end, {0, []}, MaxBody),
+ put(?SAVE_BODY, Body),
+ Body.
+
+stream_body(MaxChunkSize, ChunkFun, FunState) ->
+ stream_body(MaxChunkSize, ChunkFun, FunState, undefined).
+
+stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) ->
+
case get_header_value("expect") of
"100-continue" ->
start_raw_response({100, gb_trees:empty()});
_Else ->
ok
end,
- Body = case body_length() of
- undefined ->
- undefined;
- {unknown_transfer_encoding, Unknown} ->
- exit({unknown_transfer_encoding, Unknown});
- chunked ->
- read_chunked_body(MaxBody, []);
- 0 ->
- <<>>;
- Length when is_integer(Length), Length =< MaxBody ->
- recv(Length);
- Length ->
- exit({body_too_large, Length})
- end,
- put(?SAVE_BODY, Body),
- Body.
+ case body_length() of
+ undefined ->
+ undefined;
+ {unknown_transfer_encoding, Unknown} ->
+ exit({unknown_transfer_encoding, Unknown});
+ chunked ->
+ % In this case the MaxBody is actually used to
+ % determine the maximum allowed size of a single
+ % chunk.
+ stream_chunked_body(MaxChunkSize, ChunkFun, FunState);
+ 0 ->
+ <<>>;
+ Length when is_integer(Length) ->
+ case MaxBodyLength of
+ MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length ->
+ exit({body_too_large, content_length});
+ _ ->
+ stream_unchunked_body(Length, MaxChunkSize, ChunkFun, FunState)
+ end;
+ Length ->
+ exit({length_not_integer, Length})
+ end.
%% @spec start_response({integer(), ioheaders()}) -> response()
@@ -408,17 +434,33 @@
Cached
end.
-read_chunked_body(Max, Acc) ->
+%% @spec stream_chunked_body(integer(), fun(), term()) -> term()
+%% @doc The function is called for each chunk.
+%% Used internally by read_chunked_body.
+stream_chunked_body(MaxChunkSize, Fun, FunState) ->
case read_chunk_length() of
0 ->
- read_chunk(0),
- iolist_to_binary(lists:reverse(Acc));
- Length when Length > Max ->
- exit({body_too_large, chunked});
+ Fun({0, read_chunk(0)}, FunState);
+ Length when Length > MaxChunkSize ->
+ NewState = read_sub_chunks(Length, MaxChunkSize, Fun, FunState),
+ stream_chunked_body(MaxChunkSize, Fun, NewState);
Length ->
- read_chunked_body(Max - Length, [read_chunk(Length) | Acc])
+ NewState = Fun({Length, read_chunk(Length)}, FunState),
+ stream_chunked_body(MaxChunkSize, Fun, NewState)
end.
+stream_unchunked_body(0, _MaxChunkSize, Fun, FunState) ->
+ Fun({0, <<>>}, FunState);
+stream_unchunked_body(Length, MaxChunkSize, Fun, FunState) when Length > MaxChunkSize ->
+ Bin = recv(MaxChunkSize),
+ NewState = Fun({MaxChunkSize, Bin}, FunState),
+ stream_unchunked_body(Length - MaxChunkSize, MaxChunkSize, Fun, NewState);
+stream_unchunked_body(Length, MaxChunkSize, Fun, FunState) ->
+ Bin = recv(Length),
+ NewState = Fun({Length, Bin}, FunState),
+ stream_unchunked_body(0, MaxChunkSize, Fun, NewState).
+
+
%% @spec read_chunk_length() -> integer()
%% @doc Read the length of the next HTTP chunk.
read_chunk_length() ->
@@ -461,6 +503,14 @@
exit(normal)
end.
+read_sub_chunks(Length, MaxChunkSize, Fun, FunState) when Length > MaxChunkSize ->
+ Bin = recv(MaxChunkSize),
+ NewState = Fun({size(Bin), Bin}, FunState),
+ read_sub_chunks(Length - MaxChunkSize, MaxChunkSize, Fun, NewState);
+
+read_sub_chunks(Length, _MaxChunkSize, Fun, FunState) ->
+ Fun({Length, read_chunk(Length)}, FunState).
+
%% @spec serve_file(Path, DocRoot) -> Response
%% @doc Serve a file relative to DocRoot.
serve_file(Path, DocRoot) ->
Modified: couchdb/branches/rep_security/src/mochiweb/mochiweb_util.erl
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/src/mochiweb/mochiweb_util.erl?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/src/mochiweb/mochiweb_util.erl (original)
+++ couchdb/branches/rep_security/src/mochiweb/mochiweb_util.erl Thu Mar 5 06:14:36 2009
@@ -134,12 +134,16 @@
revjoin([S | Rest], Separator, Acc) ->
revjoin(Rest, Separator, [S, Separator | Acc]).
-%% @spec quote_plus(atom() | integer() | string()) -> string()
+%% @spec quote_plus(atom() | integer() | float() | string() | binary()) -> string()
%% @doc URL safe encoding of the given term.
quote_plus(Atom) when is_atom(Atom) ->
quote_plus(atom_to_list(Atom));
quote_plus(Int) when is_integer(Int) ->
quote_plus(integer_to_list(Int));
+quote_plus(Binary) when is_binary(Binary) ->
+ quote_plus(binary_to_list(Binary));
+quote_plus(Float) when is_float(Float) ->
+ quote_plus(mochinum:digits(Float));
quote_plus(String) ->
quote_plus(String, []).
@@ -542,6 +546,7 @@
test_quote_plus() ->
"foo" = quote_plus(foo),
"1" = quote_plus(1),
+ "1.1" = quote_plus(1.1),
"foo" = quote_plus("foo"),
"foo+bar" = quote_plus("foo bar"),
"foo%0A" = quote_plus("foo\n"),
Modified: couchdb/branches/rep_security/test/runner.sh
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/test/runner.sh?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/test/runner.sh (original)
+++ couchdb/branches/rep_security/test/runner.sh Thu Mar 5 06:14:36 2009
@@ -4,4 +4,4 @@
erl -noshell -pa ../src/couchdb -pa ../src/mochiweb -eval "runner:run()"
-cat ../share/www/script/couch.js ../share/www/script/couch_test_runner.js ../share/www/script/couch_tests.js test.js | ../src/couchdb/couchjs -
+cat ../share/www/script/couch.js ../share/www/script/couch_test_runner.js ../share/www/script/couch_tests.js ../share/www/script/test/* test.js | ../src/couchdb/couchjs -
Modified: couchdb/branches/rep_security/test/test.js
URL: http://svn.apache.org/viewvc/couchdb/branches/rep_security/test/test.js?rev=750332&r1=750331&r2=750332&view=diff
==============================================================================
--- couchdb/branches/rep_security/test/test.js (original)
+++ couchdb/branches/rep_security/test/test.js Thu Mar 5 06:14:36 2009
@@ -194,14 +194,14 @@
function runAllTestsConsole() {
var numTests = 0;
var debug = false;
- for (var t in tests) {
+ for (var t in couchTests) {
p(t);
if (t == "utf8") {
p("We skip the utf8 test because it fails due to problems in couch_js.c");
p("Run the in-browser tests to verify utf8.\n");
} else {
numTests += 1;
- var testFun = tests[t];
+ var testFun = couchTests[t];
runTestConsole(testFun, debug);
}
}