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
|
2022-03-19 01:14:00 +01:00
|
|
|
import asyncio
|
2023-05-26 00:17:49 +02:00
|
|
|
import errno
|
2023-12-11 22:44:55 +01:00
|
|
|
import logging
|
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
|
2024-06-24 09:50:13 +02:00
|
|
|
from collections.abc import Awaitable, Callable
|
2016-07-30 00:51:14 +02:00
|
|
|
|
2021-07-03 08:22:44 +02:00
|
|
|
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
sys.path.insert(0, os.path.dirname(TOOLS_DIR))
|
|
|
|
|
2017-02-05 21:24:28 +01:00
|
|
|
# check for the venv
|
2021-07-03 08:22:44 +02:00
|
|
|
from tools.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__)
|
|
|
|
|
2023-12-11 22:44:55 +01:00
|
|
|
import aiohttp
|
|
|
|
from aiohttp import hdrs, web
|
2024-02-23 00:56:11 +01:00
|
|
|
from aiohttp.http_exceptions import BadStatusLine
|
2012-10-10 00:16:25 +02:00
|
|
|
|
2021-03-03 05:00:15 +01:00
|
|
|
from tools.lib.test_script import add_provision_check_override_param, assert_provisioning_status_ok
|
2024-04-29 23:20:36 +02:00
|
|
|
from zerver.lib.partial import partial
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
if "posix" in os.name and os.geteuid() == 0:
|
2023-03-04 02:17:54 +01:00
|
|
|
raise RuntimeError("run-dev should not be run as root.")
|
2015-10-15 18:44:48 +02:00
|
|
|
|
2021-02-12 08:20:45 +01: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.
|
2021-02-12 08:20:45 +01:00
|
|
|
"""
|
2016-11-07 08:06:34 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
description=DESCRIPTION, formatter_class=argparse.RawTextHelpFormatter
|
|
|
|
)
|
|
|
|
|
2021-02-12 08:20:45 +01: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", help="Set the IP or hostname for the proxy to listen on")
|
2021-02-12 08:19:30 +01:00
|
|
|
parser.add_argument(
|
2021-02-12 08:20:45 +01:00
|
|
|
"--no-clear-memcached",
|
|
|
|
action="store_false",
|
|
|
|
dest="clear_memcached",
|
|
|
|
help="Do not clear memcached on startup",
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-05-07 00:38:24 +02:00
|
|
|
parser.add_argument("--streamlined", action="store_true", help="Avoid process_queue, etc.")
|
2023-03-13 04:06:55 +01:00
|
|
|
parser.add_argument(
|
|
|
|
"--behind-https-proxy",
|
|
|
|
action="store_true",
|
|
|
|
help="Start app server in HTTPS mode, using reverse proxy",
|
|
|
|
)
|
2024-06-24 09:50:13 +02:00
|
|
|
parser.add_argument(
|
|
|
|
"--help-center", action="store_true", help="Build and host help center with search"
|
|
|
|
)
|
2024-06-24 11:31:44 +02:00
|
|
|
parser.add_argument(
|
|
|
|
"--help-center-dev-server",
|
|
|
|
action="store_true",
|
|
|
|
help="Run dev server for help center. Hot reload will work for this mode, but search will not work in the generated website.",
|
|
|
|
)
|
2021-03-03 05:00:15 +01:00
|
|
|
add_provision_check_override_param(parser)
|
2017-09-30 08:42:22 +02:00
|
|
|
options = parser.parse_args()
|
2024-06-24 11:31:44 +02:00
|
|
|
help_center_dev_server_enabled = options.help_center_dev_server and not options.help_center
|
2012-11-08 23:23:25 +01:00
|
|
|
|
2021-03-02 20:59:19 +01:00
|
|
|
assert_provisioning_status_ok(options.skip_provision_check)
|
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
|
|
|
|
2024-07-12 02:30:17 +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"
|
2020-10-23 02:43:28 +02:00
|
|
|
# Don't auto-reload when running Puppeteer tests
|
2021-02-12 08:20:45 +01:00
|
|
|
runserver_args = ["--noreload"]
|
2023-12-09 22:31:30 +01:00
|
|
|
runtornado_command = ["./manage.py", "runtornado"]
|
2013-10-23 19:12:03 +02:00
|
|
|
else:
|
|
|
|
settings_module = "zproject.settings"
|
2023-12-09 22:31:30 +01:00
|
|
|
runtornado_command = [
|
|
|
|
"-m",
|
|
|
|
"tornado.autoreload",
|
|
|
|
"--until-success",
|
|
|
|
"./manage.py",
|
|
|
|
"runtornado",
|
|
|
|
"--autoreload",
|
2024-02-27 05:04:29 +01:00
|
|
|
"--immediate-reloads",
|
2023-12-09 22:31:30 +01:00
|
|
|
]
|
2013-10-23 19:12:03 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
manage_args = [f"--settings={settings_module}"]
|
|
|
|
os.environ["DJANGO_SETTINGS_MODULE"] = settings_module
|
2013-10-23 19:12:03 +02:00
|
|
|
|
2023-03-13 04:06:55 +01:00
|
|
|
if options.behind_https_proxy:
|
|
|
|
os.environ["BEHIND_HTTPS_PROXY"] = "1"
|
|
|
|
|
2021-08-14 01:01:37 +02:00
|
|
|
from scripts.lib.zulip_tools import CYAN, ENDC
|
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
|
2024-06-24 11:31:44 +02:00
|
|
|
help_center_port = base_port + 4
|
2012-10-10 00:16:25 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
os.chdir(os.path.join(os.path.dirname(__file__), ".."))
|
2012-10-10 00:16:25 +02:00
|
|
|
|
2016-07-18 15:24:41 +02:00
|
|
|
if options.clear_memcached:
|
2021-02-12 08:20:45 +01:00
|
|
|
subprocess.check_call("./scripts/setup/flush-memcached")
|
2016-07-18 15:24:41 +02:00
|
|
|
|
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:
|
2021-02-12 08:20:45 +01:00
|
|
|
pid_file_path = os.path.join(os.path.join(os.getcwd(), "var/puppeteer/run_dev.pid"))
|
2017-02-18 01:30:19 +01:00
|
|
|
else:
|
2021-02-12 08:20:45 +01:00
|
|
|
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))
|
2021-02-12 08:20:45 +01:00
|
|
|
with open(pid_file_path, "w+") as f:
|
2019-07-14 21:37:08 +02:00
|
|
|
f.write(str(os.getpgrp()) + "\n")
|
2017-01-26 09:57:16 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2024-07-12 02:30:17 +02:00
|
|
|
def server_processes() -> list[list[str]]:
|
2020-05-01 12:19:50 +02:00
|
|
|
main_cmds = [
|
2021-02-12 08:19:30 +01:00
|
|
|
[
|
2021-02-12 08:20:45 +01:00
|
|
|
"./manage.py",
|
|
|
|
"rundjangoserver",
|
2021-02-12 08:19:30 +01:00
|
|
|
*manage_args,
|
|
|
|
*runserver_args,
|
2021-02-12 08:20:45 +01:00
|
|
|
f"127.0.0.1:{django_port}",
|
2021-02-12 08:19:30 +01:00
|
|
|
],
|
|
|
|
[
|
2021-02-12 08:20:45 +01:00
|
|
|
"env",
|
|
|
|
"PYTHONUNBUFFERED=1",
|
2022-03-17 22:09:11 +01:00
|
|
|
"python3",
|
2023-12-09 22:31:30 +01:00
|
|
|
*runtornado_command,
|
2021-02-12 08:19:30 +01:00
|
|
|
*manage_args,
|
2021-02-12 08:20:45 +01:00
|
|
|
f"127.0.0.1:{tornado_port}",
|
2021-02-12 08:19:30 +01:00
|
|
|
],
|
2020-05-01 12:17:29 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
if options.streamlined:
|
|
|
|
# The streamlined operation allows us to do many
|
2021-05-07 00:38:24 +02:00
|
|
|
# things, but search/etc. features won't work.
|
2020-05-01 12:17:29 +02:00
|
|
|
return main_cmds
|
|
|
|
|
|
|
|
other_cmds = [
|
2021-02-12 08:20:45 +01:00
|
|
|
["./manage.py", "process_queue", "--all", *manage_args],
|
2021-02-12 08:19:30 +01:00
|
|
|
[
|
2021-02-12 08:20:45 +01:00
|
|
|
"env",
|
|
|
|
"PGHOST=127.0.0.1", # Force password authentication using .pgpass
|
|
|
|
"./puppet/zulip/files/postgresql/process_fts_updates",
|
|
|
|
"--quiet",
|
2021-02-12 08:19:30 +01:00
|
|
|
],
|
2021-02-12 08:20:45 +01:00
|
|
|
["./manage.py", "deliver_scheduled_messages"],
|
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
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
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
|
2020-10-23 02:43:28 +02:00
|
|
|
# copies on the same system, so this model lets us run the Puppeteer tests
|
2019-06-25 11:39:03 +02:00
|
|
|
# with a running development server.
|
2021-02-12 08:20:45 +01:00
|
|
|
subprocess.check_call(["./tools/webpack", "--quiet", "--test"])
|
2020-05-01 12:19:50 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-10-07 02:15:26 +02:00
|
|
|
def start_webpack_watcher() -> "subprocess.Popen[bytes]":
|
2021-02-12 08:20:45 +01:00
|
|
|
webpack_cmd = ["./tools/webpack", "--watch", f"--port={webpack_port}"]
|
2017-06-10 20:28:48 +02:00
|
|
|
if options.minify:
|
2021-02-12 08:20:45 +01:00
|
|
|
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.
|
2021-02-12 08:20:45 +01:00
|
|
|
webpack_cmd.append("--disable-host-check")
|
2017-07-16 21:14:03 +02:00
|
|
|
if options.interface:
|
2020-09-03 05:58:10 +02:00
|
|
|
webpack_cmd.append(f"--host={options.interface}")
|
2017-07-16 21:14:03 +02:00
|
|
|
else:
|
2020-09-03 05:58:10 +02:00
|
|
|
webpack_cmd.append("--host=0.0.0.0")
|
2020-10-07 02:15:26 +02:00
|
|
|
return subprocess.Popen(webpack_cmd)
|
2012-10-10 00:16:25 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2023-12-11 22:44:55 +01:00
|
|
|
session: aiohttp.ClientSession
|
|
|
|
|
|
|
|
# https://datatracker.ietf.org/doc/html/rfc2616#section-13.5.1
|
|
|
|
HOP_BY_HOP_HEADERS = {
|
|
|
|
hdrs.CONNECTION,
|
|
|
|
hdrs.KEEP_ALIVE,
|
|
|
|
hdrs.PROXY_AUTHENTICATE,
|
|
|
|
hdrs.PROXY_AUTHORIZATION,
|
|
|
|
hdrs.TE,
|
|
|
|
hdrs.TRAILER,
|
|
|
|
hdrs.TRANSFER_ENCODING,
|
|
|
|
hdrs.UPGRADE,
|
|
|
|
}
|
|
|
|
|
|
|
|
# Headers that aiohttp would otherwise generate by default
|
|
|
|
SKIP_AUTO_HEADERS = {
|
|
|
|
hdrs.ACCEPT,
|
|
|
|
hdrs.ACCEPT_ENCODING,
|
|
|
|
hdrs.CONTENT_TYPE,
|
|
|
|
hdrs.USER_AGENT,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async def forward(upstream_port: int, request: web.Request) -> web.StreamResponse:
|
|
|
|
try:
|
|
|
|
upstream_response = await session.request(
|
|
|
|
request.method,
|
|
|
|
request.url.with_host("127.0.0.1").with_port(upstream_port),
|
|
|
|
headers=[
|
|
|
|
(key, value)
|
|
|
|
for key, value in request.headers.items()
|
|
|
|
if key not in HOP_BY_HOP_HEADERS
|
2022-03-22 03:58:30 +01:00
|
|
|
],
|
2023-12-11 22:44:55 +01:00
|
|
|
data=request.content.iter_any() if request.body_exists else None,
|
|
|
|
allow_redirects=False,
|
|
|
|
auto_decompress=False,
|
|
|
|
compress=False,
|
|
|
|
skip_auto_headers=SKIP_AUTO_HEADERS,
|
|
|
|
)
|
|
|
|
except aiohttp.ClientError as error:
|
|
|
|
logging.error(
|
|
|
|
"Failed to forward %s %s to port %d: %s",
|
|
|
|
request.method,
|
|
|
|
request.url.path,
|
|
|
|
upstream_port,
|
|
|
|
error,
|
2022-03-22 03:58:30 +01:00
|
|
|
)
|
2023-12-11 22:44:55 +01:00
|
|
|
raise web.HTTPBadGateway from error
|
2022-03-22 03:58:30 +01:00
|
|
|
|
2023-12-11 22:44:55 +01:00
|
|
|
response = web.StreamResponse(status=upstream_response.status, reason=upstream_response.reason)
|
|
|
|
response.headers.extend(
|
|
|
|
(key, value)
|
|
|
|
for key, value in upstream_response.headers.items()
|
|
|
|
if key not in HOP_BY_HOP_HEADERS
|
|
|
|
)
|
|
|
|
assert request.remote is not None
|
|
|
|
response.headers["X-Real-IP"] = request.remote
|
|
|
|
response.headers["X-Forwarded-Port"] = str(proxy_port)
|
|
|
|
await response.prepare(request)
|
|
|
|
async for data in upstream_response.content.iter_any():
|
|
|
|
await response.write(data)
|
|
|
|
await response.write_eof()
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2024-06-24 11:31:44 +02:00
|
|
|
def run_help_center_dev_server() -> "subprocess.Popen[bytes]":
|
|
|
|
return subprocess.Popen(
|
|
|
|
["/usr/local/bin/corepack", "pnpm", "dev", f"--port={help_center_port}", "--host"],
|
|
|
|
cwd="help-beta",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-06-24 09:50:13 +02:00
|
|
|
@web.middleware
|
|
|
|
async def help_center_middleware(
|
|
|
|
request: web.Request, handler: Callable[[web.Request], Awaitable[web.StreamResponse]]
|
|
|
|
) -> web.StreamResponse:
|
|
|
|
if request.path.startswith("/help-beta"):
|
|
|
|
try:
|
|
|
|
filename = request.match_info["filename"]
|
|
|
|
name, ext = os.path.splitext(filename)
|
|
|
|
if not ext:
|
|
|
|
filename = os.path.join(filename, "index.html")
|
|
|
|
request.match_info["filename"] = filename
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return await handler(request)
|
|
|
|
|
|
|
|
|
|
|
|
middlewares = []
|
|
|
|
if options.help_center:
|
|
|
|
middlewares.append(help_center_middleware)
|
|
|
|
|
|
|
|
app = web.Application(middlewares=middlewares)
|
|
|
|
|
|
|
|
|
2024-06-24 11:31:44 +02:00
|
|
|
def setup_routes(
|
|
|
|
enable_help_center: bool = False, enable_help_center_dev_server: bool = False
|
|
|
|
) -> None:
|
2024-06-24 09:50:13 +02:00
|
|
|
if enable_help_center:
|
|
|
|
# Order of adding the rules matters. aiohttp will stop at the first
|
|
|
|
# match, and we want `/help-beta` to be matched before Django URIs.
|
|
|
|
try:
|
|
|
|
app.router.add_static("/help-beta", "help-beta/dist")
|
|
|
|
except ValueError:
|
|
|
|
print("""Please run the build step for the help center before enabling it.
|
|
|
|
The instructions for the build step can be found in the `./devtools`
|
|
|
|
page. `/help-beta` urls will give you an error until you complete the
|
|
|
|
build step and rerun `run-dev`.""")
|
2024-06-24 11:31:44 +02:00
|
|
|
elif enable_help_center_dev_server:
|
|
|
|
app.add_routes(
|
|
|
|
[
|
|
|
|
web.route(
|
|
|
|
hdrs.METH_ANY, r"/{path:(help-beta).*}", partial(forward, help_center_port)
|
|
|
|
),
|
|
|
|
]
|
|
|
|
)
|
2024-06-24 09:50:13 +02:00
|
|
|
app.add_routes(
|
|
|
|
[
|
|
|
|
web.route(
|
|
|
|
hdrs.METH_ANY, r"/{path:json/events|api/v1/events}", partial(forward, tornado_port)
|
|
|
|
),
|
|
|
|
web.route(hdrs.METH_ANY, r"/{path:webpack/.*}", partial(forward, webpack_port)),
|
|
|
|
web.route(hdrs.METH_ANY, r"/{path:.*}", partial(forward, django_port)),
|
|
|
|
]
|
|
|
|
)
|
2016-10-12 15:09:32 +02:00
|
|
|
|
|
|
|
|
2020-05-01 00:46:50 +02:00
|
|
|
def print_listeners() -> None:
|
2021-04-06 20:16:16 +02:00
|
|
|
# Since we can't import settings from here, we duplicate some
|
|
|
|
# EXTERNAL_HOST logic from dev_settings.py.
|
|
|
|
IS_DEV_DROPLET = pwd.getpwuid(os.getuid()).pw_name == "zulipdev"
|
|
|
|
if IS_DEV_DROPLET:
|
2021-04-16 04:37:09 +02:00
|
|
|
# Technically, the `zulip.` is a subdomain of the server, so
|
|
|
|
# this is kinda misleading, but 99% of development is done on
|
|
|
|
# the default/zulip subdomain.
|
|
|
|
default_hostname = "zulip." + os.uname()[1].lower()
|
2021-04-06 20:16:16 +02:00
|
|
|
else:
|
|
|
|
default_hostname = "localhost"
|
|
|
|
external_host = os.getenv("EXTERNAL_HOST", f"{default_hostname}:{proxy_port}")
|
2023-03-13 04:06:55 +01:00
|
|
|
http_protocol = "https" if options.behind_https_proxy else "http"
|
|
|
|
print(
|
|
|
|
f"\nStarting Zulip on:\n\n\t{CYAN}{http_protocol}://{external_host}/{ENDC}\n\nInternal ports:"
|
|
|
|
)
|
2020-05-01 00:46:50 +02:00
|
|
|
ports = [
|
2021-02-12 08:20:45 +01:00
|
|
|
(proxy_port, "Development server proxy (connect here)"),
|
|
|
|
(django_port, "Django"),
|
|
|
|
(tornado_port, "Tornado"),
|
2020-05-01 00:46:50 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
if not options.test:
|
2021-02-12 08:20:45 +01:00
|
|
|
ports.append((webpack_port, "webpack"))
|
2020-05-01 00:46:50 +02:00
|
|
|
|
2024-06-24 11:31:44 +02:00
|
|
|
if help_center_dev_server_enabled:
|
|
|
|
ports.append((help_center_port, "Help center - Astro dev server"))
|
|
|
|
|
2020-05-01 00:46:50 +02:00
|
|
|
for port, label in ports:
|
2021-02-12 08:20:45 +01:00
|
|
|
print(f" {port}: {label}")
|
2020-05-01 00:46:50 +02:00
|
|
|
print()
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2024-02-23 00:56:11 +01:00
|
|
|
def https_log_filter(record: logging.LogRecord) -> bool:
|
|
|
|
# aiohttp emits an exception with a traceback when receiving an
|
|
|
|
# https request (https://github.com/aio-libs/aiohttp/issues/8065).
|
|
|
|
# Abbreviate it to a one-line message.
|
|
|
|
if (
|
|
|
|
record.exc_info is not None
|
|
|
|
and isinstance(error := record.exc_info[1], BadStatusLine)
|
|
|
|
and error.message.startswith(
|
|
|
|
(
|
|
|
|
"Invalid method encountered:\n\n b'\\x16",
|
|
|
|
'Invalid method encountered:\n\n b"\\x16',
|
|
|
|
)
|
|
|
|
)
|
|
|
|
):
|
|
|
|
record.msg = "Rejected https request (this development server only supports http)"
|
|
|
|
record.exc_info = None
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
logging.getLogger("aiohttp.server").addFilter(https_log_filter)
|
|
|
|
|
2023-12-11 22:44:55 +01:00
|
|
|
runner: web.AppRunner
|
2024-07-12 02:30:17 +02:00
|
|
|
children: list["subprocess.Popen[bytes]"] = []
|
2020-05-01 12:19:50 +02:00
|
|
|
|
2022-03-19 01:14:00 +01:00
|
|
|
|
|
|
|
async def serve() -> None:
|
2023-12-11 22:44:55 +01:00
|
|
|
global runner, session
|
2022-03-19 01:14:00 +01:00
|
|
|
|
2020-10-07 02:15:26 +02:00
|
|
|
if options.test:
|
|
|
|
do_one_time_webpack_compile()
|
|
|
|
else:
|
|
|
|
children.append(start_webpack_watcher())
|
|
|
|
|
2024-06-24 11:31:44 +02:00
|
|
|
if help_center_dev_server_enabled:
|
|
|
|
children.append(run_help_center_dev_server())
|
|
|
|
|
|
|
|
setup_routes(options.help_center, options.help_center_dev_server)
|
2024-06-24 09:50:13 +02:00
|
|
|
|
2023-07-31 22:52:35 +02:00
|
|
|
children.extend(subprocess.Popen(cmd) for cmd in server_processes())
|
2020-10-07 02:15:26 +02:00
|
|
|
|
2023-12-11 22:44:55 +01:00
|
|
|
session = aiohttp.ClientSession()
|
|
|
|
runner = web.AppRunner(app, auto_decompress=False, handler_cancellation=True)
|
|
|
|
await runner.setup()
|
|
|
|
site = web.TCPSite(runner, host=options.interface, port=proxy_port)
|
2019-01-29 20:28:48 +01:00
|
|
|
try:
|
2023-12-11 22:44:55 +01:00
|
|
|
await site.start()
|
2019-01-29 20:28:48 +01:00
|
|
|
except OSError as e:
|
2023-05-26 00:17:49 +02:00
|
|
|
if e.errno == errno.EADDRINUSE:
|
2021-02-12 08:20:45 +01:00
|
|
|
print("\n\nERROR: You probably have another server running!!!\n\n")
|
2019-01-29 20:28:48 +01:00
|
|
|
raise
|
2020-05-01 00:46:50 +02:00
|
|
|
|
|
|
|
print_listeners()
|
|
|
|
|
2022-03-19 01:14:00 +01:00
|
|
|
|
|
|
|
loop = asyncio.new_event_loop()
|
|
|
|
|
|
|
|
try:
|
|
|
|
loop.run_until_complete(serve())
|
2016-10-12 15:09:32 +02:00
|
|
|
for s in (signal.SIGINT, signal.SIGTERM):
|
2022-03-19 01:14:00 +01:00
|
|
|
loop.add_signal_handler(s, loop.stop)
|
|
|
|
loop.run_forever()
|
2012-10-10 00:16:25 +02:00
|
|
|
finally:
|
2023-12-11 22:44:55 +01:00
|
|
|
loop.run_until_complete(runner.cleanup())
|
|
|
|
loop.run_until_complete(session.close())
|
|
|
|
|
2020-10-07 02:15:26 +02:00
|
|
|
for child in children:
|
|
|
|
child.terminate()
|
|
|
|
|
|
|
|
print("Waiting for children to stop...")
|
|
|
|
for child in children:
|
|
|
|
child.wait()
|
|
|
|
|
2017-01-26 09:57:16 +01:00
|
|
|
# Remove pid file when development server closed correctly.
|
|
|
|
os.remove(pid_file_path)
|