2017-11-16 00:53:11 +01:00
|
|
|
import time
|
tornado: Rewrite Django integration to duplicate less code.
Since essentially the first use of Tornado in Zulip, we've been
maintaining our Tornado+Django system, AsyncDjangoHandler, with
several hundred lines of Django code copied into it.
The goal for that code was simple: We wanted a way to use our Django
middleware (for code sharing reasons) inside a Tornado process (since
we wanted to use Tornado for our async events system).
As part of the Django 2.2.x upgrade, I looked at upgrading this
implementation to be based off modern Django, and it's definitely
possible to do that:
* Continue forking load_middleware to save response middleware.
* Continue manually running the Django response middleware.
* Continue working out a hack involving copying all of _get_response
to change a couple lines allowing us our Tornado code to not
actually return the Django HttpResponse so we can long-poll. The
previous hack of returning None stopped being viable with the Django 2.2
MiddlewareMixin.__call__ implementation.
But I decided to take this opportunity to look at trying to avoid
copying material Django code, and there is a way to do it:
* Replace RespondAsynchronously with a response.asynchronous attribute
on the HttpResponse; this allows Django to run its normal plumbing
happily in a way that should be stable over time, and then we
proceed to discard the response inside the Tornado `get()` method to
implement long-polling. (Better yet might be raising an
exception?). This lets us eliminate maintaining a patched copy of
_get_response.
* Removing the @asynchronous decorator, which didn't add anything now
that we only have one API endpoint backend (with two frontend call
points) that could call into this. Combined with the last bullet,
this lets us remove a significant hack from our
never_cache_responses function.
* Calling the normal Django `get_response` method from zulip_finish
after creating a duplicate request to process, rather than writing
totally custom code to do that. This lets us eliminate maintaining
a patched copy of Django's load_middleware.
* Adding detailed comments explaining how this is supposed to work,
what problems we encounter, and how we solve various problems, which
is critical to being able to modify this code in the future.
A key advantage of these changes is that the exact same code should
work on Django 1.11, Django 2.2, and Django 3.x, because we're no
longer copying large blocks of core Django code and thus should be
much less vulnerable to refactors.
There may be a modest performance downside, in that we now run both
request and response middleware twice when longpolling (once for the
request we discard). We may be able to avoid the expensive part of
it, Zulip's own request/response middleware, with a bit of additional
custom code to save work for requests where we're planning to discard
the response. Profiling will be important to understanding what's
worth doing here.
2020-02-06 22:09:10 +01:00
|
|
|
from typing import Iterable, Optional, Sequence
|
2013-01-08 17:44:22 +01:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
import orjson
|
2017-11-16 00:53:11 +01:00
|
|
|
from django.http import HttpRequest, HttpResponse
|
2021-04-16 00:57:30 +02:00
|
|
|
from django.utils.translation import gettext as _
|
2013-01-23 22:25:22 +01:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.decorator import REQ, has_request_variables, internal_notify_view, process_client
|
2017-11-16 00:53:11 +01:00
|
|
|
from zerver.lib.response import json_error, json_success
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.validator import (
|
|
|
|
check_bool,
|
|
|
|
check_int,
|
|
|
|
check_list,
|
|
|
|
check_string,
|
|
|
|
to_non_negative_int,
|
|
|
|
)
|
2018-07-13 12:58:16 +02:00
|
|
|
from zerver.models import Client, UserProfile, get_client, get_user_profile_by_id
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.tornado.event_queue import fetch_events, get_client_descriptor, process_notification
|
2017-07-21 02:20:31 +02:00
|
|
|
from zerver.tornado.exceptions import BadEventQueueIdError
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.tornado.handlers import AsyncDjangoHandler
|
|
|
|
|
2013-01-08 17:44:22 +01:00
|
|
|
|
2017-04-18 18:56:19 +02:00
|
|
|
@internal_notify_view(True)
|
2017-10-26 11:38:28 +02:00
|
|
|
def notify(request: HttpRequest) -> HttpResponse:
|
2021-02-12 08:20:45 +01:00
|
|
|
process_notification(orjson.loads(request.POST["data"]))
|
2013-01-08 17:44:22 +01:00
|
|
|
return json_success()
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2013-11-19 23:11:30 +01:00
|
|
|
@has_request_variables
|
2021-02-12 08:19:30 +01:00
|
|
|
def cleanup_event_queue(
|
|
|
|
request: HttpRequest, user_profile: UserProfile, queue_id: str = REQ()
|
|
|
|
) -> HttpResponse:
|
2016-07-03 18:09:53 +02:00
|
|
|
client = get_client_descriptor(str(queue_id))
|
2013-11-19 23:11:30 +01:00
|
|
|
if client is None:
|
2017-07-25 22:17:55 +02:00
|
|
|
raise BadEventQueueIdError(queue_id)
|
2013-11-19 23:11:30 +01:00
|
|
|
if user_profile.id != client.user_profile_id:
|
2016-05-25 15:02:02 +02:00
|
|
|
return json_error(_("You are not authorized to access this queue"))
|
2021-02-12 08:20:45 +01:00
|
|
|
request._log_data["extra"] = f"[{queue_id}]"
|
2013-11-19 23:11:30 +01:00
|
|
|
client.cleanup()
|
|
|
|
return json_success()
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2018-07-13 12:58:16 +02:00
|
|
|
@internal_notify_view(True)
|
|
|
|
@has_request_variables
|
2021-02-12 08:19:30 +01:00
|
|
|
def get_events_internal(
|
2021-04-07 22:00:44 +02:00
|
|
|
request: HttpRequest, user_profile_id: int = REQ(json_validator=check_int)
|
2021-02-12 08:19:30 +01:00
|
|
|
) -> HttpResponse:
|
2018-07-13 12:58:16 +02:00
|
|
|
user_profile = get_user_profile_by_id(user_profile_id)
|
2020-03-09 11:39:20 +01:00
|
|
|
request._requestor_for_logs = user_profile.format_requestor_for_logs()
|
2018-07-13 12:58:16 +02:00
|
|
|
process_client(request, user_profile, client_name="internal")
|
tornado: Rewrite Django integration to duplicate less code.
Since essentially the first use of Tornado in Zulip, we've been
maintaining our Tornado+Django system, AsyncDjangoHandler, with
several hundred lines of Django code copied into it.
The goal for that code was simple: We wanted a way to use our Django
middleware (for code sharing reasons) inside a Tornado process (since
we wanted to use Tornado for our async events system).
As part of the Django 2.2.x upgrade, I looked at upgrading this
implementation to be based off modern Django, and it's definitely
possible to do that:
* Continue forking load_middleware to save response middleware.
* Continue manually running the Django response middleware.
* Continue working out a hack involving copying all of _get_response
to change a couple lines allowing us our Tornado code to not
actually return the Django HttpResponse so we can long-poll. The
previous hack of returning None stopped being viable with the Django 2.2
MiddlewareMixin.__call__ implementation.
But I decided to take this opportunity to look at trying to avoid
copying material Django code, and there is a way to do it:
* Replace RespondAsynchronously with a response.asynchronous attribute
on the HttpResponse; this allows Django to run its normal plumbing
happily in a way that should be stable over time, and then we
proceed to discard the response inside the Tornado `get()` method to
implement long-polling. (Better yet might be raising an
exception?). This lets us eliminate maintaining a patched copy of
_get_response.
* Removing the @asynchronous decorator, which didn't add anything now
that we only have one API endpoint backend (with two frontend call
points) that could call into this. Combined with the last bullet,
this lets us remove a significant hack from our
never_cache_responses function.
* Calling the normal Django `get_response` method from zulip_finish
after creating a duplicate request to process, rather than writing
totally custom code to do that. This lets us eliminate maintaining
a patched copy of Django's load_middleware.
* Adding detailed comments explaining how this is supposed to work,
what problems we encounter, and how we solve various problems, which
is critical to being able to modify this code in the future.
A key advantage of these changes is that the exact same code should
work on Django 1.11, Django 2.2, and Django 3.x, because we're no
longer copying large blocks of core Django code and thus should be
much less vulnerable to refactors.
There may be a modest performance downside, in that we now run both
request and response middleware twice when longpolling (once for the
request we discard). We may be able to avoid the expensive part of
it, Zulip's own request/response middleware, with a bit of additional
custom code to save work for requests where we're planning to discard
the response. Profiling will be important to understanding what's
worth doing here.
2020-02-06 22:09:10 +01:00
|
|
|
return get_events_backend(request, user_profile)
|
2018-07-13 12:58:16 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
tornado: Rewrite Django integration to duplicate less code.
Since essentially the first use of Tornado in Zulip, we've been
maintaining our Tornado+Django system, AsyncDjangoHandler, with
several hundred lines of Django code copied into it.
The goal for that code was simple: We wanted a way to use our Django
middleware (for code sharing reasons) inside a Tornado process (since
we wanted to use Tornado for our async events system).
As part of the Django 2.2.x upgrade, I looked at upgrading this
implementation to be based off modern Django, and it's definitely
possible to do that:
* Continue forking load_middleware to save response middleware.
* Continue manually running the Django response middleware.
* Continue working out a hack involving copying all of _get_response
to change a couple lines allowing us our Tornado code to not
actually return the Django HttpResponse so we can long-poll. The
previous hack of returning None stopped being viable with the Django 2.2
MiddlewareMixin.__call__ implementation.
But I decided to take this opportunity to look at trying to avoid
copying material Django code, and there is a way to do it:
* Replace RespondAsynchronously with a response.asynchronous attribute
on the HttpResponse; this allows Django to run its normal plumbing
happily in a way that should be stable over time, and then we
proceed to discard the response inside the Tornado `get()` method to
implement long-polling. (Better yet might be raising an
exception?). This lets us eliminate maintaining a patched copy of
_get_response.
* Removing the @asynchronous decorator, which didn't add anything now
that we only have one API endpoint backend (with two frontend call
points) that could call into this. Combined with the last bullet,
this lets us remove a significant hack from our
never_cache_responses function.
* Calling the normal Django `get_response` method from zulip_finish
after creating a duplicate request to process, rather than writing
totally custom code to do that. This lets us eliminate maintaining
a patched copy of Django's load_middleware.
* Adding detailed comments explaining how this is supposed to work,
what problems we encounter, and how we solve various problems, which
is critical to being able to modify this code in the future.
A key advantage of these changes is that the exact same code should
work on Django 1.11, Django 2.2, and Django 3.x, because we're no
longer copying large blocks of core Django code and thus should be
much less vulnerable to refactors.
There may be a modest performance downside, in that we now run both
request and response middleware twice when longpolling (once for the
request we discard). We may be able to avoid the expensive part of
it, Zulip's own request/response middleware, with a bit of additional
custom code to save work for requests where we're planning to discard
the response. Profiling will be important to understanding what's
worth doing here.
2020-02-06 22:09:10 +01:00
|
|
|
def get_events(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
|
|
|
return get_events_backend(request, user_profile)
|
2018-07-13 13:10:12 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2013-03-26 18:06:00 +01:00
|
|
|
@has_request_variables
|
2021-02-12 08:19:30 +01:00
|
|
|
def get_events_backend(
|
|
|
|
request: HttpRequest,
|
|
|
|
user_profile: UserProfile,
|
|
|
|
# user_client is intended only for internal Django=>Tornado requests
|
|
|
|
# and thus shouldn't be documented for external use.
|
|
|
|
user_client: Optional[Client] = REQ(
|
|
|
|
converter=get_client, default=None, intentionally_undocumented=True
|
|
|
|
),
|
|
|
|
last_event_id: Optional[int] = REQ(converter=int, default=None),
|
|
|
|
queue_id: Optional[str] = REQ(default=None),
|
|
|
|
# apply_markdown, client_gravatar, all_public_streams, and various
|
|
|
|
# other parameters are only used when registering a new queue via this
|
|
|
|
# endpoint. This is a feature used primarily by get_events_internal
|
|
|
|
# and not expected to be used by third-party clients.
|
|
|
|
apply_markdown: bool = REQ(
|
2021-04-07 22:00:44 +02:00
|
|
|
default=False, json_validator=check_bool, intentionally_undocumented=True
|
2021-02-12 08:19:30 +01:00
|
|
|
),
|
|
|
|
client_gravatar: bool = REQ(
|
2021-04-07 22:00:44 +02:00
|
|
|
default=False, json_validator=check_bool, intentionally_undocumented=True
|
|
|
|
),
|
|
|
|
slim_presence: bool = REQ(
|
|
|
|
default=False, json_validator=check_bool, intentionally_undocumented=True
|
2021-02-12 08:19:30 +01:00
|
|
|
),
|
|
|
|
all_public_streams: bool = REQ(
|
2021-04-07 22:00:44 +02:00
|
|
|
default=False, json_validator=check_bool, intentionally_undocumented=True
|
2021-02-12 08:19:30 +01:00
|
|
|
),
|
|
|
|
event_types: Optional[Sequence[str]] = REQ(
|
2021-04-07 22:00:44 +02:00
|
|
|
default=None, json_validator=check_list(check_string), intentionally_undocumented=True
|
2021-02-12 08:19:30 +01:00
|
|
|
),
|
2021-04-07 22:00:44 +02:00
|
|
|
dont_block: bool = REQ(default=False, json_validator=check_bool),
|
2021-02-12 08:19:30 +01:00
|
|
|
narrow: Iterable[Sequence[str]] = REQ(
|
2021-04-07 22:00:44 +02:00
|
|
|
default=[],
|
|
|
|
json_validator=check_list(check_list(check_string)),
|
|
|
|
intentionally_undocumented=True,
|
2021-02-12 08:19:30 +01:00
|
|
|
),
|
|
|
|
lifespan_secs: int = REQ(
|
|
|
|
default=0, converter=to_non_negative_int, intentionally_undocumented=True
|
|
|
|
),
|
|
|
|
bulk_message_deletion: bool = REQ(
|
2021-04-07 22:00:44 +02:00
|
|
|
default=False, json_validator=check_bool, intentionally_undocumented=True
|
2021-02-12 08:19:30 +01:00
|
|
|
),
|
|
|
|
) -> HttpResponse:
|
2021-04-08 22:14:31 +02:00
|
|
|
if all_public_streams and not user_profile.can_access_public_streams():
|
|
|
|
return json_error(_("User not authorized for this query"))
|
|
|
|
|
tornado: Rewrite Django integration to duplicate less code.
Since essentially the first use of Tornado in Zulip, we've been
maintaining our Tornado+Django system, AsyncDjangoHandler, with
several hundred lines of Django code copied into it.
The goal for that code was simple: We wanted a way to use our Django
middleware (for code sharing reasons) inside a Tornado process (since
we wanted to use Tornado for our async events system).
As part of the Django 2.2.x upgrade, I looked at upgrading this
implementation to be based off modern Django, and it's definitely
possible to do that:
* Continue forking load_middleware to save response middleware.
* Continue manually running the Django response middleware.
* Continue working out a hack involving copying all of _get_response
to change a couple lines allowing us our Tornado code to not
actually return the Django HttpResponse so we can long-poll. The
previous hack of returning None stopped being viable with the Django 2.2
MiddlewareMixin.__call__ implementation.
But I decided to take this opportunity to look at trying to avoid
copying material Django code, and there is a way to do it:
* Replace RespondAsynchronously with a response.asynchronous attribute
on the HttpResponse; this allows Django to run its normal plumbing
happily in a way that should be stable over time, and then we
proceed to discard the response inside the Tornado `get()` method to
implement long-polling. (Better yet might be raising an
exception?). This lets us eliminate maintaining a patched copy of
_get_response.
* Removing the @asynchronous decorator, which didn't add anything now
that we only have one API endpoint backend (with two frontend call
points) that could call into this. Combined with the last bullet,
this lets us remove a significant hack from our
never_cache_responses function.
* Calling the normal Django `get_response` method from zulip_finish
after creating a duplicate request to process, rather than writing
totally custom code to do that. This lets us eliminate maintaining
a patched copy of Django's load_middleware.
* Adding detailed comments explaining how this is supposed to work,
what problems we encounter, and how we solve various problems, which
is critical to being able to modify this code in the future.
A key advantage of these changes is that the exact same code should
work on Django 1.11, Django 2.2, and Django 3.x, because we're no
longer copying large blocks of core Django code and thus should be
much less vulnerable to refactors.
There may be a modest performance downside, in that we now run both
request and response middleware twice when longpolling (once for the
request we discard). We may be able to avoid the expensive part of
it, Zulip's own request/response middleware, with a bit of additional
custom code to save work for requests where we're planning to discard
the response. Profiling will be important to understanding what's
worth doing here.
2020-02-06 22:09:10 +01:00
|
|
|
# Extract the Tornado handler from the request
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
handler: AsyncDjangoHandler = request._tornado_handler
|
tornado: Rewrite Django integration to duplicate less code.
Since essentially the first use of Tornado in Zulip, we've been
maintaining our Tornado+Django system, AsyncDjangoHandler, with
several hundred lines of Django code copied into it.
The goal for that code was simple: We wanted a way to use our Django
middleware (for code sharing reasons) inside a Tornado process (since
we wanted to use Tornado for our async events system).
As part of the Django 2.2.x upgrade, I looked at upgrading this
implementation to be based off modern Django, and it's definitely
possible to do that:
* Continue forking load_middleware to save response middleware.
* Continue manually running the Django response middleware.
* Continue working out a hack involving copying all of _get_response
to change a couple lines allowing us our Tornado code to not
actually return the Django HttpResponse so we can long-poll. The
previous hack of returning None stopped being viable with the Django 2.2
MiddlewareMixin.__call__ implementation.
But I decided to take this opportunity to look at trying to avoid
copying material Django code, and there is a way to do it:
* Replace RespondAsynchronously with a response.asynchronous attribute
on the HttpResponse; this allows Django to run its normal plumbing
happily in a way that should be stable over time, and then we
proceed to discard the response inside the Tornado `get()` method to
implement long-polling. (Better yet might be raising an
exception?). This lets us eliminate maintaining a patched copy of
_get_response.
* Removing the @asynchronous decorator, which didn't add anything now
that we only have one API endpoint backend (with two frontend call
points) that could call into this. Combined with the last bullet,
this lets us remove a significant hack from our
never_cache_responses function.
* Calling the normal Django `get_response` method from zulip_finish
after creating a duplicate request to process, rather than writing
totally custom code to do that. This lets us eliminate maintaining
a patched copy of Django's load_middleware.
* Adding detailed comments explaining how this is supposed to work,
what problems we encounter, and how we solve various problems, which
is critical to being able to modify this code in the future.
A key advantage of these changes is that the exact same code should
work on Django 1.11, Django 2.2, and Django 3.x, because we're no
longer copying large blocks of core Django code and thus should be
much less vulnerable to refactors.
There may be a modest performance downside, in that we now run both
request and response middleware twice when longpolling (once for the
request we discard). We may be able to avoid the expensive part of
it, Zulip's own request/response middleware, with a bit of additional
custom code to save work for requests where we're planning to discard
the response. Profiling will be important to understanding what's
worth doing here.
2020-02-06 22:09:10 +01:00
|
|
|
|
2013-05-07 17:25:25 +02:00
|
|
|
if user_client is None:
|
2018-01-08 19:30:23 +01:00
|
|
|
valid_user_client = request.client
|
|
|
|
else:
|
|
|
|
valid_user_client = user_client
|
2013-05-07 17:25:25 +02:00
|
|
|
|
2014-01-28 18:11:08 +01:00
|
|
|
events_query = dict(
|
2021-02-12 08:19:30 +01:00
|
|
|
user_profile_id=user_profile.id,
|
|
|
|
queue_id=queue_id,
|
|
|
|
last_event_id=last_event_id,
|
|
|
|
event_types=event_types,
|
|
|
|
client_type_name=valid_user_client.name,
|
|
|
|
all_public_streams=all_public_streams,
|
|
|
|
lifespan_secs=lifespan_secs,
|
|
|
|
narrow=narrow,
|
|
|
|
dont_block=dont_block,
|
|
|
|
handler_id=handler.handler_id,
|
|
|
|
)
|
2014-01-28 18:11:08 +01:00
|
|
|
|
|
|
|
if queue_id is None:
|
2021-02-12 08:20:45 +01:00
|
|
|
events_query["new_queue_data"] = dict(
|
2021-02-12 08:19:30 +01:00
|
|
|
user_profile_id=user_profile.id,
|
|
|
|
realm_id=user_profile.realm_id,
|
|
|
|
event_types=event_types,
|
|
|
|
client_type_name=valid_user_client.name,
|
|
|
|
apply_markdown=apply_markdown,
|
|
|
|
client_gravatar=client_gravatar,
|
|
|
|
slim_presence=slim_presence,
|
|
|
|
all_public_streams=all_public_streams,
|
|
|
|
queue_timeout=lifespan_secs,
|
|
|
|
last_connection_time=time.time(),
|
|
|
|
narrow=narrow,
|
|
|
|
bulk_message_deletion=bulk_message_deletion,
|
|
|
|
)
|
2014-01-28 18:11:08 +01:00
|
|
|
|
2014-01-28 20:03:05 +01:00
|
|
|
result = fetch_events(events_query)
|
|
|
|
if "extra_log_data" in result:
|
2021-02-12 08:20:45 +01:00
|
|
|
request._log_data["extra"] = result["extra_log_data"]
|
2014-01-28 20:03:05 +01:00
|
|
|
|
|
|
|
if result["type"] == "async":
|
tornado: Rewrite Django integration to duplicate less code.
Since essentially the first use of Tornado in Zulip, we've been
maintaining our Tornado+Django system, AsyncDjangoHandler, with
several hundred lines of Django code copied into it.
The goal for that code was simple: We wanted a way to use our Django
middleware (for code sharing reasons) inside a Tornado process (since
we wanted to use Tornado for our async events system).
As part of the Django 2.2.x upgrade, I looked at upgrading this
implementation to be based off modern Django, and it's definitely
possible to do that:
* Continue forking load_middleware to save response middleware.
* Continue manually running the Django response middleware.
* Continue working out a hack involving copying all of _get_response
to change a couple lines allowing us our Tornado code to not
actually return the Django HttpResponse so we can long-poll. The
previous hack of returning None stopped being viable with the Django 2.2
MiddlewareMixin.__call__ implementation.
But I decided to take this opportunity to look at trying to avoid
copying material Django code, and there is a way to do it:
* Replace RespondAsynchronously with a response.asynchronous attribute
on the HttpResponse; this allows Django to run its normal plumbing
happily in a way that should be stable over time, and then we
proceed to discard the response inside the Tornado `get()` method to
implement long-polling. (Better yet might be raising an
exception?). This lets us eliminate maintaining a patched copy of
_get_response.
* Removing the @asynchronous decorator, which didn't add anything now
that we only have one API endpoint backend (with two frontend call
points) that could call into this. Combined with the last bullet,
this lets us remove a significant hack from our
never_cache_responses function.
* Calling the normal Django `get_response` method from zulip_finish
after creating a duplicate request to process, rather than writing
totally custom code to do that. This lets us eliminate maintaining
a patched copy of Django's load_middleware.
* Adding detailed comments explaining how this is supposed to work,
what problems we encounter, and how we solve various problems, which
is critical to being able to modify this code in the future.
A key advantage of these changes is that the exact same code should
work on Django 1.11, Django 2.2, and Django 3.x, because we're no
longer copying large blocks of core Django code and thus should be
much less vulnerable to refactors.
There may be a modest performance downside, in that we now run both
request and response middleware twice when longpolling (once for the
request we discard). We may be able to avoid the expensive part of
it, Zulip's own request/response middleware, with a bit of additional
custom code to save work for requests where we're planning to discard
the response. Profiling will be important to understanding what's
worth doing here.
2020-02-06 22:09:10 +01:00
|
|
|
# Mark this response with .asynchronous; this will result in
|
|
|
|
# Tornado discarding the response and instead long-polling the
|
|
|
|
# request. See zulip_finish for more design details.
|
2014-01-27 23:21:39 +01:00
|
|
|
handler._request = request
|
tornado: Rewrite Django integration to duplicate less code.
Since essentially the first use of Tornado in Zulip, we've been
maintaining our Tornado+Django system, AsyncDjangoHandler, with
several hundred lines of Django code copied into it.
The goal for that code was simple: We wanted a way to use our Django
middleware (for code sharing reasons) inside a Tornado process (since
we wanted to use Tornado for our async events system).
As part of the Django 2.2.x upgrade, I looked at upgrading this
implementation to be based off modern Django, and it's definitely
possible to do that:
* Continue forking load_middleware to save response middleware.
* Continue manually running the Django response middleware.
* Continue working out a hack involving copying all of _get_response
to change a couple lines allowing us our Tornado code to not
actually return the Django HttpResponse so we can long-poll. The
previous hack of returning None stopped being viable with the Django 2.2
MiddlewareMixin.__call__ implementation.
But I decided to take this opportunity to look at trying to avoid
copying material Django code, and there is a way to do it:
* Replace RespondAsynchronously with a response.asynchronous attribute
on the HttpResponse; this allows Django to run its normal plumbing
happily in a way that should be stable over time, and then we
proceed to discard the response inside the Tornado `get()` method to
implement long-polling. (Better yet might be raising an
exception?). This lets us eliminate maintaining a patched copy of
_get_response.
* Removing the @asynchronous decorator, which didn't add anything now
that we only have one API endpoint backend (with two frontend call
points) that could call into this. Combined with the last bullet,
this lets us remove a significant hack from our
never_cache_responses function.
* Calling the normal Django `get_response` method from zulip_finish
after creating a duplicate request to process, rather than writing
totally custom code to do that. This lets us eliminate maintaining
a patched copy of Django's load_middleware.
* Adding detailed comments explaining how this is supposed to work,
what problems we encounter, and how we solve various problems, which
is critical to being able to modify this code in the future.
A key advantage of these changes is that the exact same code should
work on Django 1.11, Django 2.2, and Django 3.x, because we're no
longer copying large blocks of core Django code and thus should be
much less vulnerable to refactors.
There may be a modest performance downside, in that we now run both
request and response middleware twice when longpolling (once for the
request we discard). We may be able to avoid the expensive part of
it, Zulip's own request/response middleware, with a bit of additional
custom code to save work for requests where we're planning to discard
the response. Profiling will be important to understanding what's
worth doing here.
2020-02-06 22:09:10 +01:00
|
|
|
response = json_success()
|
|
|
|
response.asynchronous = True
|
|
|
|
return response
|
2014-01-28 20:03:05 +01:00
|
|
|
if result["type"] == "error":
|
2017-07-25 22:17:55 +02:00
|
|
|
raise result["exception"]
|
2014-01-28 20:03:05 +01:00
|
|
|
return json_success(result["response"])
|