add granian prod&dev, add gunicorn dev
This commit is contained in:
parent
ea06469370
commit
60ff800270
@ -697,15 +697,28 @@ class Config(Base):
|
|||||||
env_file: Optional[str] = None
|
env_file: Optional[str] = None
|
||||||
|
|
||||||
# Custom Backend Server
|
# Custom Backend Server
|
||||||
backend_server_prod: server.CustomBackendServer = server.GunicornBackendServer(
|
# backend_server_prod: server.CustomBackendServer = server.GunicornBackendServer(
|
||||||
app=f"reflex.app_module_for_backend:{constants.CompileVars.APP}.{constants.CompileVars.API}",
|
# worker_class="uvicorn.workers.UvicornH11Worker", # type: ignore
|
||||||
worker_class="uvicorn.workers.UvicornH11Worker", # type: ignore
|
# max_requests=100,
|
||||||
max_requests=100,
|
# max_requests_jitter=25,
|
||||||
max_requests_jitter=25,
|
# timeout=120,
|
||||||
preload_app=True,
|
# )
|
||||||
timeout=120,
|
backend_server_prod: server.CustomBackendServer = server.GranianBackendServer(
|
||||||
|
threads=2,
|
||||||
|
workers=4,
|
||||||
)
|
)
|
||||||
backend_server_dev: server.CustomBackendServer = server.UvicornBackendServer()
|
backend_server_dev: server.CustomBackendServer = server.GranianBackendServer(
|
||||||
|
threads=1,
|
||||||
|
workers=1,
|
||||||
|
)
|
||||||
|
# backend_server_dev: server.CustomBackendServer = server.GunicornBackendServer(
|
||||||
|
# worker_class="uvicorn.workers.UvicornH11Worker", # type: ignore
|
||||||
|
# max_requests=100,
|
||||||
|
# max_requests_jitter=25,
|
||||||
|
# timeout=120,
|
||||||
|
# threads=1,
|
||||||
|
# workers=1,
|
||||||
|
# )
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initialize the config values.
|
"""Initialize the config values.
|
||||||
@ -737,14 +750,26 @@ class Config(Base):
|
|||||||
raise ConfigError(
|
raise ConfigError(
|
||||||
"REDIS_URL is required when using the redis state manager."
|
"REDIS_URL is required when using the redis state manager."
|
||||||
)
|
)
|
||||||
|
|
||||||
print("[reflex.config::Config] --")
|
for key in (
|
||||||
for key in ("timeout", "gunicorn_worker_class", "gunicorn_workers", "gunicorn_max_requests", "gunicorn_max_requests_jitter"):
|
"timeout",
|
||||||
|
"gunicorn_worker_class",
|
||||||
|
"gunicorn_workers",
|
||||||
|
"gunicorn_max_requests",
|
||||||
|
"gunicorn_max_requests_jitter",
|
||||||
|
):
|
||||||
if isinstance(self.backend_server_prod, server.GunicornBackendServer):
|
if isinstance(self.backend_server_prod, server.GunicornBackendServer):
|
||||||
value = self.get_value(key)
|
value = self.get_value(key)
|
||||||
if value != self.backend_server_prod.get_fields()[key.replace("gunicorn_", "")].default and value is not None:
|
if (
|
||||||
setattr(self.backend_server_prod, key.replace("gunicorn_", ""), value)
|
value
|
||||||
print("[reflex.config::Config] done")
|
!= self.backend_server_prod.get_fields()[
|
||||||
|
key.replace("gunicorn_", "")
|
||||||
|
].default
|
||||||
|
and value is not None
|
||||||
|
):
|
||||||
|
setattr(
|
||||||
|
self.backend_server_prod, key.replace("gunicorn_", ""), value
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def module(self) -> str:
|
def module(self) -> str:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
"""Import every *BackendServer."""
|
||||||
|
|
||||||
from .base import CustomBackendServer
|
from .base import CustomBackendServer
|
||||||
from .granian import GranianBackendServer
|
from .granian import GranianBackendServer
|
||||||
|
@ -1,14 +1,95 @@
|
|||||||
from abc import abstractmethod, ABCMeta
|
"""The base for CustomBackendServer."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from abc import abstractmethod
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from reflex import constants
|
||||||
from reflex.base import Base
|
from reflex.base import Base
|
||||||
|
from reflex.constants.base import Env, LogLevel
|
||||||
|
|
||||||
|
|
||||||
class CustomBackendServer(Base):
|
class CustomBackendServer(Base):
|
||||||
|
"""BackendServer base."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_app_module(for_granian_target: bool = False, add_extra_api: bool = False):
|
||||||
|
"""Get the app module for the backend.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The app module for the backend.
|
||||||
|
"""
|
||||||
|
import reflex
|
||||||
|
|
||||||
|
if for_granian_target:
|
||||||
|
app_path = str(Path(reflex.__file__).parent / "app_module_for_backend.py")
|
||||||
|
else:
|
||||||
|
app_path = "reflex.app_module_for_backend"
|
||||||
|
|
||||||
|
return f"{app_path}:{constants.CompileVars.APP}{f'.{constants.CompileVars.API}' if add_extra_api else ''}"
|
||||||
|
|
||||||
|
def get_available_cpus(self) -> int:
|
||||||
|
"""Get available cpus."""
|
||||||
|
return os.cpu_count() or 1
|
||||||
|
|
||||||
|
def get_max_workers(self) -> int:
|
||||||
|
"""Get max workers."""
|
||||||
|
# https://docs.gunicorn.org/en/latest/settings.html#workers
|
||||||
|
return (os.cpu_count() or 1) * 4 + 1
|
||||||
|
|
||||||
|
def get_recommended_workers(self) -> int:
|
||||||
|
"""Get recommended workers."""
|
||||||
|
# https://docs.gunicorn.org/en/latest/settings.html#workers
|
||||||
|
return (os.cpu_count() or 1) * 2 + 1
|
||||||
|
|
||||||
|
def get_max_threads(self, wait_time_ms: int = 50, service_time_ms: int = 5) -> int:
|
||||||
|
"""Get max threads."""
|
||||||
|
# https://engineering.zalando.com/posts/2019/04/how-to-set-an-ideal-thread-pool-size.html
|
||||||
|
# Brian Goetz formula
|
||||||
|
return int(self.get_available_cpus() * (1 + wait_time_ms / service_time_ms))
|
||||||
|
|
||||||
|
def get_recommended_threads(
|
||||||
|
self,
|
||||||
|
target_reqs: int | None = None,
|
||||||
|
wait_time_ms: int = 50,
|
||||||
|
service_time_ms: int = 5,
|
||||||
|
) -> int:
|
||||||
|
"""Get recommended threads."""
|
||||||
|
# https://engineering.zalando.com/posts/2019/04/how-to-set-an-ideal-thread-pool-size.html
|
||||||
|
max_available_threads = self.get_max_threads()
|
||||||
|
|
||||||
|
if target_reqs:
|
||||||
|
# Little's law formula
|
||||||
|
need_threads = target_reqs * (
|
||||||
|
(wait_time_ms / 1000) + (service_time_ms / 1000)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
need_threads = self.get_max_threads(wait_time_ms, service_time_ms)
|
||||||
|
|
||||||
|
return int(
|
||||||
|
max_available_threads
|
||||||
|
if need_threads > max_available_threads
|
||||||
|
else need_threads
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def check_import(self, extra: bool = False):
|
||||||
|
"""Check package importation."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
||||||
|
"""Setup."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def run_prod(self):
|
def run_prod(self):
|
||||||
|
"""Run in production mode."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def run_dev(self):
|
def run_dev(self):
|
||||||
|
"""Run in development mode."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
@ -1,36 +1,272 @@
|
|||||||
|
"""The GranianBackendServer."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from dataclasses import field as dc_field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Literal, Type
|
||||||
|
|
||||||
|
from reflex.constants.base import Env, LogLevel
|
||||||
from reflex.server.base import CustomBackendServer
|
from reflex.server.base import CustomBackendServer
|
||||||
|
from reflex.utils import console
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class HTTP1Settings: # https://github.com/emmett-framework/granian/blob/261ceba3fd93bca10300e91d1498bee6df9e3576/granian/http.py#L6
|
class HTTP1Settings:
|
||||||
keep_alive: bool = True
|
"""Granian HTTP1Settings."""
|
||||||
max_buffer_size: int = 8192 + 4096 * 100
|
|
||||||
pipeline_flush: bool = False
|
# https://github.com/emmett-framework/granian/blob/261ceba3fd93bca10300e91d1498bee6df9e3576/granian/http.py#L6
|
||||||
|
keep_alive: bool = dc_field(default=True)
|
||||||
|
max_buffer_size: int = dc_field(default=8192 + 4096 * 100)
|
||||||
|
pipeline_flush: bool = dc_field(default=False)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class HTTP2Settings: # https://github.com/emmett-framework/granian/blob/261ceba3fd93bca10300e91d1498bee6df9e3576/granian/http.py#L13
|
class HTTP2Settings: # https://github.com/emmett-framework/granian/blob/261ceba3fd93bca10300e91d1498bee6df9e3576/granian/http.py#L13
|
||||||
adaptive_window: bool = False
|
"""Granian HTTP2Settings."""
|
||||||
initial_connection_window_size: int = 1024 * 1024
|
|
||||||
initial_stream_window_size: int = 1024 * 1024
|
adaptive_window: bool = dc_field(default=False)
|
||||||
keep_alive_interval: int | None = None
|
initial_connection_window_size: int = dc_field(default=1024 * 1024)
|
||||||
keep_alive_timeout: int = 20
|
initial_stream_window_size: int = dc_field(default=1024 * 1024)
|
||||||
max_concurrent_streams: int = 200
|
keep_alive_interval: int | None = dc_field(default=None)
|
||||||
max_frame_size: int = 1024 * 16
|
keep_alive_timeout: int = dc_field(default=20)
|
||||||
max_headers_size: int = 16 * 1024 * 1024
|
max_concurrent_streams: int = dc_field(default=200)
|
||||||
max_send_buffer_size: int = 1024 * 400
|
max_frame_size: int = dc_field(default=1024 * 16)
|
||||||
|
max_headers_size: int = dc_field(default=16 * 1024 * 1024)
|
||||||
|
max_send_buffer_size: int = dc_field(default=1024 * 400)
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import watchfiles
|
import watchfiles # type: ignore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
watchfiles = None
|
watchfiles = None
|
||||||
|
|
||||||
|
_mapping_attr_to_cli: dict[str, str] = {
|
||||||
|
"address": "--host",
|
||||||
|
"port": "--port",
|
||||||
|
"interface": "--interface",
|
||||||
|
"http": "--http",
|
||||||
|
"websockets": "--ws", # NOTE: when `websockets` True: `--ws`; False: `--no-ws`
|
||||||
|
"workers": "--workers",
|
||||||
|
"threads": "--threads",
|
||||||
|
"blocking_threads": "--blocking-threads",
|
||||||
|
"threading_mode": "--threading-mode",
|
||||||
|
"loop": "--loop",
|
||||||
|
"loop_opt": "--opt", # NOTE: when `loop_opt` True: `--opt`; False: `--no-opt`
|
||||||
|
"backlog": "--backlog",
|
||||||
|
"backpressure": "--backpressure",
|
||||||
|
"http1_keep_alive": "--http1-keep-alive",
|
||||||
|
"http1_max_buffer_size": "--http1-max-buffer-size",
|
||||||
|
"http1_pipeline_flush": "--http1-pipeline-flush",
|
||||||
|
"http2_adaptive_window": "--http2-adaptive-window",
|
||||||
|
"http2_initial_connection_window_size": "--http2-initial-connection-window-size",
|
||||||
|
"http2_initial_stream_window_size": "--http2-initial-stream-window-size",
|
||||||
|
"http2_keep_alive_interval": "--http2-keep-alive-interval",
|
||||||
|
"http2_keep_alive_timeout": "--http2-keep-alive-timeout",
|
||||||
|
"http2_max_concurrent_streams": "--http2-max-concurrent-streams",
|
||||||
|
"http2_max_frame_size": "--http2-max-frame-size",
|
||||||
|
"http2_max_headers_size": "--http2-max-headers-size",
|
||||||
|
"http2_max_send_buffer_size": "--http2-max-send-buffer-size",
|
||||||
|
"log_enabled": "--log", # NOTE: when `log_enabled` True: `--log`; False: `--no-log`
|
||||||
|
"log_level": "--log-level",
|
||||||
|
"log_access": "--log-access", # NOTE: when `log_access` True: `--log-access`; False: `--no-log-access`
|
||||||
|
"log_access_format": "--access-log-fmt",
|
||||||
|
"ssl_cert": "--ssl-certificate",
|
||||||
|
"ssl_key": "--ssl-keyfile",
|
||||||
|
"ssl_key_password": "--ssl-keyfile-password",
|
||||||
|
"url_path_prefix": "--url-path-prefix",
|
||||||
|
"respawn_failed_workers": "--respawn-failed-workers", # NOTE: when `respawn_failed_workers` True: `--respawn-failed-workers`; False: `--no-respawn-failed-workers`
|
||||||
|
"respawn_interval": "--respawn-interval",
|
||||||
|
"workers_lifetime": "--workers-lifetime",
|
||||||
|
"factory": "--factory", # NOTE: when `factory` True: `--factory`; False: `--no-factory`
|
||||||
|
"reload": "--reload", # NOTE: when `reload` True: `--reload`; False: `--no-reload`
|
||||||
|
"reload_paths": "--reload-paths",
|
||||||
|
"reload_ignore_dirs": "--reload-ignore-dirs",
|
||||||
|
"reload_ignore_patterns": "--reload-ignore-patterns",
|
||||||
|
"reload_ignore_paths": "--reload-ignore-paths",
|
||||||
|
"process_name": "--process-name",
|
||||||
|
"pid_file": "--pid-file",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class GranianBackendServer(CustomBackendServer):
|
class GranianBackendServer(CustomBackendServer):
|
||||||
|
"""Granian backendServer."""
|
||||||
|
|
||||||
|
# https://github.com/emmett-framework/granian/blob/fc11808ed177362fcd9359a455a733065ddbc505/granian/server.py#L69
|
||||||
|
|
||||||
|
target: str | None = None
|
||||||
|
address: str = "127.0.0.1"
|
||||||
|
port: int = 8000
|
||||||
|
interface: Literal["asgi", "asginl", "rsgi", "wsgi"] = "rsgi"
|
||||||
|
workers: int = 0
|
||||||
|
threads: int = 0
|
||||||
|
blocking_threads: int | None = None
|
||||||
|
threading_mode: Literal["runtime", "workers"] = "workers"
|
||||||
|
loop: Literal["auto", "asyncio", "uvloop"] = "auto"
|
||||||
|
loop_opt: bool = False
|
||||||
|
http: Literal["auto", "1", "2"] = "auto"
|
||||||
|
websockets: bool = True
|
||||||
|
backlog: int = 1024
|
||||||
|
backpressure: int | None = None
|
||||||
|
|
||||||
|
# http1_settings: HTTP1Settings | None = None
|
||||||
|
# NOTE: child of http1_settings, needed only for cli mode
|
||||||
|
http1_keep_alive: bool = HTTP1Settings.keep_alive
|
||||||
|
http1_max_buffer_size: int = HTTP1Settings.max_buffer_size
|
||||||
|
http1_pipeline_flush: bool = HTTP1Settings.pipeline_flush
|
||||||
|
|
||||||
|
# http2_settings: HTTP2Settings | None = None
|
||||||
|
# NOTE: child of http2_settings, needed only for cli mode
|
||||||
|
http2_adaptive_window: bool = HTTP2Settings.adaptive_window
|
||||||
|
http2_initial_connection_window_size: int = (
|
||||||
|
HTTP2Settings.initial_connection_window_size
|
||||||
|
)
|
||||||
|
http2_initial_stream_window_size: int = HTTP2Settings.initial_stream_window_size
|
||||||
|
http2_keep_alive_interval: int | None = HTTP2Settings.keep_alive_interval
|
||||||
|
http2_keep_alive_timeout: int = HTTP2Settings.keep_alive_timeout
|
||||||
|
http2_max_concurrent_streams: int = HTTP2Settings.max_concurrent_streams
|
||||||
|
http2_max_frame_size: int = HTTP2Settings.max_frame_size
|
||||||
|
http2_max_headers_size: int = HTTP2Settings.max_headers_size
|
||||||
|
http2_max_send_buffer_size: int = HTTP2Settings.max_send_buffer_size
|
||||||
|
|
||||||
|
log_enabled: bool = True
|
||||||
|
log_level: Literal["critical", "error", "warning", "warn", "info", "debug"] = "info"
|
||||||
|
log_dictconfig: dict[str, Any] | None = None
|
||||||
|
log_access: bool = False
|
||||||
|
log_access_format: str | None = None
|
||||||
|
ssl_cert: Path | None = None
|
||||||
|
ssl_key: Path | None = None
|
||||||
|
ssl_key_password: str | None = None
|
||||||
|
url_path_prefix: str | None = None
|
||||||
|
respawn_failed_workers: bool = False
|
||||||
|
respawn_interval: float = 3.5
|
||||||
|
workers_lifetime: int | None = None
|
||||||
|
factory: bool = False
|
||||||
|
reload: bool = False
|
||||||
|
reload_paths: list[Path] | None = None
|
||||||
|
reload_ignore_dirs: list[str] | None = None
|
||||||
|
reload_ignore_patterns: list[str] | None = None
|
||||||
|
reload_ignore_paths: list[Path] | None = None
|
||||||
|
reload_filter: Type[getattr(watchfiles, "BaseFilter", None)] | None = None # type: ignore
|
||||||
|
process_name: str | None = None
|
||||||
|
pid_file: Path | None = None
|
||||||
|
|
||||||
|
def check_import(self, extra: bool = False):
|
||||||
|
"""Check package importation."""
|
||||||
|
from importlib.util import find_spec
|
||||||
|
|
||||||
|
errors: list[str] = []
|
||||||
|
|
||||||
|
if find_spec("granian") is None:
|
||||||
|
errors.append(
|
||||||
|
'The `granian` package is required to run `GranianBackendServer`. Run `pip install "granian>=1.6.0"`.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if find_spec("watchfiles") is None and extra:
|
||||||
|
# NOTE: the `\[` is for force `rich.Console` to not consider it like a color or anything else which he not printing `[.*]`
|
||||||
|
errors.append(
|
||||||
|
r'Using --reload in `GranianBackendServer` requires the granian\[reload] extra. Run `pip install "granian\[reload]>=1.6.0"`.'
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
console.error("\n".join(errors))
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
||||||
|
"""Setup."""
|
||||||
|
self.target = self.get_app_module(for_granian_target=True, add_extra_api=True)
|
||||||
|
self.log_level = loglevel.value # type: ignore
|
||||||
|
self.address = host
|
||||||
|
self.port = port
|
||||||
|
self.interface = "asgi" # prevent obvious error
|
||||||
|
|
||||||
|
if env == Env.PROD:
|
||||||
|
if self.workers == self.get_fields()["workers"].default:
|
||||||
|
self.workers = self.get_recommended_workers()
|
||||||
|
else:
|
||||||
|
if self.workers > (max_threads := self.get_max_workers()):
|
||||||
|
self.workers = max_threads
|
||||||
|
|
||||||
|
if self.threads == self.get_fields()["threads"].default:
|
||||||
|
self.threads = self.get_recommended_threads()
|
||||||
|
else:
|
||||||
|
if self.threads > (max_threads := self.get_max_threads()):
|
||||||
|
self.threads = max_threads
|
||||||
|
|
||||||
|
if env == Env.DEV:
|
||||||
|
from reflex.config import get_config # prevent circular import
|
||||||
|
|
||||||
|
self.reload = True
|
||||||
|
self.reload_paths = [Path(get_config().app_name)]
|
||||||
|
self.reload_ignore_dirs = [".web"]
|
||||||
|
|
||||||
def run_prod(self):
|
def run_prod(self):
|
||||||
pass
|
"""Run in production mode."""
|
||||||
|
self.check_import()
|
||||||
|
command = ["granian"]
|
||||||
|
|
||||||
|
for key, field in self.get_fields().items():
|
||||||
|
if key != "target":
|
||||||
|
value = getattr(self, key)
|
||||||
|
if _mapping_attr_to_cli.get(key) and value != field.default:
|
||||||
|
if isinstance(value, list):
|
||||||
|
for v in value:
|
||||||
|
command += [_mapping_attr_to_cli[key], str(v)]
|
||||||
|
elif isinstance(value, bool):
|
||||||
|
command.append(
|
||||||
|
f"--{'no-' if value is False else ''}{_mapping_attr_to_cli[key][2:]}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
command += [_mapping_attr_to_cli[key], str(value)]
|
||||||
|
|
||||||
|
return command + [
|
||||||
|
self.get_app_module(for_granian_target=True, add_extra_api=True)
|
||||||
|
]
|
||||||
|
|
||||||
def run_dev(self):
|
def run_dev(self):
|
||||||
pass
|
"""Run in development mode."""
|
||||||
|
self.check_import(extra=self.reload)
|
||||||
|
from granian import Granian
|
||||||
|
|
||||||
|
exclude_keys = (
|
||||||
|
"http1_keep_alive",
|
||||||
|
"http1_max_buffer_size",
|
||||||
|
"http1_pipeline_flush",
|
||||||
|
"http2_adaptive_window",
|
||||||
|
"http2_initial_connection_window_size",
|
||||||
|
"http2_initial_stream_window_size",
|
||||||
|
"http2_keep_alive_interval",
|
||||||
|
"http2_keep_alive_timeout",
|
||||||
|
"http2_max_concurrent_streams",
|
||||||
|
"http2_max_frame_size",
|
||||||
|
"http2_max_headers_size",
|
||||||
|
"http2_max_send_buffer_size",
|
||||||
|
)
|
||||||
|
model = self.get_fields()
|
||||||
|
Granian(
|
||||||
|
**{
|
||||||
|
**{
|
||||||
|
key: value
|
||||||
|
for key, value in self.dict().items()
|
||||||
|
if key not in exclude_keys and value != model[key].default
|
||||||
|
},
|
||||||
|
"http1_settings": HTTP1Settings(
|
||||||
|
self.http1_keep_alive,
|
||||||
|
self.http1_max_buffer_size,
|
||||||
|
self.http1_pipeline_flush,
|
||||||
|
),
|
||||||
|
"http2_settings": HTTP2Settings(
|
||||||
|
self.http2_adaptive_window,
|
||||||
|
self.http2_initial_connection_window_size,
|
||||||
|
self.http2_initial_stream_window_size,
|
||||||
|
self.http2_keep_alive_interval,
|
||||||
|
self.http2_keep_alive_timeout,
|
||||||
|
self.http2_max_concurrent_streams,
|
||||||
|
self.http2_max_frame_size,
|
||||||
|
self.http2_max_headers_size,
|
||||||
|
self.http2_max_send_buffer_size,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
).serve()
|
||||||
|
@ -1,34 +1,15 @@
|
|||||||
from typing import Any, Literal, Callable
|
"""The GunicornBackendServer."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import ssl
|
import ssl
|
||||||
from pydantic import Field
|
import sys
|
||||||
|
from typing import Any, Callable, Literal
|
||||||
|
|
||||||
from gunicorn.app.base import BaseApplication
|
from reflex.constants.base import Env, LogLevel
|
||||||
|
|
||||||
import psutil
|
|
||||||
|
|
||||||
from reflex import constants
|
|
||||||
from reflex.server.base import CustomBackendServer
|
from reflex.server.base import CustomBackendServer
|
||||||
|
from reflex.utils import console
|
||||||
|
|
||||||
class StandaloneApplication(BaseApplication):
|
|
||||||
|
|
||||||
def __init__(self, app, options=None):
|
|
||||||
self.options = options or {}
|
|
||||||
self.application = app
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def load_config(self):
|
|
||||||
config = {key: value for key, value in self.options.items()
|
|
||||||
if key in self.cfg.settings and value is not None} # type: ignore
|
|
||||||
for key, value in config.items():
|
|
||||||
self.cfg.set(key.lower(), value) # type: ignore
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
return self.application
|
|
||||||
|
|
||||||
|
|
||||||
_mapping_attr_to_cli: dict[str, str] = {
|
_mapping_attr_to_cli: dict[str, str] = {
|
||||||
"config": "--config",
|
"config": "--config",
|
||||||
@ -105,10 +86,13 @@ _mapping_attr_to_cli: dict[str, str] = {
|
|||||||
"header_map": "--header-map",
|
"header_map": "--header-map",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class GunicornBackendServer(CustomBackendServer):
|
class GunicornBackendServer(CustomBackendServer):
|
||||||
|
"""Gunicorn backendServer."""
|
||||||
|
|
||||||
# https://github.com/benoitc/gunicorn/blob/bacbf8aa5152b94e44aa5d2a94aeaf0318a85248/gunicorn/config.py
|
# https://github.com/benoitc/gunicorn/blob/bacbf8aa5152b94e44aa5d2a94aeaf0318a85248/gunicorn/config.py
|
||||||
|
|
||||||
app: str
|
app_uri: str | None
|
||||||
|
|
||||||
config: str = "./gunicorn.conf.py"
|
config: str = "./gunicorn.conf.py"
|
||||||
"""\
|
"""\
|
||||||
@ -128,7 +112,7 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
A WSGI application path in pattern ``$(MODULE_NAME):$(VARIABLE_NAME)``.
|
A WSGI application path in pattern ``$(MODULE_NAME):$(VARIABLE_NAME)``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bind: list[str] = ['127.0.0.1:8000']
|
bind: list[str] = ["127.0.0.1:8000"]
|
||||||
"""\
|
"""\
|
||||||
The socket to bind.
|
The socket to bind.
|
||||||
|
|
||||||
@ -162,7 +146,7 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
Must be a positive integer. Generally set in the 64-2048 range.
|
Must be a positive integer. Generally set in the 64-2048 range.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
workers: int = 1
|
workers: int = 0
|
||||||
"""\
|
"""\
|
||||||
The number of worker processes for handling requests.
|
The number of worker processes for handling requests.
|
||||||
|
|
||||||
@ -175,7 +159,14 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
it is not defined, the default is ``1``.
|
it is not defined, the default is ``1``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
worker_class: Literal["sync", "eventlet", "gevent", "tornado", "gthread", "uvicorn.workers.UvicornH11Worker"] = "sync"
|
worker_class: Literal[
|
||||||
|
"sync",
|
||||||
|
"eventlet",
|
||||||
|
"gevent",
|
||||||
|
"tornado",
|
||||||
|
"gthread",
|
||||||
|
"uvicorn.workers.UvicornH11Worker",
|
||||||
|
] = "sync"
|
||||||
"""\
|
"""\
|
||||||
The type of workers to use.
|
The type of workers to use.
|
||||||
|
|
||||||
@ -202,7 +193,7 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
``gunicorn.workers.ggevent.GeventWorker``.
|
``gunicorn.workers.ggevent.GeventWorker``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
threads: int = 1
|
threads: int = 0
|
||||||
"""\
|
"""\
|
||||||
The number of worker threads for handling requests.
|
The number of worker threads for handling requests.
|
||||||
|
|
||||||
@ -493,7 +484,11 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
temporary directory.
|
temporary directory.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
secure_scheme_headers: dict[str, Any] = {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}
|
secure_scheme_headers: dict[str, Any] = {
|
||||||
|
"X-FORWARDED-PROTOCOL": "ssl",
|
||||||
|
"X-FORWARDED-PROTO": "https",
|
||||||
|
"X-FORWARDED-SSL": "on",
|
||||||
|
}
|
||||||
"""\
|
"""\
|
||||||
A dictionary containing headers and values that the front-end proxy
|
A dictionary containing headers and values that the front-end proxy
|
||||||
uses to indicate HTTPS requests. If the source IP is permitted by
|
uses to indicate HTTPS requests. If the source IP is permitted by
|
||||||
@ -588,7 +583,9 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
Disable redirect access logs to syslog.
|
Disable redirect access logs to syslog.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
access_log_format: str = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
access_log_format: str = (
|
||||||
|
'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
||||||
|
)
|
||||||
"""\
|
"""\
|
||||||
The access log format.
|
The access log format.
|
||||||
|
|
||||||
@ -686,7 +683,11 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
if sys.platform == "darwin"
|
if sys.platform == "darwin"
|
||||||
else (
|
else (
|
||||||
"unix:///var/run/log"
|
"unix:///var/run/log"
|
||||||
if sys.platform in ('freebsd', 'dragonfly', )
|
if sys.platform
|
||||||
|
in (
|
||||||
|
"freebsd",
|
||||||
|
"dragonfly",
|
||||||
|
)
|
||||||
else (
|
else (
|
||||||
"unix:///dev/log"
|
"unix:///dev/log"
|
||||||
if sys.platform == "openbsd"
|
if sys.platform == "openbsd"
|
||||||
@ -858,7 +859,9 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
The callable needs to accept a single instance variable for the Arbiter.
|
The callable needs to accept a single instance variable for the Arbiter.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pre_request: Callable = lambda worker, req: worker.log.debug("%s %s", req.method, req.path)
|
pre_request: Callable = lambda worker, req: worker.log.debug(
|
||||||
|
"%s %s", req.method, req.path
|
||||||
|
)
|
||||||
"""\
|
"""\
|
||||||
Called just before a worker processes the request.
|
Called just before a worker processes the request.
|
||||||
|
|
||||||
@ -908,7 +911,9 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
The callable needs to accept a single instance variable for the Arbiter.
|
The callable needs to accept a single instance variable for the Arbiter.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ssl_context: Callable = lambda config, default_ssl_context_factory: default_ssl_context_factory()
|
ssl_context: Callable = (
|
||||||
|
lambda config, default_ssl_context_factory: default_ssl_context_factory()
|
||||||
|
)
|
||||||
"""\
|
"""\
|
||||||
Called when SSLContext is needed.
|
Called when SSLContext is needed.
|
||||||
|
|
||||||
@ -975,7 +980,9 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
SSL certificate file
|
SSL certificate file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ssl_version: int = ssl.PROTOCOL_TLS if hasattr(ssl, "PROTOCOL_TLS") else ssl.PROTOCOL_SSLv23
|
ssl_version: int = (
|
||||||
|
ssl.PROTOCOL_TLS if hasattr(ssl, "PROTOCOL_TLS") else ssl.PROTOCOL_SSLv23
|
||||||
|
)
|
||||||
"""\
|
"""\
|
||||||
SSL version to use (see stdlib ssl module's).
|
SSL version to use (see stdlib ssl module's).
|
||||||
|
|
||||||
@ -1163,33 +1170,96 @@ class GunicornBackendServer(CustomBackendServer):
|
|||||||
on a proxy in front of Gunicorn.
|
on a proxy in front of Gunicorn.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# def __init__(self, *args, **kwargs):
|
def check_import(self, extra: bool = False):
|
||||||
# super().__init__(*args, **kwargs)
|
"""Check package importation."""
|
||||||
|
from importlib.util import find_spec
|
||||||
|
|
||||||
|
errors: list[str] = []
|
||||||
|
|
||||||
|
if find_spec("gunicorn") is None:
|
||||||
|
errors.append(
|
||||||
|
'The `gunicorn` package is required to run `GunicornBackendServer`. Run `pip install "gunicorn>=20.1.0"`.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
console.error("\n".join(errors))
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
||||||
|
"""Setup."""
|
||||||
|
self.app_uri = f"{self.get_app_module()}()"
|
||||||
|
self.loglevel = loglevel.value # type: ignore
|
||||||
|
self.bind = [f"{host}:{port}"]
|
||||||
|
|
||||||
|
if env == Env.PROD:
|
||||||
|
if self.workers == self.get_fields()["workers"].default:
|
||||||
|
self.workers = self.get_recommended_workers()
|
||||||
|
else:
|
||||||
|
if self.workers > (max_threads := self.get_max_workers()):
|
||||||
|
self.workers = max_threads
|
||||||
|
|
||||||
|
if self.threads == self.get_fields()["threads"].default:
|
||||||
|
self.threads = self.get_recommended_threads()
|
||||||
|
else:
|
||||||
|
if self.threads > (max_threads := self.get_max_threads()):
|
||||||
|
self.threads = max_threads
|
||||||
|
self.preload_app = True
|
||||||
|
|
||||||
|
if env == Env.DEV:
|
||||||
|
self.reload = True
|
||||||
|
|
||||||
def run_prod(self) -> list[str]:
|
def run_prod(self) -> list[str]:
|
||||||
print("[reflex.server.gunicorn::GunicornBackendServer] start")
|
"""Run in production mode."""
|
||||||
|
self.check_import()
|
||||||
|
|
||||||
command = ["gunicorn"]
|
command = ["gunicorn"]
|
||||||
|
|
||||||
for key,field in self.get_fields().items():
|
for key, field in self.get_fields().items():
|
||||||
if key != "app":
|
if key != "app":
|
||||||
value = self.__getattribute__(key)
|
value = getattr(self, key)
|
||||||
if key == "preload":
|
if _mapping_attr_to_cli.get(key) and value != field.default:
|
||||||
print(_mapping_attr_to_cli.get(key, None), value, field.default)
|
if isinstance(value, list):
|
||||||
if _mapping_attr_to_cli.get(key, None):
|
for v in value:
|
||||||
if value != field.default:
|
command += [_mapping_attr_to_cli[key], str(v)]
|
||||||
if isinstance(value, list):
|
elif isinstance(value, bool):
|
||||||
for v in value:
|
if (key == "sendfile" and value is False) or (
|
||||||
command += [_mapping_attr_to_cli[key], str(v)]
|
key != "sendfile" and value
|
||||||
elif isinstance(value, bool):
|
):
|
||||||
command.append(_mapping_attr_to_cli[key])
|
command.append(_mapping_attr_to_cli[key])
|
||||||
else:
|
else:
|
||||||
command += [_mapping_attr_to_cli[key], str(value)]
|
command += [_mapping_attr_to_cli[key], str(value)]
|
||||||
|
|
||||||
print("[reflex.server.gunicorn::GunicornBackendServer] done")
|
return command + [f"{self.get_app_module()}()"]
|
||||||
return command + [f"reflex.app_module_for_backend:{constants.CompileVars.APP}()"]
|
|
||||||
|
|
||||||
def run_dev(self):
|
def run_dev(self):
|
||||||
StandaloneApplication(
|
"""Run in development mode."""
|
||||||
app=self.app,
|
self.check_import()
|
||||||
options=self.dict().items()
|
console.info(
|
||||||
).run()
|
"For development mode, we recommand to use `UvicornBackendServer` than `GunicornBackendServer`"
|
||||||
|
)
|
||||||
|
|
||||||
|
from gunicorn.app.base import BaseApplication
|
||||||
|
from gunicorn.util import import_app as gunicorn_import_app
|
||||||
|
|
||||||
|
options_ = self.dict()
|
||||||
|
options_.pop("app", None)
|
||||||
|
|
||||||
|
class StandaloneApplication(BaseApplication):
|
||||||
|
def __init__(self, app_uri, options=None):
|
||||||
|
self.options = options or {}
|
||||||
|
self.app_uri = app_uri
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def load_config(self):
|
||||||
|
config = {
|
||||||
|
key: value
|
||||||
|
for key, value in self.options.items()
|
||||||
|
if key in self.cfg.settings and value is not None
|
||||||
|
} # type: ignore
|
||||||
|
for key, value in config.items():
|
||||||
|
self.cfg.set(key.lower(), value) # type: ignore
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
return gunicorn_import_app(self.app_uri)
|
||||||
|
|
||||||
|
StandaloneApplication(app_uri=self.app_uri, options=options_).run()
|
||||||
|
@ -1,10 +1,41 @@
|
|||||||
|
"""The UvicornBackendServer."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from reflex.constants.base import Env, LogLevel
|
||||||
from reflex.server.base import CustomBackendServer
|
from reflex.server.base import CustomBackendServer
|
||||||
|
from reflex.utils import console
|
||||||
|
|
||||||
|
|
||||||
|
# TODO
|
||||||
class UvicornBackendServer(CustomBackendServer):
|
class UvicornBackendServer(CustomBackendServer):
|
||||||
|
"""Uvicorn backendServer."""
|
||||||
|
|
||||||
|
def check_import(self, extra: bool = False):
|
||||||
|
"""Check package importation."""
|
||||||
|
from importlib.util import find_spec
|
||||||
|
|
||||||
|
errors: list[str] = []
|
||||||
|
|
||||||
|
if find_spec("uvicorn") is None:
|
||||||
|
errors.append(
|
||||||
|
'The `uvicorn` package is required to run `UvicornBackendServer`. Run `pip install "uvicorn>=0.20.0"`.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
console.error("\n".join(errors))
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
def setup(self, host: str, port: int, loglevel: LogLevel, env: Env):
|
||||||
|
"""Setup."""
|
||||||
|
pass
|
||||||
|
|
||||||
def run_prod(self):
|
def run_prod(self):
|
||||||
|
"""Run in production mode."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def run_dev(self):
|
def run_dev(self):
|
||||||
|
"""Run in development mode."""
|
||||||
pass
|
pass
|
||||||
|
@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import abstractmethod, ABCMeta
|
|
||||||
from typing import IO, Any, Literal, Sequence, Type
|
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@ -12,20 +9,14 @@ import platform
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import asyncio
|
|
||||||
import ssl
|
|
||||||
from configparser import RawConfigParser
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
from pydantic import Field
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from reflex import constants, server
|
from reflex import constants
|
||||||
from reflex.base import Base
|
|
||||||
from reflex.config import environment, get_config
|
from reflex.config import environment, get_config
|
||||||
from reflex.constants.base import LogLevel
|
from reflex.constants.base import Env, LogLevel
|
||||||
from reflex.utils import console, path_ops
|
from reflex.utils import console, path_ops
|
||||||
from reflex.utils.prerequisites import get_web_dir
|
from reflex.utils.prerequisites import get_web_dir
|
||||||
|
|
||||||
@ -187,44 +178,11 @@ def run_frontend_prod(root: Path, port: str, backend_present=True):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def should_use_granian():
|
|
||||||
"""Whether to use Granian for backend.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if Granian should be used.
|
|
||||||
"""
|
|
||||||
return environment.REFLEX_USE_GRANIAN.get()
|
|
||||||
|
|
||||||
|
|
||||||
def get_app_module():
|
|
||||||
"""Get the app module for the backend.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The app module for the backend.
|
|
||||||
"""
|
|
||||||
return f"reflex.app_module_for_backend:{constants.CompileVars.APP}"
|
|
||||||
### REWORK <--
|
### REWORK <--
|
||||||
|
|
||||||
|
|
||||||
def get_granian_target():
|
|
||||||
"""Get the Granian target for the backend.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The Granian target for the backend.
|
|
||||||
"""
|
|
||||||
import reflex
|
|
||||||
|
|
||||||
app_module_path = Path(reflex.__file__).parent / "app_module_for_backend.py"
|
|
||||||
|
|
||||||
return (
|
|
||||||
f"{app_module_path!s}:{constants.CompileVars.APP}.{constants.CompileVars.API}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run_backend(
|
def run_backend(
|
||||||
host: str,
|
host: str,
|
||||||
port: int,
|
port: int,
|
||||||
loglevel: constants.LogLevel = constants.LogLevel.ERROR,
|
loglevel: LogLevel = LogLevel.ERROR,
|
||||||
frontend_present: bool = False,
|
frontend_present: bool = False,
|
||||||
):
|
):
|
||||||
"""Run the backend.
|
"""Run the backend.
|
||||||
@ -236,6 +194,7 @@ def run_backend(
|
|||||||
frontend_present: Whether the frontend is present.
|
frontend_present: Whether the frontend is present.
|
||||||
"""
|
"""
|
||||||
web_dir = get_web_dir()
|
web_dir = get_web_dir()
|
||||||
|
config = get_config()
|
||||||
# Create a .nocompile file to skip compile for backend.
|
# Create a .nocompile file to skip compile for backend.
|
||||||
if web_dir.exists():
|
if web_dir.exists():
|
||||||
(web_dir / constants.NOCOMPILE_FILE).touch()
|
(web_dir / constants.NOCOMPILE_FILE).touch()
|
||||||
@ -244,78 +203,15 @@ def run_backend(
|
|||||||
notify_backend()
|
notify_backend()
|
||||||
|
|
||||||
# Run the backend in development mode.
|
# Run the backend in development mode.
|
||||||
if should_use_granian():
|
backend_server_prod = config.backend_server_prod
|
||||||
run_granian_backend(host, port, loglevel)
|
backend_server_prod.setup(host, port, loglevel, Env.DEV)
|
||||||
else:
|
backend_server_prod.run_dev()
|
||||||
run_uvicorn_backend(host, port, loglevel)
|
|
||||||
|
|
||||||
|
|
||||||
def run_uvicorn_backend(host, port, loglevel: LogLevel):
|
|
||||||
"""Run the backend in development mode using Uvicorn.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
host: The app host
|
|
||||||
port: The app port
|
|
||||||
loglevel: The log level.
|
|
||||||
"""
|
|
||||||
import uvicorn
|
|
||||||
|
|
||||||
uvicorn.run(
|
|
||||||
app=f"{get_app_module()}.{constants.CompileVars.API}",
|
|
||||||
host=host,
|
|
||||||
port=port,
|
|
||||||
log_level=loglevel.value,
|
|
||||||
reload=True,
|
|
||||||
reload_dirs=[get_config().app_name],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run_granian_backend(host, port, loglevel: LogLevel):
|
|
||||||
"""Run the backend in development mode using Granian.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
host: The app host
|
|
||||||
port: The app port
|
|
||||||
loglevel: The log level.
|
|
||||||
"""
|
|
||||||
console.debug("Using Granian for backend")
|
|
||||||
try:
|
|
||||||
from granian import Granian # type: ignore
|
|
||||||
from granian.constants import Interfaces # type: ignore
|
|
||||||
from granian.log import LogLevels # type: ignore
|
|
||||||
|
|
||||||
Granian(
|
|
||||||
target=get_granian_target(),
|
|
||||||
address=host,
|
|
||||||
port=port,
|
|
||||||
interface=Interfaces.ASGI,
|
|
||||||
log_level=LogLevels(loglevel.value),
|
|
||||||
reload=True,
|
|
||||||
reload_paths=[Path(get_config().app_name)],
|
|
||||||
reload_ignore_dirs=[".web"],
|
|
||||||
).serve()
|
|
||||||
except ImportError:
|
|
||||||
console.error(
|
|
||||||
'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian[reload]>=1.6.0"`)'
|
|
||||||
)
|
|
||||||
os._exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_backend_workers():
|
|
||||||
from reflex.utils import processes
|
|
||||||
|
|
||||||
config = get_config()
|
|
||||||
return (
|
|
||||||
processes.get_num_workers()
|
|
||||||
if not config.gunicorn_workers
|
|
||||||
else config.gunicorn_workers
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run_backend_prod(
|
def run_backend_prod(
|
||||||
host: str,
|
host: str,
|
||||||
port: int,
|
port: int,
|
||||||
loglevel: constants.LogLevel = constants.LogLevel.ERROR,
|
loglevel: LogLevel = LogLevel.ERROR,
|
||||||
frontend_present: bool = False,
|
frontend_present: bool = False,
|
||||||
):
|
):
|
||||||
"""Run the backend.
|
"""Run the backend.
|
||||||
@ -326,77 +222,24 @@ def run_backend_prod(
|
|||||||
loglevel: The log level.
|
loglevel: The log level.
|
||||||
frontend_present: Whether the frontend is present.
|
frontend_present: Whether the frontend is present.
|
||||||
"""
|
"""
|
||||||
print("[reflex.utils.exec::run_backend_prod] start")
|
from reflex.utils import processes
|
||||||
if not frontend_present:
|
|
||||||
notify_backend()
|
|
||||||
|
|
||||||
config = get_config()
|
config = get_config()
|
||||||
|
|
||||||
if should_use_granian():
|
if not frontend_present:
|
||||||
run_granian_backend_prod(host, port, loglevel)
|
notify_backend()
|
||||||
else:
|
|
||||||
from reflex.utils import processes
|
|
||||||
|
|
||||||
backend_server_prod = config.backend_server_prod
|
# Run the backend in production mode.
|
||||||
if isinstance(backend_server_prod, server.GunicornBackendServer):
|
backend_server_prod = config.backend_server_prod
|
||||||
backend_server_prod.app = f"{get_app_module()}()"
|
backend_server_prod.setup(host, port, loglevel, Env.PROD)
|
||||||
backend_server_prod.preload_app = True
|
processes.new_process(
|
||||||
backend_server_prod.loglevel = loglevel.value # type: ignore
|
backend_server_prod.run_prod(),
|
||||||
backend_server_prod.bind = [f"{host}:{port}"]
|
run=True,
|
||||||
backend_server_prod.threads = _get_backend_workers()
|
show_logs=True,
|
||||||
backend_server_prod.workers = _get_backend_workers()
|
env={
|
||||||
|
environment.REFLEX_SKIP_COMPILE.name: "true"
|
||||||
print(backend_server_prod.run_prod())
|
}, # skip compile for prod backend
|
||||||
processes.new_process(
|
)
|
||||||
backend_server_prod.run_prod(),
|
|
||||||
run=True,
|
|
||||||
show_logs=True,
|
|
||||||
env={
|
|
||||||
environment.REFLEX_SKIP_COMPILE.name: "true"
|
|
||||||
}, # skip compile for prod backend
|
|
||||||
)
|
|
||||||
print("[reflex.utils.exec::run_backend_prod] done")
|
|
||||||
|
|
||||||
|
|
||||||
def run_granian_backend_prod(host, port, loglevel):
|
|
||||||
"""Run the backend in production mode using Granian.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
host: The app host
|
|
||||||
port: The app port
|
|
||||||
loglevel: The log level.
|
|
||||||
"""
|
|
||||||
from reflex.utils import processes
|
|
||||||
|
|
||||||
try:
|
|
||||||
from granian.constants import Interfaces # type: ignore
|
|
||||||
|
|
||||||
command = [
|
|
||||||
"granian",
|
|
||||||
"--workers",
|
|
||||||
str(_get_backend_workers()),
|
|
||||||
"--log-level",
|
|
||||||
"critical",
|
|
||||||
"--host",
|
|
||||||
host,
|
|
||||||
"--port",
|
|
||||||
str(port),
|
|
||||||
"--interface",
|
|
||||||
str(Interfaces.ASGI),
|
|
||||||
get_granian_target(),
|
|
||||||
]
|
|
||||||
processes.new_process(
|
|
||||||
command,
|
|
||||||
run=True,
|
|
||||||
show_logs=True,
|
|
||||||
env={
|
|
||||||
environment.REFLEX_SKIP_COMPILE.name: "true"
|
|
||||||
}, # skip compile for prod backend
|
|
||||||
)
|
|
||||||
except ImportError:
|
|
||||||
console.error(
|
|
||||||
'InstallError: REFLEX_USE_GRANIAN is set but `granian` is not installed. (run `pip install "granian[reload]>=1.6.0"`)'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
### REWORK-->
|
### REWORK-->
|
||||||
@ -522,6 +365,3 @@ def should_skip_compile() -> bool:
|
|||||||
removal_version="0.7.0",
|
removal_version="0.7.0",
|
||||||
)
|
)
|
||||||
return environment.REFLEX_SKIP_COMPILE.get()
|
return environment.REFLEX_SKIP_COMPILE.get()
|
||||||
|
|
||||||
|
|
||||||
### REWORK <--
|
|
||||||
|
Loading…
Reference in New Issue
Block a user