async def handle_http_error(self, err: HTTPException, protocol, ctx: HTTPRequestContext): """ Handle a :class:`kyoukai.exc.HTTPException`. This will invoke the appropriate error handler as registered in the blueprint of the route, if we can. Otherwise, it will invoke the default error handler. """ code = err.code bp = err.blueprint or self.root # Get the error handler. error_handler = bp.get_errorhandler(code) if not error_handler: # Since there's no special error handler derived for this code, return a basic Response. # If it's a 500 and we're in debug mode, format the traceback. if err.code == 500 and self.debug: # Use the Kyoukai debugger. should_err, r = self._debugger.debug(ctx, err) protocol.handle_resp(r) return should_err else: body = str(code) if err.response is None: resp = Response(code, body) else: resp = err.response resp.request = ctx.request else: # Invoke the error handler specified. try: resp = wrap_response( await error_handler.invoke(ctx, exception=err), self.response_cls) except Exception: self.logger.error( "Unhandled exception in error handler:\n {}".format( ''.join(traceback.format_exc()))) resp = wrap_response("500", self.response_cls) resp.request = ctx.request protocol.handle_resp(resp) # Check if we should close the connection. if hasattr(ctx.request, "should_keep_alive"): if not ctx.request.should_keep_alive: protocol.close() else: # If it's that bad, just close it anyway. protocol.close() return True
def wrap_response(response, response_cls=None): """ Wrap up a response, if applicable. This allows Flask-like `return ""`. :param response: The tuple or otherwise object that is being wrapped. :param response_cls: The Response class that is being used. """ # Import inside here to prevent circular imports. if response_cls is None: from kyoukai.response import Response else: Response = response_cls if response is None: r = Response(204, "", {}) elif isinstance(response, tuple): if len(response) == 1: # Only body. r = Response(200, response[0], {}) elif len(response) == 2: # Body and code. r = Response(response[1], response[0], {}) elif len(response) == 3: # Body, code, headers. r = Response(response[1], response[0], response[2]) else: # what raise HTTPException elif isinstance(response, Response): r = response else: r = Response(200, response, {}) return r
def render_template(self, filename: str, code=200, **kwargs) -> Response: """ Render a template using the currently loaded rendering engine. Unlike :meth:`Kyoukai.render`, this returns a :class:`Response` object. :param filename: The filename of the template to load and render. :type filename: str :param code: The response code to add into the Response. :param kwargs: Additional arguments to pass to the template renderer. These are directly passed to :meth:`mako.template.Template.render` to render the template. :return: A :class:`Response` object with the rendered template. """ data = self.render(filename, **kwargs) # Wrap it in a response. return Response(code, data, headers={"Content-Type": "text/html"})
def get_static(self, filename: str) -> Response: """ Gets a file, using static, but returns a Response instead of the file handle. """ content = self.get_static_file(filename) if not content: raise HTTPException(404) with content: path = self.get_static_path(filename) mimetype = mimetypes.guess_type(path)[0] if not mimetype: if _has_magic: mimetype = magic.from_file(path, mime=True) if isinstance(mimetype, bytes): mimetype = mimetype.decode() else: mimetype = "application/octet-stream" return Response(200, body=content.read(), headers={"Content-Type": mimetype})
def debug(self, ctx: HTTPRequestContext, exc: Exception) -> typing.Tuple[bool, Response]: """ Produces debug output for the application in a new template. """ if not self.app.debug: return # Check the request's params. params = ctx.request.args debugger = params.get("__debugger__") if debugger == "yes": command = params.get("cmd") # Switch based on the command. if command is None: return Response(code=404, body="404") elif command == "resource": # Send a resource down the line. filename = params.get("f") # Get the __file__ of the werkzeug debugger. wz_f = os.path.dirname(debug.__file__) filename = os.path.join(wz_f, 'shared', os.path.basename(filename)) # Guess the content type from the filename. mimetype = mimetypes.guess_type( filename)[0] or 'application/octet-stream' if os.path.exists(filename): with open(filename, 'rb') as f: return False, Response( body=f.read(), code=200, headers={"Content-Type": mimetype}) else: return False, Response(body="404", code=404) else: # It's a console command. frame = self.frames.get(int(ctx.request.args.get('frm'))) if frame is None: return False, Response(body="404", code=404) # Run the console command inside the frame. result = frame.console.eval(command) return False, Response(body=result, code=200, headers={"Content-Type": "text/html"}) else: # Get the traceback, now. traceback = get_current_traceback(skip=0, show_hidden_frames=True, ignore_system_exceptions=False) for frame in traceback.frames: self.frames[frame.id] = frame self.tracebacks[traceback.id] = traceback # Render the base page. body = traceback.render_full( evalex=True, evalex_trusted=True, ) r = Response(code=500, body=body.encode(), headers={"X-XSS-Protection": "0"}) return True, r