You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2014/12/04 21:09:32 UTC
[01/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Repository: couchdb-couch
Updated Branches:
refs/heads/2491-refactor-couch-httpd-auth 41e9cdd62 -> 3e8286d40 (forced update)
Make couch_stream monitor the stream opener
This commit fixes a process leak which would happen when a process
opened a couch_stream and exited with reason normal before closing
the stream.
This patch makes the couch_stream gen_server monitor the process
which opens the stream. When that monitor sends an exit signal
the couch_stream gen_server is stopped and the process dies.
Closes COUCHDB-2365
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/f02a1013
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/f02a1013
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/f02a1013
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: f02a1013dc61f011ccb58f1fddb689a93795af98
Parents: 40c5c85
Author: Mike Wallace <mi...@apache.org>
Authored: Tue Oct 7 23:45:11 2014 +0100
Committer: Mike Wallace <mi...@apache.org>
Committed: Wed Oct 29 23:12:27 2014 +0000
----------------------------------------------------------------------
src/couch_stream.erl | 10 ++++++++--
test/couch_stream_tests.erl | 18 +++++++++++++++++-
2 files changed, 25 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/f02a1013/src/couch_stream.erl
----------------------------------------------------------------------
diff --git a/src/couch_stream.erl b/src/couch_stream.erl
index e71542c..6877f0f 100644
--- a/src/couch_stream.erl
+++ b/src/couch_stream.erl
@@ -29,6 +29,7 @@
-record(stream,
{fd = 0,
+ opener_monitor,
written_pointers=[],
buffer_list = [],
buffer_len = 0,
@@ -50,7 +51,7 @@ open(Fd) ->
open(Fd, []).
open(Fd, Options) ->
- gen_server:start_link(couch_stream, {Fd, Options}, []).
+ gen_server:start_link(couch_stream, {Fd, self(), Options}, []).
close(Pid) ->
gen_server:call(Pid, close, infinity).
@@ -197,7 +198,7 @@ write(Pid, Bin) ->
gen_server:call(Pid, {write, Bin}, infinity).
-init({Fd, Options}) ->
+init({Fd, OpenerPid, Options}) ->
{EncodingFun, EndEncodingFun} =
case couch_util:get_value(encoding, Options, identity) of
identity ->
@@ -207,6 +208,7 @@ init({Fd, Options}) ->
end,
{ok, #stream{
fd=Fd,
+ opener_monitor=erlang:monitor(process, OpenerPid),
md5=couch_util:md5_init(),
identity_md5=couch_util:md5_init(),
encoding_fun=EncodingFun,
@@ -266,6 +268,7 @@ handle_call({write, Bin}, _From, Stream) ->
handle_call(close, _From, Stream) ->
#stream{
fd = Fd,
+ opener_monitor = MonRef,
written_len = WrittenLen,
written_pointers = Written,
buffer_list = Buffer,
@@ -288,6 +291,7 @@ handle_call(close, _From, Stream) ->
StreamLen = WrittenLen + iolist_size(WriteBin2),
{StreamInfo, StreamLen, IdenLen, Md5Final, IdenMd5Final}
end,
+ erlang:demonitor(MonRef),
{stop, normal, Result, Stream}.
handle_cast(_Msg, State) ->
@@ -296,5 +300,7 @@ handle_cast(_Msg, State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
+handle_info({'DOWN', Ref, _, _, _}, #stream{opener_monitor=Ref} = State) ->
+ {stop, normal, State};
handle_info(_Info, State) ->
{noreply, State}.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/f02a1013/test/couch_stream_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_stream_tests.erl b/test/couch_stream_tests.erl
index 3e84ca0..e12e714 100644
--- a/test/couch_stream_tests.erl
+++ b/test/couch_stream_tests.erl
@@ -38,7 +38,8 @@ stream_test_() ->
fun should_return_stream_size_on_close/1,
fun should_return_valid_pointers/1,
fun should_recall_last_pointer_position/1,
- fun should_stream_more_with_4K_chunk_size/1
+ fun should_stream_more_with_4K_chunk_size/1,
+ fun should_stop_on_normal_exit_of_stream_opener/1
]
}
}.
@@ -94,6 +95,21 @@ should_stream_more_with_4K_chunk_size({Fd, _}) ->
?_assertMatch({[{0, 4100}, {4106, 1020}], 5120, _, _, _},
couch_stream:close(Stream)).
+should_stop_on_normal_exit_of_stream_opener({Fd, _}) ->
+ RunnerPid = self(),
+ OpenerPid = spawn(
+ fun() ->
+ {ok, StreamPid} = couch_stream:open(Fd),
+ RunnerPid ! {pid, StreamPid}
+ end),
+ StreamPid = receive
+ {pid, StreamPid0} -> StreamPid0
+ end,
+ % Confirm the validity of the test by verifying the stream opener has died
+ ?_assertNot(is_process_alive(OpenerPid)),
+ % Verify the stream itself has also died
+ ?_assertNot(is_process_alive(StreamPid)).
+
read_all(Fd, PosList) ->
Data = couch_stream:foldl(Fd, PosList, fun(Bin, Acc) -> [Bin, Acc] end, []),
[16/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
TOTP: left-pad with 0's as needed
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/0c0b438c
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/0c0b438c
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/0c0b438c
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 0c0b438c2648f11afd646fbebb1bf9c24f36a8a5
Parents: bfbf8eb
Author: Robert Newson <rn...@apache.org>
Authored: Sun Nov 2 21:17:01 2014 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Sun Nov 2 21:17:01 2014 +0000
----------------------------------------------------------------------
src/couch_httpd_auth.erl | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/0c0b438c/src/couch_httpd_auth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
index 5e528d9..752dd20 100644
--- a/src/couch_httpd_auth.erl
+++ b/src/couch_httpd_auth.erl
@@ -475,12 +475,15 @@ verify_token(Alg, Key, Len, Token) ->
end.
generate_token(Alg, Key, Len, Timestamp) ->
- integer_to_binary(couch_totp:generate(Alg, Key, Timestamp, 30, Len)).
+ integer_to_binary(couch_totp:generate(Alg, Key, Timestamp, 30, Len), Len).
-integer_to_binary(Int) when is_integer(Int) ->
- case erlang:function_exported(erlang, integer_to_binary, 1) of
+integer_to_binary(Int, Len) when is_integer(Int), is_integer(Len) ->
+ Unpadded = case erlang:function_exported(erlang, integer_to_binary, 1) of
true ->
erlang:integer_to_binary(Int);
false ->
- ?l2b(integer_to_list(Int))
- end.
+ ?l2b(integer_to_list(Int))
+ end,
+ Padding = binary:copy(<<"0">>, Len),
+ Padded = <<Padding/binary, Unpadded/binary>>,
+ binary:part(Padded, byte_size(Padded), -Len).
[15/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
TOTP secrets are stored in Base32
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/bfbf8ebe
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/bfbf8ebe
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/bfbf8ebe
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: bfbf8ebed19ba511fc50f916f3fd41cea84e815f
Parents: 6125862
Author: Robert Newson <rn...@apache.org>
Authored: Sun Nov 2 17:15:49 2014 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Sun Nov 2 18:09:18 2014 +0000
----------------------------------------------------------------------
src/couch_base32.erl | 127 +++++++++++++++++++++++++++++++++++++++
src/couch_httpd_auth.erl | 2 +-
test/couch_base32_tests.erl | 28 +++++++++
3 files changed, 156 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/bfbf8ebe/src/couch_base32.erl
----------------------------------------------------------------------
diff --git a/src/couch_base32.erl b/src/couch_base32.erl
new file mode 100644
index 0000000..d8d754f
--- /dev/null
+++ b/src/couch_base32.erl
@@ -0,0 +1,127 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_base32).
+
+-export([encode/1, decode/1]).
+
+-define(SET, <<"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567">>).
+
+
+-spec encode(binary()) -> binary().
+encode(Plain) when is_binary(Plain) ->
+ IoList = encode(Plain, 0, byte_size(Plain) * 8, []),
+ iolist_to_binary(lists:reverse(IoList)).
+
+encode(_Plain, _ByteOffset, 0, Acc) ->
+ Acc;
+
+encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 8 ->
+ <<A:5, B:3>> = binary:part(Plain, ByteOffset, 1),
+ [<<(binary:at(?SET, A)),
+ (binary:at(?SET, B bsl 2)),
+ "======">> | Acc];
+
+encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 16 ->
+ <<A:5, B:5, C:5, D:1>> = binary:part(Plain, ByteOffset, 2),
+ [<<(binary:at(?SET, A)),
+ (binary:at(?SET, B)),
+ (binary:at(?SET, C)),
+ (binary:at(?SET, D bsl 4)),
+ "====">> | Acc];
+
+encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 24 ->
+ <<A:5, B:5, C:5, D:5, E:4>> = binary:part(Plain, ByteOffset, 3),
+ [<<(binary:at(?SET, A)),
+ (binary:at(?SET, B)),
+ (binary:at(?SET, C)),
+ (binary:at(?SET, D)),
+ (binary:at(?SET, E bsl 1)),
+ "===">> | Acc];
+
+encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 32 ->
+ <<A:5, B:5, C:5, D:5, E:5, F:5, G:2>> = binary:part(Plain, ByteOffset, 4),
+ [<<(binary:at(?SET, A)),
+ (binary:at(?SET, B)),
+ (binary:at(?SET, C)),
+ (binary:at(?SET, D)),
+ (binary:at(?SET, E)),
+ (binary:at(?SET, F)),
+ (binary:at(?SET, G bsl 3)),
+ "=">> | Acc];
+
+encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining >= 40 ->
+ <<A:5, B:5, C:5, D:5, E:5, F:5, G:5, H:5>> =
+ binary:part(Plain, ByteOffset, 5),
+ Output = <<(binary:at(?SET, A)),
+ (binary:at(?SET, B)),
+ (binary:at(?SET, C)),
+ (binary:at(?SET, D)),
+ (binary:at(?SET, E)),
+ (binary:at(?SET, F)),
+ (binary:at(?SET, G)),
+ (binary:at(?SET, H))>>,
+ encode(Plain, ByteOffset + 5, BitsRemaining - 40, [Output | Acc]).
+
+
+-spec decode(binary()) -> binary().
+decode(Encoded) when is_binary(Encoded) ->
+ IoList = decode(Encoded, 0, []),
+ iolist_to_binary(lists:reverse(IoList)).
+
+decode(Encoded, ByteOffset, Acc) when ByteOffset == byte_size(Encoded) ->
+ Acc;
+decode(Encoded, ByteOffset, Acc) ->
+ case binary:part(Encoded, ByteOffset, 8) of
+ <<A:1/binary, B:1/binary, "======">> ->
+ [<<(find_in_set(A)):5,
+ (find_in_set(B) bsr 2):3>> | Acc];
+ <<A:1/binary, B:1/binary, C:1/binary, D:1/binary, "====">> ->
+ [<<(find_in_set(A)):5,
+ (find_in_set(B)):5,
+ (find_in_set(C)):5,
+ (find_in_set(D) bsr 4):1>> | Acc];
+ <<A:1/binary, B:1/binary, C:1/binary, D:1/binary, E:1/binary, "===">> ->
+ [<<(find_in_set(A)):5,
+ (find_in_set(B)):5,
+ (find_in_set(C)):5,
+ (find_in_set(D)):5,
+ (find_in_set(E) bsr 1):4>> | Acc];
+ <<A:1/binary, B:1/binary, C:1/binary, D:1/binary,
+ E:1/binary, F:1/binary, G:1/binary, "=">> ->
+ [<<(find_in_set(A)):5,
+ (find_in_set(B)):5,
+ (find_in_set(C)):5,
+ (find_in_set(D)):5,
+ (find_in_set(E)):5,
+ (find_in_set(F)):5,
+ (find_in_set(G) bsr 3):2>> | Acc];
+ <<A:1/binary, B:1/binary, C:1/binary, D:1/binary,
+ E:1/binary, F:1/binary, G:1/binary, H:1/binary>> ->
+ decode(Encoded, ByteOffset + 8,
+ [<<(find_in_set(A)):5,
+ (find_in_set(B)):5,
+ (find_in_set(C)):5,
+ (find_in_set(D)):5,
+ (find_in_set(E)):5,
+ (find_in_set(F)):5,
+ (find_in_set(G)):5,
+ (find_in_set(H)):5>> | Acc])
+ end.
+
+find_in_set(Char) ->
+ case binary:match(?SET, Char) of
+ nomatch ->
+ erlang:error(not_base32);
+ {Offset, _} ->
+ Offset
+ end.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/bfbf8ebe/src/couch_httpd_auth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
index cda51c5..5e528d9 100644
--- a/src/couch_httpd_auth.erl
+++ b/src/couch_httpd_auth.erl
@@ -448,7 +448,7 @@ verify_totp(User, Form) ->
undefined ->
ok;
{Props} ->
- Key = couch_util:get_value(<<"key">>, Props),
+ Key = couch_base32:decode(couch_util:get_value(<<"key">>, Props)),
Alg = couch_util:to_existing_atom(
couch_util:get_value(<<"algorithm">>, Props, <<"sha">>)),
Len = couch_util:get_value(<<"length">>, Props, 6),
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/bfbf8ebe/test/couch_base32_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_base32_tests.erl b/test/couch_base32_tests.erl
new file mode 100644
index 0000000..7e4d59a
--- /dev/null
+++ b/test/couch_base32_tests.erl
@@ -0,0 +1,28 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_base32_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+base32_test() ->
+ roundtrip(<<"">>, <<"">>),
+ roundtrip(<<"f">>, <<"MY======">>),
+ roundtrip(<<"fo">>, <<"MZXQ====">>),
+ roundtrip(<<"foo">>, <<"MZXW6===">>),
+ roundtrip(<<"foob">>, <<"MZXW6YQ=">>),
+ roundtrip(<<"fooba">>, <<"MZXW6YTB">>),
+ roundtrip(<<"foobar">>, <<"MZXW6YTBOI======">>).
+
+roundtrip(Plain, Encoded) ->
+ ?assertEqual(Plain, couch_base32:decode(Encoded)),
+ ?assertEqual(Encoded, couch_base32:encode(Plain)).
[06/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Make include_docs=true work for view changes
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/20cd47e7
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/20cd47e7
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/20cd47e7
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 20cd47e7164975618b32084a10774e4f8103e608
Parents: 8d5c900
Author: Benjamin Bastian <be...@gmail.com>
Authored: Tue Aug 26 15:55:07 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700
----------------------------------------------------------------------
src/couch_changes.erl | 46 ++++++++++++++++++++++++++--------------------
1 file changed, 26 insertions(+), 20 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/20cd47e7/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index 8ff109b..29920da 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -643,9 +643,9 @@ changes_enumerator(Value0, Acc) ->
changes_row(Results, DocInfo, #changes_acc{filter={fast_view,_,_,_}}=Acc) ->
format_doc_info_change(Results, DocInfo, Acc);
-changes_row(Results, KV, #changes_acc{view=#mrview{}}) ->
- {{Seq, Key}, {Id, Value, _Rev}} = KV,
- {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}]};
+changes_row(Results, KV, #changes_acc{view=#mrview{}}=Acc) ->
+ {{Seq, Key}, {Id, Value, Rev}} = KV,
+ {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}] ++ maybe_get_changes_doc({Id, Rev}, Acc)};
changes_row(Results, #doc_info{}=DocInfo, Acc) ->
format_doc_info_change(Results, DocInfo, Acc).
@@ -653,29 +653,35 @@ format_doc_info_change(Results, #doc_info{}=DocInfo, Acc) ->
#doc_info{
id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]
} = DocInfo,
+ {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Results}] ++
+ deleted_item(Del) ++ maybe_get_changes_doc(DocInfo, Acc)}.
+
+maybe_get_changes_doc(Value, #changes_acc{include_docs=true}=Acc) ->
#changes_acc{
db = Db,
include_docs = IncDoc,
doc_options = DocOpts,
conflicts = Conflicts
} = Acc,
- {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Results}] ++
- deleted_item(Del) ++ case IncDoc of
- true ->
- Opts = case Conflicts of
- true -> [deleted, conflicts];
- false -> [deleted]
- end,
- Doc = couch_index_util:load_doc(Db, DocInfo, Opts),
- case Doc of
- null ->
- [{doc, null}];
- _ ->
- [{doc, couch_doc:to_json_obj(Doc, DocOpts)}]
- end;
- false ->
- []
- end}.
+ case IncDoc of
+ true ->
+ Opts = case Conflicts of
+ true -> [deleted, conflicts];
+ false -> [deleted]
+ end,
+ Doc = couch_index_util:load_doc(Db, Value, Opts),
+ case Doc of
+ null ->
+ [{doc, null}];
+ _ ->
+ [{doc, couch_doc:to_json_obj(Doc, DocOpts)}]
+ end;
+ false ->
+ []
+ end;
+maybe_get_changes_doc(_Value, _Acc) ->
+ [].
+
deleted_item(true) -> [{<<"deleted">>, true}];
deleted_item(_) -> [].
[04/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
add supports of view changes in the _changes API
Now when the option `seq_indexed=true` is set in the design doc, the
view filter in _changes will use it to retrieve the results. Compared to
the current way, using a view index will be faster to retrieve changes.
It also gives the possibility to filter changes by key or get changes in
a key range. All the view options can be used.
Note 1: if someone is trying to filter a changes with view options when
the views are not indexed by sequence, a 400 error will be returned.
Note 2: The changes will only be returned when the view is updated if
seq_indexed=true
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/7f2af21e
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/7f2af21e
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/7f2af21e
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 7f2af21e573abcd80b8c8412332d6439b4a777b3
Parents: fcb2882
Author: benoitc <bc...@gmail.com>
Authored: Fri Feb 7 15:38:34 2014 +0100
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:52 2014 -0700
----------------------------------------------------------------------
src/couch_httpd_changes.erl | 250 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 246 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/7f2af21e/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
index 1e431e9..56ce559 100644
--- a/src/couch_httpd_changes.erl
+++ b/src/couch_httpd_changes.erl
@@ -12,7 +12,9 @@
-module(couch_httpd_changes).
--export([handle_changes_req/2]).
+-export([handle_changes_req/2,
+ handle_changes/3,
+ handle_view_changes/3]).
-include_lib("couch/include/couch_db.hrl").
@@ -34,9 +36,7 @@ handle_changes_req1(Req, #db{name=DbName}=Db) ->
% on other databases, _changes is free for all.
ok
end,
- handle_changes_req2(Req, Db).
-handle_changes_req2(Req, Db) ->
MakeCallback = fun(Resp) ->
fun({change, {ChangeProp}=Change, _}, "eventsource") ->
Seq = proplists:get_value(<<"seq">>, ChangeProp),
@@ -72,7 +72,7 @@ handle_changes_req2(Req, Db) ->
end
end,
ChangesArgs = parse_changes_query(Req, Db),
- ChangesFun = couch_changes:handle_changes(ChangesArgs, Req, Db),
+ ChangesFun = handle_changes(ChangesArgs, Req, Db),
WrapperFun = case ChangesArgs#changes_args.feed of
"normal" ->
{ok, Info} = couch_db:get_db_info(Db),
@@ -116,6 +116,164 @@ handle_changes_req2(Req, Db) ->
)
end.
+
+handle_changes(ChangesArgs, Req, Db) ->
+ case ChangesArgs#changes_args.filter of
+ "_view" ->
+ handle_view_changes(ChangesArgs, Req, Db);
+ _ ->
+ couch_changes:handle_changes(ChangesArgs, Req, Db)
+ end.
+
+%% wrapper around couch_mrview_changes.
+%% This wrapper mimic couch_changes:handle_changes/3 and return a
+%% Changefun that can be used by the handle_changes_req function. Also
+%% while couch_mrview_changes:handle_changes/6 is returning tha view
+%% changes this function return docs corresponding to the changes
+%% instead so it can be used to replace the _view filter.
+handle_view_changes(ChangesArgs, Req, Db) ->
+ %% parse view parameter
+ {DDocId, VName} = parse_view_param(Req),
+
+ %% get view options
+ Query = case Req of
+ {json_req, {Props}} ->
+ {Q} = couch_util:get_value(<<"query">>, Props, {[]}),
+ Q;
+ _ ->
+ couch_httpd:qs(Req)
+ end,
+ ViewOptions = parse_view_options(Query, []),
+
+ {ok, Infos} = couch_mrview:get_info(Db, DDocId),
+ case lists:member(<<"seq_indexed">>,
+ proplists:get_value(update_options, Infos, [])) of
+ true ->
+ handle_view_changes(Db, DDocId, VName, ViewOptions, ChangesArgs,
+ Req);
+ false when ViewOptions /= [] ->
+ ?LOG_ERROR("Tried to filter a non sequence indexed view~n",[]),
+ throw({bad_request, seqs_not_indexed});
+ false ->
+ %% old method we are getting changes using the btree instead
+ %% which is not efficient, log it
+ ?LOG_WARN("Get view changes with seq_indexed=false.~n", []),
+ couch_changes:handle_changes(ChangesArgs, Req, Db)
+ end.
+
+handle_view_changes(#db{name=DbName}=Db0, DDocId, VName, ViewOptions,
+ ChangesArgs, Req) ->
+ #changes_args{
+ feed = ResponseType,
+ since = Since,
+ db_open_options = DbOptions} = ChangesArgs,
+
+ Options0 = [{since, Since},
+ {view_options, ViewOptions}],
+ Options = case ResponseType of
+ "continuous" -> [stream | Options0];
+ "eventsource" -> [stream | Options0];
+ "longpoll" -> [{stream, once} | Options0];
+ _ -> Options0
+ end,
+
+ %% reopen the db with the db options given to the changes args
+ couch_db:close(Db0),
+ DbOptions1 = [{user_ctx, Db0#db.user_ctx} | DbOptions],
+ {ok, Db} = couch_db:open(DbName, DbOptions1),
+
+
+ %% initialise the changes fun
+ ChangesFun = fun(Callback) ->
+ Callback(start, ResponseType),
+
+ Acc0 = {"", 0, Db, Callback, ChangesArgs},
+ couch_mrview_changes:handle_changes(DbName, DDocId, VName,
+ fun view_changes_cb/2,
+ Acc0, Options)
+ end,
+ ChangesFun.
+
+
+view_changes_cb(stop, {LastSeq, {_, _, _, Callback, Args}}) ->
+ Callback({stop, LastSeq}, Args#changes_args.feed);
+
+view_changes_cb(heartbeat, {_, _, _, Callback, Args}=Acc) ->
+ Callback(timeout, Args#changes_args.feed),
+ {ok, Acc};
+view_changes_cb({{Seq, _Key, DocId}, _VAl},
+ {Prepend, OldLimit, Db0, Callback, Args}=Acc) ->
+
+ #changes_args{
+ feed = ResponseType,
+ limit = Limit} = Args,
+
+ %% if the doc sequence is > to the one in the db record, reopen the
+ %% database since it means we don't have the latest db value.
+ Db = case Db0#db.update_seq >= Seq of
+ true -> Db0;
+ false ->
+ {ok, Db1} = couch_db:reopen_db(Db0),
+ Db1
+ end,
+
+ case couch_db:get_doc_info(Db, DocId) of
+ {ok, DocInfo} ->
+ %% get change row
+ ChangeRow = view_change_row(Db, DocInfo, Args),
+ %% emit change row
+ Callback({change, ChangeRow, Prepend}, ResponseType),
+
+ %% if we achieved the limit, stop here, else continue.
+ NewLimit = OldLimit + 1,
+ if Limit > NewLimit ->
+ {ok, {<<",\n">>, Db, NewLimit, Callback, Args}};
+ true ->
+ {stop, {<<"">>, Db, NewLimit, Callback, Args}}
+ end;
+ {error, not_found} ->
+ %% doc not found, continue
+ {ok, Acc};
+ Error ->
+ throw(Error)
+ end.
+
+
+view_change_row(Db, DocInfo, Args) ->
+ #doc_info{id = Id, high_seq = Seq, revs = Revs} = DocInfo,
+ [#rev_info{rev=Rev, deleted=Del} | _] = Revs,
+
+ #changes_args{style=Style,
+ include_docs=InDoc,
+ doc_options = DocOpts,
+ conflicts=Conflicts}=Args,
+
+ Changes = case Style of
+ main_only ->
+ [{[{<<"rev">>, couch_doc:rev_to_str(Rev)}]}];
+ all_docs ->
+ [{[{<<"rev">>, couch_doc:rev_to_str(R)}]}
+ || #rev_info{rev=R} <- Revs]
+ end,
+
+ {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Changes}] ++
+ deleted_item(Del) ++ case InDoc of
+ true ->
+ Opts = case Conflicts of
+ true -> [deleted, conflicts];
+ false -> [deleted]
+ end,
+ Doc = couch_index_util:load_doc(Db, DocInfo, Opts),
+ case Doc of
+ null ->
+ [{doc, null}];
+ _ ->
+ [{doc, couch_doc:to_json_obj(Doc, DocOpts)}]
+ end;
+ false ->
+ []
+ end}.
+
parse_changes_query(Req, Db) ->
ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->
case {string:to_lower(Key), Value} of
@@ -172,3 +330,87 @@ parse_changes_query(Req, Db) ->
_ ->
ChangesArgs
end.
+
+parse_view_param({json_req, {Props}}) ->
+ {Query} = couch_util:get_value(<<"query">>, Props),
+ parse_view_param1(couch_util:get_value(<<"view">>, Query, <<"">>));
+parse_view_param(Req) ->
+ parse_view_param1(list_to_binary(couch_httpd:qs_value(Req, "view", ""))).
+
+parse_view_param1(ViewParam) ->
+ case re:split(ViewParam, <<"/">>) of
+ [DName, ViewName] ->
+ {<< "_design/", DName/binary >>, ViewName};
+ _ ->
+ throw({bad_request, "Invalid `view` parameter."})
+ end.
+
+parse_view_options([], Acc) ->
+ Acc;
+parse_view_options([{K, V} | Rest], Acc) ->
+ Acc1 = case couch_util:to_binary(K) of
+ <<"reduce">> ->
+ [{reduce, couch_mrview_http:parse_boolean(V)}];
+ <<"key">> ->
+ V1 = parse_json(V),
+ [{start_key, V1}, {end_key, V1} | Acc];
+ <<"keys">> ->
+ [{keys, parse_json(V)} | Acc];
+ <<"startkey">> ->
+ [{start_key, parse_json(V)} | Acc];
+ <<"start_key">> ->
+ [{start_key, parse_json(V)} | Acc];
+ <<"startkey_docid">> ->
+ [{start_key_docid, couch_util:to_binary(V)} | Acc];
+ <<"start_key_docid">> ->
+ [{start_key_docid, couch_util:to_binary(V)} | Acc];
+ <<"endkey">> ->
+ [{end_key, parse_json(V)} | Acc];
+ <<"end_key">> ->
+ [{end_key, parse_json(V)} | Acc];
+ <<"endkey_docid">> ->
+ [{start_key_docid, couch_util:to_binary(V)} | Acc];
+ <<"end_key_docid">> ->
+ [{start_key_docid, couch_util:to_binary(V)} | Acc];
+ <<"limit">> ->
+ [{limit, couch_mrview_http:parse_pos_int(V)} | Acc];
+ <<"count">> ->
+ throw({query_parse_error, <<"QS param `count` is not `limit`">>});
+ <<"stale">> when V =:= <<"ok">> orelse V =:= "ok" ->
+ [{stale, ok} | Acc];
+ <<"stale">> when V =:= <<"update_after">> orelse V =:= "update_after" ->
+ [{stale, update_after} | Acc];
+ <<"stale">> ->
+ throw({query_parse_error, <<"Invalid value for `stale`.">>});
+ <<"descending">> ->
+ case couch_mrview_http:parse_boolean(V) of
+ true ->
+ [{direction, rev} | Acc];
+ _ ->
+ [{direction, fwd} | Acc]
+ end;
+ <<"skip">> ->
+ [{skip, couch_mrview_http:parse_pos_int(V)} | Acc];
+ <<"group">> ->
+ case couch_mrview_http:parse_booolean(V) of
+ true ->
+ [{group_level, exact} | Acc];
+ _ ->
+ [{group_level, 0} | Acc]
+ end;
+ <<"group_level">> ->
+ [{group_level, couch_mrview_http:parse_pos_int(V)} | Acc];
+ <<"inclusive_end">> ->
+ [{inclusive_end, couch_mrview_http:parse_boolean(V)}];
+ _ ->
+ Acc
+ end,
+ parse_view_options(Rest, Acc1).
+
+parse_json(V) when is_list(V) ->
+ ?JSON_DECODE(V);
+parse_json(V) ->
+ V.
+
+deleted_item(true) -> [{<<"deleted">>, true}];
+deleted_item(_) -> [].
[20/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Fix default CSP setting for Ace Editor
Like @sebastianrothbucher noticed in apache/couchdb-fauxton#5
the Ace editor needs base64 image data as image source for their
icons
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/741a82d4
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/741a82d4
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/741a82d4
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 741a82d4cfda424bba5545a2219ecc2c4cf919c3
Parents: cb52507
Author: Robert Kowalski <ro...@kowalski.gd>
Authored: Tue Jul 22 19:22:02 2014 +0200
Committer: Robert Kowalski <ro...@kowalski.gd>
Committed: Fri Nov 28 21:51:51 2014 +0100
----------------------------------------------------------------------
src/couch_httpd_misc_handlers.erl | 2 +-
test/couchdb_csp_tests.erl | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/741a82d4/src/couch_httpd_misc_handlers.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_misc_handlers.erl b/src/couch_httpd_misc_handlers.erl
index 06fed5e..e90140f 100644
--- a/src/couch_httpd_misc_handlers.erl
+++ b/src/couch_httpd_misc_handlers.erl
@@ -81,7 +81,7 @@ handle_utils_dir_req(Req, _) ->
send_method_not_allowed(Req, "GET,HEAD").
maybe_add_csp_headers(Headers, "true") ->
- DefaultValues = "default-src 'self'; img-src 'self'; font-src 'self'; "
+ DefaultValues = "default-src 'self'; img-src 'self' data:; font-src 'self'; "
"script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';",
Value = config:get("csp", "header_value", DefaultValues),
[{"Content-Security-Policy", Value} | Headers];
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/741a82d4/test/couchdb_csp_tests.erl
----------------------------------------------------------------------
diff --git a/test/couchdb_csp_tests.erl b/test/couchdb_csp_tests.erl
index 3dbe6e3..5eb33f9 100644
--- a/test/couchdb_csp_tests.erl
+++ b/test/couchdb_csp_tests.erl
@@ -57,7 +57,7 @@ should_not_return_any_csp_headers_when_disabled(Url) ->
should_apply_default_policy(Url) ->
?_assertEqual(
- "default-src 'self'; img-src 'self'; font-src 'self'; "
+ "default-src 'self'; img-src 'self' data:; font-src 'self'; "
"script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';",
begin
{ok, _, Headers, _} = test_request:get(Url),
[13/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Add comment and do minor refactoring
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/6e7f19ce
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/6e7f19ce
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/6e7f19ce
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 6e7f19ceaf14f2824c4e3077dbbf1402c12239bc
Parents: da2836b
Author: Benjamin Bastian <be...@gmail.com>
Authored: Fri Aug 29 18:15:55 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700
----------------------------------------------------------------------
src/couch_changes.erl | 33 +++++++++++++++------------------
1 file changed, 15 insertions(+), 18 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6e7f19ce/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index 29920da..2ff78e7 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -65,23 +65,20 @@ handle_changes(Args1, Req, Db0, Type) ->
} = Args1,
Filter = configure_filter(FilterName, Style, Req, Db0),
Args = Args1#changes_args{filter_fun = Filter},
- UseViewChanges = case {Type, Filter} of
- {{view, _, _}, _} ->
- true;
- {_, {fast_view, _, _, _}} ->
- true;
+ % The type of changes feed depends on the supplied filter. If the query is
+ % for an optimized view-filtered db changes, we need to use the view
+ % sequence tree.
+ {UseViewChanges, DDocName, ViewName} = case {Type, Filter} of
+ {{view, DDocName0, ViewName0}, _} ->
+ {true, DDocName0, ViewName0};
+ {_, {fast_view, _, DDoc, ViewName0}} ->
+ {true, DDoc#doc.id, ViewName0};
_ ->
- false
+ {false, undefined, undefined}
end,
- {StartListenerFun, DDocName, ViewName} = if UseViewChanges ->
- {DDocName0, ViewName0} = case {Type, Filter} of
- {{view, DDocName1, ViewName1}, _} ->
- {DDocName1, ViewName1};
- {_, {fast_view, _, DDoc, ViewName1}} ->
- {DDoc#doc.id, ViewName1}
- end,
+ {StartListenerFun, View} = if UseViewChanges ->
{ok, {_, View0, _}, _, _} = couch_mrview_util:get_view(
- Db0#db.name, DDocName0, ViewName0, #mrargs{}),
+ Db0#db.name, DDocName, ViewName, #mrargs{}),
case View0#mrview.seq_btree of
#btree{} ->
ok;
@@ -90,17 +87,17 @@ handle_changes(Args1, Req, Db0, Type) ->
end,
SNFun = fun() ->
couch_event:link_listener(
- ?MODULE, handle_view_event, {self(), DDocName0}, [{dbname, Db0#db.name}]
+ ?MODULE, handle_view_event, {self(), DDocName}, [{dbname, Db0#db.name}]
)
end,
- {SNFun, DDocName0, ViewName0};
- true ->
+ {SNFun, View0};
+ true ->
SNFun = fun() ->
couch_event:link_listener(
?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
)
end,
- {SNFun, undefined, undefined}
+ {SNFun, undefined}
end,
Start = fun() ->
{ok, Db} = couch_db:reopen(Db0),
[07/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Add rev to view changes respose. See couch_mrview for corresponding changes
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/434b5414
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/434b5414
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/434b5414
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 434b5414ed975747e8144f9c5542cefa3f83fe3c
Parents: 20e585d
Author: Benjamin Bastian <be...@gmail.com>
Authored: Mon Aug 25 16:50:05 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700
----------------------------------------------------------------------
src/couch_changes.erl | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/434b5414/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index 259f83c..50f9d50 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -265,7 +265,7 @@ filter(Db, DocInfo, {custom, Style, Req0, DDoc, FName}) ->
{ok, Passes} = couch_query_servers:filter_docs(Req, Db, DDoc, FName, Docs),
filter_revs(Passes, Docs).
-fast_view_filter(Db, {{Seq, _}, {ID, _}}, {fast_view, Style, _, _}) ->
+fast_view_filter(Db, {{Seq, _}, {ID, _, _}}, {fast_view, Style, _, _}) ->
case couch_db:get_doc_info(Db, ID) of
{ok, #doc_info{high_seq=Seq}=DocInfo} ->
Docs = open_revs(Db, DocInfo, Style),
@@ -284,15 +284,15 @@ fast_view_filter(Db, {{Seq, _}, {ID, _}}, {fast_view, Style, _, _}) ->
% I left the Seq > HighSeq guard in so if (for some godforsaken
% reason) the seq in the view is more current than the database,
% we'll throw an error.
- {ok, []};
+ {undefined, []};
{error, not_found} ->
- {ok, []}
+ {undefined, []}
end.
-view_filter(_Db, _KV, {default, _Style}) ->
- [ok]. % TODO: make a real thing
+view_filter(Db, KV, {default, Style}) ->
+ apply_view_style(Db, KV, Style).
get_view_qs({json_req, {Props}}) ->
@@ -353,6 +353,16 @@ apply_style(#doc_info{revs=Revs}, main_only) ->
apply_style(#doc_info{revs=Revs}, all_docs) ->
[{[{<<"rev">>, couch_doc:rev_to_str(R)}]} || #rev_info{rev=R} <- Revs].
+apply_view_style(_Db, {{_Seq, _Key}, {_ID, _Value, Rev}}, main_only) ->
+ [{[{<<"rev">>, couch_doc:rev_to_str(Rev)}]}];
+apply_view_style(Db, {{_Seq, _Key}, {ID, _Value, _Rev}}, all_docs) ->
+ case couch_db:get_doc_info(Db, ID) of
+ {ok, DocInfo} ->
+ apply_style(DocInfo, all_docs);
+ {error, not_found} ->
+ []
+ end.
+
open_revs(Db, DocInfo, Style) ->
DocInfos = case Style of
@@ -627,7 +637,7 @@ changes_enumerator(Value0, Acc) ->
changes_row(Results, DocInfo, #changes_acc{filter={fast_view,_,_,_}}=Acc) ->
format_doc_info_change(Results, DocInfo, Acc);
changes_row(Results, KV, #changes_acc{view=#mrview{}}) ->
- {{Seq, Key}, {Id, Value}} = KV,
+ {{Seq, Key}, {Id, Value, _Rev}} = KV,
{[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}]};
changes_row(Results, #doc_info{}=DocInfo, Acc) ->
format_doc_info_change(Results, DocInfo, Acc).
[21/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Expose and add helper for tests
In order to make them reusable in the chhtpd integration-tests.
Additionally adding a post and delete helper.
COUCHDB-2462
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/2a45cb03
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/2a45cb03
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/2a45cb03
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 2a45cb0325b72eb0910029bed5d2c38f784f68f9
Parents: 741a82d
Author: Robert Kowalski <ro...@kowalski.gd>
Authored: Sun Nov 30 03:13:36 2014 +0100
Committer: Robert Kowalski <ro...@kowalski.gd>
Committed: Mon Dec 1 21:22:43 2014 +0100
----------------------------------------------------------------------
src/test_request.erl | 81 ++++++++++++++++++++++++++++++++++++++++++++++
src/test_util.erl | 1 +
test/test_request.erl | 75 ------------------------------------------
3 files changed, 82 insertions(+), 75 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/2a45cb03/src/test_request.erl
----------------------------------------------------------------------
diff --git a/src/test_request.erl b/src/test_request.erl
new file mode 100644
index 0000000..9693976
--- /dev/null
+++ b/src/test_request.erl
@@ -0,0 +1,81 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(test_request).
+
+-export([get/1, get/2, get/3]).
+-export([post/3]).
+-export([put/2, put/3]).
+-export([delete/1]).
+-export([options/1, options/2, options/3]).
+-export([request/3, request/4]).
+
+get(Url) ->
+ request(get, Url, []).
+
+get(Url, Headers) ->
+ request(get, Url, Headers).
+get(Url, Headers, Opts) ->
+ request(get, Url, Headers, [], Opts).
+
+post(Url, Headers, Body) ->
+ request(post, Url, Headers, Body).
+
+put(Url, Body) ->
+ request(put, Url, [], Body).
+
+put(Url, Headers, Body) ->
+ request(put, Url, Headers, Body).
+
+delete(Url) ->
+ request(delete, Url, []).
+
+options(Url) ->
+ request(options, Url, []).
+
+options(Url, Headers) ->
+ request(options, Url, Headers).
+
+options(Url, Headers, Opts) ->
+ request(options, Url, Headers, [], Opts).
+
+
+request(Method, Url, Headers) ->
+ request(Method, Url, Headers, []).
+
+request(Method, Url, Headers, Body) ->
+ request(Method, Url, Headers, Body, [], 3).
+
+request(Method, Url, Headers, Body, Opts) ->
+ request(Method, Url, Headers, Body, Opts, 3).
+
+request(_Method, _Url, _Headers, _Body, _Opts, 0) ->
+ {error, request_failed};
+request(Method, Url, Headers, Body, Opts, N) ->
+ case code:is_loaded(ibrowse) of
+ false ->
+ {ok, _} = ibrowse:start();
+ _ ->
+ ok
+ end,
+ case ibrowse:send_req(Url, Headers, Method, Body, Opts) of
+ {ok, Code0, RespHeaders, RespBody0} ->
+ Code = list_to_integer(Code0),
+ RespBody = iolist_to_binary(RespBody0),
+ {ok, Code, RespHeaders, RespBody};
+ {error, {'EXIT', {normal, _}}} ->
+ % Connection closed right after a successful request that
+ % used the same connection.
+ request(Method, Url, Headers, Body, N - 1);
+ Error ->
+ Error
+ end.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/2a45cb03/src/test_util.erl
----------------------------------------------------------------------
diff --git a/src/test_util.erl b/src/test_util.erl
index 3f90330..9340d36 100644
--- a/src/test_util.erl
+++ b/src/test_util.erl
@@ -20,6 +20,7 @@
-export([request/3, request/4]).
-export([start_couch/0, start_couch/1, stop_couch/0, stop_couch/1]).
-export([start_config/1, stop_config/1]).
+-export([start_applications/1]).
srcdir() ->
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/2a45cb03/test/test_request.erl
----------------------------------------------------------------------
diff --git a/test/test_request.erl b/test/test_request.erl
deleted file mode 100644
index 68e4956..0000000
--- a/test/test_request.erl
+++ /dev/null
@@ -1,75 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-% http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
--module(test_request).
-
--export([get/1, get/2, get/3]).
--export([put/2, put/3]).
--export([options/1, options/2, options/3]).
--export([request/3, request/4]).
-
-get(Url) ->
- request(get, Url, []).
-
-get(Url, Headers) ->
- request(get, Url, Headers).
-get(Url, Headers, Opts) ->
- request(get, Url, Headers, [], Opts).
-
-
-put(Url, Body) ->
- request(put, Url, [], Body).
-
-put(Url, Headers, Body) ->
- request(put, Url, Headers, Body).
-
-
-options(Url) ->
- request(options, Url, []).
-
-options(Url, Headers) ->
- request(options, Url, Headers).
-
-options(Url, Headers, Opts) ->
- request(options, Url, Headers, [], Opts).
-
-
-request(Method, Url, Headers) ->
- request(Method, Url, Headers, []).
-
-request(Method, Url, Headers, Body) ->
- request(Method, Url, Headers, Body, [], 3).
-
-request(Method, Url, Headers, Body, Opts) ->
- request(Method, Url, Headers, Body, Opts, 3).
-
-request(_Method, _Url, _Headers, _Body, _Opts, 0) ->
- {error, request_failed};
-request(Method, Url, Headers, Body, Opts, N) ->
- case code:is_loaded(ibrowse) of
- false ->
- {ok, _} = ibrowse:start();
- _ ->
- ok
- end,
- case ibrowse:send_req(Url, Headers, Method, Body, Opts) of
- {ok, Code0, RespHeaders, RespBody0} ->
- Code = list_to_integer(Code0),
- RespBody = iolist_to_binary(RespBody0),
- {ok, Code, RespHeaders, RespBody};
- {error, {'EXIT', {normal, _}}} ->
- % Connection closed right after a successful request that
- % used the same connection.
- request(Method, Url, Headers, Body, N - 1);
- Error ->
- Error
- end.
[19/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Add missing metrics
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/cb52507c
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/cb52507c
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/cb52507c
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: cb52507c1ced478e6900cae529584461c3d4910b
Parents: be6ddf2
Author: Robert Newson <rn...@apache.org>
Authored: Mon Nov 3 18:04:57 2014 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Mon Nov 3 18:05:12 2014 +0000
----------------------------------------------------------------------
priv/stats_descriptions.cfg | 8 ++++++++
1 file changed, 8 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/cb52507c/priv/stats_descriptions.cfg
----------------------------------------------------------------------
diff --git a/priv/stats_descriptions.cfg b/priv/stats_descriptions.cfg
index 729fca1..2dd77bc 100644
--- a/priv/stats_descriptions.cfg
+++ b/priv/stats_descriptions.cfg
@@ -86,6 +86,10 @@
{type, counter},
{desc, <<"number of HTTP HEAD requests">>}
]}.
+{[couchdb, httpd_request_methods, 'OPTIONS'], [
+ {type, counter},
+ {desc, <<"number of HTTP OPTIONS requests">>}
+]}.
{[couchdb, httpd_request_methods, 'POST'], [
{type, counter},
{desc, <<"number of HTTP POST requests">>}
@@ -106,6 +110,10 @@
{type, counter},
{desc, <<"number of HTTP 202 Accepted responses">>}
]}.
+{[couchdb, httpd_status_codes, 204], [
+ {type, counter},
+ {desc, <<"number of HTTP 204 No Content responses">>}
+]}.
{[couchdb, httpd_status_codes, 301], [
{type, counter},
{desc, <<"number of HTTP 301 Moved Permanently responses">>}
[12/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Change return format of _view_changes
This commit makes KV pairs for _view_changes requests be returned in the
format:
{..."add": [["key", "val"],["key2", "val2"]], "remove": ["oldkey"]}
Note that the "add" field is a list of lists rather than an object
because: 1) there can be multiple values for a given key and 2) keys can
be non-strings (non-string keys are invalid JSON).
Also note that if a view update causes adds or removes some (but not
all) values for a given key, all values associated with that key will be
returned in the _view_changes response.
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/6125862c
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/6125862c
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/6125862c
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 6125862cfed4e948f0f188faa46780dfb3bcbd0d
Parents: 6e7f19c
Author: Benjamin Bastian <be...@gmail.com>
Authored: Mon Sep 15 19:10:44 2014 -0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700
----------------------------------------------------------------------
src/couch_changes.erl | 154 ++++++++++++++++++++++++++++++++++++++-------
1 file changed, 131 insertions(+), 23 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6125862c/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index 2ff78e7..2b2647f 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -49,7 +49,9 @@
doc_options,
conflicts,
timeout,
- timeout_fun
+ timeout_fun,
+ aggregation_kvs,
+ aggregation_results
}).
handle_db_changes(Args, Req, Db) ->
@@ -450,7 +452,9 @@ build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun, D
timeout_fun = TimeoutFun,
ddoc_name = DDocName,
view_name = ViewName,
- view = View
+ view = View,
+ aggregation_results=[],
+ aggregation_kvs=[]
}.
send_changes(Acc, Dir, FirstRound) ->
@@ -460,16 +464,36 @@ send_changes(Acc, Dir, FirstRound) ->
filter = Filter,
view = View
} = Acc,
- EnumFun = fun changes_enumerator/2,
+ DbEnumFun = fun changes_enumerator/2,
case can_optimize(FirstRound, Filter) of
{true, Fun} ->
- Fun(Db, StartSeq, Dir, EnumFun, Acc, Filter);
+ Fun(Db, StartSeq, Dir, DbEnumFun, Acc, Filter);
_ ->
- case View of
- undefined ->
- couch_db:changes_since(Db, StartSeq, EnumFun, [{dir, Dir}], Acc);
- #mrview{} ->
- couch_mrview:view_changes_since(View, StartSeq, EnumFun, [{dir, Dir}], Acc)
+ case {View, Filter} of
+ {#mrview{}, {fast_view, _, _, _}} ->
+ couch_mrview:view_changes_since(View, StartSeq, DbEnumFun, [{dir, Dir}], Acc);
+ {undefined, _} ->
+ couch_db:changes_since(Db, StartSeq, DbEnumFun, [{dir, Dir}], Acc);
+ {#mrview{}, _} ->
+ ViewEnumFun = fun view_changes_enumerator/2,
+ {Go, Acc0} = couch_mrview:view_changes_since(View, StartSeq, ViewEnumFun, [{dir, Dir}], Acc),
+ case Acc0 of
+ #changes_acc{aggregation_results=[]} ->
+ {Go, Acc0};
+ _ ->
+ #changes_acc{
+ aggregation_results = AggResults,
+ aggregation_kvs = AggKVs,
+ user_acc = UserAcc,
+ callback = Callback,
+ resp_type = ResponseType,
+ prepend = Prepend
+ } = Acc0,
+ ChangesRow = view_changes_row(AggResults, AggKVs, Acc0),
+ UserAcc0 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc),
+ reset_heartbeat(),
+ {Go, Acc0#changes_acc{user_acc=UserAcc0}}
+ end
end
end.
@@ -589,18 +613,84 @@ maybe_refresh_view(Db, DDocName, ViewName) ->
end_sending_changes(Callback, UserAcc, EndSeq, ResponseType) ->
Callback({stop, EndSeq}, ResponseType, UserAcc).
+view_changes_enumerator(Value, Acc) ->
+ #changes_acc{
+ filter = Filter, callback = Callback, prepend = Prepend,
+ user_acc = UserAcc, limit = Limit, resp_type = ResponseType, db = Db,
+ timeout = Timeout, timeout_fun = TimeoutFun, seq = CurrentSeq,
+ aggregation_kvs=AggKVs, aggregation_results=AggResults
+ } = Acc,
+
+ Results0 = view_filter(Db, Value, Filter),
+ Results = [Result || Result <- Results0, Result /= null],
+ {{Seq, _}, _} = Value,
+
+ Go = if (Limit =< 1) andalso Results =/= [] -> stop; true -> ok end,
+
+ if CurrentSeq =:= Seq ->
+ NewAggKVs = case Results of
+ [] -> AggKVs;
+ _ -> [Value|AggKVs]
+ end,
+ {Done, UserAcc2} = maybe_heartbeat(Timeout, TimeoutFun, UserAcc),
+ Acc0 = Acc#changes_acc{
+ seq = Seq,
+ user_acc = UserAcc2,
+ aggregation_kvs=NewAggKVs
+ },
+ case Done of
+ stop -> {stop, Acc0};
+ ok -> {Go, Acc0}
+ end;
+ AggResults =/= [] ->
+ {NewAggKVs, NewAggResults} = case Results of
+ [] -> {[], []};
+ _ -> {[Value], Results}
+ end,
+ if ResponseType =:= "continuous" orelse ResponseType =:= "eventsource" ->
+ ChangesRow = view_changes_row(AggResults, AggKVs, Acc),
+ UserAcc2 = Callback({change, ChangesRow, <<>>}, ResponseType, UserAcc),
+ reset_heartbeat(),
+ {Go, Acc#changes_acc{
+ seq = Seq, user_acc = UserAcc2, limit = Limit - 1,
+ aggregation_kvs=NewAggKVs, aggregation_results=NewAggResults}};
+ true ->
+ ChangesRow = view_changes_row(AggResults, AggKVs, Acc),
+ UserAcc2 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc),
+ reset_heartbeat(),
+ {Go, Acc#changes_acc{
+ seq = Seq, prepend = <<",\n">>, user_acc = UserAcc2,
+ limit = Limit - 1, aggregation_kvs=[Value],
+ aggregation_results=Results}}
+ end;
+ true ->
+ {NewAggKVs, NewAggResults} = case Results of
+ [] -> {[], []};
+ _ -> {[Value], Results}
+ end,
+ {Done, UserAcc2} = maybe_heartbeat(Timeout, TimeoutFun, UserAcc),
+ Acc0 = Acc#changes_acc{
+ seq = Seq,
+ user_acc = UserAcc2,
+ aggregation_kvs=NewAggKVs,
+ aggregation_results=NewAggResults
+ },
+ case Done of
+ stop -> {stop, Acc0};
+ ok -> {Go, Acc0}
+ end
+ end.
+
changes_enumerator(Value0, Acc) ->
#changes_acc{
filter = Filter, callback = Callback, prepend = Prepend,
user_acc = UserAcc, limit = Limit, resp_type = ResponseType, db = Db,
- timeout = Timeout, timeout_fun = TimeoutFun, view = View
+ timeout = Timeout, timeout_fun = TimeoutFun
} = Acc,
- {Value, Results0} = case {View, Filter} of
- {_, {fast_view, _, _, _}} ->
+ {Value, Results0} = case Filter of
+ {fast_view, _, _, _} ->
fast_view_filter(Db, Value0, Filter);
- {#mrview{}, _} ->
- {Value0, view_filter(Db, Value0, Filter)};
- {_, _} ->
+ _ ->
{Value0, filter(Db, Value0, Filter)}
end,
Results = [Result || Result <- Results0, Result /= null],
@@ -638,15 +728,33 @@ changes_enumerator(Value0, Acc) ->
-changes_row(Results, DocInfo, #changes_acc{filter={fast_view,_,_,_}}=Acc) ->
- format_doc_info_change(Results, DocInfo, Acc);
-changes_row(Results, KV, #changes_acc{view=#mrview{}}=Acc) ->
- {{Seq, Key}, {Id, Value, Rev}} = KV,
- {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}] ++ maybe_get_changes_doc({Id, Rev}, Acc)};
-changes_row(Results, #doc_info{}=DocInfo, Acc) ->
- format_doc_info_change(Results, DocInfo, Acc).
+view_changes_row(Results, KVs, Acc) ->
+ {Add, Remove} = lists:foldl(fun(Row, {AddAcc, RemAcc}) ->
+ {{_Seq, Key}, {_Id, Value, _Rev}} = Row,
+ case Value of
+ removed ->
+ {AddAcc, [Key|RemAcc]};
+ {dups, DupValues} ->
+ AddAcc1 = lists:foldl(fun(DupValue, AddAcc0) ->
+ [[Key, DupValue]|AddAcc0]
+ end, AddAcc, DupValues),
+ {AddAcc1, RemAcc};
+ _ ->
+ {[[Key, Value]|AddAcc], RemAcc}
+ end
+ end, {[], []}, KVs),
+
+ % Seq, Id, and Rev should be the same for all KVs, since we're aggregating
+ % by seq.
+ [{{Seq, _Key}, {Id, _Value, Rev}}|_] = KVs,
+
+ {[
+ {<<"seq">>, Seq}, {<<"id">>, Id}, {<<"add">>, Add},
+ {<<"remove">>, Remove}, {<<"changes">>, Results}
+ ] ++ maybe_get_changes_doc({Id, Rev}, Acc)}.
+
-format_doc_info_change(Results, #doc_info{}=DocInfo, Acc) ->
+changes_row(Results, DocInfo, Acc) ->
#doc_info{
id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]
} = DocInfo,
[18/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Track the time spent executing validate_doc_update functions
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/be6ddf24
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/be6ddf24
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/be6ddf24
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: be6ddf24f62550bddd1dced0903bb508188b210e
Parents: 3e32256
Author: Alexander Shorin <kx...@apache.org>
Authored: Fri Oct 31 00:40:09 2014 +0300
Committer: Alexander Shorin <kx...@apache.org>
Committed: Mon Nov 3 12:19:59 2014 +0300
----------------------------------------------------------------------
priv/stats_descriptions.cfg | 4 ++++
src/couch_db.erl | 29 +++++++++++++++++------------
2 files changed, 21 insertions(+), 12 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/be6ddf24/priv/stats_descriptions.cfg
----------------------------------------------------------------------
diff --git a/priv/stats_descriptions.cfg b/priv/stats_descriptions.cfg
index 3f771d2..729fca1 100644
--- a/priv/stats_descriptions.cfg
+++ b/priv/stats_descriptions.cfg
@@ -186,3 +186,7 @@
{type, counter},
{desc, <<"number of rejections by validate_doc_update function">>}
]}.
+{[couchdb, query_server, vdu_process_time], [
+ {type, histogram},
+ {desc, <<"duration of validate_doc_update function calls">>}
+]}.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/be6ddf24/src/couch_db.erl
----------------------------------------------------------------------
diff --git a/src/couch_db.erl b/src/couch_db.erl
index b5ea64d..86b6558 100644
--- a/src/couch_db.erl
+++ b/src/couch_db.erl
@@ -582,18 +582,23 @@ validate_ddoc(DbName, DDoc) ->
end.
validate_doc_update_int(Db, Doc, GetDiskDocFun) ->
- DiskDoc = GetDiskDocFun(),
- JsonCtx = couch_util:json_user_ctx(Db),
- SecObj = get_security(Db),
- try [case Fun(Doc, DiskDoc, JsonCtx, SecObj) of
- ok -> ok;
- Error -> throw(Error)
- end || Fun <- Db#db.validate_doc_funs],
- ok
- catch
- throw:Error ->
- Error
- end.
+ Fun = fun() ->
+ DiskDoc = GetDiskDocFun(),
+ JsonCtx = couch_util:json_user_ctx(Db),
+ SecObj = get_security(Db),
+ try
+ [case Fun(Doc, DiskDoc, JsonCtx, SecObj) of
+ ok -> ok;
+ Error -> throw(Error)
+ end || Fun <- Db#db.validate_doc_funs],
+ ok
+ catch
+ throw:Error ->
+ Error
+ end
+ end,
+ couch_stats:update_histogram([couchdb, query_server, vdu_process_time],
+ Fun).
% to be safe, spawn a middleman here
[10/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Refactor code for sanity and so the view changes handler can reuse code better
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/da2836b6
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/da2836b6
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/da2836b6
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: da2836b6afa4fcb96a18bff5f32f5ac3d6376de8
Parents: 20cd47e
Author: Benjamin Bastian <be...@gmail.com>
Authored: Thu Aug 28 00:00:50 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700
----------------------------------------------------------------------
src/couch_httpd_changes.erl | 259 ---------------------------------------
src/couch_httpd_db.erl | 54 ++++----
2 files changed, 29 insertions(+), 284 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/da2836b6/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
deleted file mode 100644
index 4963a5f..0000000
--- a/src/couch_httpd_changes.erl
+++ /dev/null
@@ -1,259 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-% http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
--module(couch_httpd_changes).
-
--export([handle_db_changes_req/2,
- handle_changes_req/4,
- parse_changes_query/2]).
-
--include_lib("couch/include/couch_db.hrl").
-
-handle_db_changes_req(Req, Db) ->
- ChangesArgs = parse_changes_query(Req, Db),
- ChangesFun = couch_changes:handle_db_changes(ChangesArgs, Req, Db),
- handle_changes_req(Req, Db, ChangesArgs, ChangesFun).
-
-handle_changes_req(#httpd{method='POST'}=Req, Db, ChangesArgs, ChangesFun) ->
- couch_httpd:validate_ctype(Req, "application/json"),
- handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
-handle_changes_req(#httpd{method='GET'}=Req, Db, ChangesArgs, ChangesFun) ->
- handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
-handle_changes_req(#httpd{}=Req, _Db, _ChangesArgs, _ChangesFun) ->
- couch_httpd:send_method_not_allowed(Req, "GET,HEAD,POST").
-
-handle_changes_req1(Req, #db{name=DbName}=Db, ChangesArgs, ChangesFun) ->
- AuthDbName = ?l2b(config:get("couch_httpd_auth", "authentication_db")),
- case AuthDbName of
- DbName ->
- % in the authentication database, _changes is admin-only.
- ok = couch_db:check_is_admin(Db);
- _Else ->
- % on other databases, _changes is free for all.
- ok
- end,
-
- MakeCallback = fun(Resp) ->
- fun({change, {ChangeProp}=Change, _}, "eventsource") ->
- Seq = proplists:get_value(<<"seq">>, ChangeProp),
- couch_httpd:send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
- "\n", "id: ", ?JSON_ENCODE(Seq),
- "\n\n"]);
- ({change, Change, _}, "continuous") ->
- couch_httpd:send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
- ({change, Change, Prepend}, _) ->
- couch_httpd:send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
- (start, "eventsource") ->
- ok;
- (start, "continuous") ->
- ok;
- (start, _) ->
- couch_httpd:send_chunk(Resp, "{\"results\":[\n");
- ({stop, _EndSeq}, "eventsource") ->
- couch_httpd:end_json_response(Resp);
- ({stop, EndSeq}, "continuous") ->
- couch_httpd:send_chunk(
- Resp,
- [?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]
- ),
- couch_httpd:end_json_response(Resp);
- ({stop, EndSeq}, _) ->
- couch_httpd:send_chunk(
- Resp,
- io_lib:format("\n],\n\"last_seq\":~w}\n", [EndSeq])
- ),
- couch_httpd:end_json_response(Resp);
- (timeout, _) ->
- couch_httpd:send_chunk(Resp, "\n")
- end
- end,
- WrapperFun = case ChangesArgs#changes_args.feed of
- "normal" ->
- {ok, Info} = couch_db:get_db_info(Db),
- CurrentEtag = couch_httpd:make_etag(Info),
- fun(FeedChangesFun) ->
- couch_httpd:etag_respond(
- Req,
- CurrentEtag,
- fun() ->
- {ok, Resp} = couch_httpd:start_json_response(
- Req, 200, [{"ETag", CurrentEtag}]
- ),
- FeedChangesFun(MakeCallback(Resp))
- end
- )
- end;
- "eventsource" ->
- Headers = [
- {"Content-Type", "text/event-stream"},
- {"Cache-Control", "no-cache"}
- ],
- {ok, Resp} = couch_httpd:start_chunked_response(Req, 200, Headers),
- fun(FeedChangesFun) ->
- FeedChangesFun(MakeCallback(Resp))
- end;
- _ ->
- % "longpoll" or "continuous"
- {ok, Resp} = couch_httpd:start_json_response(Req, 200),
- fun(FeedChangesFun) ->
- FeedChangesFun(MakeCallback(Resp))
- end
- end,
- couch_stats_collector:increment(
- {httpd, clients_requesting_changes}
- ),
- try
- WrapperFun(ChangesFun)
- after
- couch_stats_collector:decrement(
- {httpd, clients_requesting_changes}
- )
- end.
-
-
-parse_changes_query(Req, Db) ->
- ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->
- case {string:to_lower(Key), Value} of
- {"feed", _} ->
- Args#changes_args{feed=Value};
- {"descending", "true"} ->
- Args#changes_args{dir=rev};
- {"since", "now"} ->
- UpdateSeq = couch_util:with_db(Db#db.name, fun(WDb) ->
- couch_db:get_update_seq(WDb)
- end),
- Args#changes_args{since=UpdateSeq};
- {"since", _} ->
- Args#changes_args{since=list_to_integer(Value)};
- {"last-event-id", _} ->
- Args#changes_args{since=list_to_integer(Value)};
- {"limit", _} ->
- Args#changes_args{limit=list_to_integer(Value)};
- {"style", _} ->
- Args#changes_args{style=list_to_existing_atom(Value)};
- {"heartbeat", "true"} ->
- Args#changes_args{heartbeat=true};
- {"heartbeat", _} ->
- Args#changes_args{heartbeat=list_to_integer(Value)};
- {"timeout", _} ->
- Args#changes_args{timeout=list_to_integer(Value)};
- {"include_docs", "true"} ->
- Args#changes_args{include_docs=true};
- {"attachments", "true"} ->
- Opts = Args#changes_args.doc_options,
- Args#changes_args{doc_options=[attachments|Opts]};
- {"att_encoding_info", "true"} ->
- Opts = Args#changes_args.doc_options,
- Args#changes_args{doc_options=[att_encoding_info|Opts]};
- {"conflicts", "true"} ->
- Args#changes_args{conflicts=true};
- {"filter", _} ->
- Args#changes_args{filter=Value};
- _Else -> % unknown key value pair, ignore.
- Args
- end
- end, #changes_args{}, couch_httpd:qs(Req)),
- %% if it's an EventSource request with a Last-event-ID header
- %% that should override the `since` query string, since it's
- %% probably the browser reconnecting.
- case ChangesArgs#changes_args.feed of
- "eventsource" ->
- case couch_httpd:header_value(Req, "last-event-id") of
- undefined ->
- ChangesArgs;
- Value ->
- ChangesArgs#changes_args{since=list_to_integer(Value)}
- end;
- _ ->
- ChangesArgs
- end.
-
-parse_view_param({json_req, {Props}}) ->
- {Query} = couch_util:get_value(<<"query">>, Props),
- parse_view_param1(couch_util:get_value(<<"view">>, Query, <<"">>));
-parse_view_param(Req) ->
- parse_view_param1(list_to_binary(couch_httpd:qs_value(Req, "view", ""))).
-
-parse_view_param1(ViewParam) ->
- case re:split(ViewParam, <<"/">>) of
- [DName, ViewName] ->
- {<< "_design/", DName/binary >>, ViewName};
- _ ->
- throw({bad_request, "Invalid `view` parameter."})
- end.
-
-parse_view_options([], Acc) ->
- Acc;
-parse_view_options([{K, V} | Rest], Acc) ->
- Acc1 = case couch_util:to_binary(K) of
- <<"reduce">> ->
- [{reduce, couch_mrview_http:parse_boolean(V)}];
- <<"key">> ->
- V1 = parse_json(V),
- [{start_key, V1}, {end_key, V1} | Acc];
- <<"keys">> ->
- [{keys, parse_json(V)} | Acc];
- <<"startkey">> ->
- [{start_key, parse_json(V)} | Acc];
- <<"start_key">> ->
- [{start_key, parse_json(V)} | Acc];
- <<"startkey_docid">> ->
- [{start_key_docid, couch_util:to_binary(V)} | Acc];
- <<"start_key_docid">> ->
- [{start_key_docid, couch_util:to_binary(V)} | Acc];
- <<"endkey">> ->
- [{end_key, parse_json(V)} | Acc];
- <<"end_key">> ->
- [{end_key, parse_json(V)} | Acc];
- <<"endkey_docid">> ->
- [{start_key_docid, couch_util:to_binary(V)} | Acc];
- <<"end_key_docid">> ->
- [{start_key_docid, couch_util:to_binary(V)} | Acc];
- <<"limit">> ->
- [{limit, couch_mrview_http:parse_pos_int(V)} | Acc];
- <<"count">> ->
- throw({query_parse_error, <<"QS param `count` is not `limit`">>});
- <<"stale">> when V =:= <<"ok">> orelse V =:= "ok" ->
- [{stale, ok} | Acc];
- <<"stale">> when V =:= <<"update_after">> orelse V =:= "update_after" ->
- [{stale, update_after} | Acc];
- <<"stale">> ->
- throw({query_parse_error, <<"Invalid value for `stale`.">>});
- <<"descending">> ->
- case couch_mrview_http:parse_boolean(V) of
- true ->
- [{direction, rev} | Acc];
- _ ->
- [{direction, fwd} | Acc]
- end;
- <<"skip">> ->
- [{skip, couch_mrview_http:parse_pos_int(V)} | Acc];
- <<"group">> ->
- case couch_mrview_http:parse_booolean(V) of
- true ->
- [{group_level, exact} | Acc];
- _ ->
- [{group_level, 0} | Acc]
- end;
- <<"group_level">> ->
- [{group_level, couch_mrview_http:parse_pos_int(V)} | Acc];
- <<"inclusive_end">> ->
- [{inclusive_end, couch_mrview_http:parse_boolean(V)}];
- _ ->
- Acc
- end,
- parse_view_options(Rest, Acc1).
-
-parse_json(V) when is_list(V) ->
- ?JSON_DECODE(V);
-parse_json(V) ->
- V.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/da2836b6/src/couch_httpd_db.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_db.erl b/src/couch_httpd_db.erl
index 442e872..34f04f2 100644
--- a/src/couch_httpd_db.erl
+++ b/src/couch_httpd_db.erl
@@ -14,9 +14,10 @@
-include_lib("couch/include/couch_db.hrl").
-export([handle_request/1, handle_compact_req/2, handle_design_req/2,
- db_req/2, couch_doc_open/4,handle_changes_req/2,
+ db_req/2, couch_doc_open/4, handle_db_changes_req/2,
update_doc_result_to_json/1, update_doc_result_to_json/2,
- handle_design_info_req/3, parse_copy_destination_header/1]).
+ handle_design_info_req/3, parse_copy_destination_header/1,
+ parse_changes_query/2, handle_changes_req/4]).
-import(couch_httpd,
[send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
@@ -54,15 +55,22 @@ handle_request(#httpd{path_parts=[DbName|RestParts],method=Method,
do_db_req(Req, Handler)
end.
-handle_changes_req(#httpd{method='POST'}=Req, Db) ->
+
+handle_db_changes_req(Req, Db) ->
+ ChangesArgs = parse_changes_query(Req, Db),
+ ChangesFun = couch_changes:handle_db_changes(ChangesArgs, Req, Db),
+ handle_changes_req(Req, Db, ChangesArgs, ChangesFun).
+
+
+handle_changes_req(#httpd{method='POST'}=Req, Db, ChangesArgs, ChangesFun) ->
couch_httpd:validate_ctype(Req, "application/json"),
- handle_changes_req1(Req, Db);
-handle_changes_req(#httpd{method='GET'}=Req, Db) ->
- handle_changes_req1(Req, Db);
-handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
- send_method_not_allowed(Req, "GET,HEAD,POST").
+ handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
+handle_changes_req(#httpd{method='GET'}=Req, Db, ChangesArgs, ChangesFun) ->
+ handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
+handle_changes_req(#httpd{}=Req, _Db, _ChangesArgs, _ChangesFun) ->
+ couch_httpd:send_method_not_allowed(Req, "GET,HEAD,POST").
-handle_changes_req1(Req, #db{name=DbName}=Db) ->
+handle_changes_req1(Req, #db{name=DbName}=Db, ChangesArgs, ChangesFun) ->
AuthDbName = ?l2b(config:get("couch_httpd_auth", "authentication_db")),
case AuthDbName of
DbName ->
@@ -72,47 +80,41 @@ handle_changes_req1(Req, #db{name=DbName}=Db) ->
% on other databases, _changes is free for all.
ok
end,
- handle_changes_req2(Req, Db).
-handle_changes_req2(Req, Db) ->
MakeCallback = fun(Resp) ->
fun({change, {ChangeProp}=Change, _}, "eventsource") ->
Seq = proplists:get_value(<<"seq">>, ChangeProp),
- send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
+ couch_httpd:send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
"\n", "id: ", ?JSON_ENCODE(Seq),
"\n\n"]);
({change, Change, _}, "continuous") ->
- send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
+ couch_httpd:send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
({change, Change, Prepend}, _) ->
- send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
+ couch_httpd:send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
(start, "eventsource") ->
ok;
(start, "continuous") ->
ok;
(start, _) ->
- send_chunk(Resp, "{\"results\":[\n");
+ couch_httpd:send_chunk(Resp, "{\"results\":[\n");
({stop, _EndSeq}, "eventsource") ->
- end_json_response(Resp);
+ couch_httpd:end_json_response(Resp);
({stop, EndSeq}, "continuous") ->
- send_chunk(
+ couch_httpd:send_chunk(
Resp,
[?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]
),
- end_json_response(Resp);
+ couch_httpd:end_json_response(Resp);
({stop, EndSeq}, _) ->
- send_chunk(
+ couch_httpd:send_chunk(
Resp,
io_lib:format("\n],\n\"last_seq\":~w}\n", [EndSeq])
),
- end_json_response(Resp);
- (timeout, "eventsource") ->
- send_chunk(Resp, "event: heartbeat\ndata: \n\n");
+ couch_httpd:end_json_response(Resp);
(timeout, _) ->
- send_chunk(Resp, "\n")
+ couch_httpd:send_chunk(Resp, "\n")
end
end,
- ChangesArgs = parse_changes_query(Req, Db),
- ChangesFun = couch_changes:handle_db_changes(ChangesArgs, Req, Db),
WrapperFun = case ChangesArgs#changes_args.feed of
"normal" ->
{ok, Info} = couch_db:get_db_info(Db),
@@ -154,6 +156,8 @@ handle_changes_req2(Req, Db) ->
[couchdb, httpd, clients_requesting_changes])
end.
+
+
handle_compact_req(#httpd{method='POST'}=Req, Db) ->
case Req#httpd.path_parts of
[_DbName, <<"_compact">>] ->
[09/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Open view in changes start function rather than outside
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/8d5c9009
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/8d5c9009
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/8d5c9009
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 8d5c90094214ad186641fe25f1c70d621adda378
Parents: 434b541
Author: Benjamin Bastian <be...@gmail.com>
Authored: Tue Aug 26 15:34:21 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700
----------------------------------------------------------------------
src/couch_changes.erl | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/8d5c9009/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index 50f9d50..8ff109b 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -73,7 +73,7 @@ handle_changes(Args1, Req, Db0, Type) ->
_ ->
false
end,
- {StartListenerFun, DDocName, ViewName, View} = if UseViewChanges ->
+ {StartListenerFun, DDocName, ViewName} = if UseViewChanges ->
{DDocName0, ViewName0} = case {Type, Filter} of
{{view, DDocName1, ViewName1}, _} ->
{DDocName1, ViewName1};
@@ -93,14 +93,14 @@ handle_changes(Args1, Req, Db0, Type) ->
?MODULE, handle_view_event, {self(), DDocName0}, [{dbname, Db0#db.name}]
)
end,
- {SNFun, DDocName0, ViewName0, View0};
- true ->
+ {SNFun, DDocName0, ViewName0};
+ true ->
SNFun = fun() ->
couch_event:link_listener(
?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
)
end,
- {SNFun, undefined, undefined, undefined}
+ {SNFun, undefined, undefined}
end,
Start = fun() ->
{ok, Db} = couch_db:reopen(Db0),
@@ -110,7 +110,14 @@ handle_changes(Args1, Req, Db0, Type) ->
fwd ->
Since
end,
- {Db, StartSeq}
+ View2 = if UseViewChanges ->
+ {ok, {_, View1, _}, _, _} = couch_mrview_util:get_view(
+ Db0#db.name, DDocName, ViewName, #mrargs{}),
+ View1;
+ true ->
+ undefined
+ end,
+ {Db, View2, StartSeq}
end,
% begin timer to deal with heartbeat when filter function fails
case Args#changes_args.heartbeat of
@@ -126,7 +133,7 @@ handle_changes(Args1, Req, Db0, Type) ->
{Callback, UserAcc} = get_callback_acc(CallbackAcc),
{ok, Listener} = StartListenerFun(),
- {Db, StartSeq} = Start(),
+ {Db, View, StartSeq} = Start(),
UserAcc2 = start_sending_changes(Callback, UserAcc, Feed),
{Timeout, TimeoutFun} = get_changes_timeout(Args, Callback),
Acc0 = build_acc(Args, Callback, UserAcc2, Db, StartSeq,
@@ -147,7 +154,7 @@ handle_changes(Args1, Req, Db0, Type) ->
{Callback, UserAcc} = get_callback_acc(CallbackAcc),
UserAcc2 = start_sending_changes(Callback, UserAcc, Feed),
{Timeout, TimeoutFun} = get_changes_timeout(Args, Callback),
- {Db, StartSeq} = Start(),
+ {Db, View, StartSeq} = Start(),
Acc0 = build_acc(Args#changes_args{feed="normal"}, Callback,
UserAcc2, Db, StartSeq, <<>>, Timeout, TimeoutFun,
DDocName, ViewName, View),
[14/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
couch_httpd_changes: check removed keys from the view filter
Make sure to only emit deleted document when a deleted key is passed to
the view filter.
Conflicts:
src/couch_httpd_changes.erl
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/071dedf1
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/071dedf1
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/071dedf1
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 071dedf1d35b6cd5f5bb0e553de7c81acc43954a
Parents: 7f2af21
Author: benoitc <be...@apache.org>
Authored: Sat Feb 8 21:27:58 2014 +0100
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700
----------------------------------------------------------------------
src/couch_httpd_changes.erl | 40 +++++++++++++++++++++++++++-------------
1 file changed, 27 insertions(+), 13 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/071dedf1/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
index 56ce559..b494b0d 100644
--- a/src/couch_httpd_changes.erl
+++ b/src/couch_httpd_changes.erl
@@ -201,9 +201,15 @@ view_changes_cb(stop, {LastSeq, {_, _, _, Callback, Args}}) ->
view_changes_cb(heartbeat, {_, _, _, Callback, Args}=Acc) ->
Callback(timeout, Args#changes_args.feed),
{ok, Acc};
-view_changes_cb({{Seq, _Key, DocId}, _VAl},
+view_changes_cb({{Seq, _Key, DocId}, Val},
{Prepend, OldLimit, Db0, Callback, Args}=Acc) ->
+ %% is the key removed from the index?
+ Removed = case Val of
+ {[{<<"_removed">>, true}]} -> true;
+ _ -> false
+ end,
+
#changes_args{
feed = ResponseType,
limit = Limit} = Args,
@@ -220,16 +226,24 @@ view_changes_cb({{Seq, _Key, DocId}, _VAl},
case couch_db:get_doc_info(Db, DocId) of
{ok, DocInfo} ->
%% get change row
- ChangeRow = view_change_row(Db, DocInfo, Args),
- %% emit change row
- Callback({change, ChangeRow, Prepend}, ResponseType),
-
- %% if we achieved the limit, stop here, else continue.
- NewLimit = OldLimit + 1,
- if Limit > NewLimit ->
- {ok, {<<",\n">>, Db, NewLimit, Callback, Args}};
- true ->
- {stop, {<<"">>, Db, NewLimit, Callback, Args}}
+ {Deleted, ChangeRow} = view_change_row(Db, DocInfo, Args),
+
+ case Removed of
+ true when Deleted /= true ->
+ %% the key has been removed from the view but the
+ %% document hasn't been deleted so ignore it.
+ {ok, Acc};
+ _ ->
+ %% emit change row
+ Callback({change, ChangeRow, Prepend}, ResponseType),
+
+ %% if we achieved the limit, stop here, else continue.
+ NewLimit = OldLimit + 1,
+ if Limit > NewLimit ->
+ {ok, {<<",\n">>, NewLimit, Db, Callback, Args}};
+ true ->
+ {stop, {<<"">>, NewLimit, Db, Callback, Args}}
+ end
end;
{error, not_found} ->
%% doc not found, continue
@@ -256,7 +270,7 @@ view_change_row(Db, DocInfo, Args) ->
|| #rev_info{rev=R} <- Revs]
end,
- {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Changes}] ++
+ {Del, {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Changes}] ++
deleted_item(Del) ++ case InDoc of
true ->
Opts = case Conflicts of
@@ -272,7 +286,7 @@ view_change_row(Db, DocInfo, Args) ->
end;
false ->
[]
- end}.
+ end}}.
parse_changes_query(Req, Db) ->
ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->
[22/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Refactor couch_httpd_auth's AuthModule construct
We were missing the change necessary to upgrade user documents with the
move to pbkdf2 in a cluster. This also adds the ability for an
AuthModule to return a context that will be used when updating user
credentials.
COUCHDB-2491
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/3e8286d4
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/3e8286d4
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/3e8286d4
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 3e8286d40664fa0d7f32a3d375f64e64d181022c
Parents: 2a45cb0
Author: Paul J. Davis <pa...@gmail.com>
Authored: Thu Dec 4 13:07:56 2014 -0600
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Thu Dec 4 14:04:57 2014 -0600
----------------------------------------------------------------------
src/couch_auth_cache.erl | 26 ++++++++++++++++++++------
src/couch_httpd_auth.erl | 37 +++++++++++++++++++------------------
src/couch_httpd_oauth.erl | 2 +-
3 files changed, 40 insertions(+), 25 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e8286d4/src/couch_auth_cache.erl
----------------------------------------------------------------------
diff --git a/src/couch_auth_cache.erl b/src/couch_auth_cache.erl
index 8cf631b..9eaa7c6 100644
--- a/src/couch_auth_cache.erl
+++ b/src/couch_auth_cache.erl
@@ -16,7 +16,8 @@
-behaviour(config_listener).
% public API
--export([get_user_creds/1, get_admin/1, add_roles/2]).
+-export([get_user_creds/1, get_user_creds/2, update_user_creds/3]).
+-export([get_admin/1, add_roles/2]).
% gen_server API
-export([start_link/0, init/1, handle_call/3, handle_info/2, handle_cast/2]).
@@ -42,12 +43,18 @@
-spec get_user_creds(UserName::string() | binary()) ->
- Credentials::list() | nil.
-
-get_user_creds(UserName) when is_list(UserName) ->
- get_user_creds(?l2b(UserName));
+ {ok, Credentials::list(), term()} | nil.
get_user_creds(UserName) ->
+ get_user_creds(nil, UserName).
+
+-spec get_user_creds(Req::#httpd{}, UserName::string() | binary()) ->
+ {ok, Credentials::list(), term()} | nil.
+
+get_user_creds(Req, UserName) when is_list(UserName) ->
+ get_user_creds(Req, ?l2b(UserName));
+
+get_user_creds(_Req, UserName) ->
UserCreds = case get_admin(UserName) of
nil ->
get_from_cache(UserName);
@@ -61,6 +68,13 @@ get_user_creds(UserName) ->
end,
validate_user_creds(UserCreds).
+update_user_creds(_Req, UserDoc, _AuthCtx) ->
+ DbNameList = config:get("couch_httpd_auth", "authentication_db", "_users"),
+ couch_util:with_db(?l2b(DbNameList), fun(UserDb) ->
+ {ok, _NewRev} = couch_db:update_doc(UserDb, UserDoc, []),
+ ok
+ end).
+
add_roles(Props, ExtraRoles) ->
CurrentRoles = couch_util:get_value(<<"roles">>, Props),
lists:keyreplace(<<"roles">>, 1, Props, {<<"roles">>, CurrentRoles ++ ExtraRoles}).
@@ -123,7 +137,7 @@ validate_user_creds(UserCreds) ->
" is used for authentication purposes.">>
})
end,
- UserCreds.
+ {ok, UserCreds, nil}.
start_link() ->
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e8286d4/src/couch_httpd_auth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
index 752dd20..e1af621 100644
--- a/src/couch_httpd_auth.erl
+++ b/src/couch_httpd_auth.erl
@@ -73,17 +73,18 @@ default_authentication_handler(Req) ->
default_authentication_handler(Req, AuthModule) ->
case basic_name_pw(Req) of
{User, Pass} ->
- case AuthModule:get_user_creds(User) of
+ case AuthModule:get_user_creds(Req, User) of
nil ->
throw({unauthorized, <<"Name or password is incorrect.">>});
- UserProps ->
+ {ok, UserProps, AuthCtx} ->
reject_if_totp(UserProps),
UserName = ?l2b(User),
Password = ?l2b(Pass),
case authenticate(Password, UserProps) of
true ->
- UserProps2 = maybe_upgrade_password_hash(UserName, Password, UserProps,
- AuthModule),
+ UserProps2 = maybe_upgrade_password_hash(
+ Req, UserName, Password, UserProps,
+ AuthModule, AuthCtx),
Req#httpd{user_ctx=#user_ctx{
name=UserName,
roles=couch_util:get_value(<<"roles">>, UserProps2, [])
@@ -195,9 +196,9 @@ cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req, AuthModule) ->
Req;
SecretStr ->
Secret = ?l2b(SecretStr),
- case AuthModule:get_user_creds(User) of
+ case AuthModule:get_user_creds(Req, User) of
nil -> Req;
- UserProps ->
+ {ok, UserProps, _AuthCtx} ->
UserSalt = couch_util:get_value(<<"salt">>, UserProps, <<"">>),
FullSecret = <<Secret/binary, UserSalt/binary>>,
ExpectedHash = crypto:sha_mac(FullSecret, User ++ ":" ++ TimeStr),
@@ -284,14 +285,15 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req, AuthModule) ->
UserName = ?l2b(couch_util:get_value("name", Form, "")),
Password = ?l2b(couch_util:get_value("password", Form, "")),
couch_log:debug("Attempt Login: ~s",[UserName]),
- UserProps = case AuthModule:get_user_creds(UserName) of
- nil -> [];
+ {ok, UserProps, AuthCtx} = case AuthModule:get_user_creds(Req, UserName) of
+ nil -> {ok, [], nil};
Result -> Result
end,
case authenticate(Password, UserProps) of
true ->
verify_totp(UserProps, Form),
- UserProps2 = maybe_upgrade_password_hash(UserName, Password, UserProps, AuthModule),
+ UserProps2 = maybe_upgrade_password_hash(
+ Req, UserName, Password, UserProps, AuthModule, AuthCtx),
% setup the session cookie
Secret = ?l2b(ensure_cookie_auth_secret()),
UserSalt = couch_util:get_value(<<"salt">>, UserProps2),
@@ -363,18 +365,17 @@ maybe_value(_Key, undefined, _Fun) -> [];
maybe_value(Key, Else, Fun) ->
[{Key, Fun(Else)}].
-maybe_upgrade_password_hash(UserName, Password, UserProps, AuthModule) ->
+maybe_upgrade_password_hash(Req, UserName, Password, UserProps,
+ AuthModule, AuthCtx) ->
IsAdmin = lists:member(<<"_admin">>, couch_util:get_value(<<"roles">>, UserProps, [])),
case {IsAdmin, couch_util:get_value(<<"password_scheme">>, UserProps, <<"simple">>)} of
{false, <<"simple">>} ->
- DbName = ?l2b(config:get("couch_httpd_auth", "authentication_db", "_users")),
- couch_util:with_db(DbName, fun(UserDb) ->
- UserProps2 = proplists:delete(<<"password_sha">>, UserProps),
- UserProps3 = [{<<"password">>, Password} | UserProps2],
- NewUserDoc = couch_doc:from_json_obj({UserProps3}),
- {ok, _NewRev} = couch_db:update_doc(UserDb, NewUserDoc, []),
- AuthModule:get_user_creds(UserName)
- end);
+ UserProps2 = proplists:delete(<<"password_sha">>, UserProps),
+ UserProps3 = [{<<"password">>, Password} | UserProps2],
+ NewUserDoc = couch_doc:from_json_obj({UserProps3}),
+ ok = AuthModule:update_user_creds(Req, NewUserDoc, AuthCtx),
+ {ok, NewUserProps, _} = AuthModule:get_user_creds(Req, UserName),
+ NewUserProps;
_ ->
UserProps
end.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e8286d4/src/couch_httpd_oauth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_oauth.erl b/src/couch_httpd_oauth.erl
index 0215240..b72e72d 100644
--- a/src/couch_httpd_oauth.erl
+++ b/src/couch_httpd_oauth.erl
@@ -78,7 +78,7 @@ set_user_ctx(Req, Name) ->
couch_log:debug("OAuth handler: user `~p` credentials not found",
[Name]),
Req;
- User ->
+ {ok, User, _AuthCtx} ->
Roles = couch_util:get_value(<<"roles">>, User, []),
Req#httpd{user_ctx=#user_ctx{name=Name, roles=Roles}}
end.
[03/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Implement two factor authentication
If enabled, require a second factor to acquire a session cookie and
reject basic authentication attempts (as second factor cannot be
presented). Allow previous and next token for clock skew.
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/011975a8
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/011975a8
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/011975a8
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 011975a8cdb8611a7b05c3cd4a2f47872469140a
Parents: 9ee298f
Author: Robert Newson <rn...@apache.org>
Authored: Wed Oct 29 17:39:21 2014 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Oct 31 11:32:17 2014 +0000
----------------------------------------------------------------------
src/couch_hotp.erl | 40 ++++++++++++++++++++++++++++++
src/couch_httpd_auth.erl | 54 +++++++++++++++++++++++++++++++++++++++++
src/couch_totp.erl | 23 ++++++++++++++++++
test/couch_hotp_tests.erl | 28 +++++++++++++++++++++
test/couch_totp_tests.erl | 55 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 200 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/011975a8/src/couch_hotp.erl
----------------------------------------------------------------------
diff --git a/src/couch_hotp.erl b/src/couch_hotp.erl
new file mode 100644
index 0000000..896c52a
--- /dev/null
+++ b/src/couch_hotp.erl
@@ -0,0 +1,40 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_hotp).
+
+-export([generate/4]).
+
+generate(Alg, Key, Counter, OutputLen)
+ when is_atom(Alg), is_binary(Key), is_integer(Counter), is_integer(OutputLen) ->
+ Hmac = hmac(Alg, Key, <<Counter:64>>),
+ Offset = binary:last(Hmac) band 16#f,
+ Code =
+ ((binary:at(Hmac, Offset) band 16#7f) bsl 24) +
+ ((binary:at(Hmac, Offset + 1) band 16#ff) bsl 16) +
+ ((binary:at(Hmac, Offset + 2) band 16#ff) bsl 8) +
+ ((binary:at(Hmac, Offset + 3) band 16#ff)),
+ case OutputLen of
+ 6 -> Code rem 1000000;
+ 7 -> Code rem 10000000;
+ 8 -> Code rem 100000000
+ end.
+
+hmac(Alg, Key, Data) ->
+ case {Alg, erlang:function_exported(crypto, hmac, 3)} of
+ {_, true} ->
+ crypto:hmac(Alg, Key, Data);
+ {sha, false} ->
+ crypto:sha_mac(Key, Data);
+ {Alg, false} ->
+ throw({unsupported, Alg})
+ end.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/011975a8/src/couch_httpd_auth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
index 7c55a2b..cda51c5 100644
--- a/src/couch_httpd_auth.erl
+++ b/src/couch_httpd_auth.erl
@@ -23,6 +23,8 @@
-import(couch_httpd, [header_value/2, send_json/2,send_json/4, send_method_not_allowed/2]).
+-compile({no_auto_import,[integer_to_binary/1]}).
+
special_test_authentication_handler(Req) ->
case header_value(Req, "WWW-Authenticate") of
"X-Couch-Test-Auth " ++ NamePass ->
@@ -75,6 +77,7 @@ default_authentication_handler(Req, AuthModule) ->
nil ->
throw({unauthorized, <<"Name or password is incorrect.">>});
UserProps ->
+ reject_if_totp(UserProps),
UserName = ?l2b(User),
Password = ?l2b(Pass),
case authenticate(Password, UserProps) of
@@ -287,6 +290,7 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req, AuthModule) ->
end,
case authenticate(Password, UserProps) of
true ->
+ verify_totp(UserProps, Form),
UserProps2 = maybe_upgrade_password_hash(UserName, Password, UserProps, AuthModule),
% setup the session cookie
Secret = ?l2b(ensure_cookie_auth_secret()),
@@ -430,3 +434,53 @@ max_age() ->
config:get("couch_httpd_auth", "timeout", "600")),
[{max_age, Timeout}]
end.
+
+reject_if_totp(User) ->
+ case get_totp_config(User) of
+ undefined ->
+ ok;
+ _ ->
+ throw({unauthorized, <<"Name or password is incorrect.">>})
+ end.
+
+verify_totp(User, Form) ->
+ case get_totp_config(User) of
+ undefined ->
+ ok;
+ {Props} ->
+ Key = couch_util:get_value(<<"key">>, Props),
+ Alg = couch_util:to_existing_atom(
+ couch_util:get_value(<<"algorithm">>, Props, <<"sha">>)),
+ Len = couch_util:get_value(<<"length">>, Props, 6),
+ Token = ?l2b(couch_util:get_value("token", Form, "")),
+ verify_token(Alg, Key, Len, Token)
+ end.
+
+get_totp_config(User) ->
+ couch_util:get_value(<<"totp">>, User).
+
+verify_token(Alg, Key, Len, Token) ->
+ Now = make_cookie_time(),
+ Tokens = [generate_token(Alg, Key, Len, Now - 30),
+ generate_token(Alg, Key, Len, Now),
+ generate_token(Alg, Key, Len, Now + 30)],
+ %% evaluate all tokens in constant time
+ Match = lists:foldl(fun(T, Acc) -> couch_util:verify(T, Token) or Acc end,
+ false, Tokens),
+ case Match of
+ true ->
+ ok;
+ _ ->
+ throw({unauthorized, <<"Name or password is incorrect.">>})
+ end.
+
+generate_token(Alg, Key, Len, Timestamp) ->
+ integer_to_binary(couch_totp:generate(Alg, Key, Timestamp, 30, Len)).
+
+integer_to_binary(Int) when is_integer(Int) ->
+ case erlang:function_exported(erlang, integer_to_binary, 1) of
+ true ->
+ erlang:integer_to_binary(Int);
+ false ->
+ ?l2b(integer_to_list(Int))
+ end.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/011975a8/src/couch_totp.erl
----------------------------------------------------------------------
diff --git a/src/couch_totp.erl b/src/couch_totp.erl
new file mode 100644
index 0000000..56e70d8
--- /dev/null
+++ b/src/couch_totp.erl
@@ -0,0 +1,23 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_totp).
+
+-export([generate/5]).
+
+generate(Alg, Key, CounterSecs, StepSecs, OutputLen)
+ when is_atom(Alg),
+ is_binary(Key),
+ is_integer(CounterSecs),
+ is_integer(StepSecs),
+ is_integer(OutputLen) ->
+ couch_hotp:generate(Alg, Key, CounterSecs div StepSecs, OutputLen).
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/011975a8/test/couch_hotp_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_hotp_tests.erl b/test/couch_hotp_tests.erl
new file mode 100644
index 0000000..fee10ff
--- /dev/null
+++ b/test/couch_hotp_tests.erl
@@ -0,0 +1,28 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_hotp_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+hotp_test() ->
+ Key = <<"12345678901234567890">>,
+ ?assertEqual(755224, couch_hotp:generate(sha, Key, 0, 6)),
+ ?assertEqual(287082, couch_hotp:generate(sha, Key, 1, 6)),
+ ?assertEqual(359152, couch_hotp:generate(sha, Key, 2, 6)),
+ ?assertEqual(969429, couch_hotp:generate(sha, Key, 3, 6)),
+ ?assertEqual(338314, couch_hotp:generate(sha, Key, 4, 6)),
+ ?assertEqual(254676, couch_hotp:generate(sha, Key, 5, 6)),
+ ?assertEqual(287922, couch_hotp:generate(sha, Key, 6, 6)),
+ ?assertEqual(162583, couch_hotp:generate(sha, Key, 7, 6)),
+ ?assertEqual(399871, couch_hotp:generate(sha, Key, 8, 6)),
+ ?assertEqual(520489, couch_hotp:generate(sha, Key, 9, 6)).
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/011975a8/test/couch_totp_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_totp_tests.erl b/test/couch_totp_tests.erl
new file mode 100644
index 0000000..6817a09
--- /dev/null
+++ b/test/couch_totp_tests.erl
@@ -0,0 +1,55 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_totp_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+totp_sha_test() ->
+ Key = <<"12345678901234567890">>,
+ ?assertEqual(94287082, couch_totp:generate(sha, Key, 59, 30, 8)),
+ ?assertEqual(07081804, couch_totp:generate(sha, Key, 1111111109, 30, 8)),
+ ?assertEqual(14050471, couch_totp:generate(sha, Key, 1111111111, 30, 8)),
+ ?assertEqual(89005924, couch_totp:generate(sha, Key, 1234567890, 30, 8)),
+ ?assertEqual(69279037, couch_totp:generate(sha, Key, 2000000000, 30, 8)),
+ ?assertEqual(65353130, couch_totp:generate(sha, Key, 20000000000, 30, 8)).
+
+totp_sha256_test() ->
+ Key = <<"12345678901234567890123456789012">>,
+ case sha_256_512_supported() of
+ true ->
+ ?assertEqual(46119246, couch_totp:generate(sha256, Key, 59, 30, 8)),
+ ?assertEqual(68084774, couch_totp:generate(sha256, Key, 1111111109, 30, 8)),
+ ?assertEqual(67062674, couch_totp:generate(sha256, Key, 1111111111, 30, 8)),
+ ?assertEqual(91819424, couch_totp:generate(sha256, Key, 1234567890, 30, 8)),
+ ?assertEqual(90698825, couch_totp:generate(sha256, Key, 2000000000, 30, 8)),
+ ?assertEqual(77737706, couch_totp:generate(sha256, Key, 20000000000, 30, 8));
+ false ->
+ ?debugMsg("sha256 not supported, tests skipped")
+ end.
+
+totp_sha512_test() ->
+ Key = <<"1234567890123456789012345678901234567890123456789012345678901234">>,
+ case sha_256_512_supported() of
+ true ->
+ ?assertEqual(90693936, couch_totp:generate(sha512, Key, 59, 30, 8)),
+ ?assertEqual(25091201, couch_totp:generate(sha512, Key, 1111111109, 30, 8)),
+ ?assertEqual(99943326, couch_totp:generate(sha512, Key, 1111111111, 30, 8)),
+ ?assertEqual(93441116, couch_totp:generate(sha512, Key, 1234567890, 30, 8)),
+ ?assertEqual(38618901, couch_totp:generate(sha512, Key, 2000000000, 30, 8)),
+ ?assertEqual(47863826, couch_totp:generate(sha512, Key, 20000000000, 30, 8));
+ false ->
+ ?debugMsg("sha512 not supported, tests skipped")
+ end.
+
+sha_256_512_supported() ->
+ erlang:function_exported(crypto, hmac, 3).
[17/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Count the number of rejections by validate_doc_update functions
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/3e32256c
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/3e32256c
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/3e32256c
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 3e32256cf6922679a9ab3567fb84a83c25641073
Parents: 0c0b438
Author: Alexander Shorin <kx...@apache.org>
Authored: Fri Oct 31 22:13:19 2014 +0300
Committer: Alexander Shorin <kx...@apache.org>
Committed: Mon Nov 3 12:19:54 2014 +0300
----------------------------------------------------------------------
priv/stats_descriptions.cfg | 4 ++++
src/couch_query_servers.erl | 11 ++++++++++-
2 files changed, 14 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e32256c/priv/stats_descriptions.cfg
----------------------------------------------------------------------
diff --git a/priv/stats_descriptions.cfg b/priv/stats_descriptions.cfg
index 53aca37..3f771d2 100644
--- a/priv/stats_descriptions.cfg
+++ b/priv/stats_descriptions.cfg
@@ -182,3 +182,7 @@
{type, counter},
{desc, <<"number of couch_server LRU operations skipped">>}
]}.
+{[couchdb, query_server, vdu_rejects], [
+ {type, counter},
+ {desc, <<"number of rejections by validate_doc_update function">>}
+]}.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e32256c/src/couch_query_servers.erl
----------------------------------------------------------------------
diff --git a/src/couch_query_servers.erl b/src/couch_query_servers.erl
index d38f040..c99f673 100644
--- a/src/couch_query_servers.erl
+++ b/src/couch_query_servers.erl
@@ -260,7 +260,16 @@ get_number(Key, Props) ->
validate_doc_update(DDoc, EditDoc, DiskDoc, Ctx, SecObj) ->
JsonEditDoc = couch_doc:to_json_obj(EditDoc, [revs]),
JsonDiskDoc = json_doc(DiskDoc),
- case ddoc_prompt(DDoc, [<<"validate_doc_update">>], [JsonEditDoc, JsonDiskDoc, Ctx, SecObj]) of
+ Resp = ddoc_prompt(DDoc,
+ [<<"validate_doc_update">>],
+ [JsonEditDoc, JsonDiskDoc, Ctx, SecObj]),
+ case Resp of
+ _ when Resp /= 1 ->
+ couch_stats:increment_counter(
+ [couchdb, query_server, vdu_rejects], 1);
+ _ -> ok
+ end,
+ case Resp of
1 ->
ok;
{[{<<"forbidden">>, Message}]} ->
[08/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Add view filtering optimization to changes feeds
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/20e585dd
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/20e585dd
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/20e585dd
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 20e585dd07e4da0fe67ace09f0a291e3fe759534
Parents: 6d6b801
Author: Benjamin Bastian <be...@gmail.com>
Authored: Fri Aug 22 23:56:56 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700
----------------------------------------------------------------------
src/couch_changes.erl | 181 ++++++++++++++++++++++++---------------
src/couch_httpd_changes.erl | 180 +-------------------------------------
2 files changed, 116 insertions(+), 245 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/20e585dd/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index abe8cf9..259f83c 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -63,25 +63,45 @@ handle_changes(Args1, Req, Db0, Type) ->
dir = Dir,
since = Since
} = Args1,
- {StartListenerFun, DDocName, ViewName, View} = case Type of
- {view, DDocName0, ViewName0} ->
- SNFun = fun() ->
- couch_event:link_listener(
- ?MODULE, handle_view_event, self(), [{dbname, Db0#db.name}]
- )
- end,
- {ok, {_, View0, _}, _, _} = couch_mrview_util:get_view(Db0#db.name, DDocName0, ViewName0, #mrargs{}),
- {SNFun, DDocName0, ViewName0, View0};
- db ->
- SNFun = fun() ->
- couch_event:link_listener(
- ?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
- )
- end,
- {SNFun, undefined, undefined, undefined}
- end,
Filter = configure_filter(FilterName, Style, Req, Db0),
Args = Args1#changes_args{filter_fun = Filter},
+ UseViewChanges = case {Type, Filter} of
+ {{view, _, _}, _} ->
+ true;
+ {_, {fast_view, _, _, _}} ->
+ true;
+ _ ->
+ false
+ end,
+ {StartListenerFun, DDocName, ViewName, View} = if UseViewChanges ->
+ {DDocName0, ViewName0} = case {Type, Filter} of
+ {{view, DDocName1, ViewName1}, _} ->
+ {DDocName1, ViewName1};
+ {_, {fast_view, _, DDoc, ViewName1}} ->
+ {DDoc#doc.id, ViewName1}
+ end,
+ {ok, {_, View0, _}, _, _} = couch_mrview_util:get_view(
+ Db0#db.name, DDocName0, ViewName0, #mrargs{}),
+ case View0#mrview.seq_btree of
+ #btree{} ->
+ ok;
+ _ ->
+ throw({bad_request, "view changes not enabled"})
+ end,
+ SNFun = fun() ->
+ couch_event:link_listener(
+ ?MODULE, handle_view_event, {self(), DDocName0}, [{dbname, Db0#db.name}]
+ )
+ end,
+ {SNFun, DDocName0, ViewName0, View0};
+ true ->
+ SNFun = fun() ->
+ couch_event:link_listener(
+ ?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
+ )
+ end,
+ {SNFun, undefined, undefined, undefined}
+ end,
Start = fun() ->
{ok, Db} = couch_db:reopen(Db0),
StartSeq = case Dir of
@@ -180,7 +200,15 @@ configure_filter("_view", Style, Req, Db) ->
[DName, VName] ->
{ok, DDoc} = open_ddoc(Db, <<"_design/", DName/binary>>),
check_member_exists(DDoc, [<<"views">>, VName]),
- {view, Style, DDoc, VName};
+ try
+ true = couch_util:get_nested_json_value(
+ DDoc#doc.body,
+ [<<"options">>, <<"seq_indexed">>]
+ ),
+ {fast_view, Style, DDoc, VName}
+ catch _:_ ->
+ {view, Style, DDoc, VName}
+ end;
[] ->
Msg = "`view` must be of the form `designname/viewname`",
throw({bad_request, Msg})
@@ -237,6 +265,36 @@ filter(Db, DocInfo, {custom, Style, Req0, DDoc, FName}) ->
{ok, Passes} = couch_query_servers:filter_docs(Req, Db, DDoc, FName, Docs),
filter_revs(Passes, Docs).
+fast_view_filter(Db, {{Seq, _}, {ID, _}}, {fast_view, Style, _, _}) ->
+ case couch_db:get_doc_info(Db, ID) of
+ {ok, #doc_info{high_seq=Seq}=DocInfo} ->
+ Docs = open_revs(Db, DocInfo, Style),
+ Changes = lists:map(fun(#doc{revs={RevPos, [RevId | _]}}) ->
+ RevStr = couch_doc:rev_to_str({RevPos, RevId}),
+ {[{<<"rev">>, RevStr}]}
+ end, Docs),
+ {DocInfo, Changes};
+ {ok, #doc_info{high_seq=HighSeq}} when Seq > HighSeq ->
+ % If the view seq tree is out of date (or if the view seq tree
+ % was opened before the db) seqs may come by from the seq tree
+ % which correspond to the not-most-current revision of a document.
+ % The proper thing to do is to not send this old revision, but wait
+ % until we reopen the up-to-date view seq tree and continue the
+ % fold.
+ % I left the Seq > HighSeq guard in so if (for some godforsaken
+ % reason) the seq in the view is more current than the database,
+ % we'll throw an error.
+ {ok, []};
+ {error, not_found} ->
+ {ok, []}
+ end.
+
+
+
+view_filter(_Db, _KV, {default, _Style}) ->
+ [ok]. % TODO: make a real thing
+
+
get_view_qs({json_req, {Props}}) ->
{Query} = couch_util:get_value(<<"query">>, Props, {[]}),
binary_to_list(couch_util:get_value(<<"view">>, Query, ""));
@@ -477,7 +535,7 @@ keep_sending_changes(Args, Acc0, FirstRound) ->
db = Db, callback = Callback,
timeout = Timeout, timeout_fun = TimeoutFun, seq = EndSeq,
prepend = Prepend2, user_acc = UserAcc2, limit = NewLimit,
- ddoc_name = DDocName, view_name = ViewName, view = View
+ ddoc_name = DDocName, view_name = ViewName
} = ChangesAcc,
couch_db:close(Db),
@@ -517,54 +575,27 @@ maybe_refresh_view(Db, DDocName, ViewName) ->
end_sending_changes(Callback, UserAcc, EndSeq, ResponseType) ->
Callback({stop, EndSeq}, ResponseType, UserAcc).
-changes_enumerator(Value, #changes_acc{resp_type = ResponseType} = Acc)
- when ResponseType =:= "continuous"
- orelse ResponseType =:= "eventsource" ->
- #changes_acc{
- filter = Filter, callback = Callback,
- user_acc = UserAcc, limit = Limit, db = Db,
- timeout = Timeout, timeout_fun = TimeoutFun,
- view = View
- } = Acc,
- {Seq, Results0} = case View of
- undefined ->
- {Value#doc_info.high_seq, filter(Db, Value, Filter)};
- #mrview{} ->
- {{Seq0, _}, _} = Value,
- {Seq0, [ok]} % TODO
- end,
- Results = [Result || Result <- Results0, Result /= null],
- %% TODO: I'm thinking this should be < 1 and not =< 1
- Go = if Limit =< 1 -> stop; true -> ok end,
- case Results of
- [] ->
- {Done, UserAcc2} = maybe_heartbeat(Timeout, TimeoutFun, UserAcc),
- case Done of
- stop ->
- {stop, Acc#changes_acc{seq = Seq, user_acc = UserAcc2}};
- ok ->
- {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2}}
- end;
- _ ->
- ChangesRow = changes_row(Results, Value, Acc),
- UserAcc2 = Callback({change, ChangesRow, <<>>}, ResponseType, UserAcc),
- reset_heartbeat(),
- {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2, limit = Limit - 1}}
- end;
-changes_enumerator(Value, Acc) ->
+changes_enumerator(Value0, Acc) ->
#changes_acc{
filter = Filter, callback = Callback, prepend = Prepend,
user_acc = UserAcc, limit = Limit, resp_type = ResponseType, db = Db,
timeout = Timeout, timeout_fun = TimeoutFun, view = View
} = Acc,
- {Seq, Results0} = case View of
- undefined ->
- {Value#doc_info.high_seq, filter(Db, Value, Filter)};
- #mrview{} ->
- {{Seq0,_}, _} = Value,
- {Seq0, [ok]} % TODO view filter
+ {Value, Results0} = case {View, Filter} of
+ {_, {fast_view, _, _, _}} ->
+ fast_view_filter(Db, Value0, Filter);
+ {#mrview{}, _} ->
+ {Value0, view_filter(Db, Value0, Filter)};
+ {_, _} ->
+ {Value0, filter(Db, Value0, Filter)}
end,
Results = [Result || Result <- Results0, Result /= null],
+ Seq = case Value of
+ #doc_info{} ->
+ Value#doc_info.high_seq;
+ {{Seq0, _}, _} ->
+ Seq0
+ end,
Go = if (Limit =< 1) andalso Results =/= [] -> stop; true -> ok end,
case Results of
[] ->
@@ -576,20 +607,32 @@ changes_enumerator(Value, Acc) ->
{Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2}}
end;
_ ->
- ChangesRow = changes_row(Results, Value, Acc),
- UserAcc2 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc),
- reset_heartbeat(),
- {Go, Acc#changes_acc{
- seq = Seq, prepend = <<",\n">>,
- user_acc = UserAcc2, limit = Limit - 1}}
+ if ResponseType =:= "continuous" orelse ResponseType =:= "eventsource" ->
+ ChangesRow = changes_row(Results, Value, Acc),
+ UserAcc2 = Callback({change, ChangesRow, <<>>}, ResponseType, UserAcc),
+ reset_heartbeat(),
+ {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2, limit = Limit - 1}};
+ true ->
+ ChangesRow = changes_row(Results, Value, Acc),
+ UserAcc2 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc),
+ reset_heartbeat(),
+ {Go, Acc#changes_acc{
+ seq = Seq, prepend = <<",\n">>,
+ user_acc = UserAcc2, limit = Limit - 1}}
+ end
end.
-changes_row(Results, SeqStuff, #changes_acc{view=#mrview{}}) ->
- {{Seq, Key}, {Id, Value}} = SeqStuff,
+changes_row(Results, DocInfo, #changes_acc{filter={fast_view,_,_,_}}=Acc) ->
+ format_doc_info_change(Results, DocInfo, Acc);
+changes_row(Results, KV, #changes_acc{view=#mrview{}}) ->
+ {{Seq, Key}, {Id, Value}} = KV,
{[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}]};
-changes_row(Results, #doc_info{}=DocInfo, #changes_acc{view=undefined}=Acc) ->
+changes_row(Results, #doc_info{}=DocInfo, Acc) ->
+ format_doc_info_change(Results, DocInfo, Acc).
+
+format_doc_info_change(Results, #doc_info{}=DocInfo, Acc) ->
#doc_info{
id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]
} = DocInfo,
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/20e585dd/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
index 9f141d9..4963a5f 100644
--- a/src/couch_httpd_changes.erl
+++ b/src/couch_httpd_changes.erl
@@ -14,19 +14,13 @@
-export([handle_db_changes_req/2,
handle_changes_req/4,
- handle_view_filtered_changes/3,
- parse_changes_query/3]).
+ parse_changes_query/2]).
-include_lib("couch/include/couch_db.hrl").
handle_db_changes_req(Req, Db) ->
- ChangesArgs = parse_changes_query(Req, Db, false),
- ChangesFun = case ChangesArgs#changes_args.filter of
- "_view" ->
- handle_view_filtered_changes(ChangesArgs, Req, Db);
- _ ->
- couch_changes:handle_db_changes(ChangesArgs, Req, Db)
- end,
+ ChangesArgs = parse_changes_query(Req, Db),
+ ChangesFun = couch_changes:handle_db_changes(ChangesArgs, Req, Db),
handle_changes_req(Req, Db, ChangesArgs, ChangesFun).
handle_changes_req(#httpd{method='POST'}=Req, Db, ChangesArgs, ChangesFun) ->
@@ -126,170 +120,7 @@ handle_changes_req1(Req, #db{name=DbName}=Db, ChangesArgs, ChangesFun) ->
end.
-%% wrapper around couch_mrview_changes.
-%% This wrapper mimic couch_changes:handle_db_changes/3 and return a
-%% Changefun that can be used by the handle_changes_req function. Also
-%% while couch_mrview_changes:handle_changes/6 is returning tha view
-%% changes this function return docs corresponding to the changes
-%% instead so it can be used to replace the _view filter.
-handle_view_filtered_changes(ChangesArgs, Req, Db) ->
- %% parse view parameter
- {DDocId, VName} = parse_view_param(Req),
-
- %% get view options
- Query = case Req of
- {json_req, {Props}} ->
- {Q} = couch_util:get_value(<<"query">>, Props, {[]}),
- Q;
- _ ->
- couch_httpd:qs(Req)
- end,
- ViewOptions = parse_view_options(Query, []),
-
- {ok, Infos} = couch_mrview:get_info(Db, DDocId),
- case lists:member(<<"seq_indexed">>,
- proplists:get_value(update_options, Infos, [])) of
- true ->
- handle_view_filtered_changes(Db, DDocId, VName, ViewOptions, ChangesArgs,
- Req);
- false when ViewOptions /= [] ->
- ?LOG_ERROR("Tried to filter a non sequence indexed view~n",[]),
- throw({bad_request, seqs_not_indexed});
- false ->
- %% old method we are getting changes using the btree instead
- %% which is not efficient, log it
- ?LOG_WARN("Get view changes with seq_indexed=false.~n", []),
- couch_changes:handle_db_changes(ChangesArgs, Req, Db)
- end.
-
-handle_view_filtered_changes(#db{name=DbName}=Db0, DDocId, VName, ViewOptions,
- ChangesArgs, Req) ->
- #changes_args{
- feed = ResponseType,
- since = Since,
- db_open_options = DbOptions} = ChangesArgs,
-
- Options0 = [{since, Since},
- {view_options, ViewOptions}],
- Options = case ResponseType of
- "continuous" -> [stream | Options0];
- "eventsource" -> [stream | Options0];
- "longpoll" -> [{stream, once} | Options0];
- _ -> Options0
- end,
-
- %% reopen the db with the db options given to the changes args
- couch_db:close(Db0),
- DbOptions1 = [{user_ctx, Db0#db.user_ctx} | DbOptions],
- {ok, Db} = couch_db:open(DbName, DbOptions1),
-
-
- %% initialise the changes fun
- ChangesFun = fun(Callback) ->
- Callback(start, ResponseType),
-
- Acc0 = {"", 0, Db, Callback, ChangesArgs},
- couch_mrview_changes:handle_changes(DbName, DDocId, VName,
- fun view_changes_cb/2,
- Acc0, Options)
- end,
- ChangesFun.
-
-
-view_changes_cb(stop, {LastSeq, {_, _, _, Callback, Args}}) ->
- Callback({stop, LastSeq}, Args#changes_args.feed);
-
-view_changes_cb(heartbeat, {_, _, _, Callback, Args}=Acc) ->
- Callback(timeout, Args#changes_args.feed),
- {ok, Acc};
-view_changes_cb({{Seq, _Key, DocId}, Val},
- {Prepend, OldLimit, Db0, Callback, Args}=Acc) ->
-
- %% is the key removed from the index?
- Removed = case Val of
- {[{<<"_removed">>, true}]} -> true;
- _ -> false
- end,
-
- #changes_args{
- feed = ResponseType,
- limit = Limit} = Args,
-
- %% if the doc sequence is > to the one in the db record, reopen the
- %% database since it means we don't have the latest db value.
- Db = case Db0#db.update_seq >= Seq of
- true -> Db0;
- false ->
- {ok, Db1} = couch_db:reopen_db(Db0),
- Db1
- end,
-
- case couch_db:get_doc_info(Db, DocId) of
- {ok, DocInfo} ->
- %% get change row
- {Deleted, ChangeRow} = view_change_row(Db, DocInfo, Args),
-
- case Removed of
- true when Deleted /= true ->
- %% the key has been removed from the view but the
- %% document hasn't been deleted so ignore it.
- {ok, Acc};
- _ ->
- %% emit change row
- Callback({change, ChangeRow, Prepend}, ResponseType),
-
- %% if we achieved the limit, stop here, else continue.
- NewLimit = OldLimit + 1,
- if Limit > NewLimit ->
- {ok, {<<",\n">>, NewLimit, Db, Callback, Args}};
- true ->
- {stop, {<<"">>, NewLimit, Db, Callback, Args}}
- end
- end;
- {error, not_found} ->
- %% doc not found, continue
- {ok, Acc};
- Error ->
- throw(Error)
- end.
-
-
-view_change_row(Db, DocInfo, Args) ->
- #doc_info{id = Id, high_seq = Seq, revs = Revs} = DocInfo,
- [#rev_info{rev=Rev, deleted=Del} | _] = Revs,
-
- #changes_args{style=Style,
- include_docs=InDoc,
- doc_options = DocOpts,
- conflicts=Conflicts}=Args,
-
- Changes = case Style of
- main_only ->
- [{[{<<"rev">>, couch_doc:rev_to_str(Rev)}]}];
- all_docs ->
- [{[{<<"rev">>, couch_doc:rev_to_str(R)}]}
- || #rev_info{rev=R} <- Revs]
- end,
-
- {Del, {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"changes">>, Changes}] ++
- deleted_item(Del) ++ case InDoc of
- true ->
- Opts = case Conflicts of
- true -> [deleted, conflicts];
- false -> [deleted]
- end,
- Doc = couch_index_util:load_doc(Db, DocInfo, Opts),
- case Doc of
- null ->
- [{doc, null}];
- _ ->
- [{doc, couch_doc:to_json_obj(Doc, DocOpts)}]
- end;
- false ->
- []
- end}}.
-
-parse_changes_query(Req, Db, IsViewChanges) ->
+parse_changes_query(Req, Db) ->
ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->
case {string:to_lower(Key), Value} of
{"feed", _} ->
@@ -426,6 +257,3 @@ parse_json(V) when is_list(V) ->
?JSON_DECODE(V);
parse_json(V) ->
V.
-
-deleted_item(true) -> [{<<"deleted">>, true}];
-deleted_item(_) -> [].
[11/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Add preliminary version of view changes
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/6d6b801f
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/6d6b801f
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/6d6b801f
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 6d6b801f8440d0fdad1f0c22261d0b64da9362d0
Parents: 071dedf
Author: Benjamin Bastian <be...@gmail.com>
Authored: Fri Aug 22 15:51:47 2014 +0700
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:53 2014 -0700
----------------------------------------------------------------------
src/couch_changes.erl | 177 +++++++++++++++++++++++++++------------
src/couch_db.erl | 6 +-
src/couch_httpd_changes.erl | 53 ++++++------
src/couch_httpd_db.erl | 2 +-
4 files changed, 157 insertions(+), 81 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6d6b801f/src/couch_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_changes.erl b/src/couch_changes.erl
index b5e8f89..abe8cf9 100644
--- a/src/couch_changes.erl
+++ b/src/couch_changes.erl
@@ -12,15 +12,19 @@
-module(couch_changes).
-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch_mrview/include/couch_mrview.hrl").
-export([
- handle_changes/3,
+ handle_db_changes/3,
+ handle_changes/4,
get_changes_timeout/2,
- wait_db_updated/3,
- get_rest_db_updated/1,
+ wait_updated/3,
+ get_rest_updated/1,
configure_filter/4,
filter/3,
- handle_db_event/3
+ handle_db_event/3,
+ handle_view_event/3,
+ view_filter/3
]).
-export([changes_enumerator/2]).
@@ -31,6 +35,9 @@
-record(changes_acc, {
db,
+ view_name,
+ ddoc_name,
+ view,
seq,
prepend,
filter,
@@ -45,8 +52,10 @@
timeout_fun
}).
-%% @type Req -> #httpd{} | {json_req, JsonObj()}
-handle_changes(Args1, Req, Db0) ->
+handle_db_changes(Args, Req, Db) ->
+ handle_changes(Args, Req, Db, db).
+
+handle_changes(Args1, Req, Db0, Type) ->
#changes_args{
style = Style,
filter = FilterName,
@@ -54,6 +63,23 @@ handle_changes(Args1, Req, Db0) ->
dir = Dir,
since = Since
} = Args1,
+ {StartListenerFun, DDocName, ViewName, View} = case Type of
+ {view, DDocName0, ViewName0} ->
+ SNFun = fun() ->
+ couch_event:link_listener(
+ ?MODULE, handle_view_event, self(), [{dbname, Db0#db.name}]
+ )
+ end,
+ {ok, {_, View0, _}, _, _} = couch_mrview_util:get_view(Db0#db.name, DDocName0, ViewName0, #mrargs{}),
+ {SNFun, DDocName0, ViewName0, View0};
+ db ->
+ SNFun = fun() ->
+ couch_event:link_listener(
+ ?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
+ )
+ end,
+ {SNFun, undefined, undefined, undefined}
+ end,
Filter = configure_filter(FilterName, Style, Req, Db0),
Args = Args1#changes_args{filter_fun = Filter},
Start = fun() ->
@@ -78,14 +104,14 @@ handle_changes(Args1, Req, Db0) ->
true ->
fun(CallbackAcc) ->
{Callback, UserAcc} = get_callback_acc(CallbackAcc),
- {ok, Listener} = couch_event:link_listener(
- ?MODULE, handle_db_event, self(), [{dbname, Db0#db.name}]
- ),
+ {ok, Listener} = StartListenerFun(),
+
{Db, StartSeq} = Start(),
UserAcc2 = start_sending_changes(Callback, UserAcc, Feed),
{Timeout, TimeoutFun} = get_changes_timeout(Args, Callback),
Acc0 = build_acc(Args, Callback, UserAcc2, Db, StartSeq,
- <<"">>, Timeout, TimeoutFun),
+ <<"">>, Timeout, TimeoutFun, DDocName, ViewName,
+ View),
try
keep_sending_changes(
Args#changes_args{dir=fwd},
@@ -93,7 +119,7 @@ handle_changes(Args1, Req, Db0) ->
true)
after
couch_event:stop_listener(Listener),
- get_rest_db_updated(ok) % clean out any remaining update messages
+ get_rest_updated(ok) % clean out any remaining update messages
end
end;
false ->
@@ -103,11 +129,12 @@ handle_changes(Args1, Req, Db0) ->
{Timeout, TimeoutFun} = get_changes_timeout(Args, Callback),
{Db, StartSeq} = Start(),
Acc0 = build_acc(Args#changes_args{feed="normal"}, Callback,
- UserAcc2, Db, StartSeq, <<>>, Timeout, TimeoutFun),
+ UserAcc2, Db, StartSeq, <<>>, Timeout, TimeoutFun,
+ DDocName, ViewName, View),
{ok, #changes_acc{seq = LastSeq, user_acc = UserAcc3}} =
send_changes(
- Args#changes_args{feed="normal"},
Acc0,
+ Dir,
true),
end_sending_changes(Callback, UserAcc3, LastSeq, Feed)
end
@@ -115,13 +142,24 @@ handle_changes(Args1, Req, Db0) ->
handle_db_event(_DbName, updated, Parent) ->
- Parent ! db_updated,
+ Parent ! updated,
{ok, Parent};
handle_db_event(_DbName, _Event, Parent) ->
{ok, Parent}.
+handle_view_event(_DbName, Msg, {Parent, DDocId}) ->
+ case Msg of
+ {index_commit, DDocId} ->
+ Parent ! updated;
+ {index_delete, DDocId} ->
+ Parent ! deleted;
+ _ ->
+ ok
+ end,
+ {ok, {Parent, DDocId}}.
+
get_callback_acc({Callback, _UserAcc} = Pair) when is_function(Callback, 3) ->
Pair;
get_callback_acc(Callback) when is_function(Callback, 2) ->
@@ -133,7 +171,7 @@ configure_filter("_doc_ids", Style, Req, _Db) ->
configure_filter("_design", Style, _Req, _Db) ->
{design_docs, Style};
configure_filter("_view", Style, Req, Db) ->
- ViewName = couch_httpd:qs_value(Req, "view", ""),
+ ViewName = get_view_qs(Req),
if ViewName /= "" -> ok; true ->
throw({bad_request, "`view` filter parameter is not provided."})
end,
@@ -199,6 +237,11 @@ filter(Db, DocInfo, {custom, Style, Req0, DDoc, FName}) ->
{ok, Passes} = couch_query_servers:filter_docs(Req, Db, DDoc, FName, Docs),
filter_revs(Passes, Docs).
+get_view_qs({json_req, {Props}}) ->
+ {Query} = couch_util:get_value(<<"query">>, Props, {[]}),
+ binary_to_list(couch_util:get_value(<<"view">>, Query, ""));
+get_view_qs(Req) ->
+ couch_httpd:qs_value(Req, "view", "").
get_doc_ids({json_req, {Props}}) ->
check_docids(couch_util:get_value(<<"doc_ids">>, Props));
@@ -310,7 +353,7 @@ start_sending_changes(_Callback, UserAcc, ResponseType)
start_sending_changes(Callback, UserAcc, ResponseType) ->
Callback(start, ResponseType, UserAcc).
-build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun) ->
+build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun, DDocName, ViewName, View) ->
#changes_args{
include_docs = IncludeDocs,
doc_options = DocOpts,
@@ -332,24 +375,30 @@ build_acc(Args, Callback, UserAcc, Db, StartSeq, Prepend, Timeout, TimeoutFun) -
doc_options = DocOpts,
conflicts = Conflicts,
timeout = Timeout,
- timeout_fun = TimeoutFun
+ timeout_fun = TimeoutFun,
+ ddoc_name = DDocName,
+ view_name = ViewName,
+ view = View
}.
-send_changes(Args, Acc0, FirstRound) ->
- #changes_args{
- dir = Dir
- } = Args,
+send_changes(Acc, Dir, FirstRound) ->
#changes_acc{
db = Db,
seq = StartSeq,
- filter = Filter
- } = Acc0,
- EnumFun = fun ?MODULE:changes_enumerator/2,
+ filter = Filter,
+ view = View
+ } = Acc,
+ EnumFun = fun changes_enumerator/2,
case can_optimize(FirstRound, Filter) of
{true, Fun} ->
- Fun(Db, StartSeq, Dir, EnumFun, Acc0, Filter);
+ Fun(Db, StartSeq, Dir, EnumFun, Acc, Filter);
_ ->
- couch_db:changes_since(Db, StartSeq, EnumFun, [{dir, Dir}], Acc0)
+ case View of
+ undefined ->
+ couch_db:changes_since(Db, StartSeq, EnumFun, [{dir, Dir}], Acc);
+ #mrview{} ->
+ couch_mrview:view_changes_since(View, StartSeq, EnumFun, [{dir, Dir}], Acc)
+ end
end.
@@ -422,20 +471,20 @@ keep_sending_changes(Args, Acc0, FirstRound) ->
db_open_options = DbOptions
} = Args,
- {ok, ChangesAcc} = send_changes(
- Args#changes_args{dir=fwd},
- Acc0,
- FirstRound),
+ {ok, ChangesAcc} = send_changes(Acc0, fwd, FirstRound),
+
#changes_acc{
- db = Db, callback = Callback, timeout = Timeout, timeout_fun = TimeoutFun,
- seq = EndSeq, prepend = Prepend2, user_acc = UserAcc2, limit = NewLimit
+ db = Db, callback = Callback,
+ timeout = Timeout, timeout_fun = TimeoutFun, seq = EndSeq,
+ prepend = Prepend2, user_acc = UserAcc2, limit = NewLimit,
+ ddoc_name = DDocName, view_name = ViewName, view = View
} = ChangesAcc,
couch_db:close(Db),
if Limit > NewLimit, ResponseType == "longpoll" ->
end_sending_changes(Callback, UserAcc2, EndSeq, ResponseType);
true ->
- case wait_db_updated(Timeout, TimeoutFun, UserAcc2) of
+ case wait_updated(Timeout, TimeoutFun, UserAcc2) of
{updated, UserAcc4} ->
DbOptions1 = [{user_ctx, Db#db.user_ctx} | DbOptions],
case couch_db:open(Db#db.name, DbOptions1) of
@@ -444,6 +493,7 @@ keep_sending_changes(Args, Acc0, FirstRound) ->
Args#changes_args{limit=NewLimit},
ChangesAcc#changes_acc{
db = Db2,
+ view = maybe_refresh_view(Db2, DDocName, ViewName),
user_acc = UserAcc4,
seq = EndSeq,
prepend = Prepend2,
@@ -458,19 +508,31 @@ keep_sending_changes(Args, Acc0, FirstRound) ->
end
end.
+maybe_refresh_view(_, undefined, undefined) ->
+ undefined;
+maybe_refresh_view(Db, DDocName, ViewName) ->
+ {ok, {_, View, _}, _, _} = couch_mrview_util:get_view(Db#db.name, DDocName, ViewName, #mrargs{}),
+ View.
+
end_sending_changes(Callback, UserAcc, EndSeq, ResponseType) ->
Callback({stop, EndSeq}, ResponseType, UserAcc).
-changes_enumerator(DocInfo, #changes_acc{resp_type = ResponseType} = Acc)
+changes_enumerator(Value, #changes_acc{resp_type = ResponseType} = Acc)
when ResponseType =:= "continuous"
orelse ResponseType =:= "eventsource" ->
#changes_acc{
filter = Filter, callback = Callback,
user_acc = UserAcc, limit = Limit, db = Db,
- timeout = Timeout, timeout_fun = TimeoutFun
+ timeout = Timeout, timeout_fun = TimeoutFun,
+ view = View
} = Acc,
- #doc_info{high_seq = Seq} = DocInfo,
- Results0 = filter(Db, DocInfo, Filter),
+ {Seq, Results0} = case View of
+ undefined ->
+ {Value#doc_info.high_seq, filter(Db, Value, Filter)};
+ #mrview{} ->
+ {{Seq0, _}, _} = Value,
+ {Seq0, [ok]} % TODO
+ end,
Results = [Result || Result <- Results0, Result /= null],
%% TODO: I'm thinking this should be < 1 and not =< 1
Go = if Limit =< 1 -> stop; true -> ok end,
@@ -484,19 +546,24 @@ changes_enumerator(DocInfo, #changes_acc{resp_type = ResponseType} = Acc)
{Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2}}
end;
_ ->
- ChangesRow = changes_row(Results, DocInfo, Acc),
+ ChangesRow = changes_row(Results, Value, Acc),
UserAcc2 = Callback({change, ChangesRow, <<>>}, ResponseType, UserAcc),
reset_heartbeat(),
{Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2, limit = Limit - 1}}
end;
-changes_enumerator(DocInfo, Acc) ->
+changes_enumerator(Value, Acc) ->
#changes_acc{
filter = Filter, callback = Callback, prepend = Prepend,
user_acc = UserAcc, limit = Limit, resp_type = ResponseType, db = Db,
- timeout = Timeout, timeout_fun = TimeoutFun
+ timeout = Timeout, timeout_fun = TimeoutFun, view = View
} = Acc,
- #doc_info{high_seq = Seq} = DocInfo,
- Results0 = filter(Db, DocInfo, Filter),
+ {Seq, Results0} = case View of
+ undefined ->
+ {Value#doc_info.high_seq, filter(Db, Value, Filter)};
+ #mrview{} ->
+ {{Seq0,_}, _} = Value,
+ {Seq0, [ok]} % TODO view filter
+ end,
Results = [Result || Result <- Results0, Result /= null],
Go = if (Limit =< 1) andalso Results =/= [] -> stop; true -> ok end,
case Results of
@@ -509,7 +576,7 @@ changes_enumerator(DocInfo, Acc) ->
{Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2}}
end;
_ ->
- ChangesRow = changes_row(Results, DocInfo, Acc),
+ ChangesRow = changes_row(Results, Value, Acc),
UserAcc2 = Callback({change, ChangesRow, Prepend}, ResponseType, UserAcc),
reset_heartbeat(),
{Go, Acc#changes_acc{
@@ -518,7 +585,11 @@ changes_enumerator(DocInfo, Acc) ->
end.
-changes_row(Results, DocInfo, Acc) ->
+
+changes_row(Results, SeqStuff, #changes_acc{view=#mrview{}}) ->
+ {{Seq, Key}, {Id, Value}} = SeqStuff,
+ {[{<<"seq">>, Seq}, {<<"id">>, Id}, {<<"key">>, Key}, {<<"value">>, Value}, {<<"changes">>, Results}]};
+changes_row(Results, #doc_info{}=DocInfo, #changes_acc{view=undefined}=Acc) ->
#doc_info{
id = Id, high_seq = Seq, revs = [#rev_info{deleted = Del} | _]
} = DocInfo,
@@ -549,25 +620,27 @@ changes_row(Results, DocInfo, Acc) ->
deleted_item(true) -> [{<<"deleted">>, true}];
deleted_item(_) -> [].
-% waits for a db_updated msg, if there are multiple msgs, collects them.
-wait_db_updated(Timeout, TimeoutFun, UserAcc) ->
+% waits for a updated msg, if there are multiple msgs, collects them.
+wait_updated(Timeout, TimeoutFun, UserAcc) ->
receive
- db_updated ->
- get_rest_db_updated(UserAcc)
+ updated ->
+ get_rest_updated(UserAcc);
+ deleted ->
+ {stop, UserAcc}
after Timeout ->
{Go, UserAcc2} = TimeoutFun(UserAcc),
case Go of
ok ->
- wait_db_updated(Timeout, TimeoutFun, UserAcc2);
+ wait_updated(Timeout, TimeoutFun, UserAcc2);
stop ->
{stop, UserAcc2}
end
end.
-get_rest_db_updated(UserAcc) ->
+get_rest_updated(UserAcc) ->
receive
- db_updated ->
- get_rest_db_updated(UserAcc)
+ updated ->
+ get_rest_updated(UserAcc)
after 0 ->
{updated, UserAcc}
end.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6d6b801f/src/couch_db.erl
----------------------------------------------------------------------
diff --git a/src/couch_db.erl b/src/couch_db.erl
index 5d4619a..b5ea64d 100644
--- a/src/couch_db.erl
+++ b/src/couch_db.erl
@@ -1173,7 +1173,9 @@ enum_docs_reduce_to_count(Reds) ->
changes_since(Db, StartSeq, Fun, Acc) ->
changes_since(Db, StartSeq, Fun, [], Acc).
-changes_since(Db, StartSeq, Fun, Options, Acc) ->
+changes_since(Db, StartSeq, Fun, Options, Acc) when is_record(Db, db) ->
+ changes_since(Db#db.seq_tree, StartSeq, Fun, Options, Acc);
+changes_since(SeqTree, StartSeq, Fun, Options, Acc) ->
Wrapper = fun(FullDocInfo, _Offset, Acc2) ->
DocInfo = case FullDocInfo of
#full_doc_info{} ->
@@ -1183,7 +1185,7 @@ changes_since(Db, StartSeq, Fun, Options, Acc) ->
end,
Fun(DocInfo, Acc2)
end,
- {ok, _LastReduction, AccOut} = couch_btree:fold(Db#db.seq_tree,
+ {ok, _LastReduction, AccOut} = couch_btree:fold(SeqTree,
Wrapper, Acc, [{start_key, StartSeq + 1}] ++ Options),
{ok, AccOut}.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6d6b801f/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
index b494b0d..9f141d9 100644
--- a/src/couch_httpd_changes.erl
+++ b/src/couch_httpd_changes.erl
@@ -12,22 +12,33 @@
-module(couch_httpd_changes).
--export([handle_changes_req/2,
- handle_changes/3,
- handle_view_changes/3]).
+-export([handle_db_changes_req/2,
+ handle_changes_req/4,
+ handle_view_filtered_changes/3,
+ parse_changes_query/3]).
-include_lib("couch/include/couch_db.hrl").
-handle_changes_req(#httpd{method='POST'}=Req, Db) ->
+handle_db_changes_req(Req, Db) ->
+ ChangesArgs = parse_changes_query(Req, Db, false),
+ ChangesFun = case ChangesArgs#changes_args.filter of
+ "_view" ->
+ handle_view_filtered_changes(ChangesArgs, Req, Db);
+ _ ->
+ couch_changes:handle_db_changes(ChangesArgs, Req, Db)
+ end,
+ handle_changes_req(Req, Db, ChangesArgs, ChangesFun).
+
+handle_changes_req(#httpd{method='POST'}=Req, Db, ChangesArgs, ChangesFun) ->
couch_httpd:validate_ctype(Req, "application/json"),
- handle_changes_req1(Req, Db);
-handle_changes_req(#httpd{method='GET'}=Req, Db) ->
- handle_changes_req1(Req, Db);
-handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
+ handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
+handle_changes_req(#httpd{method='GET'}=Req, Db, ChangesArgs, ChangesFun) ->
+ handle_changes_req1(Req, Db, ChangesArgs, ChangesFun);
+handle_changes_req(#httpd{}=Req, _Db, _ChangesArgs, _ChangesFun) ->
couch_httpd:send_method_not_allowed(Req, "GET,HEAD,POST").
-handle_changes_req1(Req, #db{name=DbName}=Db) ->
- AuthDbName = ?l2b(couch_config:get("couch_httpd_auth", "authentication_db")),
+handle_changes_req1(Req, #db{name=DbName}=Db, ChangesArgs, ChangesFun) ->
+ AuthDbName = ?l2b(config:get("couch_httpd_auth", "authentication_db")),
case AuthDbName of
DbName ->
% in the authentication database, _changes is admin-only.
@@ -71,8 +82,6 @@ handle_changes_req1(Req, #db{name=DbName}=Db) ->
couch_httpd:send_chunk(Resp, "\n")
end
end,
- ChangesArgs = parse_changes_query(Req, Db),
- ChangesFun = handle_changes(ChangesArgs, Req, Db),
WrapperFun = case ChangesArgs#changes_args.feed of
"normal" ->
{ok, Info} = couch_db:get_db_info(Db),
@@ -117,21 +126,13 @@ handle_changes_req1(Req, #db{name=DbName}=Db) ->
end.
-handle_changes(ChangesArgs, Req, Db) ->
- case ChangesArgs#changes_args.filter of
- "_view" ->
- handle_view_changes(ChangesArgs, Req, Db);
- _ ->
- couch_changes:handle_changes(ChangesArgs, Req, Db)
- end.
-
%% wrapper around couch_mrview_changes.
-%% This wrapper mimic couch_changes:handle_changes/3 and return a
+%% This wrapper mimic couch_changes:handle_db_changes/3 and return a
%% Changefun that can be used by the handle_changes_req function. Also
%% while couch_mrview_changes:handle_changes/6 is returning tha view
%% changes this function return docs corresponding to the changes
%% instead so it can be used to replace the _view filter.
-handle_view_changes(ChangesArgs, Req, Db) ->
+handle_view_filtered_changes(ChangesArgs, Req, Db) ->
%% parse view parameter
{DDocId, VName} = parse_view_param(Req),
@@ -149,7 +150,7 @@ handle_view_changes(ChangesArgs, Req, Db) ->
case lists:member(<<"seq_indexed">>,
proplists:get_value(update_options, Infos, [])) of
true ->
- handle_view_changes(Db, DDocId, VName, ViewOptions, ChangesArgs,
+ handle_view_filtered_changes(Db, DDocId, VName, ViewOptions, ChangesArgs,
Req);
false when ViewOptions /= [] ->
?LOG_ERROR("Tried to filter a non sequence indexed view~n",[]),
@@ -158,10 +159,10 @@ handle_view_changes(ChangesArgs, Req, Db) ->
%% old method we are getting changes using the btree instead
%% which is not efficient, log it
?LOG_WARN("Get view changes with seq_indexed=false.~n", []),
- couch_changes:handle_changes(ChangesArgs, Req, Db)
+ couch_changes:handle_db_changes(ChangesArgs, Req, Db)
end.
-handle_view_changes(#db{name=DbName}=Db0, DDocId, VName, ViewOptions,
+handle_view_filtered_changes(#db{name=DbName}=Db0, DDocId, VName, ViewOptions,
ChangesArgs, Req) ->
#changes_args{
feed = ResponseType,
@@ -288,7 +289,7 @@ view_change_row(Db, DocInfo, Args) ->
[]
end}}.
-parse_changes_query(Req, Db) ->
+parse_changes_query(Req, Db, IsViewChanges) ->
ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->
case {string:to_lower(Key), Value} of
{"feed", _} ->
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/6d6b801f/src/couch_httpd_db.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_db.erl b/src/couch_httpd_db.erl
index 4ff4d98..442e872 100644
--- a/src/couch_httpd_db.erl
+++ b/src/couch_httpd_db.erl
@@ -112,7 +112,7 @@ handle_changes_req2(Req, Db) ->
end
end,
ChangesArgs = parse_changes_query(Req, Db),
- ChangesFun = couch_changes:handle_changes(ChangesArgs, Req, Db),
+ ChangesFun = couch_changes:handle_db_changes(ChangesArgs, Req, Db),
WrapperFun = case ChangesArgs#changes_args.feed of
"normal" ->
{ok, Info} = couch_db:get_db_info(Db),
[05/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
extract couch_httpd changes API in its own module
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/fcb2882d
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/fcb2882d
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/fcb2882d
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: fcb2882dfb12c36b1be3e9cda6b1328c324e667e
Parents: 011975a
Author: benoitc <bc...@gmail.com>
Authored: Sun Feb 2 19:54:01 2014 +0100
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Oct 31 12:43:52 2014 -0700
----------------------------------------------------------------------
src/couch_httpd_changes.erl | 174 +++++++++++++++++++++++++++++++++++++++
1 file changed, 174 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/fcb2882d/src/couch_httpd_changes.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_changes.erl b/src/couch_httpd_changes.erl
new file mode 100644
index 0000000..1e431e9
--- /dev/null
+++ b/src/couch_httpd_changes.erl
@@ -0,0 +1,174 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_httpd_changes).
+
+-export([handle_changes_req/2]).
+
+-include_lib("couch/include/couch_db.hrl").
+
+handle_changes_req(#httpd{method='POST'}=Req, Db) ->
+ couch_httpd:validate_ctype(Req, "application/json"),
+ handle_changes_req1(Req, Db);
+handle_changes_req(#httpd{method='GET'}=Req, Db) ->
+ handle_changes_req1(Req, Db);
+handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
+ couch_httpd:send_method_not_allowed(Req, "GET,HEAD,POST").
+
+handle_changes_req1(Req, #db{name=DbName}=Db) ->
+ AuthDbName = ?l2b(couch_config:get("couch_httpd_auth", "authentication_db")),
+ case AuthDbName of
+ DbName ->
+ % in the authentication database, _changes is admin-only.
+ ok = couch_db:check_is_admin(Db);
+ _Else ->
+ % on other databases, _changes is free for all.
+ ok
+ end,
+ handle_changes_req2(Req, Db).
+
+handle_changes_req2(Req, Db) ->
+ MakeCallback = fun(Resp) ->
+ fun({change, {ChangeProp}=Change, _}, "eventsource") ->
+ Seq = proplists:get_value(<<"seq">>, ChangeProp),
+ couch_httpd:send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
+ "\n", "id: ", ?JSON_ENCODE(Seq),
+ "\n\n"]);
+ ({change, Change, _}, "continuous") ->
+ couch_httpd:send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
+ ({change, Change, Prepend}, _) ->
+ couch_httpd:send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
+ (start, "eventsource") ->
+ ok;
+ (start, "continuous") ->
+ ok;
+ (start, _) ->
+ couch_httpd:send_chunk(Resp, "{\"results\":[\n");
+ ({stop, _EndSeq}, "eventsource") ->
+ couch_httpd:end_json_response(Resp);
+ ({stop, EndSeq}, "continuous") ->
+ couch_httpd:send_chunk(
+ Resp,
+ [?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]
+ ),
+ couch_httpd:end_json_response(Resp);
+ ({stop, EndSeq}, _) ->
+ couch_httpd:send_chunk(
+ Resp,
+ io_lib:format("\n],\n\"last_seq\":~w}\n", [EndSeq])
+ ),
+ couch_httpd:end_json_response(Resp);
+ (timeout, _) ->
+ couch_httpd:send_chunk(Resp, "\n")
+ end
+ end,
+ ChangesArgs = parse_changes_query(Req, Db),
+ ChangesFun = couch_changes:handle_changes(ChangesArgs, Req, Db),
+ WrapperFun = case ChangesArgs#changes_args.feed of
+ "normal" ->
+ {ok, Info} = couch_db:get_db_info(Db),
+ CurrentEtag = couch_httpd:make_etag(Info),
+ fun(FeedChangesFun) ->
+ couch_httpd:etag_respond(
+ Req,
+ CurrentEtag,
+ fun() ->
+ {ok, Resp} = couch_httpd:start_json_response(
+ Req, 200, [{"ETag", CurrentEtag}]
+ ),
+ FeedChangesFun(MakeCallback(Resp))
+ end
+ )
+ end;
+ "eventsource" ->
+ Headers = [
+ {"Content-Type", "text/event-stream"},
+ {"Cache-Control", "no-cache"}
+ ],
+ {ok, Resp} = couch_httpd:start_chunked_response(Req, 200, Headers),
+ fun(FeedChangesFun) ->
+ FeedChangesFun(MakeCallback(Resp))
+ end;
+ _ ->
+ % "longpoll" or "continuous"
+ {ok, Resp} = couch_httpd:start_json_response(Req, 200),
+ fun(FeedChangesFun) ->
+ FeedChangesFun(MakeCallback(Resp))
+ end
+ end,
+ couch_stats_collector:increment(
+ {httpd, clients_requesting_changes}
+ ),
+ try
+ WrapperFun(ChangesFun)
+ after
+ couch_stats_collector:decrement(
+ {httpd, clients_requesting_changes}
+ )
+ end.
+
+parse_changes_query(Req, Db) ->
+ ChangesArgs = lists:foldl(fun({Key, Value}, Args) ->
+ case {string:to_lower(Key), Value} of
+ {"feed", _} ->
+ Args#changes_args{feed=Value};
+ {"descending", "true"} ->
+ Args#changes_args{dir=rev};
+ {"since", "now"} ->
+ UpdateSeq = couch_util:with_db(Db#db.name, fun(WDb) ->
+ couch_db:get_update_seq(WDb)
+ end),
+ Args#changes_args{since=UpdateSeq};
+ {"since", _} ->
+ Args#changes_args{since=list_to_integer(Value)};
+ {"last-event-id", _} ->
+ Args#changes_args{since=list_to_integer(Value)};
+ {"limit", _} ->
+ Args#changes_args{limit=list_to_integer(Value)};
+ {"style", _} ->
+ Args#changes_args{style=list_to_existing_atom(Value)};
+ {"heartbeat", "true"} ->
+ Args#changes_args{heartbeat=true};
+ {"heartbeat", _} ->
+ Args#changes_args{heartbeat=list_to_integer(Value)};
+ {"timeout", _} ->
+ Args#changes_args{timeout=list_to_integer(Value)};
+ {"include_docs", "true"} ->
+ Args#changes_args{include_docs=true};
+ {"attachments", "true"} ->
+ Opts = Args#changes_args.doc_options,
+ Args#changes_args{doc_options=[attachments|Opts]};
+ {"att_encoding_info", "true"} ->
+ Opts = Args#changes_args.doc_options,
+ Args#changes_args{doc_options=[att_encoding_info|Opts]};
+ {"conflicts", "true"} ->
+ Args#changes_args{conflicts=true};
+ {"filter", _} ->
+ Args#changes_args{filter=Value};
+ _Else -> % unknown key value pair, ignore.
+ Args
+ end
+ end, #changes_args{}, couch_httpd:qs(Req)),
+ %% if it's an EventSource request with a Last-event-ID header
+ %% that should override the `since` query string, since it's
+ %% probably the browser reconnecting.
+ case ChangesArgs#changes_args.feed of
+ "eventsource" ->
+ case couch_httpd:header_value(Req, "last-event-id") of
+ undefined ->
+ ChangesArgs;
+ Value ->
+ ChangesArgs#changes_args{since=list_to_integer(Value)}
+ end;
+ _ ->
+ ChangesArgs
+ end.
[02/22] couch commit: updated
refs/heads/2491-refactor-couch-httpd-auth to 3e8286d
Posted by da...@apache.org.
Fix header for documents with newlines in the name
Properly urlencode the Document-Id in the Location field of the
header
Based on a patch from Sean Bartell <wi...@gmail.com>
Added some eunit-tests
COUCHDB-708
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/9ee298ff
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/9ee298ff
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/9ee298ff
Branch: refs/heads/2491-refactor-couch-httpd-auth
Commit: 9ee298ff0af335f7449eca0f839caf0c5646887a
Parents: f02a101
Author: Robert Kowalski <ro...@kowalski.gd>
Authored: Sat Oct 25 03:16:38 2014 +0200
Committer: Robert Kowalski <ro...@apache.org>
Committed: Thu Oct 30 15:20:32 2014 +0100
----------------------------------------------------------------------
src/couch_httpd_db.erl | 5 +-
test/couchdb_location_header_tests.erl | 78 +++++++++++++++++++++++++++++
2 files changed, 80 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/9ee298ff/src/couch_httpd_db.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_db.erl b/src/couch_httpd_db.erl
index 13e2c44..4ff4d98 100644
--- a/src/couch_httpd_db.erl
+++ b/src/couch_httpd_db.erl
@@ -721,7 +721,7 @@ update_doc_result_to_json(DocId, Error) ->
update_doc(Req, Db, DocId, #doc{deleted=false}=Doc) ->
- Loc = absolute_uri(Req, "/" ++ ?b2l(Db#db.name) ++ "/" ++ ?b2l(DocId)),
+ Loc = absolute_uri(Req, "/" ++ ?b2l(Db#db.name) ++ "/" ++ couch_util:url_encode(DocId)),
update_doc(Req, Db, DocId, Doc, [{"Location", Loc}]);
update_doc(Req, Db, DocId, Doc) ->
update_doc(Req, Db, DocId, Doc, []).
@@ -1022,7 +1022,7 @@ db_attachment_req(#httpd{method=Method,mochi_req=MochiReq}=Req, Db, DocId, FileN
_ ->
[{"Location", absolute_uri(Req, "/" ++
?b2l(Db#db.name) ++ "/" ++
- ?b2l(DocId) ++ "/" ++
+ couch_util:url_encode(DocId) ++ "/" ++
?b2l(FileName)
)}]
end,
@@ -1233,4 +1233,3 @@ validate_attachment_name(Name) ->
true -> Name;
false -> throw({bad_request, <<"Attachment name is not UTF-8 encoded">>})
end.
-
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/9ee298ff/test/couchdb_location_header_tests.erl
----------------------------------------------------------------------
diff --git a/test/couchdb_location_header_tests.erl b/test/couchdb_location_header_tests.erl
new file mode 100644
index 0000000..74d1281
--- /dev/null
+++ b/test/couchdb_location_header_tests.erl
@@ -0,0 +1,78 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couchdb_location_header_tests).
+
+-include_lib("couch/include/couch_eunit.hrl").
+-include_lib("couch/include/couch_db.hrl").
+
+-define(TIMEOUT, 1000).
+
+
+setup() ->
+ DbName = ?tempdb(),
+ {ok, Db} = couch_db:create(DbName, [?ADMIN_USER]),
+ couch_db:close(Db),
+
+ Addr = config:get("httpd", "bind_address", "127.0.0.1"),
+ Port = integer_to_list(mochiweb_socket_server:get(couch_httpd, port)),
+ Host = "http://" ++ Addr ++ ":" ++ Port,
+ {Host, ?b2l(DbName)}.
+
+teardown({_, DbName}) ->
+ ok = couch_server:delete(?l2b(DbName), [?ADMIN_USER]),
+ ok.
+
+
+header_test_() ->
+ {
+ "CouchDB Location Header Tests",
+ {
+ setup,
+ fun test_util:start_couch/0, fun test_util:stop_couch/1,
+ {
+ foreach,
+ fun setup/0, fun teardown/1,
+ [
+ fun should_work_with_newlines_in_docs/1,
+ fun should_work_with_newlines_in_attachments/1
+ ]
+ }
+ }
+ }.
+
+should_work_with_newlines_in_docs({Host, DbName}) ->
+ Url = Host ++ "/" ++ DbName ++ "/docid%0A",
+ {"COUCHDB-708",
+ ?_assertEqual(
+ Url,
+ begin
+ {ok, _, Headers, _} = test_request:put(Url,
+ [{"Content-Type", "application/json"}], "{}"),
+ proplists:get_value("Location", Headers)
+ end)}.
+
+should_work_with_newlines_in_attachments({Host, DbName}) ->
+ Url = Host ++ "/" ++ DbName,
+ AttUrl = Url ++ "/docid%0A/readme.txt",
+ {"COUCHDB-708",
+ ?_assertEqual(
+ AttUrl,
+ begin
+ Body = "We all live in a yellow submarine!",
+ Headers0 = [
+ {"Content-Length", "34"},
+ {"Content-Type", "text/plain"}
+ ],
+ {ok, _, Headers, _} = test_request:put(AttUrl, Headers0, Body),
+ proplists:get_value("Location", Headers)
+ end)}.