「译文」如何编写 Python-Web 框架(四)

简介: 「译文」如何编写 Python-Web 框架(四)

提示:

这个系列是基于alcazar 框架, 我写它是为了学习的目的。如果你喜欢这个系列,通过 star 该 repo 来表达你的喜爱。

在之前的博客文章系列中,我们开始编写我们自己的 Python 框架,并实现了以下功能:

  • WSGI 兼容性
  • 请求处理程序
  • 路由:简单且参数化
  • 检查重复 path
  • 基于类的处理程序
  • 单元测试
  • 测试客户端
  • 添加 path 的替代方法(类似 Django)
  • 模板支持

在这部分,我们将添加一些更真棒功能到列表中:

  • 自定义异常处理程序
  • 静态文件的支持
  • 中间件

自定义异常处理程序

异常情况不可避免地会发生。用户可能会做一些我们意想不到的事情。我们可以编写一些在某些情况下不起作用的代码。用户可以转到不存在的页面。以我们现在所拥有的,如果出现一些异常,我们显示一个很大的丑陋的 Internal Server Error 信息。取而代之,我们可以展示一些友好的信息。如 Oops! Something went wrong.Please, contact our customer support . 为此,我们需要能够捕获这些异常,并根据我们的需求处理它们。

看起来是这样的:

# app.py
from api import API
app = API()
def custom_exception_handler(request, response, exception_cls):
    response.text = "Oops! Something went wrong. Please, contact our customer support at +1-202-555-0127."
app.add_exception_handler(custom_exception_handler)
PYTHON

在这里,我们创建一个自定义异常处理程序。它看起来像我们简单的请求处理程序,除了它有作为它的第三个参数 exception_cls。现在,如果我们有一个抛出异常的请求处理程序,那么应该调用上述的自定义异常处理程序。

# app.py
@app.route("/home")
def exception_throwing_handler(request, response):
    raise AssertionError("This handler should not be user")
PYTHON

如果我们访问http://localhost:8000/home,而不是之前的 Internal Server Error,我们应该能够看到我们的自定义消息 Oops! Something went wrong. Please, contact our customer support at +1-202-555-0127.。它看起来够好吗? 让我们继续并实现它。

我们首先需要的是在主 API 类中存储异常处理程序的一个变量:

# api.py
class API:
    def __init__(self, templates_dir="templates"):
        ...
        self.exception_handler = None
PYTHON

现在我们需要添加方法add_exception_handler

# api.py
class API:
    ...
    def add_exception_handler(self, exception_handler):
        self.exception_handler = exception_handler
PYTHON

注册了自定义异常处理程序后,我们需要在异常发生时调用。异常在哪里发生?没错:当处理程序 handle_request 被调用时。我们调用我们方法内的处理程序。因此,我们需要用 try/except 语句包装它,并调用我们的自定义异常处理器的 except 部分:

# api.py
class API:
    ...
    def handle_request(self, request):
        response = Response()
        handler, kwargs = self.find_handler(request_path=request.path)
        try:
            if handler is not None:
                if inspect.isclass(handler):
                    handler = getattr(handler(), request.method.lower(), None)
                    if handler is None:
                        raise AttributeError("Method now allowed", request.method)
                handler(request, response, **kwargs)
            else:
                self.default_response(response)
        except Exception as e:
            if self.exception_handler is None:
                raise e
            else:
                self.exception_handler(request, response, e)
        return response
PYTHON

我们还需要确保,如果没有注册异常处理程序,则会传播异常。

我们已把所有东西都安排好。继续前进,重新启动你的 gunicorn,访问 http://localhost:8000/home。你应该看到我们的小可爱的消息,而不是大丑陋的默认之一。当然,请确保您的 app.py 有上述异常处理程序和错误的请求处理程序。

如果您想更进一步,请创建一个不错的模板,并在异常处理器内使用我们的方法api.template()。但是,我们的框架不支持静态文件,因此您将很难用 CSS 和 JavaScript 设计模板。不要伤心,因为这正是我们下一步要做的。

静态文件的支持

没有好的 CSS 和 JavaScript 的模板就不是真正的模板,不是吗? 那么我们是否需要添加对这些文件的支持呢?

就像我们使用 Jinja2 进行模板支持一样,我们将使用 WhiteNoise 用于静态文件服务。安装它:

pip install whitenoise
SHELL

WhiteNoise 很简单。我们唯一需要做的是包装我们的 WSGI 应用程序,并给它静态文件夹路径作为参数。在我们这样做之前,让我们记住我们的 __call__ 方法是什么样子的:

# api.py
class API:
    ...
    def __call__(self, environ, start_response):
        request = Request(environ)
        response = self.handle_request(request)
        return response(environ, start_response)
    ...
PYTHON

这基本上是我们的 WSGI 应用程序的入口点, 这正是我们需要用 WhiteNoise 包装的。因此,让我们将其内容重构为单独的方法,以便更容易用 WhiteNoise 包装它:

# api.py
class API:
    ...
    def wsgi_app(self, environ, start_response):
        request = Request(environ)
        response = self.handle_request(request)
        return response(environ, start_response)
    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)
PYTHON

现在,在我们的构造器中,我们可以初始化 WhiteNoise 实例:

# api.py
...
from whitenoise import WhiteNoise
class API:
    ...
    def __init__(self, templates_dir="templates", static_dir="static"):
        self.routes = {}
        self.templates_env = Environment(loader=FileSystemLoader(templates_dir))
        self.exception_handler = None
        self.whitenoise = WhiteNoise(self.wsgi_app, root=static_dir)
PYTHON

正如你所看到的,我们用 WhiteNoise 包裹了我们的 wsgi_app,并给它一个路径静态文件夹作为第二个参数。唯一需要做的事情就是将此self.whitenoise 作为我们框架的切入点:

# api.py
class API:
    ...
    def __call__(self, environ, start_response):
        return self.whitenoise(environ, start_response)
PYTHON

在一切到位后,在项目根部创建文件夹static,在其中创建文件main.css,并将以下内容放入其中:

body {
    background-color: chocolate;
}
CSS

「译文」如何编写 Python Web 框架(三) 中,我们创建了templates/index.html。现在,我们可以将新创建的 css 文件放入此模板中:

<html>
    <header>
        <title>{{ title }}</title>
        <link href="/main.css" type="text/css" rel="stylesheet">
    </header>
    <body>
        <h1>The name of the framework is {{ name }}</h1>
    </body>
</html>
HANDLEBARS

重新启动你的 gunicorn,访问 http://localhost/template。你应该看到,整个背景的颜色是巧克力色,而不是白色,这意味着我们的静态文件正在被送达。棒!

中间件

如果您需要回顾一下中间件是什么以及它们的工作原理,请先阅读 这篇文章。否则,这部分可能看起来有点混乱。我将等着你们。回来了吗? 太好了。我们继续。

你知道他们是什么,他们是如何工作的,但你可能想知道他们被用来做什么。基本上,中间件是一个可以修改 HTTP 请求和 / 或响应的组件,它被设计成链接在一起,在请求处理期间形成行为更改的管道。中间件任务的示例可以请求日志记录和 HTTP 身份验证。关键是,这些都不完全负责对客户做出回应。相反,每个中间件都以某种方式更改作为管道的一部分的行为,让实际的响应来自管道后面的东西。在我们的情况下,真正响应客户端的是我们的请求处理程序。中间件是围绕我们的 WSGI 应用程序的包装,能够修改请求和响应。

从鸟瞰图看,代码会是这样的:

FirstMiddleware(SecondMiddleware(our_wsgi_app))
PYTHON

因此,当请求进来时,它首先进入FirstMiddleware。它修改请求并将其发送到SecondMiddleware。现在,SecondMiddleware 修改请求并将其发送给 our_wsgi_app。我们的应用程序处理请求,准备响应并将其发送回SecondMiddleware。如果它想,它可以修改响应,并将其发送回 FirstMiddleware。它修改了响应并将其发送回 Web 服务器(例如 gunicorn)。

让我们继续前进,创建一个类 Middleware,其他中间件将继承,并包裹我们的 wsgi 应用程序。

首先创建文件 middleware.py

touch middleware.py
SHELL

现在,我们可以开始我们的 Middleware class

# middleware.py
class Middleware:
    def __init__(self, app):
        self.app = app
PYTHON

正如我们上面提到的,它应该包装一个 wsgi 应用程序 ,并在多个中间件的情况下,app也可以是另一个中间件。

作为基础中间件类,它也应该能够将另一个中间件添加到堆栈中:

# middleware.py
class Middleware:
    ...
    def add(self, middleware_cls):
        self.app = middleware_cls(self.app)
PYTHON

它只是将给定的中间件类包裹在我们当前的应用程序上。

它还应有其主要方法,即请求处理和响应处理。现在,他们什么也不做。继承自这个类的子类将实现这些方法:

# middleware.py
class Middleware:
    ...
    def process_request(self, req):
        pass
    def process_response(self, req, resp):
        pass
PYTHON

现在,最重要的部分,处理传入请求的方法:

# middleware.py
class Middleware:
    ...
    def handle_request(self, request):
        self.process_request(request)
        response = self.app.handle_request(request)
        self.process_response(request, response)
        return response
PYTHON

它首先调用 self.process_request 对请求做一些事情。然后将响应创建委托给它正在包装的应用程序。最后,它调用 process_response 来处理响应对象。然后简单地向上返回响应。

由于中间件现在是我们应用程序的第一个入口点,它们是由我们的 Web 服务器(如 gunicorn)调用的。因此,中间件应实现 WSGI 入口点接口:

# middleware.py
from webob import Request
class Middleware:
    def __call__(self, environ, start_response):
        request = Request(environ)
        response = self.app.handle_request(request)
        return response(environ, start_response)
PYTHON

这只是我们上面创建的 wsgi_app 函数的副本。

随着我们的中间件类的实现,让我们将其添加到我们的 main API类:

# api.py

...

from middleware import Middleware

class API:

    def __init__(self, templates_dir="templates", static_dir="static"):

        ...

        self.middleware = Middleware(self)

PYTHON

它环绕着我们的 wsgi 应用程序的self。现在,让我们给它添加中间件的能力:

# api.py
class API:
    ...
    def add_middleware(self, middleware_cls):
        self.middleware.add(middleware_cls)
PYTHON

唯一要做的就是在入口点调用此中间件,而不是我们自己的 wsgi 应用程序:

# api.py
class API:
    ...
    def __call__(self, environ, start_response):
        return self.middleware(environ, start_response)
PYTHON

你为什么这么问? 因为我们现在正在将作为中间件入口点的工作委托给中间件。记住,我们在 Middleware 类中实现了 WSGI 入口点接口。现在让我们来创建一个简单的中间件,它可以简单地打印到控制台:

# app.py
from api import API
from middleware import Middleware
app = API()
...
class SimpleCustomMiddleware(Middleware):
    def process_request(self, req):
        print("Processing request", req.url)
    def process_response(self, req, res):
        print("Processing response", req.url)
app.add_middleware(SimpleCustomMiddleware)
...
PYTHON

重新启动你的 gunicorn,去访问任何网址(例如http://localhost:8000/home)。一切都应该像以前一样工作。唯一的区别是以下文本应该显示在控制台中。打开主机,您应该看到以下情况:

Processing request http://localhost:8000/home
Processing response http://localhost:8000/home
BASH

这里有一个陷阱。你找到了吗?静态文件现在不起作用。原因是我们停止使用WhiteNoise。我们删除了它。我们不是调用WhiteNoise,而是调用中间件。以下我们应该做的。我们需要区分静态文件请求和其他请求。当请求进入静态文件时,我们应该调用WhiteNoise。对于其他,我们应该调用中间件。问题是我们如何区分它们。现在,静态文件请求看起来像这样http://localhost:8000/main.css: . 其他请求看起来是这样的http://localhost:8000/home。对于我们 class API 来说,它们看起来是一样的。因此,我们将添加一个根到静态文件的网址,使他们看起来像这样http://localhost:8000/static/main.css。我们将检查请求路径是否以/static 开头。如果是这样,我们将调用WhiteNoise,否则我们将调用中间件。我们还应该确保剪切 /static 部分。 否则 WhiteNoise 将找不到文件:

# api.py
class API:
    ...
    def __call__(self, environ, start_response):
        path_info = environ["PATH_INFO"]
        if path_info.startswith("/static"):
            environ["PATH_INFO"] = path_info[len("/static"):]
            return self.whitenoise(environ, start_response)
        return self.middleware(environ, start_response)
PYTHON

现在,在模板中,我们应该这样调用静态文件:

<link href="/static/main.css" type="text/css" rel="stylesheet">
HTML

去改变你的 index.html

重新启动你的 gunicorn,并检查一切是否正常工作。

我们将在以后的帖子中使用此中间件功能来将身份验证添加到我们的应用中。

我认为这个中间件部分比其他人更难理解。我也认为我没有做好解释它的工作。因此,请编写代码,让它深入理解,如果有什么不清楚的地方可以在评论中问我。

查看第一部分, 「译文」如何编写 Python Web 框架(一)

查看第二部分,「译文」如何编写 Python Web 框架(二)

查看第三部分,「译文」如何编写 Python Web 框架(三)

提示:

这个系列是基于alcazar 框架, 我写它是为了学习的目的。如果你喜欢这个系列,通过 star 该 repo 来表达你的喜爱。

这就是我今天要说的.

Fight on!

相关文章
|
8天前
|
设计模式 开发框架 数据库
Python Web开发主要常用的框架
【5月更文挑战第12天】Python Web开发框架包括Django、Flask、Tornado和Pyramid。Django适用于复杂应用,提供ORM、模板引擎等全套功能;Flask轻量级,易于扩展,适合小型至中型项目;Tornado擅长处理高并发,支持异步和WebSockets;Pyramid灵活强大,可适配多种数据库和模板引擎,适用于各种规模项目。选择框架需依据项目需求和技术栈。
125 2
|
1天前
|
设计模式 缓存 前端开发
|
2天前
|
JSON JavaScript API
Python进阶---FastAPI框架
Python进阶---FastAPI框架
25 2
|
3天前
|
IDE Java 开发工具
初学 Python 需要安装哪些软件?超级实用,小白必看!_python框架下其他软件
初学 Python 需要安装哪些软件?超级实用,小白必看!_python框架下其他软件
初学 Python 需要安装哪些软件?超级实用,小白必看!_python框架下其他软件
|
3天前
|
缓存 API 数据库
构建高效Python Web应用:Flask框架与RESTful API设计原则
【5月更文挑战第20天】 在现代Web开发中,构建一个轻量级且高效的后端服务至关重要。本文将深入探讨如何使用Python的Flask框架结合RESTful API设计原则来创建可扩展和易于维护的Web应用程序。我们将通过分析Flask的核心特性,以及如何利用它来实现资源的合理划分、接口的版本控制和请求处理优化等,来指导读者打造高性能的API服务。文中不仅提供了理论指导,还包括了实践案例,旨在帮助开发者提升开发效率,并增强应用的稳定性和用户体验。
|
5天前
|
缓存 监控 安全
Django框架在大型Web应用中的架构设计与实战
【5月更文挑战第18天】Django框架在构建大型Web应用中扮演重要角色,采用分层架构(数据、业务逻辑、表示层)和多应用组织模式,结合缓存策略(如Memcached、Redis)提升性能。通过异步处理、分布式部署提高响应速度和扩展性。关注数据分区、安全设计及监控日志,确保系统高效、稳定。Django为复杂业务提供坚实基础,助力打造卓越Web系统。
33 7
|
5天前
|
开发框架 中间件 数据库
Django 框架入门全攻略:轻松构建 Web 应用
【5月更文挑战第18天】本文是 Django 入门教程,介绍了如何使用 Django 构建 Web 应用。内容包括安装、项目与应用创建、模型定义、数据库迁移、视图编写、路由配置、模板系统、表单处理和中间件的使用。通过实例展示了 Django 基本流程,帮助初学者快速上手。Django 提供高效工具,便于开发者聚焦业务逻辑,轻松构建功能丰富的 Web 应用。
31 5
|
5天前
|
存储 缓存 API
Flask 框架在大型 Web 应用中的使用与挑战
【5月更文挑战第18天】Flask框架适用于快速开发轻量级Web应用,但用于大型应用时需应对性能、代码管理和团队协作的挑战。通过集成扩展处理复杂需求,使用蓝图组织代码,以及引入缓存优化性能,结合明确的代码规范和开发流程,可有效应对挑战,构建高效稳定的应用。
34 5
|
5天前
|
数据库连接 Python
Flask 框架入门与实践:构建你的第一个 Web 应用
【5月更文挑战第18天】本文介绍了使用 Flask 框架构建第一个 Web 应用的步骤。首先通过 `pip install Flask` 安装框架,然后编写基本的 Python 代码创建应用,包括定义路由和响应。示例展示如何显示 &quot;Hello, World!&quot;,并扩展到显示用户信息的功能。利用模板(如 `index.html`)可使页面更丰富。随着学习深入,可以利用 Flask 的更多特性,如表单处理和数据库连接,来构建更复杂的 Web 应用。本文旨在激发读者对 Flask 和 Web 开发的兴趣,鼓励不断探索和实践。
41 7
|
6天前
|
iOS开发 Python
mac:python安装路径,带你全面解析Python框架体系架构view篇
mac:python安装路径,带你全面解析Python框架体系架构view篇