py3: Switch almost all shebang lines to use `python3`.
This causes `upgrade-zulip-from-git`, as well as a no-option run of
`tools/build-release-tarball`, to produce a Zulip install running
Python 3, rather than Python 2. In particular this means that the
virtualenv we create, in which all application code runs, is Python 3.
One shebang line, on `zulip-ec2-configure-interfaces`, explicitly
keeps Python 2, and at least one external ops script, `wal-e`, also
still runs on Python 2. See discussion on the respective previous
commits that made those explicit. There may also be some other
third-party scripts we use, outside of this source tree and running
outside our virtualenv, that still run on Python 2.
2017-08-02 23:15:16 +02:00
|
|
|
#!/usr/bin/env python3
|
2017-09-30 08:42:22 +02:00
|
|
|
import argparse
|
2016-10-12 15:09:32 +02:00
|
|
|
import os
|
2016-10-04 01:36:38 +02:00
|
|
|
import pwd
|
2012-11-09 20:59:43 +01:00
|
|
|
import signal
|
2016-10-12 15:09:32 +02:00
|
|
|
import subprocess
|
2013-10-23 19:12:03 +02:00
|
|
|
import sys
|
2016-10-12 15:09:32 +02:00
|
|
|
import traceback
|
2020-06-13 03:34:01 +02:00
|
|
|
from typing import Any, Callable, Generator, List, Sequence
|
2017-11-06 03:14:57 +01:00
|
|
|
from urllib.parse import urlunparse
|
2016-07-30 00:51:14 +02:00
|
|
|
|
2017-02-05 21:24:28 +01:00
|
|
|
# check for the venv
|
|
|
|
from lib import sanity_check
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2017-02-05 21:24:28 +01:00
|
|
|
sanity_check.check_venv(__file__)
|
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
from tornado import gen, httpclient, httputil, web
|
2016-10-12 15:09:32 +02:00
|
|
|
from tornado.ioloop import IOLoop
|
2012-10-10 00:16:25 +02:00
|
|
|
|
2020-05-01 13:01:18 +02:00
|
|
|
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
sys.path.insert(0, os.path.dirname(TOOLS_DIR))
|
2013-08-21 18:01:40 +02:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
from tools.lib.test_script import assert_provisioning_status_ok
|
|
|
|
|
2015-10-15 18:44:48 +02:00
|
|
|
if 'posix' in os.name and os.geteuid() == 0:
|
|
|
|
raise RuntimeError("run-dev.py should not be run as root.")
|
|
|
|
|
2020-05-01 13:01:18 +02:00
|
|
|
DESCRIPTION = '''
|
2012-10-10 00:16:25 +02:00
|
|
|
Starts the app listening on localhost, for local development.
|
|
|
|
|
|
|
|
This script launches the Django and Tornado servers, then runs a reverse proxy
|
|
|
|
which serves to both of them. After it's all up and running, browse to
|
|
|
|
|
|
|
|
http://localhost:9991/
|
|
|
|
|
|
|
|
Note that, while runserver and runtornado have the usual auto-restarting
|
|
|
|
behavior, the reverse proxy itself does *not* automatically restart on changes
|
|
|
|
to this file.
|
2020-05-01 13:01:18 +02:00
|
|
|
'''
|
2016-11-07 08:06:34 +01:00
|
|
|
|
2020-05-01 13:01:18 +02:00
|
|
|
parser = argparse.ArgumentParser(description=DESCRIPTION,
|
|
|
|
formatter_class=argparse.RawTextHelpFormatter)
|
2016-11-07 08:06:34 +01:00
|
|
|
|
2017-09-30 08:42:22 +02:00
|
|
|
parser.add_argument('--test',
|
|
|
|
action='store_true',
|
|
|
|
help='Use the testing database and ports')
|
|
|
|
parser.add_argument('--minify',
|
|
|
|
action='store_true',
|
|
|
|
help='Minifies assets for testing in dev')
|
|
|
|
parser.add_argument('--interface',
|
|
|
|
action='store',
|
|
|
|
default=None, help='Set the IP or hostname for the proxy to listen on')
|
|
|
|
parser.add_argument('--no-clear-memcached',
|
|
|
|
action='store_false', dest='clear_memcached',
|
|
|
|
default=True, help='Do not clear memcached')
|
2020-05-01 12:17:29 +02:00
|
|
|
parser.add_argument('--streamlined',
|
|
|
|
action="store_true",
|
|
|
|
default=False, help='Avoid thumbor, etc.')
|
2017-09-30 08:42:22 +02:00
|
|
|
parser.add_argument('--force',
|
|
|
|
action="store_true",
|
|
|
|
default=False, help='Run command despite possible problems.')
|
|
|
|
parser.add_argument('--enable-tornado-logging',
|
|
|
|
action="store_true",
|
|
|
|
default=False, help='Enable access logs from tornado proxy server.')
|
|
|
|
options = parser.parse_args()
|
2012-11-08 23:23:25 +01:00
|
|
|
|
2019-06-20 18:27:09 +02:00
|
|
|
assert_provisioning_status_ok(options.force)
|
2016-11-07 08:06:34 +01:00
|
|
|
|
2016-10-04 01:36:38 +02:00
|
|
|
if options.interface is None:
|
|
|
|
user_id = os.getuid()
|
|
|
|
user_name = pwd.getpwuid(user_id).pw_name
|
2016-11-20 22:30:28 +01:00
|
|
|
if user_name in ["vagrant", "zulipdev"]:
|
2016-10-04 01:36:38 +02:00
|
|
|
# In the Vagrant development environment, we need to listen on
|
|
|
|
# all ports, and it's safe to do so, because Vagrant is only
|
2016-11-20 22:30:28 +01:00
|
|
|
# exposing certain guest ports (by default just 9991) to the
|
|
|
|
# host. The same argument applies to the remote development
|
|
|
|
# servers using username "zulipdev".
|
|
|
|
options.interface = None
|
2016-10-04 01:36:38 +02:00
|
|
|
else:
|
|
|
|
# Otherwise, only listen to requests on localhost for security.
|
|
|
|
options.interface = "127.0.0.1"
|
2016-11-20 22:30:28 +01:00
|
|
|
elif options.interface == "":
|
|
|
|
options.interface = None
|
2016-10-04 01:36:38 +02:00
|
|
|
|
2020-04-22 01:09:50 +02:00
|
|
|
runserver_args: List[str] = []
|
2016-11-02 04:27:44 +01:00
|
|
|
base_port = 9991
|
2012-11-08 23:23:25 +01:00
|
|
|
if options.test:
|
2016-11-02 04:27:44 +01:00
|
|
|
base_port = 9981
|
2013-10-23 19:12:03 +02:00
|
|
|
settings_module = "zproject.test_settings"
|
2017-03-18 04:43:27 +01:00
|
|
|
# Don't auto-reload when running casper tests
|
|
|
|
runserver_args = ['--noreload']
|
2013-10-23 19:12:03 +02:00
|
|
|
else:
|
|
|
|
settings_module = "zproject.settings"
|
|
|
|
|
2020-06-10 06:41:04 +02:00
|
|
|
manage_args = [f'--settings={settings_module}']
|
2013-10-23 19:12:03 +02:00
|
|
|
os.environ['DJANGO_SETTINGS_MODULE'] = settings_module
|
|
|
|
|
|
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
2012-10-10 00:16:25 +02:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
from scripts.lib.zulip_tools import CYAN, ENDC, FAIL, WARNING
|
2017-03-30 15:12:44 +02:00
|
|
|
|
2016-11-02 04:27:44 +01:00
|
|
|
proxy_port = base_port
|
|
|
|
django_port = base_port + 1
|
|
|
|
tornado_port = base_port + 2
|
|
|
|
webpack_port = base_port + 3
|
2017-11-09 16:31:57 +01:00
|
|
|
thumbor_port = base_port + 4
|
2012-10-10 00:16:25 +02:00
|
|
|
|
2013-10-28 15:54:32 +01:00
|
|
|
os.chdir(os.path.join(os.path.dirname(__file__), '..'))
|
2012-10-10 00:16:25 +02:00
|
|
|
|
2013-03-25 23:21:39 +01:00
|
|
|
# Clean up stale .pyc files etc.
|
|
|
|
subprocess.check_call('./tools/clean-repo')
|
|
|
|
|
2016-07-18 15:24:41 +02:00
|
|
|
if options.clear_memcached:
|
|
|
|
print("Clearing memcached ...")
|
|
|
|
subprocess.check_call('./scripts/setup/flush-memcached')
|
|
|
|
|
2012-11-09 20:59:43 +01:00
|
|
|
# Set up a new process group, so that we can later kill run{server,tornado}
|
|
|
|
# and all of the processes they spawn.
|
|
|
|
os.setpgrp()
|
|
|
|
|
2017-01-26 09:57:16 +01:00
|
|
|
# Save pid of parent process to the pid file. It can be used later by
|
|
|
|
# tools/stop-run-dev to kill the server without having to find the
|
|
|
|
# terminal in question.
|
2017-02-18 01:30:19 +01:00
|
|
|
|
|
|
|
if options.test:
|
|
|
|
pid_file_path = os.path.join(os.path.join(os.getcwd(), 'var/casper/run_dev.pid'))
|
|
|
|
else:
|
|
|
|
pid_file_path = os.path.join(os.path.join(os.getcwd(), 'var/run/run_dev.pid'))
|
2017-01-26 09:57:16 +01:00
|
|
|
|
|
|
|
# Required for compatibility python versions.
|
|
|
|
if not os.path.exists(os.path.dirname(pid_file_path)):
|
|
|
|
os.makedirs(os.path.dirname(pid_file_path))
|
2019-07-14 21:37:08 +02:00
|
|
|
with open(pid_file_path, 'w+') as f:
|
|
|
|
f.write(str(os.getpgrp()) + "\n")
|
2017-01-26 09:57:16 +01:00
|
|
|
|
2020-05-01 12:19:50 +02:00
|
|
|
def server_processes() -> List[List[str]]:
|
|
|
|
main_cmds = [
|
|
|
|
['./manage.py', 'runserver'] +
|
2020-06-13 08:59:37 +02:00
|
|
|
manage_args + runserver_args + [f'127.0.0.1:{django_port}'],
|
2016-11-22 01:44:16 +01:00
|
|
|
['env', 'PYTHONUNBUFFERED=1', './manage.py', 'runtornado'] +
|
2020-06-13 08:59:37 +02:00
|
|
|
manage_args + [f'127.0.0.1:{tornado_port}'],
|
2020-05-01 12:17:29 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
if options.streamlined:
|
|
|
|
# The streamlined operation allows us to do many
|
|
|
|
# things, but search/thumbor/etc. features won't work.
|
|
|
|
return main_cmds
|
|
|
|
|
|
|
|
other_cmds = [
|
2020-03-18 02:22:44 +01:00
|
|
|
['./manage.py', 'process_queue', '--all'] + manage_args,
|
2016-10-12 15:09:32 +02:00
|
|
|
['env', 'PGHOST=127.0.0.1', # Force password authentication using .pgpass
|
2020-05-01 20:51:18 +02:00
|
|
|
'./puppet/zulip/files/postgresql/process_fts_updates', '--quiet'],
|
2017-11-09 16:31:57 +01:00
|
|
|
['./manage.py', 'deliver_scheduled_messages'],
|
|
|
|
['/srv/zulip-thumbor-venv/bin/thumbor', '-c', './zthumbor/thumbor.conf',
|
2020-06-10 06:41:04 +02:00
|
|
|
'-p', f'{thumbor_port}'],
|
2020-05-01 12:19:50 +02:00
|
|
|
]
|
|
|
|
|
2020-05-01 12:17:29 +02:00
|
|
|
# NORMAL (but slower) operation:
|
|
|
|
return main_cmds + other_cmds
|
2020-05-01 12:19:50 +02:00
|
|
|
|
|
|
|
def do_one_time_webpack_compile() -> None:
|
2019-06-25 11:39:03 +02:00
|
|
|
# We just need to compile webpack assets once at startup, not run a daemon,
|
|
|
|
# in test mode. Additionally, webpack-dev-server doesn't support running 2
|
|
|
|
# copies on the same system, so this model lets us run the casper tests
|
|
|
|
# with a running development server.
|
tests: Clean up calls to tools/webpack.
Before this change, the way we loaded
webpack for various tools was brittle.
First, I addressed test-api and test-help-documentation.
These tools used to be unable to run standalone on a
clean provision, because they were (indirectly)
calling tools/webpack without the `--test` option.
The problem was a bit obscure, since running things
like `./tools/test-backend` or `./tools/test-all` in
your workflow would create `./var/webpack-stats-test.json`
for the broken tools (and then they would work).
The tools themselves weren't broken; they were the
only relying on the common `test_server_running` helper.
And even that helper wasn't broken; it was just that
`run-dev.py` wasn't respecting the `--test` option.
So I made it so that `./tools/run-dev` passes in `--test` to
`./tools/webpack`.
To confuse matters even more, for some reason Casper
uses `./webpack-stats-production.json` via various
hacks for its webpack configuration, so when I fixed
the other tests, it broke Casper.
Here is the Casper-related hack in zproject/test_settings.py,
which was in place before my change and remains
after it:
if CASPER_TESTS:
WEBPACK_FILE = 'webpack-stats-production.json'
else:
WEBPACK_FILE = os.path.join('var', 'webpack-stats-test.json')
I added similar logic in tools/webpack:
if "CASPER_TESTS" in os.environ:
build_for_prod_or_casper(args.quiet)
I also made the helper functions in `./tools/webpack` have
nicer names.
So, now tools should all be able to run standalone and not
rely on previous tools creating webpack stats files for
them and leaving them in the file system. That's good.
Things are still a bit janky, though. It's not completely
clear to me why `test-js-with-casper` should work off of
a different webpack configuration than the other tests.
For now most of the jankiness is around Casper, and we have
hacks in two different places, `zproject/test_settings.py` and
`tools/webpack` to force it to use the production stats
file instead of the "test" one, even though Casper uses
test-like settings for other things like which database
you're using.
2018-09-06 21:01:45 +02:00
|
|
|
subprocess.check_call(['./tools/webpack', '--quiet', '--test'])
|
2020-05-01 12:19:50 +02:00
|
|
|
|
|
|
|
def start_webpack_watcher() -> None:
|
2017-06-10 20:28:48 +02:00
|
|
|
webpack_cmd = ['./tools/webpack', '--watch', '--port', str(webpack_port)]
|
|
|
|
if options.minify:
|
|
|
|
webpack_cmd.append('--minify')
|
2018-04-24 15:27:15 +02:00
|
|
|
if options.interface is None:
|
|
|
|
# If interface is None and we're listening on all ports, we also need
|
|
|
|
# to disable the webpack host check so that webpack will serve assets.
|
|
|
|
webpack_cmd.append('--disable-host-check')
|
2017-07-16 21:14:03 +02:00
|
|
|
if options.interface:
|
|
|
|
webpack_cmd += ["--host", options.interface]
|
|
|
|
else:
|
|
|
|
webpack_cmd += ["--host", "0.0.0.0"]
|
2020-05-01 12:19:50 +02:00
|
|
|
subprocess.Popen(webpack_cmd)
|
2012-10-10 00:16:25 +02:00
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def transform_url(protocol: str, path: str, query: str, target_port: int, target_host: str) -> str:
|
2016-10-12 15:09:32 +02:00
|
|
|
# generate url with target host
|
|
|
|
host = ":".join((target_host, str(target_port)))
|
2017-11-09 16:31:57 +01:00
|
|
|
# Here we are going to rewrite the path a bit so that it is in parity with
|
|
|
|
# what we will have for production
|
|
|
|
if path.startswith('/thumbor'):
|
|
|
|
path = path[len('/thumbor'):]
|
2016-10-12 15:09:32 +02:00
|
|
|
newpath = urlunparse((protocol, host, path, '', query, ''))
|
|
|
|
return newpath
|
|
|
|
|
|
|
|
@gen.engine
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
|
2016-10-12 15:09:32 +02:00
|
|
|
# use large timeouts to handle polling requests
|
2019-09-24 23:16:07 +02:00
|
|
|
req = httpclient.HTTPRequest(
|
|
|
|
url,
|
|
|
|
connect_timeout=240.0,
|
|
|
|
request_timeout=240.0,
|
|
|
|
decompress_response=False,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
**kwargs,
|
2019-09-24 23:16:07 +02:00
|
|
|
)
|
2016-10-12 15:09:32 +02:00
|
|
|
client = httpclient.AsyncHTTPClient()
|
|
|
|
# wait for response
|
|
|
|
response = yield gen.Task(client.fetch, req)
|
|
|
|
callback(response)
|
|
|
|
|
|
|
|
|
dependencies: Remove WebSockets system for sending messages.
Zulip has had a small use of WebSockets (specifically, for the code
path of sending messages, via the webapp only) since ~2013. We
originally added this use of WebSockets in the hope that the latency
benefits of doing so would allow us to avoid implementing a markdown
local echo; they were not. Further, HTTP/2 may have eliminated the
latency difference we hoped to exploit by using WebSockets in any
case.
While we’d originally imagined using WebSockets for other endpoints,
there was never a good justification for moving more components to the
WebSockets system.
This WebSockets code path had a lot of downsides/complexity,
including:
* The messy hack involving constructing an emulated request object to
hook into doing Django requests.
* The `message_senders` queue processor system, which increases RAM
needs and must be provisioned independently from the rest of the
server).
* A duplicate check_send_receive_time Nagios test specific to
WebSockets.
* The requirement for users to have their firewalls/NATs allow
WebSocket connections, and a setting to disable them for networks
where WebSockets don’t work.
* Dependencies on the SockJS family of libraries, which has at times
been poorly maintained, and periodically throws random JavaScript
exceptions in our production environments without a deep enough
traceback to effectively investigate.
* A total of about 1600 lines of our code related to the feature.
* Increased load on the Tornado system, especially around a Zulip
server restart, and especially for large installations like
zulipchat.com, resulting in extra delay before messages can be sent
again.
As detailed in
https://github.com/zulip/zulip/pull/12862#issuecomment-536152397, it
appears that removing WebSockets moderately increases the time it
takes for the `send_message` API query to return from the server, but
does not significantly change the time between when a message is sent
and when it is received by clients. We don’t understand the reason
for that change (suggesting the possibility of a measurement error),
and even if it is a real change, we consider that potential small
latency regression to be acceptable.
If we later want WebSockets, we’ll likely want to just use Django
Channels.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-07-23 01:43:40 +02:00
|
|
|
class BaseHandler(web.RequestHandler):
|
2016-10-12 15:09:32 +02:00
|
|
|
# target server ip
|
2020-04-22 01:09:50 +02:00
|
|
|
target_host: str = '127.0.0.1'
|
2016-10-12 15:09:32 +02:00
|
|
|
# target server port
|
2020-04-22 01:09:50 +02:00
|
|
|
target_port: int
|
2016-10-12 15:09:32 +02:00
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def _add_request_headers(
|
2020-06-13 03:34:01 +02:00
|
|
|
self, exclude_lower_headers_list: Sequence[str] = [],
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
) -> httputil.HTTPHeaders:
|
2016-10-12 15:09:32 +02:00
|
|
|
headers = httputil.HTTPHeaders()
|
|
|
|
for header, v in self.request.headers.get_all():
|
|
|
|
if header.lower() not in exclude_lower_headers_list:
|
|
|
|
headers.add(header, v)
|
|
|
|
return headers
|
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def get(self) -> None:
|
dependencies: Remove WebSockets system for sending messages.
Zulip has had a small use of WebSockets (specifically, for the code
path of sending messages, via the webapp only) since ~2013. We
originally added this use of WebSockets in the hope that the latency
benefits of doing so would allow us to avoid implementing a markdown
local echo; they were not. Further, HTTP/2 may have eliminated the
latency difference we hoped to exploit by using WebSockets in any
case.
While we’d originally imagined using WebSockets for other endpoints,
there was never a good justification for moving more components to the
WebSockets system.
This WebSockets code path had a lot of downsides/complexity,
including:
* The messy hack involving constructing an emulated request object to
hook into doing Django requests.
* The `message_senders` queue processor system, which increases RAM
needs and must be provisioned independently from the rest of the
server).
* A duplicate check_send_receive_time Nagios test specific to
WebSockets.
* The requirement for users to have their firewalls/NATs allow
WebSocket connections, and a setting to disable them for networks
where WebSockets don’t work.
* Dependencies on the SockJS family of libraries, which has at times
been poorly maintained, and periodically throws random JavaScript
exceptions in our production environments without a deep enough
traceback to effectively investigate.
* A total of about 1600 lines of our code related to the feature.
* Increased load on the Tornado system, especially around a Zulip
server restart, and especially for large installations like
zulipchat.com, resulting in extra delay before messages can be sent
again.
As detailed in
https://github.com/zulip/zulip/pull/12862#issuecomment-536152397, it
appears that removing WebSockets moderately increases the time it
takes for the `send_message` API query to return from the server, but
does not significantly change the time between when a message is sent
and when it is received by clients. We don’t understand the reason
for that change (suggesting the possibility of a measurement error),
and even if it is a real change, we consider that potential small
latency regression to be acceptable.
If we later want WebSockets, we’ll likely want to just use Django
Channels.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-07-23 01:43:40 +02:00
|
|
|
pass
|
2016-10-12 15:09:32 +02:00
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def head(self) -> None:
|
2016-10-12 15:09:32 +02:00
|
|
|
pass
|
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def post(self) -> None:
|
2016-10-12 15:09:32 +02:00
|
|
|
pass
|
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def put(self) -> None:
|
2016-10-12 15:09:32 +02:00
|
|
|
pass
|
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def patch(self) -> None:
|
2016-10-12 15:09:32 +02:00
|
|
|
pass
|
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def options(self) -> None:
|
2016-10-12 15:09:32 +02:00
|
|
|
pass
|
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def delete(self) -> None:
|
2016-10-12 15:09:32 +02:00
|
|
|
pass
|
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def handle_response(self, response: Any) -> None:
|
2016-10-12 15:09:32 +02:00
|
|
|
if response.error and not isinstance(response.error, httpclient.HTTPError):
|
|
|
|
self.set_status(500)
|
|
|
|
self.write('Internal server error:\n' + str(response.error))
|
|
|
|
else:
|
|
|
|
self.set_status(response.code, response.reason)
|
|
|
|
self._headers = httputil.HTTPHeaders() # clear tornado default header
|
|
|
|
|
|
|
|
for header, v in response.headers.get_all():
|
2019-08-12 05:25:32 +02:00
|
|
|
# some header appear multiple times, eg 'Set-Cookie'
|
|
|
|
self.add_header(header, v)
|
2016-10-12 15:09:32 +02:00
|
|
|
if response.body:
|
|
|
|
self.write(response.body)
|
|
|
|
self.finish()
|
|
|
|
|
|
|
|
@web.asynchronous
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def prepare(self) -> None:
|
2016-12-21 11:40:22 +01:00
|
|
|
if 'X-REAL-IP' not in self.request.headers:
|
|
|
|
self.request.headers['X-REAL-IP'] = self.request.remote_ip
|
2019-10-07 06:16:51 +02:00
|
|
|
if 'X-FORWARDED_PORT' not in self.request.headers:
|
|
|
|
self.request.headers['X-FORWARDED-PORT'] = str(proxy_port)
|
2016-10-12 15:09:32 +02:00
|
|
|
url = transform_url(
|
|
|
|
self.request.protocol,
|
|
|
|
self.request.path,
|
|
|
|
self.request.query,
|
|
|
|
self.target_port,
|
|
|
|
self.target_host,
|
|
|
|
)
|
|
|
|
try:
|
|
|
|
fetch_request(
|
|
|
|
url=url,
|
|
|
|
callback=self.handle_response,
|
|
|
|
method=self.request.method,
|
|
|
|
headers=self._add_request_headers(["upgrade-insecure-requests"]),
|
|
|
|
follow_redirects=False,
|
|
|
|
body=getattr(self.request, 'body'),
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
allow_nonstandard_methods=True,
|
2016-10-12 15:09:32 +02:00
|
|
|
)
|
|
|
|
except httpclient.HTTPError as e:
|
|
|
|
if hasattr(e, 'response') and e.response:
|
|
|
|
self.handle_response(e.response)
|
|
|
|
else:
|
|
|
|
self.set_status(500)
|
|
|
|
self.write('Internal server error:\n' + str(e))
|
|
|
|
self.finish()
|
|
|
|
|
|
|
|
|
dependencies: Remove WebSockets system for sending messages.
Zulip has had a small use of WebSockets (specifically, for the code
path of sending messages, via the webapp only) since ~2013. We
originally added this use of WebSockets in the hope that the latency
benefits of doing so would allow us to avoid implementing a markdown
local echo; they were not. Further, HTTP/2 may have eliminated the
latency difference we hoped to exploit by using WebSockets in any
case.
While we’d originally imagined using WebSockets for other endpoints,
there was never a good justification for moving more components to the
WebSockets system.
This WebSockets code path had a lot of downsides/complexity,
including:
* The messy hack involving constructing an emulated request object to
hook into doing Django requests.
* The `message_senders` queue processor system, which increases RAM
needs and must be provisioned independently from the rest of the
server).
* A duplicate check_send_receive_time Nagios test specific to
WebSockets.
* The requirement for users to have their firewalls/NATs allow
WebSocket connections, and a setting to disable them for networks
where WebSockets don’t work.
* Dependencies on the SockJS family of libraries, which has at times
been poorly maintained, and periodically throws random JavaScript
exceptions in our production environments without a deep enough
traceback to effectively investigate.
* A total of about 1600 lines of our code related to the feature.
* Increased load on the Tornado system, especially around a Zulip
server restart, and especially for large installations like
zulipchat.com, resulting in extra delay before messages can be sent
again.
As detailed in
https://github.com/zulip/zulip/pull/12862#issuecomment-536152397, it
appears that removing WebSockets moderately increases the time it
takes for the `send_message` API query to return from the server, but
does not significantly change the time between when a message is sent
and when it is received by clients. We don’t understand the reason
for that change (suggesting the possibility of a measurement error),
and even if it is a real change, we consider that potential small
latency regression to be acceptable.
If we later want WebSockets, we’ll likely want to just use Django
Channels.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-07-23 01:43:40 +02:00
|
|
|
class WebPackHandler(BaseHandler):
|
2016-10-12 15:09:32 +02:00
|
|
|
target_port = webpack_port
|
|
|
|
|
|
|
|
|
dependencies: Remove WebSockets system for sending messages.
Zulip has had a small use of WebSockets (specifically, for the code
path of sending messages, via the webapp only) since ~2013. We
originally added this use of WebSockets in the hope that the latency
benefits of doing so would allow us to avoid implementing a markdown
local echo; they were not. Further, HTTP/2 may have eliminated the
latency difference we hoped to exploit by using WebSockets in any
case.
While we’d originally imagined using WebSockets for other endpoints,
there was never a good justification for moving more components to the
WebSockets system.
This WebSockets code path had a lot of downsides/complexity,
including:
* The messy hack involving constructing an emulated request object to
hook into doing Django requests.
* The `message_senders` queue processor system, which increases RAM
needs and must be provisioned independently from the rest of the
server).
* A duplicate check_send_receive_time Nagios test specific to
WebSockets.
* The requirement for users to have their firewalls/NATs allow
WebSocket connections, and a setting to disable them for networks
where WebSockets don’t work.
* Dependencies on the SockJS family of libraries, which has at times
been poorly maintained, and periodically throws random JavaScript
exceptions in our production environments without a deep enough
traceback to effectively investigate.
* A total of about 1600 lines of our code related to the feature.
* Increased load on the Tornado system, especially around a Zulip
server restart, and especially for large installations like
zulipchat.com, resulting in extra delay before messages can be sent
again.
As detailed in
https://github.com/zulip/zulip/pull/12862#issuecomment-536152397, it
appears that removing WebSockets moderately increases the time it
takes for the `send_message` API query to return from the server, but
does not significantly change the time between when a message is sent
and when it is received by clients. We don’t understand the reason
for that change (suggesting the possibility of a measurement error),
and even if it is a real change, we consider that potential small
latency regression to be acceptable.
If we later want WebSockets, we’ll likely want to just use Django
Channels.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-07-23 01:43:40 +02:00
|
|
|
class DjangoHandler(BaseHandler):
|
2016-10-12 15:09:32 +02:00
|
|
|
target_port = django_port
|
|
|
|
|
|
|
|
|
dependencies: Remove WebSockets system for sending messages.
Zulip has had a small use of WebSockets (specifically, for the code
path of sending messages, via the webapp only) since ~2013. We
originally added this use of WebSockets in the hope that the latency
benefits of doing so would allow us to avoid implementing a markdown
local echo; they were not. Further, HTTP/2 may have eliminated the
latency difference we hoped to exploit by using WebSockets in any
case.
While we’d originally imagined using WebSockets for other endpoints,
there was never a good justification for moving more components to the
WebSockets system.
This WebSockets code path had a lot of downsides/complexity,
including:
* The messy hack involving constructing an emulated request object to
hook into doing Django requests.
* The `message_senders` queue processor system, which increases RAM
needs and must be provisioned independently from the rest of the
server).
* A duplicate check_send_receive_time Nagios test specific to
WebSockets.
* The requirement for users to have their firewalls/NATs allow
WebSocket connections, and a setting to disable them for networks
where WebSockets don’t work.
* Dependencies on the SockJS family of libraries, which has at times
been poorly maintained, and periodically throws random JavaScript
exceptions in our production environments without a deep enough
traceback to effectively investigate.
* A total of about 1600 lines of our code related to the feature.
* Increased load on the Tornado system, especially around a Zulip
server restart, and especially for large installations like
zulipchat.com, resulting in extra delay before messages can be sent
again.
As detailed in
https://github.com/zulip/zulip/pull/12862#issuecomment-536152397, it
appears that removing WebSockets moderately increases the time it
takes for the `send_message` API query to return from the server, but
does not significantly change the time between when a message is sent
and when it is received by clients. We don’t understand the reason
for that change (suggesting the possibility of a measurement error),
and even if it is a real change, we consider that potential small
latency regression to be acceptable.
If we later want WebSockets, we’ll likely want to just use Django
Channels.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-07-23 01:43:40 +02:00
|
|
|
class TornadoHandler(BaseHandler):
|
2016-10-12 15:09:32 +02:00
|
|
|
target_port = tornado_port
|
|
|
|
|
|
|
|
|
dependencies: Remove WebSockets system for sending messages.
Zulip has had a small use of WebSockets (specifically, for the code
path of sending messages, via the webapp only) since ~2013. We
originally added this use of WebSockets in the hope that the latency
benefits of doing so would allow us to avoid implementing a markdown
local echo; they were not. Further, HTTP/2 may have eliminated the
latency difference we hoped to exploit by using WebSockets in any
case.
While we’d originally imagined using WebSockets for other endpoints,
there was never a good justification for moving more components to the
WebSockets system.
This WebSockets code path had a lot of downsides/complexity,
including:
* The messy hack involving constructing an emulated request object to
hook into doing Django requests.
* The `message_senders` queue processor system, which increases RAM
needs and must be provisioned independently from the rest of the
server).
* A duplicate check_send_receive_time Nagios test specific to
WebSockets.
* The requirement for users to have their firewalls/NATs allow
WebSocket connections, and a setting to disable them for networks
where WebSockets don’t work.
* Dependencies on the SockJS family of libraries, which has at times
been poorly maintained, and periodically throws random JavaScript
exceptions in our production environments without a deep enough
traceback to effectively investigate.
* A total of about 1600 lines of our code related to the feature.
* Increased load on the Tornado system, especially around a Zulip
server restart, and especially for large installations like
zulipchat.com, resulting in extra delay before messages can be sent
again.
As detailed in
https://github.com/zulip/zulip/pull/12862#issuecomment-536152397, it
appears that removing WebSockets moderately increases the time it
takes for the `send_message` API query to return from the server, but
does not significantly change the time between when a message is sent
and when it is received by clients. We don’t understand the reason
for that change (suggesting the possibility of a measurement error),
and even if it is a real change, we consider that potential small
latency regression to be acceptable.
If we later want WebSockets, we’ll likely want to just use Django
Channels.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-07-23 01:43:40 +02:00
|
|
|
class ThumborHandler(BaseHandler):
|
2017-11-09 16:31:57 +01:00
|
|
|
target_port = thumbor_port
|
|
|
|
|
|
|
|
|
2020-05-01 12:17:29 +02:00
|
|
|
class ErrorHandler(BaseHandler):
|
|
|
|
@web.asynchronous
|
|
|
|
def prepare(self) -> None:
|
|
|
|
print(FAIL + 'Unexpected request: ' + ENDC, self.request.path)
|
|
|
|
self.set_status(500)
|
|
|
|
self.write('path not supported')
|
|
|
|
self.finish()
|
|
|
|
|
|
|
|
def using_thumbor() -> bool:
|
|
|
|
return not options.streamlined
|
|
|
|
|
2016-10-12 15:09:32 +02:00
|
|
|
class Application(web.Application):
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def __init__(self, enable_logging: bool = False) -> None:
|
2016-10-12 15:09:32 +02:00
|
|
|
handlers = [
|
|
|
|
(r"/json/events.*", TornadoHandler),
|
|
|
|
(r"/api/v1/events.*", TornadoHandler),
|
|
|
|
(r"/webpack.*", WebPackHandler),
|
2020-05-01 12:17:29 +02:00
|
|
|
(r"/thumbor.*", ThumborHandler if using_thumbor() else ErrorHandler),
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
(r"/.*", DjangoHandler),
|
2016-10-12 15:09:32 +02:00
|
|
|
]
|
2017-10-27 08:28:23 +02:00
|
|
|
super().__init__(handlers, enable_logging=enable_logging)
|
2016-12-19 10:47:46 +01:00
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def log_request(self, handler: BaseHandler) -> None:
|
2016-12-19 10:47:46 +01:00
|
|
|
if self.settings['enable_logging']:
|
2017-10-27 08:28:23 +02:00
|
|
|
super().log_request(handler)
|
2016-10-12 15:09:32 +02:00
|
|
|
|
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def on_shutdown() -> None:
|
2016-10-12 15:09:32 +02:00
|
|
|
IOLoop.instance().stop()
|
|
|
|
|
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def shutdown_handler(*args: Any, **kwargs: Any) -> None:
|
2016-10-12 15:09:32 +02:00
|
|
|
io_loop = IOLoop.instance()
|
|
|
|
if io_loop._callbacks:
|
2017-11-30 00:15:49 +01:00
|
|
|
io_loop.call_later(1, shutdown_handler)
|
2016-10-02 18:20:06 +02:00
|
|
|
else:
|
2016-10-12 15:09:32 +02:00
|
|
|
io_loop.stop()
|
|
|
|
|
2020-05-01 00:46:50 +02:00
|
|
|
def print_listeners() -> None:
|
|
|
|
print("\nZulip services will listen on ports:")
|
|
|
|
ports = [
|
|
|
|
(proxy_port, CYAN + 'web proxy' + ENDC),
|
|
|
|
(django_port, 'Django'),
|
|
|
|
(tornado_port, 'Tornado'),
|
|
|
|
]
|
|
|
|
|
|
|
|
if not options.test:
|
|
|
|
ports.append((webpack_port, 'webpack'))
|
|
|
|
|
2020-05-01 12:17:29 +02:00
|
|
|
if using_thumbor():
|
|
|
|
ports.append((thumbor_port, 'Thumbor'))
|
2016-10-02 18:20:06 +02:00
|
|
|
|
2020-05-01 00:46:50 +02:00
|
|
|
for port, label in ports:
|
|
|
|
print(f' {port}: {label}')
|
|
|
|
print()
|
|
|
|
|
|
|
|
proxy_warning = f"Only the proxy port ({proxy_port}) is exposed."
|
|
|
|
print(WARNING + "Note to Vagrant users: " + ENDC + proxy_warning + '\n')
|
2016-10-02 18:20:06 +02:00
|
|
|
|
2020-05-01 12:19:50 +02:00
|
|
|
if options.test:
|
|
|
|
do_one_time_webpack_compile()
|
|
|
|
else:
|
|
|
|
start_webpack_watcher()
|
|
|
|
|
|
|
|
for cmd in server_processes():
|
|
|
|
subprocess.Popen(cmd)
|
|
|
|
|
2012-10-10 00:16:25 +02:00
|
|
|
try:
|
2016-12-19 10:47:46 +01:00
|
|
|
app = Application(enable_logging=options.enable_tornado_logging)
|
2019-01-29 20:28:48 +01:00
|
|
|
try:
|
|
|
|
app.listen(proxy_port, address=options.interface)
|
|
|
|
except OSError as e:
|
|
|
|
if e.errno == 98:
|
|
|
|
print('\n\nERROR: You probably have another server running!!!\n\n')
|
|
|
|
raise
|
2020-05-01 00:46:50 +02:00
|
|
|
|
|
|
|
print_listeners()
|
|
|
|
|
2016-10-12 15:09:32 +02:00
|
|
|
ioloop = IOLoop.instance()
|
|
|
|
for s in (signal.SIGINT, signal.SIGTERM):
|
|
|
|
signal.signal(s, shutdown_handler)
|
|
|
|
ioloop.start()
|
2017-03-05 10:25:27 +01:00
|
|
|
except Exception:
|
2012-11-14 19:46:12 +01:00
|
|
|
# Print the traceback before we get SIGTERM and die.
|
|
|
|
traceback.print_exc()
|
|
|
|
raise
|
2012-10-10 00:16:25 +02:00
|
|
|
finally:
|
2012-11-09 20:59:43 +01:00
|
|
|
# Kill everything in our process group.
|
|
|
|
os.killpg(0, signal.SIGTERM)
|
2017-01-26 09:57:16 +01:00
|
|
|
# Remove pid file when development server closed correctly.
|
|
|
|
os.remove(pid_file_path)
|