mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 23:00:17 +00:00
Chore: A few updates from code review etc.
This commit is contained in:
committed by
Andreas Motl
parent
b5723303c8
commit
1b63d2943a
@@ -2,7 +2,7 @@
|
||||
Responder - a familiar HTTP Service Framework.
|
||||
|
||||
This module exports the core functionality of the Responder framework,
|
||||
including the API, Request, Response classes and CLI interface.
|
||||
including the API, Request, and Response classes.
|
||||
"""
|
||||
|
||||
from . import ext
|
||||
|
||||
+9
-1
@@ -2,7 +2,6 @@ import os
|
||||
from pathlib import Path
|
||||
|
||||
import uvicorn
|
||||
from starlette.exceptions import ExceptionMiddleware
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.middleware.errors import ServerErrorMiddleware
|
||||
from starlette.middleware.gzip import GZipMiddleware
|
||||
@@ -11,6 +10,15 @@ from starlette.middleware.sessions import SessionMiddleware
|
||||
from starlette.middleware.trustedhost import TrustedHostMiddleware
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
# Python 3.7+
|
||||
try:
|
||||
from starlette.middleware.exceptions import ExceptionMiddleware
|
||||
# Python 3.6
|
||||
except ImportError:
|
||||
from starlette.exceptions import ( # type: ignore[attr-defined,no-redef]
|
||||
ExceptionMiddleware,
|
||||
)
|
||||
|
||||
from . import status_codes
|
||||
from .background import BackgroundQueue
|
||||
from .formats import get_formats
|
||||
|
||||
@@ -69,12 +69,14 @@ def cli() -> None:
|
||||
sys.exit(1)
|
||||
npm_cmd = "npm.cmd" if platform.system() == "Windows" else "npm"
|
||||
try:
|
||||
# # S603, S607 are addressed by validating the target directory.
|
||||
logger.info("Starting frontend asset build")
|
||||
# S603, S607 are addressed by validating the target directory.
|
||||
subprocess.check_call( # noqa: S603, S607
|
||||
[npm_cmd, "run", "build"],
|
||||
cwd=target_path,
|
||||
timeout=300,
|
||||
)
|
||||
logger.info("Frontend asset build completed successfully")
|
||||
except FileNotFoundError:
|
||||
logger.error("npm not found. Please install Node.js and npm.")
|
||||
sys.exit(1)
|
||||
|
||||
+40
-7
@@ -4,6 +4,7 @@
|
||||
# 1. Only execute the 'responder' binary from PATH
|
||||
# 2. Validate all user inputs before passing to subprocess
|
||||
# 3. Use Path.resolve() to prevent path traversal
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
@@ -20,10 +21,19 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class ResponderProgram:
|
||||
"""
|
||||
Provide full path to the `responder` program.
|
||||
Utility class for managing Responder program execution.
|
||||
|
||||
This class provides methods for:
|
||||
- Locating the responder executable in PATH
|
||||
- Building frontend assets using npm
|
||||
|
||||
Example:
|
||||
>>> program_path = ResponderProgram.path()
|
||||
>>> build_status = ResponderProgram.build(Path("app_dir"))
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def path():
|
||||
name = "responder"
|
||||
if sys.platform == "win32":
|
||||
@@ -105,6 +115,13 @@ class ResponderServer(threading.Thread):
|
||||
):
|
||||
raise ValueError("limit_max_requests must be a positive integer if specified")
|
||||
|
||||
# Check if port is available.
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(("localhost", port))
|
||||
except OSError as ex:
|
||||
raise ValueError(f"Port {port} is already in use") from ex
|
||||
|
||||
# Instance variables after validation.
|
||||
self.target = target
|
||||
self.port = port
|
||||
@@ -114,6 +131,7 @@ class ResponderServer(threading.Thread):
|
||||
# Allow the thread to be terminated when the main program exits.
|
||||
self.process: subprocess.Popen
|
||||
self.daemon = True
|
||||
self._process_lock = threading.Lock()
|
||||
|
||||
# Setup signal handlers.
|
||||
signal.signal(signal.SIGTERM, self._signal_handler)
|
||||
@@ -134,19 +152,27 @@ class ResponderServer(threading.Thread):
|
||||
if self.port is not None:
|
||||
env["PORT"] = str(self.port)
|
||||
|
||||
self.process = subprocess.Popen(
|
||||
command,
|
||||
env=env,
|
||||
universal_newlines=True,
|
||||
)
|
||||
with self._process_lock:
|
||||
self.process = subprocess.Popen(
|
||||
command,
|
||||
env=env,
|
||||
universal_newlines=True,
|
||||
)
|
||||
self.process.wait()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Gracefully stop the process.
|
||||
Gracefully stop the process (API).
|
||||
"""
|
||||
if self._stopping:
|
||||
return
|
||||
with self._process_lock:
|
||||
self._stop()
|
||||
|
||||
def _stop(self):
|
||||
"""
|
||||
Gracefully stop the process (impl).
|
||||
"""
|
||||
self._stopping = True
|
||||
if self.process and self.process.poll() is None:
|
||||
logger.info("Attempting to terminate server process...")
|
||||
@@ -179,6 +205,7 @@ class ResponderServer(threading.Thread):
|
||||
bool: True if server is ready and accepting connections, False otherwise.
|
||||
"""
|
||||
start_time = time.time()
|
||||
last_error = None
|
||||
while time.time() - start_time < timeout:
|
||||
if not self.is_running():
|
||||
if self.process is None:
|
||||
@@ -198,8 +225,14 @@ class ResponderServer(threading.Thread):
|
||||
socket.gaierror,
|
||||
OSError,
|
||||
) as ex:
|
||||
last_error = ex
|
||||
logger.debug(f"Server not ready yet: {ex}")
|
||||
time.sleep(delay)
|
||||
logger.error(
|
||||
"Server failed to start within %d seconds. Last error: %s",
|
||||
timeout,
|
||||
last_error,
|
||||
)
|
||||
return False
|
||||
|
||||
def is_running(self):
|
||||
|
||||
@@ -138,7 +138,14 @@ setup(
|
||||
"graphql": ["graphene<3", "graphql-server-core>=1.2,<2"],
|
||||
"openapi": ["apispec>=1.0.0"],
|
||||
"release": ["build", "twine"],
|
||||
"test": ["flask", "mypy", "pytest", "pytest-cov", "pytest-mock", "pytest-rerunfailures"],
|
||||
"test": [
|
||||
"flask",
|
||||
"mypy",
|
||||
"pytest",
|
||||
"pytest-cov",
|
||||
"pytest-mock",
|
||||
"pytest-rerunfailures",
|
||||
],
|
||||
},
|
||||
include_package_data=True,
|
||||
license="Apache 2.0",
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ This module tests the following CLI commands:
|
||||
- responder run: Server execution
|
||||
|
||||
Requirements:
|
||||
- The `docopt` package must be installed
|
||||
- The `docopt-ng` package must be installed
|
||||
- Example application must be present at `examples/helloworld.py`
|
||||
- This file should implement a basic HTTP server with a "/hello" endpoint
|
||||
that returns "hello, world!" as response
|
||||
|
||||
Reference in New Issue
Block a user