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

相关文章
|
9天前
|
安全 数据库 C++
Python Web框架比较:Django vs Flask vs Pyramid
【4月更文挑战第9天】本文对比了Python三大Web框架Django、Flask和Pyramid。Django功能全面,适合快速开发,但学习曲线较陡;Flask轻量灵活,易于入门,但默认配置简单,需自行添加功能;Pyramid兼顾灵活性和可扩展性,适合不同规模项目,但社区及资源相对较少。选择框架应考虑项目需求和开发者偏好。
|
28天前
|
数据采集 数据挖掘 调度
异步爬虫实践攻略:利用Python Aiohttp框架实现高效数据抓取
本文介绍了如何使用Python的Aiohttp框架构建异步爬虫,以提升数据抓取效率。异步爬虫利用异步IO和协程技术,在等待响应时执行其他任务,提高效率。Aiohttp是一个高效的异步HTTP客户端/服务器框架,适合构建此类爬虫。文中还展示了如何通过代理访问HTTPS网页的示例代码,并以爬取微信公众号文章为例,说明了实际应用中的步骤。
|
3天前
|
前端开发 数据挖掘 API
使用Python中的Flask框架进行Web应用开发
【4月更文挑战第15天】在Python的Web开发领域,Flask是一个备受欢迎的轻量级Web框架。它简洁、灵活且易于扩展,使得开发者能够快速地构建出高质量的Web应用。本文将深入探讨Flask框架的核心特性、使用方法以及在实际开发中的应用。
|
5天前
|
关系型数据库 数据库 开发者
Python中的Peewee框架:轻量级ORM的优雅之旅
【4月更文挑战第13天】在Python的众多ORM框架中,Peewee以其轻量级、简洁和易于上手的特点,受到了许多开发者的青睐。Peewee的设计理念是“小而美”,它提供了基本的ORM功能,同时保持了代码的清晰和高效。本文将深入探讨Peewee的核心概念、使用场景以及实战应用,帮助读者更好地理解和使用这一框架。
|
5天前
|
SQL API 数据库
Python中的SQLAlchemy框架:深度解析与实战应用
【4月更文挑战第13天】在Python的众多ORM(对象关系映射)框架中,SQLAlchemy以其功能强大、灵活性和易扩展性脱颖而出,成为许多开发者首选的数据库操作工具。本文将深入探讨SQLAlchemy的核心概念、功能特点以及实战应用,帮助读者更好地理解和使用这一框架。
|
15天前
|
前端开发 安全 Java
使用Java Web框架:Spring MVC的全面指南
【4月更文挑战第3天】Spring MVC是Spring框架的一部分,用于构建高效、模块化的Web应用。它基于MVC模式,支持多种视图技术。核心概念包括DispatcherServlet(前端控制器)、HandlerMapping(请求映射)、Controller(处理请求)、ViewResolver(视图解析)和ModelAndView(模型和视图容器)。开发流程涉及配置DispatcherServlet、定义Controller、创建View、处理数据、绑定模型和异常处理。
使用Java Web框架:Spring MVC的全面指南
|
28天前
|
存储 数据库连接 数据处理
Python语言的程序框架
Python语言的程序框架
|
29天前
|
数据库
最全三大框架整合(使用映射)——struts.xml和web.xml配置
最全三大框架整合(使用映射)——数据库资源文件jdbc.properties
9 0
|
30天前
|
存储 Linux 调度
太好用了!Python 定时任务调度框架 APScheduler 详解!
太好用了!Python 定时任务调度框架 APScheduler 详解!
|
30天前
|
前端开发 API 网络架构
Python 如何开发出RESTful Web接口,DRF框架助力灵活实现!
Python 如何开发出RESTful Web接口,DRF框架助力灵活实现!

热门文章

最新文章