summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/couchdb/couch_doc.erl2
-rw-r--r--src/couchdb/couch_httpd.erl26
-rw-r--r--src/couchdb/couch_httpd_db.erl15
-rw-r--r--src/couchdb/couch_httpd_show.erl88
-rw-r--r--src/couchdb/couch_query_servers.erl5
5 files changed, 86 insertions, 50 deletions
diff --git a/src/couchdb/couch_doc.erl b/src/couchdb/couch_doc.erl
index db1f2625..5bad7854 100644
--- a/src/couchdb/couch_doc.erl
+++ b/src/couchdb/couch_doc.erl
@@ -147,7 +147,7 @@ from_json_obj({Props}) ->
attachments = Bins
};
-from_json_obj(Other) ->
+from_json_obj(_Other) ->
throw({invalid_json_object, "Document must be a JSON object"}).
to_doc_info(FullDocInfo) ->
diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl
index 996bcf49..acd6af40 100644
--- a/src/couchdb/couch_httpd.erl
+++ b/src/couchdb/couch_httpd.erl
@@ -17,7 +17,7 @@
-export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,path/1]).
-export([verify_is_server_admin/1,unquote/1,quote/1,recv/2]).
--export([parse_form/1,json_body/1,body/1,doc_etag/1]).
+-export([parse_form/1,json_body/1,body/1,doc_etag/1, make_etag/1, etag_respond/3]).
-export([primary_header_value/2,partition/1,serve_file/3]).
-export([start_chunked_response/3,send_chunk/2]).
-export([start_json_response/2, start_json_response/3, end_json_response/1]).
@@ -261,7 +261,29 @@ json_body(#httpd{mochi_req=MochiReq}) ->
?JSON_DECODE(MochiReq:recv_body(?MAX_DOC_SIZE)).
doc_etag(#doc{revs=[DiskRev|_]}) ->
- "\"" ++ binary_to_list(DiskRev) ++ "\"".
+ "\"" ++ binary_to_list(DiskRev) ++ "\"".
+
+make_etag(Term) ->
+ <<SigInt:128/integer>> = erlang:md5(term_to_binary(Term)),
+ list_to_binary("\"" ++ lists:flatten(io_lib:format("~.36B",[SigInt])) ++ "\"").
+
+etag_match(Req, CurrentEtag) when is_binary(CurrentEtag) ->
+ etag_match(Req, binary_to_list(CurrentEtag));
+
+etag_match(Req, CurrentEtag) ->
+ EtagsToMatch = string:tokens(
+ couch_httpd:header_value(Req, "If-None-Match", ""), ", "),
+ lists:member(CurrentEtag, EtagsToMatch).
+
+etag_respond(Req, CurrentEtag, RespFun) ->
+ case etag_match(Req, CurrentEtag) of
+ true ->
+ % the client has this in their cache.
+ couch_httpd:send_response(Req, 304, [{"Etag", CurrentEtag}], <<>>);
+ false ->
+ % Run the function.
+ RespFun()
+ end.
verify_is_server_admin(#httpd{user_ctx=#user_ctx{roles=Roles}}) ->
case lists:member(<<"_admin">>, Roles) of
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index 9ef7387c..73d90fe6 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -387,20 +387,13 @@ db_doc_req(#httpd{method='GET'}=Req, Db, DocId) ->
[] ->
Doc = couch_doc_open(Db, DocId, Rev, Options),
DiskEtag = couch_httpd:doc_etag(Doc),
- EtagsToMatch = string:tokens(
- couch_httpd:header_value(Req, "If-None-Match", ""), ", "),
- case lists:member(DiskEtag, EtagsToMatch) of
- true ->
- % the client has this in their cache.
- couch_httpd:send_response(Req, 304, [{"Etag", DiskEtag}], <<>>);
- false ->
- Headers =
- case Doc#doc.meta of
+ couch_httpd:etag_respond(Req, DiskEtag, fun() ->
+ Headers = case Doc#doc.meta of
[] -> [{"Etag", DiskEtag}]; % output etag only when we have no meta
_ -> []
end,
- send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options))
- end;
+ send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options))
+ end);
_ ->
{ok, Results} = couch_db:open_doc_revs(Db, DocId, Revs, Options),
{ok, Resp} = start_json_response(Req, 200),
diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl
index 47d3fb46..f724098a 100644
--- a/src/couchdb/couch_httpd_show.erl
+++ b/src/couchdb/couch_httpd_show.erl
@@ -33,6 +33,16 @@ handle_doc_show_req(#httpd{
Doc = couch_httpd_db:couch_doc_open(Db, Docid, [], []),
send_doc_show_response(Lang, ShowSrc, Doc, Req, Db);
+handle_doc_show_req(#httpd{
+ method='GET',
+ path_parts=[_, _, DesignName, ShowName]
+ }=Req, Db) ->
+ DesignId = <<"_design/", DesignName/binary>>,
+ #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []),
+ Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>),
+ ShowSrc = get_nested_json_value({Props}, [<<"shows">>, ShowName]),
+ send_doc_show_response(Lang, ShowSrc, nil, Req, Db);
+
handle_doc_show_req(#httpd{method='GET'}=Req, _Db) ->
send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>);
@@ -158,45 +168,32 @@ finish_view_list(Req, Db, QueryServer, TotalRows,
throw(Error)
end.
+send_doc_show_response(Lang, ShowSrc, nil, #httpd{mochi_req=MReq}=Req, Db) ->
+ % compute etag with no doc
+ Headers = MReq:get(headers),
+ Hlist = mochiweb_headers:to_list(Headers),
+ Accept = proplists:get_value('Accept', Hlist),
+ 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),
+ JsonResp = apply_etag(ExternalResp, CurrentEtag),
+ couch_httpd_external:send_external_response(Req, JsonResp)
+ end);
send_doc_show_response(Lang, ShowSrc, #doc{revs=[DocRev|_]}=Doc, #httpd{mochi_req=MReq}=Req, Db) ->
- % make a term with etag-effecting Req components, but not always changing ones.
+ % calculate the etag
Headers = MReq:get(headers),
Hlist = mochiweb_headers:to_list(Headers),
Accept = proplists:get_value('Accept', Hlist),
- <<SigInt:128/integer>> = erlang:md5(term_to_binary({Lang, ShowSrc, DocRev, Accept})),
- CurrentEtag = list_to_binary("\"" ++ lists:flatten(io_lib:format("form_~.36B",[SigInt])) ++ "\""),
- EtagsToMatch = string:tokens(
- couch_httpd:header_value(Req, "If-None-Match", ""), ", "),
- % We know our etag now
- case lists:member(binary_to_list(CurrentEtag), EtagsToMatch) of
- true ->
- % the client has this in their cache.
- couch_httpd:send_response(Req, 304, [{"Etag", CurrentEtag}], <<>>);
- false ->
- % Run the external form renderer.
- {JsonResponse} = couch_query_servers:render_doc_show(Lang, ShowSrc, Doc, Req, Db),
- % Here we embark on the delicate task of replacing or creating the
- % headers on the JsonResponse object. We need to control the Etag and
- % Vary headers. If the external function controls the Etag, we'd have to
- % run it to check for a match, which sort of defeats the purpose.
- JsonResponse2 = case proplists:get_value(<<"headers">>, JsonResponse, nil) of
- nil ->
- % no JSON headers
- % add our Etag and Vary headers to the response
- [{<<"headers">>, {[{<<"Etag">>, CurrentEtag}, {<<"Vary">>, <<"Accept">>}]}} | JsonResponse];
- {JsonHeaders} ->
- [case Field of
- {<<"headers">>, {JsonHeaders}} -> % add our headers
- JsonHeadersEtagged = set_or_replace_header({<<"Etag">>, CurrentEtag}, JsonHeaders),
- JsonHeadersVaried = set_or_replace_header({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged),
- {<<"headers">>, {JsonHeadersVaried}};
- _ -> % skip non-header fields
- Field
- end || Field <- JsonResponse]
- end,
- couch_httpd_external:send_external_response(Req, {JsonResponse2})
- end.
+ CurrentEtag = couch_httpd:make_etag({Lang, ShowSrc, DocRev, Accept}),
+ % We know our etag now
+ couch_httpd:etag_respond(Req, CurrentEtag, fun() ->
+ ExternalResp = couch_query_servers:render_doc_show(Lang, ShowSrc,
+ Doc, Req, Db),
+ JsonResp = apply_etag(ExternalResp, CurrentEtag),
+ couch_httpd_external:send_external_response(Req, JsonResp)
+ end).
set_or_replace_header(H, L) ->
set_or_replace_header(H, L, []).
@@ -209,4 +206,25 @@ set_or_replace_header({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) ->
set_or_replace_header({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]);
set_or_replace_header({Key, NewValue}, [], Acc) ->
% end of list, add ours
- [{Key, NewValue}|Acc]. \ No newline at end of file
+ [{Key, NewValue}|Acc].
+
+apply_etag({ExternalResponse}, CurrentEtag) ->
+ % Here we embark on the delicate task of replacing or creating the
+ % headers on the JsonResponse object. We need to control the Etag and
+ % Vary headers. If the external function controls the Etag, we'd have to
+ % run it to check for a match, which sort of defeats the purpose.
+ case proplists:get_value(<<"headers">>, ExternalResponse, nil) of
+ nil ->
+ % no JSON headers
+ % add our Etag and Vary headers to the response
+ {[{<<"headers">>, {[{<<"Etag">>, CurrentEtag}, {<<"Vary">>, <<"Accept">>}]}} | ExternalResponse]};
+ {JsonHeaders} ->
+ {[case Field of
+ {<<"headers">>, {JsonHeaders}} -> % add our headers
+ JsonHeadersEtagged = set_or_replace_header({<<"Etag">>, CurrentEtag}, JsonHeaders),
+ JsonHeadersVaried = set_or_replace_header({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged),
+ {<<"headers">>, {JsonHeadersVaried}};
+ _ -> % skip non-header fields
+ Field
+ end || Field <- ExternalResponse]}
+ end.
diff --git a/src/couchdb/couch_query_servers.erl b/src/couchdb/couch_query_servers.erl
index a6bb6e06..5eca63c2 100644
--- a/src/couchdb/couch_query_servers.erl
+++ b/src/couchdb/couch_query_servers.erl
@@ -125,7 +125,10 @@ validate_doc_update(Lang, FunSrc, EditDoc, DiskDoc, Ctx) ->
render_doc_show(Lang, ShowSrc, Doc, Req, Db) ->
Pid = get_os_process(Lang),
- JsonDoc = couch_doc:to_json_obj(Doc, [revs]),
+ JsonDoc = case Doc of
+ nil -> null;
+ _ -> 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