「译文」如何编写 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!

相关文章
|
26天前
|
数据采集 存储 JSON
Python网络爬虫:Scrapy框架的实战应用与技巧分享
【10月更文挑战第27天】本文介绍了Python网络爬虫Scrapy框架的实战应用与技巧。首先讲解了如何创建Scrapy项目、定义爬虫、处理JSON响应、设置User-Agent和代理,以及存储爬取的数据。通过具体示例,帮助读者掌握Scrapy的核心功能和使用方法,提升数据采集效率。
76 6
|
2月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
48 4
|
26天前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
134 45
|
21天前
|
Java 测试技术 持续交付
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
本文重点讲解如何搭建App自动化测试框架的思路,而非完整源码。主要内容包括实现目的、框架设计、环境依赖和框架的主要组成部分。适用于初学者,旨在帮助其快速掌握App自动化测试的基本技能。文中详细介绍了从需求分析到技术栈选择,再到具体模块的封装与实现,包括登录、截图、日志、测试报告和邮件服务等。同时提供了运行效果的展示,便于理解和实践。
64 4
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
|
10天前
|
缓存 API 数据库
Python哪个框架合适开发速卖通商品详情api?
在跨境电商平台速卖通的商品详情数据获取与整合中,Python 语言及其多种框架(如 Flask、Django、Tornado 和 FastAPI)提供了高效解决方案。Flask 简洁灵活,适合快速开发;Django 功能全面,适用于大型项目;Tornado 性能卓越,擅长处理高并发;FastAPI 结合类型提示和异步编程,开发体验优秀。选择合适的框架需综合考虑项目规模、性能要求和团队技术栈。
23 2
|
22天前
|
SQL 安全 PHP
探索PHP的现代演进:从Web开发到框架创新
PHP是一种流行的服务器端脚本语言,自诞生以来在Web开发领域占据重要地位。从简单的网页脚本到支持面向对象编程的现代语言,PHP经历了多次重大更新。本文探讨PHP的现代演进历程,重点介绍其在Web开发中的应用及框架创新,如Laravel、Symfony等。这些框架不仅简化了开发流程,还提高了开发效率和安全性。
24 3
|
21天前
|
前端开发 JavaScript 开发工具
从框架到现代Web开发实践
从框架到现代Web开发实践
33 1
|
25天前
|
SQL 安全 PHP
探索PHP的现代演进:从Web开发到框架创新
PHP 自发布以来一直在 Web 开发领域占据重要地位,历经多次重大更新,从简单的脚本语言进化为支持面向对象编程的现代语言。本文探讨 PHP 的演进历程,重点介绍其在 Web 开发中的应用及框架创新。自 PHP 5.3 引入命名空间后,PHP 迈向了面向对象编程时代;PHP 7 通过优化内核大幅提升性能;PHP 8 更是带来了属性、刚性类型等新特性。
25 3
|
27天前
|
数据采集 前端开发 中间件
Python网络爬虫:Scrapy框架的实战应用与技巧分享
【10月更文挑战第26天】Python是一种强大的编程语言,在数据抓取和网络爬虫领域应用广泛。Scrapy作为高效灵活的爬虫框架,为开发者提供了强大的工具集。本文通过实战案例,详细解析Scrapy框架的应用与技巧,并附上示例代码。文章介绍了Scrapy的基本概念、创建项目、编写简单爬虫、高级特性和技巧等内容。
54 4
|
27天前
|
安全 数据库 开发者
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第26天】本文详细介绍了如何在Django框架下进行全栈开发,包括环境安装与配置、创建项目和应用、定义模型类、运行数据库迁移、创建视图和URL映射、编写模板以及启动开发服务器等步骤,并通过示例代码展示了具体实现过程。
40 2