def run(self, host=None, port=None, debug=False, ssl=None, sock=None, workers=1, protocol=None, backlog=100, stop_event=None, register_sys_signals=True, access_log=True): """Run the HTTP Server and listen until keyboard interrupt or term signal. On termination, drain connections before closing. :param host: Address to host on :param port: Port to host on :param debug: Enables debug output (slows server) :param ssl: SSLContext, or location of certificate and key for SSL encryption of worker(s) :param sock: Socket for the server to accept connections from :param workers: Number of processes received before it is respected :param backlog: :param stop_event: :param register_sys_signals: :param protocol: Subclass of asyncio protocol class :return: Nothing """ if sock is None: host, port = host or "127.0.0.1", port or 8000 if protocol is None: protocol = (WebSocketProtocol if self.websocket_enabled else HttpProtocol) if stop_event is not None: if debug: warnings.simplefilter('default') warnings.warn("stop_event will be removed from future versions.", DeprecationWarning) server_settings = self._helper( host=host, port=port, debug=debug, ssl=ssl, sock=sock, workers=workers, protocol=protocol, backlog=backlog, register_sys_signals=register_sys_signals, access_log=access_log) try: self.is_running = True if workers == 1: serve(**server_settings) else: serve_multiple(server_settings, workers) except BaseException: error_logger.exception( 'Experienced exception while trying to serve') raise finally: self.is_running = False logger.info("Server Stopped")
def form(self): if self.parsed_form is None: self.parsed_form = RequestParameters() self.parsed_files = RequestParameters() content_type = self.headers.get( 'Content-Type', DEFAULT_HTTP_CONTENT_TYPE) content_type, parameters = parse_header(content_type) try: if content_type == 'application/x-www-form-urlencoded': self.parsed_form = RequestParameters( parse_qs(self.body.decode('utf-8'))) elif content_type == 'multipart/form-data': # TODO: Stream this instead of reading to/from memory boundary = parameters['boundary'].encode('utf-8') self.parsed_form, self.parsed_files = ( parse_multipart_form(self.body, boundary)) except Exception: error_logger.exception("Failed when parsing form") return self.parsed_form
def run( self, host: Optional[str] = None, port: Optional[int] = None, debug: bool = False, ssl: Union[dict, SSLContext, None] = None, sock: Optional[socket] = None, workers: int = 1, protocol: Type[Protocol] = None, backlog: int = 100, stop_event: Any = None, register_sys_signals: bool = True, access_log: Optional[bool] = None, **kwargs: Any ) -> None: """Run the HTTP Server and listen until keyboard interrupt or term signal. On termination, drain connections before closing. :param host: Address to host on :type host: str :param port: Port to host on :type port: int :param debug: Enables debug output (slows server) :type debug: bool :param ssl: SSLContext, or location of certificate and key for SSL encryption of worker(s) :type ssl:SSLContext or dict :param sock: Socket for the server to accept connections from :type sock: socket :param workers: Number of processes received before it is respected :type workers: int :param protocol: Subclass of asyncio Protocol class :type protocol: type[Protocol] :param backlog: a number of unaccepted connections that the system will allow before refusing new connections :type backlog: int :param stop_event: event to be triggered before stopping the app - deprecated :type stop_event: None :param register_sys_signals: Register SIG* events :type register_sys_signals: bool :param access_log: Enables writing access logs (slows server) :type access_log: bool :return: Nothing """ if "loop" in kwargs: raise TypeError( "loop is not a valid argument. To use an existing loop, " "change to create_server().\nSee more: " "https://sanic.readthedocs.io/en/latest/sanic/deploying.html" "#asynchronous-support" ) # Default auto_reload to false auto_reload = False # If debug is set, default it to true (unless on windows) if debug and os.name == "posix": auto_reload = True # Allow for overriding either of the defaults auto_reload = kwargs.get("auto_reload", auto_reload) if sock is None: host, port = host or "127.0.0.1", port or 8000 if protocol is None: protocol = ( WebSocketProtocol if self.websocket_enabled else HttpProtocol ) if stop_event is not None: if debug: warnings.simplefilter("default") warnings.warn( "stop_event will be removed from future versions.", DeprecationWarning, ) # if access_log is passed explicitly change config.ACCESS_LOG if access_log is not None: self.config.ACCESS_LOG = access_log server_settings = self._helper( host=host, port=port, debug=debug, ssl=ssl, sock=sock, workers=workers, protocol=protocol, backlog=backlog, register_sys_signals=register_sys_signals, auto_reload=auto_reload, ) try: self.is_running = True if workers == 1: if auto_reload and os.name != "posix": # This condition must be removed after implementing # auto reloader for other operating systems. raise NotImplementedError if ( auto_reload and os.environ.get("SANIC_SERVER_RUNNING") != "true" ): reloader_helpers.watchdog(2) else: serve(**server_settings) else: serve_multiple(server_settings, workers) except BaseException: error_logger.exception( "Experienced exception while trying to serve" ) raise finally: self.is_running = False logger.info("Server Stopped")
async def handle_request(self, request, write_callback, stream_callback): """Take a request from the HTTP Server and return a response object to be sent back The HTTP Server only expects a response object, so exception handling must be done here :param request: HTTP Request object :param write_callback: Synchronous response function to be called with the response as the only argument :param stream_callback: Coroutine that handles streaming a StreamingHTTPResponse if produced by the handler. :return: Nothing """ # Define `response` var here to remove warnings about # allocation before assignment below. response = None cancelled = False try: # -------------------------------------------- # # Request Middleware # -------------------------------------------- # response = await self._run_request_middleware(request) # No middleware results if not response: # -------------------------------------------- # # Execute Handler # -------------------------------------------- # # Fetch handler from router handler, args, kwargs, uri = self.router.get(request) request.uri_template = uri if handler is None: raise ServerError( ( "'None' was returned while requesting a " "handler from the router" ) ) else: if not getattr(handler, "__blueprintname__", False): request.endpoint = self._build_endpoint_name( handler.__name__ ) else: request.endpoint = self._build_endpoint_name( getattr(handler, "__blueprintname__", ""), handler.__name__, ) # Run response handler response = handler(request, *args, **kwargs) if isawaitable(response): response = await response except CancelledError: # If response handler times out, the server handles the error # and cancels the handle_request job. # In this case, the transport is already closed and we cannot # issue a response. response = None cancelled = True except Exception as e: # -------------------------------------------- # # Response Generation Failed # -------------------------------------------- # try: response = self.error_handler.response(request, e) if isawaitable(response): response = await response except Exception as e: if isinstance(e, SanicException): response = self.error_handler.default( request=request, exception=e ) elif self.debug: response = HTTPResponse( "Error while handling error: {}\nStack: {}".format( e, format_exc() ), status=500, ) else: response = HTTPResponse( "An error occurred while handling an error", status=500 ) finally: # -------------------------------------------- # # Response Middleware # -------------------------------------------- # # Don't run response middleware if response is None if response is not None: try: response = await self._run_response_middleware( request, response ) except CancelledError: # Response middleware can timeout too, as above. response = None cancelled = True except BaseException: error_logger.exception( "Exception occurred in one of response " "middleware handlers" ) if cancelled: raise CancelledError() # pass the response to the correct callback if isinstance(response, StreamingHTTPResponse): await stream_callback(response) else: write_callback(response)
def run( self, host: Optional[str] = None, port: Optional[int] = None, *, debug: bool = False, auto_reload: Optional[bool] = None, ssl: Union[dict, SSLContext, None] = None, sock: Optional[socket] = None, workers: int = 1, protocol: Optional[Type[Protocol]] = None, backlog: int = 100, register_sys_signals: bool = True, access_log: Optional[bool] = None, unix: Optional[str] = None, loop: None = None, ) -> None: """Run the HTTP Server and listen until keyboard interrupt or term signal. On termination, drain connections before closing. :param host: Address to host on :type host: str :param port: Port to host on :type port: int :param debug: Enables debug output (slows server) :type debug: bool :param auto_reload: Reload app whenever its source code is changed. Enabled by default in debug mode. :type auto_relaod: bool :param ssl: SSLContext, or location of certificate and key for SSL encryption of worker(s) :type ssl: SSLContext or dict :param sock: Socket for the server to accept connections from :type sock: socket :param workers: Number of processes received before it is respected :type workers: int :param protocol: Subclass of asyncio Protocol class :type protocol: type[Protocol] :param backlog: a number of unaccepted connections that the system will allow before refusing new connections :type backlog: int :param register_sys_signals: Register SIG* events :type register_sys_signals: bool :param access_log: Enables writing access logs (slows server) :type access_log: bool :param unix: Unix socket to listen on instead of TCP port :type unix: str :return: Nothing """ if loop is not None: raise TypeError( "loop is not a valid argument. To use an existing loop, " "change to create_server().\nSee more: " "https://sanic.readthedocs.io/en/latest/sanic/deploying.html" "#asynchronous-support") if auto_reload or auto_reload is None and debug: if os.environ.get("SANIC_SERVER_RUNNING") != "true": return reloader_helpers.watchdog(1.0) if sock is None: host, port = host or "127.0.0.1", port or 8000 if protocol is None: protocol = (WebSocketProtocol if self.websocket_enabled else HttpProtocol) # if access_log is passed explicitly change config.ACCESS_LOG if access_log is not None: self.config.ACCESS_LOG = access_log server_settings = self._helper( host=host, port=port, debug=debug, ssl=ssl, sock=sock, unix=unix, workers=workers, protocol=protocol, backlog=backlog, register_sys_signals=register_sys_signals, auto_reload=auto_reload, ) try: self.is_running = True self.is_stopping = False if workers > 1 and os.name != "posix": logger.warn( f"Multiprocessing is currently not supported on {os.name}," " using workers=1 instead") workers = 1 if workers == 1: serve(**server_settings) else: serve_multiple(server_settings, workers) except BaseException: error_logger.exception( "Experienced exception while trying to serve") raise finally: self.is_running = False logger.info("Server Stopped")
async def _static_request_handler( file_or_directory, use_modified_since, use_content_range, stream_large_files, request, content_type=None, file_uri=None, ): # Using this to determine if the URL is trying to break out of the path # served. os.path.realpath seems to be very slow if file_uri and "../" in file_uri: raise InvalidUsage("Invalid URL") # Merge served directory and requested file if provided # Strip all / that in the beginning of the URL to help prevent python # from herping a derp and treating the uri as an absolute path root_path = file_path = file_or_directory if file_uri: file_path = path.join(file_or_directory, sub("^[/]*", "", file_uri)) # URL decode the path sent by the browser otherwise we won't be able to # match filenames which got encoded (filenames with spaces etc) file_path = path.abspath(unquote(file_path)) if not file_path.startswith(path.abspath(unquote(root_path))): error_logger.exception(f"File not found: path={file_or_directory}, " f"relative_url={file_uri}") raise FileNotFound("File not found", path=file_or_directory, relative_url=file_uri) try: headers = {} # Check if the client has been sent this file before # and it has not been modified since stats = None if use_modified_since: stats = await stat_async(file_path) modified_since = strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime(stats.st_mtime)) if request.headers.get("If-Modified-Since") == modified_since: return HTTPResponse(status=304) headers["Last-Modified"] = modified_since _range = None if use_content_range: _range = None if not stats: stats = await stat_async(file_path) headers["Accept-Ranges"] = "bytes" headers["Content-Length"] = str(stats.st_size) if request.method != "HEAD": try: _range = ContentRangeHandler(request, stats) except HeaderNotFound: pass else: del headers["Content-Length"] for key, value in _range.headers.items(): headers[key] = value headers["Content-Type"] = (content_type or guess_type(file_path)[0] or "text/plain") if request.method == "HEAD": return HTTPResponse(headers=headers) else: if stream_large_files: if type(stream_large_files) == int: threshold = stream_large_files else: threshold = 1024 * 1024 if not stats: stats = await stat_async(file_path) if stats.st_size >= threshold: return await file_stream(file_path, headers=headers, _range=_range) return await file(file_path, headers=headers, _range=_range) except ContentRangeError: raise except Exception: error_logger.exception(f"File not found: path={file_or_directory}, " f"relative_url={file_uri}") raise FileNotFound("File not found", path=file_or_directory, relative_url=file_uri)
async def handle_request(self, request, write_callback, stream_callback): """Take a request from the HTTP Server and return a response object to be sent back The HTTP Server only expects a response object, so exception handling must be done here :param request: HTTP Request object :param write_callback: Synchronous response function to be called with the response as the only argument :param stream_callback: Coroutine that handles streaming a StreamingHTTPResponse if produced by the handler. :return: Nothing """ try: # -------------------------------------------- # # Request Middleware # -------------------------------------------- # request.app = self response = await self._run_request_middleware(request) # No middleware results if not response: # -------------------------------------------- # # Execute Handler # -------------------------------------------- # # Fetch handler from router handler, args, kwargs, uri = self.router.get(request) request.uri_template = uri if handler is None: raise ServerError( ("'None' was returned while requesting a " "handler from the router")) # Run response handler response = handler(request, *args, **kwargs) if isawaitable(response): response = await response except Exception as e: # -------------------------------------------- # # Response Generation Failed # -------------------------------------------- # try: response = self.error_handler.response(request, e) if isawaitable(response): response = await response except Exception as e: if isinstance(e, SanicException): response = self.error_handler.default(request=request, exception=e) elif self.debug: response = HTTPResponse( "Error while handling error: {}\nStack: {}".format( e, format_exc()), status=500) else: response = HTTPResponse( "An error occurred while handling an error", status=500) finally: # -------------------------------------------- # # Response Middleware # -------------------------------------------- # try: response = await self._run_response_middleware( request, response) except BaseException: error_logger.exception( 'Exception occurred in one of response middleware handlers' ) # pass the response to the correct callback if isinstance(response, StreamingHTTPResponse): await stream_callback(response) else: write_callback(response)
def main(): parser = SanicArgumentParser( prog="sanic", description=BASE_LOGO, formatter_class=RawDescriptionHelpFormatter, ) parser.add_argument( "-H", "--host", dest="host", type=str, default="127.0.0.1", help="host address [default 127.0.0.1]", ) parser.add_argument( "-p", "--port", dest="port", type=int, default=8000, help="port to serve on [default 8000]", ) parser.add_argument( "-u", "--unix", dest="unix", type=str, default="", help="location of unix socket", ) parser.add_argument("--cert", dest="cert", type=str, help="location of certificate for SSL") parser.add_argument("--key", dest="key", type=str, help="location of keyfile for SSL.") parser.add_argument( "-w", "--workers", dest="workers", type=int, default=1, help="number of worker processes [default 1]", ) parser.add_argument("-d", "--debug", dest="debug", action="store_true") parser.add_argument( "-r", "--auto-reload", dest="auto_reload", action="store_true", help="Watch source directory for file changes and reload on changes", ) parser.add_argument( "-v", "--version", action="version", version=f"Sanic {__version__}; Routing {__routing_version__}", ) parser.add_bool_arguments("--access-logs", dest="access_log", help="display access logs") parser.add_argument( "module", help="path to your Sanic app. Example: path.to.server:app") args = parser.parse_args() try: module_path = os.path.abspath(os.getcwd()) if module_path not in sys.path: sys.path.append(module_path) delimiter = ":" if ":" in args.module else "." module_name, app_name = args.module.rsplit(delimiter, 1) module = import_module(module_name) app = getattr(module, app_name, None) app_name = type(app).__name__ if not isinstance(app, Sanic): raise ValueError( f"Module is not a Sanic app, it is a {app_name}. " f"Perhaps you meant {args.module}.app?") if args.cert is not None or args.key is not None: ssl: Optional[Dict[str, Any]] = { "cert": args.cert, "key": args.key, } else: ssl = None kwargs = { "host": args.host, "port": args.port, "unix": args.unix, "workers": args.workers, "debug": args.debug, "access_log": args.access_log, "ssl": ssl, } if args.auto_reload: kwargs["auto_reload"] = True app.run(**kwargs) except ImportError as e: if module_name.startswith(e.name): error_logger.error( f"No module named {e.name} found.\n" " Example File: project/sanic_server.py -> app\n" " Example Module: project.sanic_server.app") else: raise e except ValueError: error_logger.exception("Failed to run app")
def run(self, host=None, port=None, debug=False, ssl=None, sock=None, workers=1, protocol=None, backlog=100, stop_event=None, register_sys_signals=True, access_log=True, **kwargs): """Run the HTTP Server and listen until keyboard interrupt or term signal. On termination, drain connections before closing. :param host: Address to host on :param port: Port to host on :param debug: Enables debug output (slows server) :param ssl: SSLContext, or location of certificate and key for SSL encryption of worker(s) :param sock: Socket for the server to accept connections from :param workers: Number of processes received before it is respected :param backlog: :param stop_event: :param register_sys_signals: :param protocol: Subclass of asyncio protocol class :return: Nothing """ # Default auto_reload to false auto_reload = False # If debug is set, default it to true (unless on windows) if debug and os.name == 'posix': auto_reload = True # Allow for overriding either of the defaults auto_reload = kwargs.get("auto_reload", auto_reload) if sock is None: host, port = host or "127.0.0.1", port or 8000 if protocol is None: protocol = (WebSocketProtocol if self.websocket_enabled else HttpProtocol) if stop_event is not None: if debug: warnings.simplefilter('default') warnings.warn("stop_event will be removed from future versions.", DeprecationWarning) server_settings = self._helper( host=host, port=port, debug=debug, ssl=ssl, sock=sock, workers=workers, protocol=protocol, backlog=backlog, register_sys_signals=register_sys_signals, access_log=access_log, auto_reload=auto_reload) try: self.is_running = True if workers == 1: if auto_reload and os.name != 'posix': # This condition must be removed after implementing # auto reloader for other operating systems. raise NotImplementedError if auto_reload and \ os.environ.get('SANIC_SERVER_RUNNING') != 'true': reloader_helpers.watchdog(2) else: serve(**server_settings) else: serve_multiple(server_settings, workers) except BaseException: error_logger.exception( 'Experienced exception while trying to serve') raise finally: self.is_running = False logger.info("Server Stopped")
async def respond( self, response: Optional[BaseHTTPResponse] = None, *, status: int = 200, headers: Optional[Union[Header, Dict[str, str]]] = None, content_type: Optional[str] = None, ): """Respond to the request without returning. This method can only be called once, as you can only respond once. If no ``response`` argument is passed, one will be created from the ``status``, ``headers`` and ``content_type`` arguments. **The first typical usecase** is if you wish to respond to the request without returning from the handler: .. code-block:: python @app.get("/") async def handler(request: Request): data = ... # Process something json_response = json({"data": data}) await request.respond(json_response) # You are now free to continue executing other code ... @app.on_response async def add_header(_, response: HTTPResponse): # Middlewares still get executed as expected response.headers["one"] = "two" **The second possible usecase** is for when you want to directly respond to the request: .. code-block:: python response = await request.respond(content_type="text/csv") await response.send("foo,") await response.send("bar") # You can control the completion of the response by calling # the 'eof()' method: await response.eof() :param response: response instance to send :param status: status code to return in the response :param headers: headers to return in the response :param content_type: Content-Type header of the response :return: final response being sent (may be different from the ``response`` parameter because of middlewares) which can be used to manually send data """ try: if self.stream is not None and self.stream.response: raise ServerError("Second respond call is not allowed.") except AttributeError: pass # This logic of determining which response to use is subject to change if response is None: response = HTTPResponse( status=status, headers=headers, content_type=content_type, ) # Connect the response if isinstance(response, BaseHTTPResponse) and self.stream: response = self.stream.respond(response) # Run response middleware try: response = await self.app._run_response_middleware( self, response, request_name=self.name) except CancelledErrors: raise except Exception: error_logger.exception( "Exception occurred in one of response middleware handlers") self.responded = True return response
def serve( host, port, app: Sanic, ssl: Optional[SSLContext] = None, sock: Optional[socket.socket] = None, unix: Optional[str] = None, reuse_port: bool = False, loop=None, protocol: Type[asyncio.Protocol] = HttpProtocol, backlog: int = 100, register_sys_signals: bool = True, run_multiple: bool = False, run_async: bool = False, connections=None, signal=Signal(), state=None, asyncio_server_kwargs=None, ): """Start asynchronous HTTP Server on an individual process. :param host: Address to host on :param port: Port to host on :param before_start: function to be executed before the server starts listening. Takes arguments `app` instance and `loop` :param after_start: function to be executed after the server starts listening. Takes arguments `app` instance and `loop` :param before_stop: function to be executed when a stop signal is received before it is respected. Takes arguments `app` instance and `loop` :param after_stop: function to be executed when a stop signal is received after it is respected. Takes arguments `app` instance and `loop` :param ssl: SSLContext :param sock: Socket for the server to accept connections from :param unix: Unix socket to listen on instead of TCP port :param reuse_port: `True` for multiple workers :param loop: asyncio compatible event loop :param run_async: bool: Do not create a new event loop for the server, and return an AsyncServer object rather than running it :param asyncio_server_kwargs: key-value args for asyncio/uvloop create_server method :return: Nothing """ if not run_async and not loop: # create new event_loop after fork loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) if app.debug: loop.set_debug(app.debug) app.asgi = False connections = connections if connections is not None else set() protocol_kwargs = _build_protocol_kwargs(protocol, app.config) server = partial( protocol, loop=loop, connections=connections, signal=signal, app=app, state=state, unix=unix, **protocol_kwargs, ) asyncio_server_kwargs = (asyncio_server_kwargs if asyncio_server_kwargs else {}) # UNIX sockets are always bound by us (to preserve semantics between modes) if unix: sock = bind_unix_socket(unix, backlog=backlog) server_coroutine = loop.create_server( server, None if sock else host, None if sock else port, ssl=ssl, reuse_port=reuse_port, sock=sock, backlog=backlog, **asyncio_server_kwargs, ) if run_async: return AsyncioServer( app=app, loop=loop, serve_coro=server_coroutine, connections=connections, ) loop.run_until_complete(app._startup()) loop.run_until_complete(app._server_event("init", "before")) try: http_server = loop.run_until_complete(server_coroutine) except BaseException: error_logger.exception("Unable to start server") return # Ignore SIGINT when run_multiple if run_multiple: signal_func(SIGINT, SIG_IGN) # Register signals for graceful termination if register_sys_signals: if OS_IS_WINDOWS: ctrlc_workaround_for_windows(app) else: for _signal in [SIGTERM] if run_multiple else [SIGINT, SIGTERM]: loop.add_signal_handler(_signal, app.stop) loop.run_until_complete(app._server_event("init", "after")) pid = os.getpid() try: logger.info("Starting worker [%s]", pid) loop.run_forever() finally: logger.info("Stopping worker [%s]", pid) # Run the on_stop function if provided loop.run_until_complete(app._server_event("shutdown", "before")) # Wait for event loop to finish and all connections to drain http_server.close() loop.run_until_complete(http_server.wait_closed()) # Complete all tasks on the loop signal.stopped = True for connection in connections: connection.close_if_idle() # Gracefully shutdown timeout. # We should provide graceful_shutdown_timeout, # instead of letting connection hangs forever. # Let's roughly calcucate time. graceful = app.config.GRACEFUL_SHUTDOWN_TIMEOUT start_shutdown: float = 0 while connections and (start_shutdown < graceful): loop.run_until_complete(asyncio.sleep(0.1)) start_shutdown = start_shutdown + 0.1 # Force close non-idle connection after waiting for # graceful_shutdown_timeout for conn in connections: if hasattr(conn, "websocket") and conn.websocket: conn.websocket.fail_connection(code=1001) else: conn.abort() loop.run_until_complete(app._server_event("shutdown", "after")) remove_unix_socket(unix)
def serve(cls, primary: Optional[Sanic] = None) -> None: apps = list(cls._app_registry.values()) if not primary: try: primary = apps[0] except IndexError: raise RuntimeError("Did not find any applications.") reloader_start = primary.listeners.get("reload_process_start") reloader_stop = primary.listeners.get("reload_process_stop") # We want to run auto_reload if ANY of the applications have it enabled if ( cls.should_auto_reload() and os.environ.get("SANIC_SERVER_RUNNING") != "true" ): # no cov loop = new_event_loop() trigger_events(reloader_start, loop, primary) reload_dirs: Set[Path] = primary.state.reload_dirs.union( *(app.state.reload_dirs for app in apps) ) reloader_helpers.watchdog(1.0, reload_dirs) trigger_events(reloader_stop, loop, primary) return # This exists primarily for unit testing if not primary.state.server_info: # no cov for app in apps: app.state.server_info.clear() return primary_server_info = primary.state.server_info[0] primary.before_server_start(partial(primary._start_servers, apps=apps)) try: primary_server_info.stage = ServerStage.SERVING if primary.state.workers > 1 and os.name != "posix": # no cov logger.warn( f"Multiprocessing is currently not supported on {os.name}," " using workers=1 instead" ) primary.state.workers = 1 if primary.state.workers == 1: serve_single(primary_server_info.settings) elif primary.state.workers == 0: raise RuntimeError("Cannot serve with no workers") else: serve_multiple( primary_server_info.settings, primary.state.workers ) except BaseException: error_logger.exception( "Experienced exception while trying to serve" ) raise finally: primary_server_info.stage = ServerStage.STOPPED logger.info("Server Stopped") for app in apps: app.state.server_info.clear() app.router.reset() app.signal_router.reset()
async def handle_request(self, request, write_callback, stream_callback): # Define `response` var here to remove warnings about # allocation before assignment below. response = None cancelled = False middlewares = [] try: # --------------------------------------------------------------- # # request "global" middlewares # --------------------------------------------------------------- # request.app = self if self.request_middleware: response = await self._run_request_middleware( request, self.request_middleware ) # No middleware result if not response: # Fetch handler from router handler, middlewares, kwargs, uri = self.router.get(request) request.uri_template = uri # handler = self.request.route_handlers.endpoint # middlewares = self.request.route_handlers.middlewares # kwargs = self.request.route_params # run layered request middlewares request_middleware = [ m.handler for m in middlewares if m.attach_to == MiddlewareType.REQUEST ] if request_middleware: response = await self._run_request_middleware( request, request_middleware ) if not response: # run response handler ret = await self.resolver.resolve( request=request, func=handler, prefetched=kwargs ) response = handler(**ret) if isawaitable(response): response = await response except CancelledError: # If response handler times out, the server handles the error # and cancels the handle_request job. # In this case, the transport is already closed and we cannot # issue a response. response = None cancelled = True except Exception as e: # noqa this was copied from Sanic "as is" # -------------------------------------------- # # Response Generation Failed # -------------------------------------------- # try: response = self.error_handler.response(request, e) if isawaitable(response): response = await response except Exception as e: if isinstance(e, SanicException): response = self.error_handler.default( request=request, exception=e ) elif self.debug: response = HTTPResponse( "Error while handling error: {}\nStack: {}".format( e, format_exc() ), status=500, ) else: response = HTTPResponse( "An error occurred while handling an error", status=500 ) finally: # --------------------------------------------------------------- # # Response Middleware # --------------------------------------------------------------- # # Don't run response middleware if response is None if response is not None: try: if self.response_middleware: response = await self._run_response_middleware( request, response, self.response_middleware ) # run layered response middlewares response_middleware = [ m.handler for m in middlewares if m.attach_to == MiddlewareType.RESPONSE ] if response_middleware: response = await self._run_response_middleware( request, response, response_middleware ) except CancelledError: # Response middleware can timeout too, as above. response = None cancelled = True except BaseException: error_logger.exception( "Exception occurred in one of response " "middleware handlers" ) if cancelled: raise CancelledError() # pass the response to the correct callback if isinstance(response, StreamingHTTPResponse): # noqa copied code await stream_callback(response) else: write_callback(response)
async def handle_request(self, request, write_callback, stream_callback): """Take a request from the HTTP Server and return a response object to be sent back The HTTP Server only expects a response object, so exception handling must be done here :param request: HTTP Request object :param write_callback: Synchronous response function to be called with the response as the only argument :param stream_callback: Coroutine that handles streaming a StreamingHTTPResponse if produced by the handler. :return: Nothing """ try: # -------------------------------------------- # # Request Middleware # -------------------------------------------- # request.app = self response = await self._run_request_middleware(request) # No middleware results if not response: # -------------------------------------------- # # Execute Handler # -------------------------------------------- # # Fetch handler from router handler, args, kwargs, uri = self.router.get(request) request.uri_template = uri if handler is None: raise ServerError( ("'None' was returned while requesting a " "handler from the router")) # Run response handler response = handler(request, *args, **kwargs) if isawaitable(response): response = await response except Exception as e: # -------------------------------------------- # # Response Generation Failed # -------------------------------------------- # try: response = self.error_handler.response(request, e) if isawaitable(response): response = await response except Exception as e: if self.debug: response = HTTPResponse( "Error while handling error: {}\nStack: {}".format( e, format_exc())) else: response = HTTPResponse( "An error occurred while handling an error") finally: # -------------------------------------------- # # Response Middleware # -------------------------------------------- # try: response = await self._run_response_middleware(request, response) except BaseException: error_logger.exception( 'Exception occurred in one of response middleware handlers' ) # pass the response to the correct callback if isinstance(response, StreamingHTTPResponse): await stream_callback(response) else: write_callback(response)
def main(): parser = SanicArgumentParser( prog="sanic", description=BASE_LOGO, formatter_class=RawDescriptionHelpFormatter, ) parser.add_argument( "-H", "--host", dest="host", type=str, default="127.0.0.1", help="host address [default 127.0.0.1]", ) parser.add_argument( "-p", "--port", dest="port", type=int, default=8000, help="port to serve on [default 8000]", ) parser.add_argument( "-u", "--unix", dest="unix", type=str, default="", help="location of unix socket", ) parser.add_argument("--cert", dest="cert", type=str, help="location of certificate for SSL") parser.add_argument("--key", dest="key", type=str, help="location of keyfile for SSL.") parser.add_argument( "-w", "--workers", dest="workers", type=int, default=1, help="number of worker processes [default 1]", ) parser.add_argument("--debug", dest="debug", action="store_true") parser.add_bool_arguments("--access-logs", dest="access_log", help="display access logs") parser.add_argument( "-v", "--version", action="version", version=f"Sanic {__version__}", ) parser.add_argument( "module", help="path to your Sanic app. Example: path.to.server:app") args = parser.parse_args() try: module_path = os.path.abspath(os.getcwd()) if module_path not in sys.path: sys.path.append(module_path) if ":" in args.module: module_name, app_name = args.module.rsplit(":", 1) else: module_parts = args.module.split(".") module_name = ".".join(module_parts[:-1]) app_name = module_parts[-1] module = import_module(module_name) app = getattr(module, app_name, None) app_name = type(app).__name__ if not isinstance(app, Sanic): raise ValueError( f"Module is not a Sanic app, it is a {app_name}. " f"Perhaps you meant {args.module}.app?") if args.cert is not None or args.key is not None: ssl = { "cert": args.cert, "key": args.key, } # type: Optional[Dict[str, Any]] else: ssl = None app.run( host=args.host, port=args.port, unix=args.unix, workers=args.workers, debug=args.debug, access_log=args.access_log, ssl=ssl, ) except ImportError as e: error_logger.error(f"No module named {e.name} found.\n" f" Example File: project/sanic_server.py -> app\n" f" Example Module: project.sanic_server.app") except ValueError: error_logger.exception("Failed to run app")