Return a special value to initiate longpolling

Fixes #261.

(imported from commit dd0312c3574c9ca924ab76edcb2fb6c03085be32)
This commit is contained in:
Keegan McAllister 2012-11-28 00:16:28 -05:00
parent 30933a9639
commit b5dccbfa87
4 changed files with 24 additions and 19 deletions

View File

@ -8,17 +8,18 @@ from django.conf import settings
from functools import wraps
import types
class _RespondAsynchronously(object):
pass
class TornadoAsyncException(Exception): pass
# Return RespondAsynchronously from an @asynchronous view if the
# response will be provided later by calling handler.finish(), or has
# already been provided this way. We use this for longpolling mode.
RespondAsynchronously = _RespondAsynchronously()
def asynchronous(method):
@wraps(method)
def wrapper(request, *args, **kwargs):
v = method(request, handler=request._tornado_handler, *args, **kwargs)
if v == None or type(v) == types.GeneratorType:
raise TornadoAsyncException
return v
return method(request, handler=request._tornado_handler, *args, **kwargs)
if getattr(method, 'csrf_exempt', False):
wrapper.csrf_exempt = True
return wrapper

View File

@ -190,13 +190,12 @@ class AsyncDjangoHandler(tornado.web.RequestHandler, base.BaseHandler):
break
if response is None:
from ...decorator import TornadoAsyncException
from ...decorator import RespondAsynchronously
try:
response = callback(request, *callback_args, **callback_kwargs)
except TornadoAsyncException, e:
# TODO: Maybe add debugging output here
return
if response is RespondAsynchronously:
return
except Exception, e:
# If the view raised an exception, run it through exception
# middleware, and if the exception middleware returns a

View File

@ -7,7 +7,7 @@ from django.db.models import Q
from zephyr.models import Message, UserProfile, Stream, Recipient, Subscription, \
filter_by_subscriptions, Realm, do_send_message, Client
from zephyr.views import json_get_updates, api_get_messages
from zephyr.decorator import TornadoAsyncException
from zephyr.decorator import RespondAsynchronously
from zephyr.lib.initial_password import initial_password, initial_api_key
import simplejson
@ -457,10 +457,7 @@ class GetUpdatesTest(AuthedTestCase):
post_data = {"last": str(1), "first": str(1)}
post_data.update(extra_post_data)
request = POSTRequestMock(post_data, user, callback)
# json_get_updates returns None, which raises an exception in the
# @asynchronous decorator, which raises a TornadoAsyncException. So this
# is expected, but should probably change.
self.assertRaises(TornadoAsyncException, view_func, request)
self.assertEquals(view_func(request), RespondAsynchronously)
def test_json_get_updates(self):
"""
@ -497,7 +494,7 @@ class GetUpdatesTest(AuthedTestCase):
messages.extend(data)
request = POSTRequestMock({"last": str(last_received), "first": "1"}, user, callback)
self.assertRaises(TornadoAsyncException, json_get_updates, request)
self.assertEquals(json_get_updates(request), RespondAsynchronously)
self.assertEquals(len(messages), 0)
def test_missing_last_received(self):
@ -514,7 +511,7 @@ class GetUpdatesTest(AuthedTestCase):
self.assertTrue(message.id > 1)
request = POSTRequestMock({}, user, callback)
self.assertRaises(TornadoAsyncException, json_get_updates, request)
self.assertEquals(json_get_updates(request), RespondAsynchronously)
class Runner(DjangoTestSuiteRunner):
option_list = (

View File

@ -22,7 +22,7 @@ from django.views.decorators.csrf import csrf_exempt
from zephyr.decorator import asynchronous, require_post, \
authenticated_api_view, authenticated_json_view, \
internal_notify_view, \
internal_notify_view, RespondAsynchronously, \
has_request_variables, POST
from zephyr.lib.query import last_n
from zephyr.lib.avatar import gravatar_hash
@ -447,7 +447,12 @@ def get_updates_backend(request, user_profile, handler, client_id,
dont_block, **kwargs)
if resp is not None:
send_with_safety_check(resp, handler, **kwargs)
return
# We have already invoked handler.finish(), so we bypass the usual view
# response path. We are "responding asynchronously" except that it
# already happened. This is slightly weird, but lets us share
# send_with_safety_check with the code below.
return RespondAsynchronously
# Enter long-polling mode.
#
@ -472,6 +477,9 @@ def get_updates_backend(request, user_profile, handler, client_id,
user_profile.add_receive_callback(handler.async_callback(cb))
user_profile.add_pointer_update_callback(handler.async_callback(cb))
# runtornado recognizes this special return value.
return RespondAsynchronously
def generate_client_id():
return base64.b16encode(os.urandom(16)).lower()