app-server
Advanced tools
+1
-1
| Metadata-Version: 2.1 | ||
| Name: app_server | ||
| Version: 0.9.9 | ||
| Version: 0.9.10 | ||
| Summary: a lightweight web application launcher for gunicorn and static files. | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/viur-framework/viur-app_server |
| Metadata-Version: 2.1 | ||
| Name: app_server | ||
| Version: 0.9.9 | ||
| Version: 0.9.10 | ||
| Summary: a lightweight web application launcher for gunicorn and static files. | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/viur-framework/viur-app_server |
+157
-92
@@ -1,21 +0,30 @@ | ||
| import sys, os, re, subprocess, yaml, argparse, time, mimetypes, logging | ||
| import argparse | ||
| import logging | ||
| import mimetypes | ||
| import os | ||
| import re | ||
| import subprocess | ||
| import sys | ||
| import time | ||
| import typing as t | ||
| import yaml | ||
| from werkzeug._internal import _logger # noqa | ||
| from werkzeug.http import http_date, is_resource_modified | ||
| from werkzeug.middleware.dispatcher import DispatcherMiddleware | ||
| from werkzeug.middleware.http_proxy import ProxyMiddleware | ||
| from werkzeug.middleware.shared_data import SharedDataMiddleware | ||
| from werkzeug.serving import run_simple, WSGIRequestHandler, _ansi_style, \ | ||
| _log_add_style | ||
| from werkzeug.urls import uri_to_iri | ||
| from werkzeug.utils import get_content_type | ||
| from werkzeug.wrappers import Request, Response | ||
| from werkzeug.middleware.shared_data import SharedDataMiddleware | ||
| from werkzeug.middleware.http_proxy import ProxyMiddleware | ||
| from werkzeug.middleware.dispatcher import DispatcherMiddleware | ||
| from werkzeug.serving import run_simple, WSGIRequestHandler,_ansi_style,_log_add_style | ||
| from werkzeug.wsgi import get_path_info, wrap_file | ||
| from werkzeug.utils import get_content_type | ||
| from werkzeug.http import http_date, is_resource_modified | ||
| from werkzeug._internal import _logger | ||
| from werkzeug.urls import uri_to_iri, url_unquote | ||
| __version__ = "0.9.10" | ||
| subprocesses = [] | ||
| __version__ = "0.9.9" | ||
| subprocesses = [] | ||
| class myWSGIRequestHandler(WSGIRequestHandler): | ||
| class MainWSGIRequestHandler(WSGIRequestHandler): | ||
| def log_date_time_string(self): | ||
@@ -26,7 +35,10 @@ """Return the current time formatted for logging.""" | ||
| s = "%04d-%02d-%02d %02d:%02d:%02d" % ( | ||
| year , month,day , hh, mm, ss) | ||
| year, month, day, hh, mm, ss) | ||
| return s | ||
| def log_request(self, code: t.Union[int, str] = "-", size: t.Union[int, str] = "-") -> None: | ||
| def log_request( | ||
| self, | ||
| code: t.Union[int, str] = "-", | ||
| size: t.Union[int, str] = "-", | ||
| ) -> None: | ||
| """coloring the status code""" | ||
@@ -43,3 +55,3 @@ try: | ||
| log_type = "info" | ||
| if code != "200": #possibility to filter 200 requests | ||
| if code != "200": # possibility to filter 200 requests | ||
| log_type = "warning" | ||
@@ -73,6 +85,7 @@ | ||
| getattr(_logger, type)(f"[{self.log_date_time_string()}] {message % args}") | ||
| getattr(_logger, type)( | ||
| f"[{self.log_date_time_string()}] {message % args}") | ||
| class WrappingApp(object): | ||
| class WrappingApp: | ||
| """simple wrapping app""" | ||
@@ -85,3 +98,4 @@ | ||
| request = Request(environ) | ||
| response = Response(f'Path not found or invalid: {request.path}', status=404) | ||
| response = Response(f'Path not found or invalid: {request.path}', | ||
| status=404) | ||
| return response(environ, start_response) | ||
@@ -92,3 +106,4 @@ | ||
| class myProxy(ProxyMiddleware): | ||
| class Proxy(ProxyMiddleware): | ||
| """this addition allows to redirect all routes to given targets""" | ||
@@ -110,3 +125,4 @@ | ||
| def __call__(self, environ: "WSGIEnvironment", start_response: "StartResponse") -> t.Iterable[bytes]: | ||
| def __call__(self, environ: "WSGIEnvironment", | ||
| start_response: "StartResponse") -> t.Iterable[bytes]: | ||
| path = get_path_info(environ, charset='utf-8', errors='replace') | ||
@@ -121,3 +137,4 @@ app = self.app | ||
| class myDispatcher(DispatcherMiddleware): | ||
| class Dispatcher(DispatcherMiddleware): | ||
| """use regex to find a matching route""" | ||
@@ -135,16 +152,17 @@ | ||
| class mySharedData(SharedDataMiddleware): | ||
| class SharedData(SharedDataMiddleware): | ||
| """use regex to find a matching files""" | ||
| def __init__( | ||
| self, | ||
| app, | ||
| exports, | ||
| disallow: None = None, | ||
| cache: bool = True, | ||
| cache_timeout: int = 60 * 60 * 12, | ||
| fallback_mimetype: str = "application/octet-stream", | ||
| self, | ||
| app, | ||
| exports, | ||
| disallow: None = None, | ||
| cache: bool = True, | ||
| cache_timeout: int = 60 * 60 * 12, | ||
| fallback_mimetype: str = "application/octet-stream", | ||
| ) -> None: | ||
| self.org_exports = exports.copy() | ||
| super().__init__(app, exports, disallow, cache, cache_timeout, fallback_mimetype) | ||
| super().__init__(app, exports, disallow, cache, cache_timeout, | ||
| fallback_mimetype) | ||
@@ -158,4 +176,6 @@ def __call__(self, environ, start_response): | ||
| if re.match(search_path, path): | ||
| real_path = re.sub(search_path, self.org_exports[search_path], path, 1) | ||
| real_filename, file_loader = self.get_file_loader(real_path)(None) | ||
| real_path = re.sub(search_path, self.org_exports[search_path], | ||
| path, 1) | ||
| real_filename, file_loader = self.get_file_loader(real_path)( | ||
| None) | ||
@@ -180,7 +200,8 @@ if file_loader is not None: | ||
| if file_loader is None or not self.is_allowed(real_filename): # type: ignore | ||
| if file_loader is None or not self.is_allowed(real_filename): # noqa | ||
| return self.app(environ, start_response) | ||
| guessed_type = mimetypes.guess_type(real_filename) # type: ignore | ||
| mime_type = get_content_type(guessed_type[0] or self.fallback_mimetype, "utf-8") | ||
| mime_type = get_content_type(guessed_type[0] or self.fallback_mimetype, | ||
| "utf-8") | ||
@@ -196,3 +217,4 @@ try: | ||
| timeout = self.cache_timeout | ||
| etag = self.generate_etag(mtime, file_size, real_filename) # type: ignore | ||
| etag = self.generate_etag(mtime, file_size, | ||
| real_filename) # type: ignore | ||
| headers += [ | ||
@@ -223,3 +245,11 @@ ("Etag", f'"{etag}"'), | ||
| def start_server(host, port, gunicorn_port, appFolder, appYaml, timeout, protocol="http"): | ||
| def start_server( | ||
| host: str, | ||
| port: int, | ||
| gunicorn_port: int, | ||
| app_folder: str, | ||
| app_yaml: dict, | ||
| timeout: int, | ||
| protocol: str = "http", | ||
| ) -> None: | ||
| """use the dispatcherMiddleware to connect SharedDataMiddleware and ProxyMiddleware with the wrapping app.""" | ||
@@ -230,3 +260,3 @@ app = WrappingApp({}) | ||
| # make shared middlewares for static files as configured in app.yaml | ||
| for route in appYaml["handlers"]: | ||
| for route in app_yaml["handlers"]: | ||
| if path := route.get("static_dir"): | ||
@@ -242,5 +272,7 @@ pattern = route["url"] + "/.*" | ||
| # print(pattern, route["url"], path) | ||
| apps[pattern] = mySharedData(app.wsgi_app, {route["url"]: os.path.join(appFolder, path)}) | ||
| apps[pattern] = SharedData( | ||
| app.wsgi_app, {route["url"]: os.path.join(app_folder, path)} | ||
| ) | ||
| apps.update({"/": myProxy(app.wsgi_app, { | ||
| apps["/"] = Proxy(app.wsgi_app, { | ||
| "/": { | ||
@@ -250,9 +282,10 @@ "target": f"{protocol}://{host}:{gunicorn_port}/", | ||
| } | ||
| },timeout=timeout)}) | ||
| app.wsgi_app = myDispatcher(app.wsgi_app, apps) | ||
| }, timeout=timeout) | ||
| app.wsgi_app = Dispatcher(app.wsgi_app, apps) | ||
| run_simple(host, port, app, use_debugger=False, use_reloader=True, threaded=True, request_handler=myWSGIRequestHandler) | ||
| run_simple(host, port, app, use_debugger=False, use_reloader=True, | ||
| threaded=True, request_handler=MainWSGIRequestHandler) | ||
| def envVars(application_id: str, args: argparse.Namespace, app_yaml: dict): | ||
| def set_env_vars(application_id: str, args: argparse.Namespace, app_yaml: dict): | ||
| """set necessary environment variables""" | ||
@@ -263,3 +296,4 @@ # First, merge the app.yaml into the environment so that the variables | ||
| if not isinstance(env_vars, dict): | ||
| raise TypeError(f"env_variables section in app.yaml must be a dict. Got {type(env_vars)}") | ||
| raise TypeError( | ||
| f"env_variables section in app.yaml must be a dict. Got {type(env_vars)}") | ||
| os.environ |= {k: str(v) for k, v in app_yaml["env_variables"].items()} | ||
@@ -274,3 +308,4 @@ | ||
| if args.storage: | ||
| os.environ["STORAGE_EMULATOR_HOST"] = f"http://{args.host}:{args.storage_port}" | ||
| os.environ["STORAGE_EMULATOR_HOST"] = \ | ||
| f"http://{args.host}:{args.storage_port}" | ||
@@ -284,2 +319,3 @@ if args.tasks: | ||
| def patch_gunicorn(): | ||
@@ -300,14 +336,22 @@ import gunicorn.workers.base | ||
| def start_gunicorn(args, appYaml, appFolder, myFolder): | ||
| def start_gunicorn( | ||
| args: argparse.Namespace, | ||
| app_yaml: dict, | ||
| app_folder: str, | ||
| ) -> None: | ||
| # Gunicorn call command | ||
| entrypoint = appYaml.get("entrypoint", "gunicorn -b :$PORT -w $WORKER --threads $THREADS " | ||
| "--disable-redirect-access-to-syslog main:app") | ||
| for var, value in { | ||
| "PORT": args.gunicorn_port, | ||
| "WORKER": args.worker, | ||
| "THREADS": args.threads | ||
| }.items(): | ||
| entrypoint = entrypoint.replace(f"${var}", str(value)) | ||
| if not (entrypoint := args.entrypoint): | ||
| entrypoint = app_yaml.get( | ||
| "entrypoint", | ||
| "gunicorn -b :$PORT --disable-redirect-access-to-syslog main:app" | ||
| ) | ||
| entrypoint = entrypoint.replace(f"$PORT", str(args.gunicorn_port)) | ||
| # Remove -w / --workers / --threads arguments, | ||
| # we set them later with the values from our argparser | ||
| entrypoint = re.sub(r"\s+-(w|-workers|-threads)\s+\d+", " ", entrypoint) | ||
| entrypoint = entrypoint.split() | ||
| entrypoint.extend(["--workers", str(args.workers)]) | ||
| entrypoint.extend(["--threads", str(args.threads)]) | ||
| if "--reload" not in entrypoint: | ||
@@ -320,6 +364,5 @@ entrypoint.insert(1, "--reload") | ||
| os.chdir(appFolder) | ||
| subprocesses.append(subprocess.Popen(entrypoint)) | ||
| os.chdir(myFolder) | ||
| subprocesses.append(subprocess.Popen(entrypoint, cwd=app_folder)) | ||
| def main(): | ||
@@ -334,23 +377,43 @@ """main entrypoint | ||
| ap = argparse.ArgumentParser( | ||
| description="alternative dev_appserver" | ||
| description="alternative dev_appserver", | ||
| epilog=f"Version: {__version__}" | ||
| ) | ||
| ap.add_argument("config_paths", metavar='yaml_path', nargs='+', help='Path to app.yaml file') | ||
| ap.add_argument("config_paths", metavar='yaml_path', nargs='+', | ||
| help='Path to app.yaml file') | ||
| ap.add_argument( | ||
| '-A', '--application', action='store', dest='app_id', required=True, help='Set the application id') | ||
| ap.add_argument('--host', default="localhost", help='host name to which application modules should bind') | ||
| ap.add_argument('--port', type=int, default=8080, help='port to which we bind the application') | ||
| ap.add_argument('--gunicorn_port', type=int, default=8090, help='internal gunicorn port') | ||
| ap.add_argument('--worker', type=int, default=1, help='amount of gunicorn workers') | ||
| ap.add_argument('--threads', type=int, default=5, help='amount of gunicorn threads') | ||
| ap.add_argument('--timeout', type=int, default=60, help='Time is seconds before gunicorn abort a request') | ||
| ap.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__) | ||
| '-A', '--application', action='store', dest='app_id', required=True, | ||
| help='Set the application id') | ||
| ap.add_argument('--host', default="localhost", | ||
| help='host name to which application modules should bind') | ||
| ap.add_argument('--entrypoint', type=str, default=None, | ||
| help='The entrypoint is the basic gunicorn command. By default, it\'s taken from app.yaml. ' | ||
| 'This parameter can be used to set a different entrypoint. ' | ||
| 'To provide this parameter via ViUR-CLI, you have to double quote it: ' | ||
| ' --entrypoint "\'gunicorn -b :\$PORT --disable-redirect-access-to-syslog main:app\'"') | ||
| ap.add_argument('--port', type=int, default=8080, | ||
| help='port to which we bind the application') | ||
| ap.add_argument('--gunicorn_port', type=int, default=8090, | ||
| help='internal gunicorn port') | ||
| ap.add_argument('--workers', '--worker', type=int, default=1, | ||
| help='amount of gunicorn workers') | ||
| ap.add_argument('--threads', type=int, default=5, | ||
| help='amount of gunicorn threads') | ||
| ap.add_argument('--timeout', type=int, default=60, | ||
| help='Time is seconds before gunicorn abort a request') | ||
| ap.add_argument('-V', '--version', action='version', | ||
| version='%(prog)s ' + __version__) | ||
| ap.add_argument('--storage', default=False, action="store_true", dest="storage", help="also start Storage Emulator") | ||
| ap.add_argument('--storage_port', type=int, default=8092, help='internal Storage Emulator Port') | ||
| ap.add_argument('--storage', default=False, action="store_true", | ||
| dest="storage", help="also start Storage Emulator") | ||
| ap.add_argument('--storage_port', type=int, default=8092, | ||
| help='internal Storage Emulator Port') | ||
| ap.add_argument('--tasks', default=False, action='store_true', dest="tasks", help='also start Task-Queue Emulator') | ||
| ap.add_argument('--tasks_port', type=int, default=8091, help='internal Task-Queue Emulator Port') | ||
| ap.add_argument('--tasks', default=False, action='store_true', dest="tasks", | ||
| help='also start Task-Queue Emulator') | ||
| ap.add_argument('--tasks_port', type=int, default=8091, | ||
| help='internal Task-Queue Emulator Port') | ||
| ap.add_argument('--cron', default=False, action='store_true', dest="cron", help='also start Cron Emulator') | ||
| ap.add_argument('--cron', default=False, action='store_true', dest="cron", | ||
| help='also start Cron Emulator') | ||
@@ -366,40 +429,43 @@ ap.add_argument( | ||
| appFolder = os.path.abspath(args.config_paths[0]) | ||
| app_folder = os.path.abspath(args.config_paths[0]) | ||
| # load & parse the app.yaml | ||
| with open(os.path.join(appFolder, "app.yaml"), "r") as f: | ||
| appYaml = yaml.load(f, Loader=yaml.Loader) | ||
| with open(os.path.join(app_folder, "app.yaml"), "r") as f: | ||
| app_yaml = yaml.load(f, Loader=yaml.Loader) | ||
| envVars(args.app_id, args, appYaml) | ||
| set_env_vars(args.app_id, args, app_yaml) | ||
| patch_gunicorn() | ||
| myFolder = os.getcwd() | ||
| # Check for correct runtime | ||
| myRuntime = f"python{sys.version_info.major}{sys.version_info.minor}" | ||
| appRuntime = appYaml["runtime"] | ||
| assert appRuntime == myRuntime, f"app.yaml specifies {appRuntime} but you're on {myRuntime}, please correct this." | ||
| current_runtime = f"python{sys.version_info.major}{sys.version_info.minor}" | ||
| app_runtime = app_yaml["runtime"] | ||
| assert app_runtime == current_runtime, f"app.yaml specifies {app_runtime} but you're on {current_runtime}, please correct this." | ||
| if "WERKZEUG_RUN_MAIN" in os.environ and os.environ["WERKZEUG_RUN_MAIN"]: | ||
| #only start subprocesses wenn reloader starts | ||
| # only start subprocesses wenn reloader starts | ||
| if args.storage: | ||
| storage_subprocess =subprocess.Popen( | ||
| f"gcloud-storage-emulator start --port={args.storage_port} --default-bucket={args.app_id}.appspot.com".split()) | ||
| storage_subprocess = subprocess.Popen( | ||
| f"gcloud-storage-emulator start --port={args.storage_port}" | ||
| f" --default-bucket={args.app_id}.appspot.com".split()) | ||
| subprocesses.append(storage_subprocess) | ||
| if args.tasks and os.path.exists(os.path.join(appFolder, 'queue.yaml')): | ||
| if args.tasks and os.path.exists( | ||
| os.path.join(app_folder, 'queue.yaml')): | ||
| cron = "" | ||
| if args.cron: | ||
| cron = f"--cron-yaml={os.path.join(appFolder, 'cron.yaml')}" | ||
| cron = f"--cron-yaml={os.path.join(app_folder, 'cron.yaml')}" | ||
| tasks_subprocess = subprocess.Popen( | ||
| f"gcloud-tasks-emulator start -p={args.tasks_port} -t={args.port} {cron} --queue-yaml={os.path.join(appFolder, 'queue.yaml')} --queue-yaml-project={args.app_id} --queue-yaml-location=local -r 50".split()) | ||
| f"gcloud-tasks-emulator start -p={args.tasks_port} -t={args.port} {cron}" | ||
| f" --queue-yaml={os.path.join(app_folder, 'queue.yaml')}" | ||
| f" --queue-yaml-project={args.app_id} --queue-yaml-location=local -r 50".split()) | ||
| subprocesses.append(tasks_subprocess) | ||
| start_gunicorn(args, appYaml, appFolder, myFolder) | ||
| start_gunicorn(args, app_yaml, app_folder) | ||
| start_server(args.host, args.port, args.gunicorn_port, appFolder, appYaml, args.timeout) | ||
| start_server(args.host, args.port, args.gunicorn_port, app_folder, app_yaml, | ||
| args.timeout) | ||
@@ -415,2 +481,1 @@ try: | ||
| main() | ||
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
266945
0.55%6110
1.04%