Skip to content

Commit

Permalink
use JSON content type in replicator, require it in the _bulk_docs and…
Browse files Browse the repository at this point in the history
… other POST apis

git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@957422 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
jchris committed Jun 24, 2010
1 parent a96cc93 commit 4b079e2
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 38 deletions.
2 changes: 2 additions & 0 deletions share/www/script/couch.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ CouchDB.newXhr = function() {

CouchDB.request = function(method, uri, options) {
options = options || {};
options.headers = options.headers || {};
options.headers["Content-Type"] = options.headers["Content-Type"] || "application/json";
var req = CouchDB.newXhr();
if(uri.substr(0, "http://".length) != "http://") {
uri = CouchDB.urlPrefix + uri
Expand Down
9 changes: 7 additions & 2 deletions share/www/script/test/basics.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ couchTests.basics = function(debug) {

// test that the POST response has a Location header
var xhr = CouchDB.request("POST", "/test_suite_db", {
body: JSON.stringify({"foo":"bar"})
body: JSON.stringify({"foo":"bar"}),
headers: {"Content-Type": "application/json"}
});
var resp = JSON.parse(xhr.responseText);
T(resp.ok);
Expand All @@ -164,6 +165,7 @@ couchTests.basics = function(debug) {

// test that that POST's with an _id aren't overriden with a UUID.
var xhr = CouchDB.request("POST", "/test_suite_db", {
headers: {"Content-Type": "application/json"},
body: JSON.stringify({"_id": "oppossum", "yar": "matey"})
});
var resp = JSON.parse(xhr.responseText);
Expand Down Expand Up @@ -202,7 +204,10 @@ couchTests.basics = function(debug) {
result = JSON.parse(xhr.responseText);
T(result.error == "doc_validation");

xhr = CouchDB.request("POST", "/test_suite_db/", {body: data});
xhr = CouchDB.request("POST", "/test_suite_db/", {
headers: {"Content-Type": "application/json"},
body: data
});
T(xhr.status == 500);
result = JSON.parse(xhr.responseText);
T(result.error == "doc_validation");
Expand Down
5 changes: 4 additions & 1 deletion share/www/script/test/batch_save.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ couchTests.batch_save = function(debug) {

// repeat the tests for POST
for(i=0; i < 100; i++) {
var resp = db.request("POST", db.uri + "?batch=ok", {body: JSON.stringify({a:1})});
var resp = db.request("POST", db.uri + "?batch=ok", {
headers: {"Content-Type": "application/json"},
body: JSON.stringify({a:1})
});
T(JSON.parse(resp.responseText).ok);
}

Expand Down
5 changes: 4 additions & 1 deletion share/www/script/test/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,10 @@ couchTests.stats = function(debug) {

runTest("couchdb", "database_writes", {
run: function(db) {
CouchDB.request("POST", "/test_suite_db", {body: '{"a": "1"}'})
CouchDB.request("POST", "/test_suite_db", {
headers: {"Content-Type": "application/json"},
body: '{"a": "1"}'
})
},
test: function(before, after) {
TEquals(before+1, after, "POST'ing new docs increments doc writes.");
Expand Down
39 changes: 35 additions & 4 deletions src/couchdb/couch_httpd.erl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
-export([start_json_response/2, start_json_response/3, end_json_response/1]).
-export([send_response/4,send_method_not_allowed/2,send_error/4, send_redirect/2,send_chunked_error/2]).
-export([send_json/2,send_json/3,send_json/4,last_chunk/1,parse_multipart_request/3]).
-export([accepted_encodings/1,handle_request_int/5]).
-export([accepted_encodings/1,handle_request_int/5,validate_referer/1,validate_ctype/2]).

start_link() ->
% read config and register for configuration changes
Expand Down Expand Up @@ -321,6 +321,34 @@ vhost_global(VhostGlobals, MochiReq) ->
end,
[true] == [true||V <- VhostGlobals, V == Front].

validate_referer(Req) ->
Host = host_for_request(Req),
Referer = header_value(Req, "Referer", fail),
case Referer of
fail ->
throw({bad_request, <<"Referer header required.">>});
Referer ->
{_,RefererHost,_,_,_} = mochiweb_util:urlsplit(Referer),
if
RefererHost =:= Host -> ok;
true -> throw({bad_request, <<"Referer header must match host.">>})
end
end.

validate_ctype(Req, Ctype) ->
case couch_httpd:header_value(Req, "Content-Type") of
undefined ->
throw({bad_ctype, "Content-Type must be "++Ctype});
ReqCtype ->
% ?LOG_ERROR("Ctype ~p ReqCtype ~p",[Ctype,ReqCtype]),
case re:split(ReqCtype, ";", [{return, list}]) of
[Ctype] -> ok;
[Ctype, _Rest] -> ok;
_Else ->
throw({bad_ctype, "Content-Type must be "++Ctype})
end
end.

% Utilities

partition(Path) ->
Expand Down Expand Up @@ -367,9 +395,9 @@ qs(#httpd{mochi_req=MochiReq}) ->
path(#httpd{mochi_req=MochiReq}) ->
MochiReq:get(path).

absolute_uri(#httpd{mochi_req=MochiReq}, Path) ->
host_for_request(#httpd{mochi_req=MochiReq}) ->
XHost = couch_config:get("httpd", "x_forwarded_host", "X-Forwarded-Host"),
Host = case MochiReq:get_header_value(XHost) of
case MochiReq:get_header_value(XHost) of
undefined ->
case MochiReq:get_header_value("Host") of
undefined ->
Expand All @@ -379,7 +407,10 @@ absolute_uri(#httpd{mochi_req=MochiReq}, Path) ->
Value1
end;
Value -> Value
end,
end.

absolute_uri(#httpd{mochi_req=MochiReq}=Req, Path) ->
Host = host_for_request(Req),
XSsl = couch_config:get("httpd", "x_forwarded_ssl", "X-Forwarded-Ssl"),
Scheme = case MochiReq:get_header_value(XSsl) of
"on" -> "https";
Expand Down
1 change: 1 addition & 0 deletions src/couchdb/couch_httpd_auth.erl
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ ensure_cookie_auth_secret() ->
handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) ->
ReqBody = MochiReq:recv_body(),
Form = case MochiReq:get_primary_header_value("content-type") of
% content type should be json
"application/x-www-form-urlencoded" ++ _ ->
mochiweb_util:parse_qs(ReqBody);
_ ->
Expand Down
14 changes: 7 additions & 7 deletions src/couchdb/couch_httpd_db.erl
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
send_method_not_allowed(Req, "GET,HEAD").

handle_compact_req(#httpd{method='POST',path_parts=[DbName,_,Id|_]}=Req, _Db) ->
couch_httpd:validate_ctype(Req, "application/json"),
ok = couch_view_compactor:start_compact(DbName, Id),
send_json(Req, 202, {[{ok, true}]});

Expand Down Expand Up @@ -195,6 +196,7 @@ db_req(#httpd{method='GET',path_parts=[_DbName]}=Req, Db) ->
send_json(Req, {DbInfo});

db_req(#httpd{method='POST',path_parts=[DbName]}=Req, Db) ->
couch_httpd:validate_ctype(Req, "application/json"),
Doc = couch_doc:from_json_obj(couch_httpd:json_body(Req)),
Doc2 = case Doc#doc.id of
<<"">> ->
Expand Down Expand Up @@ -262,6 +264,7 @@ db_req(#httpd{path_parts=[_,<<"_ensure_full_commit">>]}=Req, _Db) ->

db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) ->
couch_stats_collector:increment({httpd, bulk_requests}),
couch_httpd:validate_ctype(Req, "application/json"),
{JsonProps} = couch_httpd:json_body_obj(Req),
DocsArray = couch_util:get_value(<<"docs">>, JsonProps),
case couch_httpd:header_value(Req, "X-Couch-Full-Commit") of
Expand Down Expand Up @@ -323,6 +326,7 @@ db_req(#httpd{path_parts=[_,<<"_bulk_docs">>]}=Req, _Db) ->
send_method_not_allowed(Req, "POST");

db_req(#httpd{method='POST',path_parts=[_,<<"_purge">>]}=Req, Db) ->
couch_httpd:validate_ctype(Req, "application/json"),
{IdsRevs} = couch_httpd:json_body_obj(Req),
IdsRevs2 = [{Id, couch_doc:parse_revs(Revs)} || {Id, Revs} <- IdsRevs],

Expand Down Expand Up @@ -367,7 +371,6 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_missing_revs">>]}=Req, Db) ->
db_req(#httpd{path_parts=[_,<<"_missing_revs">>]}=Req, _Db) ->
send_method_not_allowed(Req, "POST");


db_req(#httpd{method='POST',path_parts=[_,<<"_revs_diff">>]}=Req, Db) ->
{JsonDocIdRevs} = couch_httpd:json_body_obj(Req),
JsonDocIdRevs2 =
Expand Down Expand Up @@ -586,14 +589,11 @@ db_doc_req(#httpd{method='GET'}=Req, Db, DocId) ->
end
end;


db_doc_req(#httpd{method='POST'}=Req, Db, DocId) ->
couch_httpd:validate_referer(Req),
couch_doc:validate_docid(DocId),
case couch_httpd:header_value(Req, "Content-Type") of
"multipart/form-data" ++ _Rest ->
ok;
_Else ->
throw({bad_ctype, <<"Invalid Content-Type header for form upload">>})
end,
couch_httpd:validate_ctype(Req, "multipart/form-data"),
Form = couch_httpd:parse_form(Req),
case proplists:is_defined("_doc", Form) of
true ->
Expand Down
21 changes: 3 additions & 18 deletions src/couchdb/couch_httpd_show.erl
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ send_doc_update_response(Req, Db, DDoc, UpdateName, Doc, DocId) ->
{200, JsonResp}
end,

JsonResp2 = json_apply_field({<<"code">>, Code}, JsonResp1),
JsonResp2 = couch_util:json_apply_field({<<"code">>, Code}, JsonResp1),
% todo set location field
couch_httpd_external:send_external_response(Req, JsonResp2).

Expand Down Expand Up @@ -376,21 +376,6 @@ render_head_for_empty_list(StartListRespFun, Req, Etag, CurrentSeq, null) ->
render_head_for_empty_list(StartListRespFun, Req, Etag, CurrentSeq, TotalRows) ->
StartListRespFun(Req, Etag, TotalRows, null, [], CurrentSeq).


% Maybe this is in the proplists API
% todo move to couch_util
json_apply_field(H, {L}) ->
json_apply_field(H, L, []).
json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) ->
% drop matching keys
json_apply_field({Key, NewValue}, Headers, Acc);
json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) ->
% something else is next, leave it alone.
json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]);
json_apply_field({Key, NewValue}, [], Acc) ->
% end of list, add ours
{[{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
Expand All @@ -404,8 +389,8 @@ apply_etag({ExternalResponse}, CurrentEtag) ->
JsonHeaders ->
{[case Field of
{<<"headers">>, JsonHeaders} -> % add our headers
JsonHeadersEtagged = json_apply_field({<<"Etag">>, CurrentEtag}, JsonHeaders),
JsonHeadersVaried = json_apply_field({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged),
JsonHeadersEtagged = couch_util:json_apply_field({<<"Etag">>, CurrentEtag}, JsonHeaders),
JsonHeadersVaried = couch_util:json_apply_field({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged),
{<<"headers">>, JsonHeadersVaried};
_ -> % skip non-header fields
Field
Expand Down
10 changes: 6 additions & 4 deletions src/couchdb/couch_rep.erl
Original file line number Diff line number Diff line change
Expand Up @@ -653,10 +653,11 @@ commit_to_both(Source, Target, RequiredSeq) ->
end,
{SourceStartTime, TargetStartTime}.

ensure_full_commit(#http_db{} = Target) ->
ensure_full_commit(#http_db{headers = Headers} = Target) ->
Req = Target#http_db{
resource = "_ensure_full_commit",
method = post
method = post,
headers = [{"content-type", "application/json"} | Headers]
},
{ResultProps} = couch_rep_httpc:request(Req),
true = couch_util:get_value(<<"ok">>, ResultProps),
Expand All @@ -677,11 +678,12 @@ ensure_full_commit(Target) ->
InstanceStartTime
end.

ensure_full_commit(#http_db{} = Source, RequiredSeq) ->
ensure_full_commit(#http_db{headers = Headers} = Source, RequiredSeq) ->
Req = Source#http_db{
resource = "_ensure_full_commit",
method = post,
qs = [{seq, RequiredSeq}]
qs = [{seq, RequiredSeq}],
headers = [{"content-type", "application/json"} | Headers]
},
{ResultProps} = couch_rep_httpc:request(Req),
case couch_util:get_value(<<"ok">>, ResultProps) of
Expand Down
3 changes: 2 additions & 1 deletion src/couchdb/couch_rep_writer.erl
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ write_bulk_docs(#http_db{headers = Headers} = Db, Docs) ->
resource = "_bulk_docs",
method = post,
body = {[{new_edits, false}, {docs, JsonDocs}]},
headers = [{"x-couch-full-commit", "false"} | Headers]
headers = couch_util:proplist_apply_field({"Content-Type", "application/json"}, [{"X-Couch-Full-Commit", "false"} | Headers])
},
?LOG_ERROR("headers ~p",[Request#http_db.headers]),
ErrorsJson = case couch_rep_httpc:request(Request) of
{FailProps} ->
exit({target_error, couch_util:get_value(<<"error">>, FailProps)});
Expand Down
14 changes: 14 additions & 0 deletions src/couchdb/couch_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
-export([encodeBase64Url/1, decodeBase64Url/1]).
-export([to_hex/1, parse_term/1, dict_find/3]).
-export([file_read_size/1, get_nested_json_value/2, json_user_ctx/1]).
-export([proplist_apply_field/2, json_apply_field/2]).
-export([to_binary/1, to_integer/1, to_list/1, url_encode/1]).
-export([json_encode/1, json_decode/1]).
-export([verify/2,simple_call/2,shutdown_sync/1]).
Expand Down Expand Up @@ -143,6 +144,19 @@ get_nested_json_value(Value, []) ->
get_nested_json_value(_NotJSONObj, _) ->
throw({not_found, json_mismatch}).

proplist_apply_field(H, L) ->
{R} = json_apply_field(H, {L}),
R.

json_apply_field(H, {L}) ->
json_apply_field(H, L, []).
json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) ->
json_apply_field({Key, NewValue}, Headers, Acc);
json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) ->
json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]);
json_apply_field({Key, NewValue}, [], Acc) ->
{[{Key, NewValue}|Acc]}.

json_user_ctx(#db{name=DbName, user_ctx=Ctx}) ->
{[{<<"db">>, DbName},
{<<"name">>,Ctx#user_ctx.name},
Expand Down

0 comments on commit 4b079e2

Please sign in to comment.