You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@couchdb.apache.org by Paul Davis <pa...@gmail.com> on 2012/05/16 07:42:39 UTC

Re: git commit: add Server-Sent Events protocol to db changes API. close #COUCHDB-986

That JS test looks broken for JS environments that don't support
EventSource. Notably the CLI test suite but also browsers that don't
implement it. I'd either move the entire test into the conditional or
reimplement it by parsing the actual data sent across.

On Wed, May 16, 2012 at 12:36 AM,  <be...@apache.org> wrote:
> Updated Branches:
>  refs/heads/master af7441d8d -> 093d2aa65
>
>
> add Server-Sent Events protocol to db changes API. close #COUCHDB-986
>
> This patch add support for the new specification of w3c by adding a new
> feed type named `eventsource`:
>
> http://www.w3.org/TR/2009/WD-eventsource-20090423/
>
> This patch is based on @indutny patch with edits.
>
>
> Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
> Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/093d2aa6
> Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/093d2aa6
> Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/093d2aa6
>
> Branch: refs/heads/master
> Commit: 093d2aa6544546a95f6133f1db3c4f4179793f3c
> Parents: af7441d
> Author: benoitc <be...@apache.org>
> Authored: Wed May 16 07:30:19 2012 +0200
> Committer: benoitc <be...@apache.org>
> Committed: Wed May 16 07:30:19 2012 +0200
>
> ----------------------------------------------------------------------
>  share/www/script/test/changes.js |   28 ++++++++++++++++++++++++++++
>  src/couchdb/couch_changes.erl    |   15 ++++++++++-----
>  src/couchdb/couch_httpd_db.erl   |   24 ++++++++++++++++++++++--
>  3 files changed, 60 insertions(+), 7 deletions(-)
> ----------------------------------------------------------------------
>
>
> http://git-wip-us.apache.org/repos/asf/couchdb/blob/093d2aa6/share/www/script/test/changes.js
> ----------------------------------------------------------------------
> diff --git a/share/www/script/test/changes.js b/share/www/script/test/changes.js
> index 19e22fd..c529b21 100644
> --- a/share/www/script/test/changes.js
> +++ b/share/www/script/test/changes.js
> @@ -139,6 +139,34 @@ couchTests.changes = function(debug) {
>     // otherwise we'll continue to receive heartbeats forever
>     xhr.abort();
>
> +    // test Server Sent Event (eventsource)
> +    if (window.EventSource) {
> +      var source = new EventSource(
> +              "/test_suite_db/_changes?feed=eventsource");
> +      var results = [];
> +      var sourceListener = function(e) {
> +        var data = JSON.parse(e.data);
> +        results.push(data);
> +
> +      };
> +
> +      source.addEventListener('message', sourceListener , false);
> +
> +      waitForSuccess(function() {
> +        if (results.length != 3)
> +          throw "bad seq, try again";
> +      });
> +
> +      source.removeEventListener('message', sourceListener, false);
> +
> +      T(results[0].seq == 1);
> +      T(results[0].id == "foo");
> +
> +      T(results[1].seq == 2);
> +      T(results[1].id == "bar");
> +      T(results[1].changes[0].rev == docBar._rev);
> +    }
> +
>     // test longpolling
>     xhr = CouchDB.newXhr();
>
>
> http://git-wip-us.apache.org/repos/asf/couchdb/blob/093d2aa6/src/couchdb/couch_changes.erl
> ----------------------------------------------------------------------
> diff --git a/src/couchdb/couch_changes.erl b/src/couchdb/couch_changes.erl
> index aec7873..85c9e54 100644
> --- a/src/couchdb/couch_changes.erl
> +++ b/src/couchdb/couch_changes.erl
> @@ -63,7 +63,8 @@ handle_changes(Args1, Req, Db0) ->
>         put(last_changes_heartbeat, now())
>     end,
>
> -    if Feed == "continuous" orelse Feed == "longpoll" ->
> +    case lists:member(Feed, ["continuous", "longpoll", "eventsource"]) of
> +    true ->
>         fun(CallbackAcc) ->
>             {Callback, UserAcc} = get_callback_acc(CallbackAcc),
>             Self = self(),
> @@ -89,7 +90,7 @@ handle_changes(Args1, Req, Db0) ->
>                 get_rest_db_updated(ok) % clean out any remaining update messages
>             end
>         end;
> -    true ->
> +    false ->
>         fun(CallbackAcc) ->
>             {Callback, UserAcc} = get_callback_acc(CallbackAcc),
>             UserAcc2 = start_sending_changes(Callback, UserAcc, Feed),
> @@ -261,7 +262,9 @@ get_changes_timeout(Args, Callback) ->
>             fun(UserAcc) -> {ok, Callback(timeout, ResponseType, UserAcc)} end}
>     end.
>
> -start_sending_changes(_Callback, UserAcc, "continuous") ->
> +start_sending_changes(_Callback, UserAcc, ResponseType)
> +        when ResponseType =:= "continuous"
> +        orelse ResponseType =:= "eventsource" ->
>     UserAcc;
>  start_sending_changes(Callback, UserAcc, ResponseType) ->
>     Callback(start, ResponseType, UserAcc).
> @@ -434,7 +437,9 @@ keep_sending_changes(Args, Acc0, FirstRound) ->
>  end_sending_changes(Callback, UserAcc, EndSeq, ResponseType) ->
>     Callback({stop, EndSeq}, ResponseType, UserAcc).
>
> -changes_enumerator(DocInfo, #changes_acc{resp_type = "continuous"} = Acc) ->
> +changes_enumerator(DocInfo, #changes_acc{resp_type = ResponseType} = Acc)
> +        when ResponseType =:= "continuous"
> +        orelse ResponseType =:= "eventsource" ->
>     #changes_acc{
>         filter = FilterFun, callback = Callback,
>         user_acc = UserAcc, limit = Limit, db = Db,
> @@ -456,7 +461,7 @@ changes_enumerator(DocInfo, #changes_acc{resp_type = "continuous"} = Acc) ->
>         end;
>     _ ->
>         ChangesRow = changes_row(Results, DocInfo, Acc),
> -        UserAcc2 = Callback({change, ChangesRow, <<>>}, "continuous", UserAcc),
> +        UserAcc2 = Callback({change, ChangesRow, <<>>}, ResponseType, UserAcc),
>         reset_heartbeat(),
>         {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2, limit = Limit - 1}}
>     end;
>
> http://git-wip-us.apache.org/repos/asf/couchdb/blob/093d2aa6/src/couchdb/couch_httpd_db.erl
> ----------------------------------------------------------------------
> diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
> index de39b9e..0920014 100644
> --- a/src/couchdb/couch_httpd_db.erl
> +++ b/src/couchdb/couch_httpd_db.erl
> @@ -76,14 +76,23 @@ handle_changes_req1(Req, #db{name=DbName}=Db) ->
>
>  handle_changes_req2(Req, Db) ->
>     MakeCallback = fun(Resp) ->
> -        fun({change, Change, _}, "continuous") ->
> +        fun({change, {ChangeProp}=Change, _}, "eventsource") ->
> +            Seq = proplists:get_value(<<"seq">>, ChangeProp),
> +            send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
> +                              "\n", "id: ", ?JSON_ENCODE(Seq),
> +                              "\n\n"]);
> +        ({change, Change, _}, "continuous") ->
>             send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
>         ({change, Change, Prepend}, _) ->
>             send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
> +        (start, "eventsource") ->
> +            ok;
>         (start, "continuous") ->
>             ok;
>         (start, _) ->
>             send_chunk(Resp, "{\"results\":[\n");
> +        ({stop, _EndSeq}, "eventsource") ->
> +            end_json_response(Resp);
>         ({stop, EndSeq}, "continuous") ->
>             send_chunk(
>                 Resp,
> @@ -118,6 +127,15 @@ handle_changes_req2(Req, Db) ->
>                 end
>             )
>         end;
> +    "eventsource" ->
> +        Headers = [
> +            {"Content-Type", "text/event-stream"},
> +            {"Cache-Control", "no-cache"}
> +        ],
> +        {ok, Resp} = couch_httpd:start_json_response(Req, 200, Headers),
> +        fun(FeedChangesFun) ->
> +            FeedChangesFun(MakeCallback(Resp))
> +        end;
>     _ ->
>         % "longpoll" or "continuous"
>         {ok, Resp} = couch_httpd:start_json_response(Req, 200),
> @@ -1097,13 +1115,15 @@ parse_doc_query(Req) ->
>
>  parse_changes_query(Req) ->
>     lists:foldl(fun({Key, Value}, Args) ->
> -        case {Key, Value} of
> +        case {string:to_lower(Key), Value} of
>         {"feed", _} ->
>             Args#changes_args{feed=Value};
>         {"descending", "true"} ->
>             Args#changes_args{dir=rev};
>         {"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", _} ->
>

Re: git commit: add Server-Sent Events protocol to db changes API. close #COUCHDB-986

Posted by Benoit Chesneau <bc...@gmail.com>.
On Wed, May 16, 2012 at 7:44 AM, Benoit Chesneau <bc...@gmail.com> wrote:
> On Wed, May 16, 2012 at 7:42 AM, Paul Davis <pa...@gmail.com> wrote:
>> That JS test looks broken for JS environments that don't support
>> EventSource. Notably the CLI test suite but also browsers that don't
>> implement it. I'd either move the entire test into the conditional or
>> reimplement it by parsing the actual data sent across.
>>
>
>
> well there is an if() . Isn't it enough ?
>
> - benoit

i've changed the test from `if (window.EventSource) {`  to `if
(!!window.EventSource) {` so every browser should be happy.

(I blame js to have a weird syntax)

- benoit

Re: git commit: add Server-Sent Events protocol to db changes API. close #COUCHDB-986

Posted by Benoit Chesneau <bc...@gmail.com>.
On Thu, May 17, 2012 at 3:21 PM, Jan Lehnardt <ja...@apache.org> wrote:
>
> On May 16, 2012, at 08:25 , Paul Davis wrote:
>
>>> It should be better on latest change witch take the undefined bit in
>>> consideration. CLI tests pass here.
>>
>> Good enough for me.
>
> with the if() around it, the test may pass on the CLI, but the particular
> EventSource test code is never run. Or are you confirming that the the
> test code actually runs on the CLI?
>
The code will never be abble to run on cli. Except if we emulate the
EventSource object. What we could do however is testing also the
return using an etap test.

- benoit

Re: git commit: add Server-Sent Events protocol to db changes API. close #COUCHDB-986

Posted by Jan Lehnardt <ja...@apache.org>.
On May 16, 2012, at 08:25 , Paul Davis wrote:

>> It should be better on latest change witch take the undefined bit in
>> consideration. CLI tests pass here.
> 
> Good enough for me.

with the if() around it, the test may pass on the CLI, but the particular
EventSource test code is never run. Or are you confirming that the the
test code actually runs on the CLI?

Cheers
Jan
-- 


Re: git commit: add Server-Sent Events protocol to db changes API. close #COUCHDB-986

Posted by Paul Davis <pa...@gmail.com>.
> It should be better on latest change witch take the undefined bit in
> consideration. CLI tests pass here.

Good enough for me.

Re: git commit: add Server-Sent Events protocol to db changes API. close #COUCHDB-986

Posted by Benoit Chesneau <bc...@gmail.com>.
On Wed, May 16, 2012 at 7:49 AM, Paul Davis <pa...@gmail.com> wrote:
> Oh, I missed the scoping a bit on that. Should be for browsers, but
> for the CLI tests I wouldn't be surprised if that fails with a "no
> property named EventSource on undefined" or whatever. I'd just add a
> if(window && window.EventSource) to fix if that's the case.
>
> Though we should add a ticket to revisit this since we're
> theoretically moving to the CLI tests, no point having test code that
> never runs and all that.
>

It should be better on latest change witch take the undefined bit in
consideration. CLI tests pass here.

- benoit

Re: git commit: add Server-Sent Events protocol to db changes API. close #COUCHDB-986

Posted by Paul Davis <pa...@gmail.com>.
Oh, I missed the scoping a bit on that. Should be for browsers, but
for the CLI tests I wouldn't be surprised if that fails with a "no
property named EventSource on undefined" or whatever. I'd just add a
if(window && window.EventSource) to fix if that's the case.

Though we should add a ticket to revisit this since we're
theoretically moving to the CLI tests, no point having test code that
never runs and all that.

On Wed, May 16, 2012 at 12:44 AM, Benoit Chesneau <bc...@gmail.com> wrote:
> On Wed, May 16, 2012 at 7:42 AM, Paul Davis <pa...@gmail.com> wrote:
>> That JS test looks broken for JS environments that don't support
>> EventSource. Notably the CLI test suite but also browsers that don't
>> implement it. I'd either move the entire test into the conditional or
>> reimplement it by parsing the actual data sent across.
>>
>
>
> well there is an if() . Isn't it enough ?
>
> - benoit
>> On Wed, May 16, 2012 at 12:36 AM,  <be...@apache.org> wrote:
>>> Updated Branches:
>>>  refs/heads/master af7441d8d -> 093d2aa65
>>>
>>>
>>> add Server-Sent Events protocol to db changes API. close #COUCHDB-986
>>>
>>> This patch add support for the new specification of w3c by adding a new
>>> feed type named `eventsource`:
>>>
>>> http://www.w3.org/TR/2009/WD-eventsource-20090423/
>>>
>>> This patch is based on @indutny patch with edits.
>>>
>>>
>>> Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
>>> Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/093d2aa6
>>> Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/093d2aa6
>>> Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/093d2aa6
>>>
>>> Branch: refs/heads/master
>>> Commit: 093d2aa6544546a95f6133f1db3c4f4179793f3c
>>> Parents: af7441d
>>> Author: benoitc <be...@apache.org>
>>> Authored: Wed May 16 07:30:19 2012 +0200
>>> Committer: benoitc <be...@apache.org>
>>> Committed: Wed May 16 07:30:19 2012 +0200
>>>
>>> ----------------------------------------------------------------------
>>>  share/www/script/test/changes.js |   28 ++++++++++++++++++++++++++++
>>>  src/couchdb/couch_changes.erl    |   15 ++++++++++-----
>>>  src/couchdb/couch_httpd_db.erl   |   24 ++++++++++++++++++++++--
>>>  3 files changed, 60 insertions(+), 7 deletions(-)
>>> ----------------------------------------------------------------------
>>>
>>>
>>> http://git-wip-us.apache.org/repos/asf/couchdb/blob/093d2aa6/share/www/script/test/changes.js
>>> ----------------------------------------------------------------------
>>> diff --git a/share/www/script/test/changes.js b/share/www/script/test/changes.js
>>> index 19e22fd..c529b21 100644
>>> --- a/share/www/script/test/changes.js
>>> +++ b/share/www/script/test/changes.js
>>> @@ -139,6 +139,34 @@ couchTests.changes = function(debug) {
>>>     // otherwise we'll continue to receive heartbeats forever
>>>     xhr.abort();
>>>
>>> +    // test Server Sent Event (eventsource)
>>> +    if (window.EventSource) {
>>> +      var source = new EventSource(
>>> +              "/test_suite_db/_changes?feed=eventsource");
>>> +      var results = [];
>>> +      var sourceListener = function(e) {
>>> +        var data = JSON.parse(e.data);
>>> +        results.push(data);
>>> +
>>> +      };
>>> +
>>> +      source.addEventListener('message', sourceListener , false);
>>> +
>>> +      waitForSuccess(function() {
>>> +        if (results.length != 3)
>>> +          throw "bad seq, try again";
>>> +      });
>>> +
>>> +      source.removeEventListener('message', sourceListener, false);
>>> +
>>> +      T(results[0].seq == 1);
>>> +      T(results[0].id == "foo");
>>> +
>>> +      T(results[1].seq == 2);
>>> +      T(results[1].id == "bar");
>>> +      T(results[1].changes[0].rev == docBar._rev);
>>> +    }
>>> +
>>>     // test longpolling
>>>     xhr = CouchDB.newXhr();
>>>
>>>
>>> http://git-wip-us.apache.org/repos/asf/couchdb/blob/093d2aa6/src/couchdb/couch_changes.erl
>>> ----------------------------------------------------------------------
>>> diff --git a/src/couchdb/couch_changes.erl b/src/couchdb/couch_changes.erl
>>> index aec7873..85c9e54 100644
>>> --- a/src/couchdb/couch_changes.erl
>>> +++ b/src/couchdb/couch_changes.erl
>>> @@ -63,7 +63,8 @@ handle_changes(Args1, Req, Db0) ->
>>>         put(last_changes_heartbeat, now())
>>>     end,
>>>
>>> -    if Feed == "continuous" orelse Feed == "longpoll" ->
>>> +    case lists:member(Feed, ["continuous", "longpoll", "eventsource"]) of
>>> +    true ->
>>>         fun(CallbackAcc) ->
>>>             {Callback, UserAcc} = get_callback_acc(CallbackAcc),
>>>             Self = self(),
>>> @@ -89,7 +90,7 @@ handle_changes(Args1, Req, Db0) ->
>>>                 get_rest_db_updated(ok) % clean out any remaining update messages
>>>             end
>>>         end;
>>> -    true ->
>>> +    false ->
>>>         fun(CallbackAcc) ->
>>>             {Callback, UserAcc} = get_callback_acc(CallbackAcc),
>>>             UserAcc2 = start_sending_changes(Callback, UserAcc, Feed),
>>> @@ -261,7 +262,9 @@ get_changes_timeout(Args, Callback) ->
>>>             fun(UserAcc) -> {ok, Callback(timeout, ResponseType, UserAcc)} end}
>>>     end.
>>>
>>> -start_sending_changes(_Callback, UserAcc, "continuous") ->
>>> +start_sending_changes(_Callback, UserAcc, ResponseType)
>>> +        when ResponseType =:= "continuous"
>>> +        orelse ResponseType =:= "eventsource" ->
>>>     UserAcc;
>>>  start_sending_changes(Callback, UserAcc, ResponseType) ->
>>>     Callback(start, ResponseType, UserAcc).
>>> @@ -434,7 +437,9 @@ keep_sending_changes(Args, Acc0, FirstRound) ->
>>>  end_sending_changes(Callback, UserAcc, EndSeq, ResponseType) ->
>>>     Callback({stop, EndSeq}, ResponseType, UserAcc).
>>>
>>> -changes_enumerator(DocInfo, #changes_acc{resp_type = "continuous"} = Acc) ->
>>> +changes_enumerator(DocInfo, #changes_acc{resp_type = ResponseType} = Acc)
>>> +        when ResponseType =:= "continuous"
>>> +        orelse ResponseType =:= "eventsource" ->
>>>     #changes_acc{
>>>         filter = FilterFun, callback = Callback,
>>>         user_acc = UserAcc, limit = Limit, db = Db,
>>> @@ -456,7 +461,7 @@ changes_enumerator(DocInfo, #changes_acc{resp_type = "continuous"} = Acc) ->
>>>         end;
>>>     _ ->
>>>         ChangesRow = changes_row(Results, DocInfo, Acc),
>>> -        UserAcc2 = Callback({change, ChangesRow, <<>>}, "continuous", UserAcc),
>>> +        UserAcc2 = Callback({change, ChangesRow, <<>>}, ResponseType, UserAcc),
>>>         reset_heartbeat(),
>>>         {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2, limit = Limit - 1}}
>>>     end;
>>>
>>> http://git-wip-us.apache.org/repos/asf/couchdb/blob/093d2aa6/src/couchdb/couch_httpd_db.erl
>>> ----------------------------------------------------------------------
>>> diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
>>> index de39b9e..0920014 100644
>>> --- a/src/couchdb/couch_httpd_db.erl
>>> +++ b/src/couchdb/couch_httpd_db.erl
>>> @@ -76,14 +76,23 @@ handle_changes_req1(Req, #db{name=DbName}=Db) ->
>>>
>>>  handle_changes_req2(Req, Db) ->
>>>     MakeCallback = fun(Resp) ->
>>> -        fun({change, Change, _}, "continuous") ->
>>> +        fun({change, {ChangeProp}=Change, _}, "eventsource") ->
>>> +            Seq = proplists:get_value(<<"seq">>, ChangeProp),
>>> +            send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
>>> +                              "\n", "id: ", ?JSON_ENCODE(Seq),
>>> +                              "\n\n"]);
>>> +        ({change, Change, _}, "continuous") ->
>>>             send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
>>>         ({change, Change, Prepend}, _) ->
>>>             send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
>>> +        (start, "eventsource") ->
>>> +            ok;
>>>         (start, "continuous") ->
>>>             ok;
>>>         (start, _) ->
>>>             send_chunk(Resp, "{\"results\":[\n");
>>> +        ({stop, _EndSeq}, "eventsource") ->
>>> +            end_json_response(Resp);
>>>         ({stop, EndSeq}, "continuous") ->
>>>             send_chunk(
>>>                 Resp,
>>> @@ -118,6 +127,15 @@ handle_changes_req2(Req, Db) ->
>>>                 end
>>>             )
>>>         end;
>>> +    "eventsource" ->
>>> +        Headers = [
>>> +            {"Content-Type", "text/event-stream"},
>>> +            {"Cache-Control", "no-cache"}
>>> +        ],
>>> +        {ok, Resp} = couch_httpd:start_json_response(Req, 200, Headers),
>>> +        fun(FeedChangesFun) ->
>>> +            FeedChangesFun(MakeCallback(Resp))
>>> +        end;
>>>     _ ->
>>>         % "longpoll" or "continuous"
>>>         {ok, Resp} = couch_httpd:start_json_response(Req, 200),
>>> @@ -1097,13 +1115,15 @@ parse_doc_query(Req) ->
>>>
>>>  parse_changes_query(Req) ->
>>>     lists:foldl(fun({Key, Value}, Args) ->
>>> -        case {Key, Value} of
>>> +        case {string:to_lower(Key), Value} of
>>>         {"feed", _} ->
>>>             Args#changes_args{feed=Value};
>>>         {"descending", "true"} ->
>>>             Args#changes_args{dir=rev};
>>>         {"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", _} ->
>>>

Re: git commit: add Server-Sent Events protocol to db changes API. close #COUCHDB-986

Posted by Benoit Chesneau <bc...@gmail.com>.
On Wed, May 16, 2012 at 7:42 AM, Paul Davis <pa...@gmail.com> wrote:
> That JS test looks broken for JS environments that don't support
> EventSource. Notably the CLI test suite but also browsers that don't
> implement it. I'd either move the entire test into the conditional or
> reimplement it by parsing the actual data sent across.
>


well there is an if() . Isn't it enough ?

- benoit
> On Wed, May 16, 2012 at 12:36 AM,  <be...@apache.org> wrote:
>> Updated Branches:
>>  refs/heads/master af7441d8d -> 093d2aa65
>>
>>
>> add Server-Sent Events protocol to db changes API. close #COUCHDB-986
>>
>> This patch add support for the new specification of w3c by adding a new
>> feed type named `eventsource`:
>>
>> http://www.w3.org/TR/2009/WD-eventsource-20090423/
>>
>> This patch is based on @indutny patch with edits.
>>
>>
>> Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
>> Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/093d2aa6
>> Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/093d2aa6
>> Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/093d2aa6
>>
>> Branch: refs/heads/master
>> Commit: 093d2aa6544546a95f6133f1db3c4f4179793f3c
>> Parents: af7441d
>> Author: benoitc <be...@apache.org>
>> Authored: Wed May 16 07:30:19 2012 +0200
>> Committer: benoitc <be...@apache.org>
>> Committed: Wed May 16 07:30:19 2012 +0200
>>
>> ----------------------------------------------------------------------
>>  share/www/script/test/changes.js |   28 ++++++++++++++++++++++++++++
>>  src/couchdb/couch_changes.erl    |   15 ++++++++++-----
>>  src/couchdb/couch_httpd_db.erl   |   24 ++++++++++++++++++++++--
>>  3 files changed, 60 insertions(+), 7 deletions(-)
>> ----------------------------------------------------------------------
>>
>>
>> http://git-wip-us.apache.org/repos/asf/couchdb/blob/093d2aa6/share/www/script/test/changes.js
>> ----------------------------------------------------------------------
>> diff --git a/share/www/script/test/changes.js b/share/www/script/test/changes.js
>> index 19e22fd..c529b21 100644
>> --- a/share/www/script/test/changes.js
>> +++ b/share/www/script/test/changes.js
>> @@ -139,6 +139,34 @@ couchTests.changes = function(debug) {
>>     // otherwise we'll continue to receive heartbeats forever
>>     xhr.abort();
>>
>> +    // test Server Sent Event (eventsource)
>> +    if (window.EventSource) {
>> +      var source = new EventSource(
>> +              "/test_suite_db/_changes?feed=eventsource");
>> +      var results = [];
>> +      var sourceListener = function(e) {
>> +        var data = JSON.parse(e.data);
>> +        results.push(data);
>> +
>> +      };
>> +
>> +      source.addEventListener('message', sourceListener , false);
>> +
>> +      waitForSuccess(function() {
>> +        if (results.length != 3)
>> +          throw "bad seq, try again";
>> +      });
>> +
>> +      source.removeEventListener('message', sourceListener, false);
>> +
>> +      T(results[0].seq == 1);
>> +      T(results[0].id == "foo");
>> +
>> +      T(results[1].seq == 2);
>> +      T(results[1].id == "bar");
>> +      T(results[1].changes[0].rev == docBar._rev);
>> +    }
>> +
>>     // test longpolling
>>     xhr = CouchDB.newXhr();
>>
>>
>> http://git-wip-us.apache.org/repos/asf/couchdb/blob/093d2aa6/src/couchdb/couch_changes.erl
>> ----------------------------------------------------------------------
>> diff --git a/src/couchdb/couch_changes.erl b/src/couchdb/couch_changes.erl
>> index aec7873..85c9e54 100644
>> --- a/src/couchdb/couch_changes.erl
>> +++ b/src/couchdb/couch_changes.erl
>> @@ -63,7 +63,8 @@ handle_changes(Args1, Req, Db0) ->
>>         put(last_changes_heartbeat, now())
>>     end,
>>
>> -    if Feed == "continuous" orelse Feed == "longpoll" ->
>> +    case lists:member(Feed, ["continuous", "longpoll", "eventsource"]) of
>> +    true ->
>>         fun(CallbackAcc) ->
>>             {Callback, UserAcc} = get_callback_acc(CallbackAcc),
>>             Self = self(),
>> @@ -89,7 +90,7 @@ handle_changes(Args1, Req, Db0) ->
>>                 get_rest_db_updated(ok) % clean out any remaining update messages
>>             end
>>         end;
>> -    true ->
>> +    false ->
>>         fun(CallbackAcc) ->
>>             {Callback, UserAcc} = get_callback_acc(CallbackAcc),
>>             UserAcc2 = start_sending_changes(Callback, UserAcc, Feed),
>> @@ -261,7 +262,9 @@ get_changes_timeout(Args, Callback) ->
>>             fun(UserAcc) -> {ok, Callback(timeout, ResponseType, UserAcc)} end}
>>     end.
>>
>> -start_sending_changes(_Callback, UserAcc, "continuous") ->
>> +start_sending_changes(_Callback, UserAcc, ResponseType)
>> +        when ResponseType =:= "continuous"
>> +        orelse ResponseType =:= "eventsource" ->
>>     UserAcc;
>>  start_sending_changes(Callback, UserAcc, ResponseType) ->
>>     Callback(start, ResponseType, UserAcc).
>> @@ -434,7 +437,9 @@ keep_sending_changes(Args, Acc0, FirstRound) ->
>>  end_sending_changes(Callback, UserAcc, EndSeq, ResponseType) ->
>>     Callback({stop, EndSeq}, ResponseType, UserAcc).
>>
>> -changes_enumerator(DocInfo, #changes_acc{resp_type = "continuous"} = Acc) ->
>> +changes_enumerator(DocInfo, #changes_acc{resp_type = ResponseType} = Acc)
>> +        when ResponseType =:= "continuous"
>> +        orelse ResponseType =:= "eventsource" ->
>>     #changes_acc{
>>         filter = FilterFun, callback = Callback,
>>         user_acc = UserAcc, limit = Limit, db = Db,
>> @@ -456,7 +461,7 @@ changes_enumerator(DocInfo, #changes_acc{resp_type = "continuous"} = Acc) ->
>>         end;
>>     _ ->
>>         ChangesRow = changes_row(Results, DocInfo, Acc),
>> -        UserAcc2 = Callback({change, ChangesRow, <<>>}, "continuous", UserAcc),
>> +        UserAcc2 = Callback({change, ChangesRow, <<>>}, ResponseType, UserAcc),
>>         reset_heartbeat(),
>>         {Go, Acc#changes_acc{seq = Seq, user_acc = UserAcc2, limit = Limit - 1}}
>>     end;
>>
>> http://git-wip-us.apache.org/repos/asf/couchdb/blob/093d2aa6/src/couchdb/couch_httpd_db.erl
>> ----------------------------------------------------------------------
>> diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
>> index de39b9e..0920014 100644
>> --- a/src/couchdb/couch_httpd_db.erl
>> +++ b/src/couchdb/couch_httpd_db.erl
>> @@ -76,14 +76,23 @@ handle_changes_req1(Req, #db{name=DbName}=Db) ->
>>
>>  handle_changes_req2(Req, Db) ->
>>     MakeCallback = fun(Resp) ->
>> -        fun({change, Change, _}, "continuous") ->
>> +        fun({change, {ChangeProp}=Change, _}, "eventsource") ->
>> +            Seq = proplists:get_value(<<"seq">>, ChangeProp),
>> +            send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
>> +                              "\n", "id: ", ?JSON_ENCODE(Seq),
>> +                              "\n\n"]);
>> +        ({change, Change, _}, "continuous") ->
>>             send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
>>         ({change, Change, Prepend}, _) ->
>>             send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
>> +        (start, "eventsource") ->
>> +            ok;
>>         (start, "continuous") ->
>>             ok;
>>         (start, _) ->
>>             send_chunk(Resp, "{\"results\":[\n");
>> +        ({stop, _EndSeq}, "eventsource") ->
>> +            end_json_response(Resp);
>>         ({stop, EndSeq}, "continuous") ->
>>             send_chunk(
>>                 Resp,
>> @@ -118,6 +127,15 @@ handle_changes_req2(Req, Db) ->
>>                 end
>>             )
>>         end;
>> +    "eventsource" ->
>> +        Headers = [
>> +            {"Content-Type", "text/event-stream"},
>> +            {"Cache-Control", "no-cache"}
>> +        ],
>> +        {ok, Resp} = couch_httpd:start_json_response(Req, 200, Headers),
>> +        fun(FeedChangesFun) ->
>> +            FeedChangesFun(MakeCallback(Resp))
>> +        end;
>>     _ ->
>>         % "longpoll" or "continuous"
>>         {ok, Resp} = couch_httpd:start_json_response(Req, 200),
>> @@ -1097,13 +1115,15 @@ parse_doc_query(Req) ->
>>
>>  parse_changes_query(Req) ->
>>     lists:foldl(fun({Key, Value}, Args) ->
>> -        case {Key, Value} of
>> +        case {string:to_lower(Key), Value} of
>>         {"feed", _} ->
>>             Args#changes_args{feed=Value};
>>         {"descending", "true"} ->
>>             Args#changes_args{dir=rev};
>>         {"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", _} ->
>>