Python WSGI 与 Web 开发框架

news2024/11/27 6:17:19

目录

文章目录

  • 目录
  • WSGI
    • WSGI 的工作原理
      • environ 参数
      • start_resposne 参数
    • WSGI 的中间件
  • WSGI Web 开发框架
  • OpenStack 中的应用案例
    • 进程入口
    • WSGI Application 加载
      • Paste/PasteDeploy
      • Routes
      • WebOb
    • WSGI Server 启动

WSGI

WSGI(Web Server Gateway Interface,Web 服务器网关接口)是一个 Python Web Application 和 Web Server 之间的标准交互接口规范,定义了 Web Application 如何集成到不同的 Web Server(e.g. Apache、Nginx 等)、或高并发的网络框架(e.g. Eventlet)中的交互标准,包括调用接口函数、请求和响应的数据结构以及环境变量等等,使得它们能够协同工作。类似于 Java 的 Servlet。

WSGI 的核心思想就是将 Web Application 和 Web Server 进行解耦,在解耦之后,还可以在它们两者之间再加入一个 “中间层" 来完成灵活的功能组合:

  • WSGI Server:本质是一个 HTTP 服务器,提供 HTTP 协议的处理,例如:IP、Port、虚拟主机等。HTTP Request 首先到达 WSGI Server,然后再被转发到 WSGI Middleware 或 WSGI Application。
  • WSGI Middleware:作为一个中间层,可以在传递 HTTP Request 到 WSGI Application 的过程中完成一系列可组合的预先处理。例如:身份验证、日志记录等。
  • WSGI Application:是 Web Application 的核心业务实现,可以使用不同的 Web 框架(e.g. Flask、Django 等)进行开发,根据功能需求提供多样的 API 资源类型和处理逻辑。

通过这样的解耦,使得开发者可以选用理想的 Web 框架进行开发而无需关注 Web Server 的实现细节,同时也能够将这些 Web Application 部署到不同类型的 Web Server 上运行,例如:在 Apache 上安装 wsgi_mod 模块、在 Nginx 上安装 uWSGI 模块以支持 WSGI 规范。定义这些规范的目的是为了定义统一的标准,提升程序的可移植性。

WSGI 的工作原理

从 WSGI Server 的角度看,它首先需要知道如何调用 WSGI Application,并将 HTTP Request 传递给它,然后再从 Application 处接收到返回的 HTTP Response,再最终返回给 Client。

所以 WSGI 规范为 Server 和 Application 之间的交互定义了一个唯一且统一的 application() 接口,并由 Application 实现、由 Server 调用。接口的原型如下:

def application(environ, start_response):
      pass

Application 通常会以 Python Module 或 app 实例对象的方式来提供这个 application() 接口。以 web.py 框架为例,它本身是一个 Python Module 实现了 application(),可以被 Server 导入并调用。application() 作为 Application 的唯一入口,所以 Server 会将所有 HTTP Requests 都调用这个接口来进行处理。

import web

urls = (
    '/.*', 'hello',
)

class hello(object):
    def GET(self):
        return "Hello, world."

app = web.application(urls, globals()).wsgifunc()

environ 参数

environ(环境参数)是一个 Dict 类型对象,用于存放下列数据:

  1. HTTP Request 信息;
  2. CGI(通用网管接口规范)环境变量信息;
  3. WSGI 环境变量信息;
  4. OS 环境变量信息;
  5. Server 环境变量信息。

其中,CGI 环境变量信息包括:

  • SERVER_NAME 和 SERVER_PORT:HTTP Request 的 URL 中的 Hostname 和 Port。
  • PATH_INFO(URL 路径信息):HTTP Request 的 URL 中核心的 API Resource 路径信息。
  • QUERY_STRING(查询字符串): HTTP Request 的 URL 中可能会包含查询字符串,使用 ? 号标识。
  • SCRIPT_NAME(脚本名称):HTTP Request 的 URL 中可能会包含一些脚本的名称。
  • REQUEST_METHOD(请求方法):HTTP Request 的 Methods,包括:GET、POST、PUT、DELETE 等;
  • CONTENT_TYPE:HTTP Headers 中的 content-type 信息。
  • CONTENT_LENGTH:HTTP Headers 中的 content-length 信息。
  • SERVER_PROTOCOL:Server 的 HTTP 协议版本信息。

WSGI 环境变量信息包括:

  • wsgi.version:WSGI 规范的版本;
  • wsgi.url_scheme:HTTP 或 HTTPS;
  • wsgi.input:一个类文件的输入流,Application 通过这个获取 HTTP Request Body;
  • wsgi.errors: 一个类文件的输出流,当 Application 出错时,可以将错误信息写入这里;
  • wsgi.multithread:当 Application 可能被多个线程同时调用时,这个值需要为 True;
  • wsgi.multiprocess: 当 Application 对象可能被多个进程同时调用时,这个值需要为 True;
  • wsgi.run_once:当 Server 期望 Application 在进程的生命周期内只被调用一次时,该值需要为 True。

start_resposne 参数

start_resposne(回调函数参数)指向一个 Server 实现的回调函数,Application 通过这个回调函数返回 HTTP Response 给 Server。

WSGI 规范定义了这个由 Server 实现的回调函数可以接受 2 个必选参数和 1 个可选参数:

start_resposne(status, resposne_headers, exc_info=None)
  • status:必选,String 类型,返回 HTTP Response Status Code;
  • resposne_headers:必选,Dict 类型,返回 HTTP Response Headers;
  • exc_info:可选,返回可能的 HTTP Response Error Message 或 Body 信息。
def application(environ, start_response):
      status = '200 OK'
      response_headers = [('Content-type', 'text/plain')]
      start_response(status, response_headers)
      return ['hello, world']

WSGI 的中间件

如下图所示,WSGI Middleware 本身兼具了 WSGI Applicant 和 WSGI Server 的角色。因此它可以在两端之间起协调作用,经过不同的 Middleware,便拥有了不同的功能,例如:URL 路由转发、身份权限认证。

  1. Server 收到 HTTP Request 后,生成 environ_s,并且定义了 start_response_s 回调函数。
  2. Server 调用 Middleware 的 application(),传递 environ_s 和 start_response_s。
  3. Middleware 根据 environ_s 执行业务逻辑,生成 environ_m,并且定义了 start_response_m 回调函数。
  4. Middleware 再调用 Application 的 application(),传递 environ_m 和 start_response_m。
  5. Application 的 application() 处理完成后,调用 start_response_m 并且返回结果给 Middleware。Middleware 将结果存放在 result_m 中。
  6. Middleware 处理 result_m,然后生成 result_s,接着调用 start_response_s 返回结果 result_s 给 Server。
  7. Server 获取到 result_s 后封装 HTTP Response 返回给 Client。

这里写图片描述

WSGI Web 开发框架

Paste + PasteDeploy + Routes + WebOb 是一个常见的 WSGI Web 开发框架。

  • Paste:是一个 WSGI 工具库,用于处理 Python WSGI Application 和 WSGI Server 之间的通信标准,同时提供了许多 WSGI Middleware 组件,用于处理 Request 和 Response,包括:会话管理,身份验证等等。满足 WSGI 规范的解耦架构设计。

  • PasteDeploy:是 Paste 的一个用于加载 WSGI Application 到 WSGI Server 的插件模块,支持通过 api-paste.ini 配置文件的方式和机制来定义与管理 WSGI Middleware 和 WSGI Application 在 Web Server 上的部署形态。通过 api-paste.ini 配置文件,开发者可以通过可插拔的方式来组装一个 Web Application。

  • Routes:是一个基于 URL 的 HTTP Request 路由分发库,用于将不同的 HTTP URLs/Methods(API Resources)与 API Controllers/Functions(资源表现层状态转移控制器)映射起来,实现将 HTTP Request 转发到对应的 Functions 进行处理并返回。

  • WebOb:是一个将 HTTP 协议中的 Request 和 Response 标准进行 Python Object 封装的库。以 Python Object 的风格和形式提供了对 Request URL、Request Header、Request Body、Request Filter & Query Parameter 以及 Response Header、Response Status Codes、Response Message、Response Body 的操作方式。此外,WebOb 还提供了一些简便的方法来处理 Cookie、会话管理、文件上传等常见的 Web 功能。

Paste + PasteDeploy + Routes + WebOb 能够开发符合 WSGI 的 Web Application。但该框架的弊端是实现复杂,代码量大,所以在 OpenStack 中只有 Neutron 等几个初始项目在使用。后来的新项目都采用了更简单高效的 Pecan 框架。

OpenStack 中的应用案例

NOTE:下文中以早期的 neutron-icehouse-eol 版本的代码来进行说明,介绍 Neutron Server 启动 API Service(Web Application)的流程。

进程入口

# /Users/fanguiju/workspace/neutron-icehouse-eol/setup.cfg
[entry_points]
console_scripts =
......
    neutron-server = neutron.server:main


# /Users/fanguiju/workspace/neutron-icehouse-eol/neutron/server/__init__.py
        # 实例化 API Service
        neutron_api = service.serve_wsgi(service.NeutronApiService)
......
            # 实例化 RPC Worker
            neutron_rpc = service.serve_rpc()

WSGI Application 加载

# /Users/fanguiju/workspace/neutron-icehouse-eol/neutron/common/config.py
def load_paste_app(app_name):
    """Builds and returns a WSGI app from a paste config file.

    :param app_name: Name of the application to load
    :raises ConfigFilesNotFoundError when config file cannot be located
    :raises RuntimeError when application cannot be loaded from config file
    """

    config_path = cfg.CONF.find_file(cfg.CONF.api_paste_config)
    ......
        app = deploy.loadapp("config:%s" % config_path, name=app_name)

Paste/PasteDeploy

此时,PasteDeploy 通过 api-paste.ini 文件来配置 WSGI Middleware 和 WSGI Application 的组合。

Neutron 的 api-paste.ini 文件如下:

# /Users/fanguiju/workspace/neutron-icehouse-eol/etc/api-paste.ini

[composite:neutron]
use = egg:Paste#urlmap
/: neutronversions
/v2.0: neutronapi_v2_0

[composite:neutronapi_v2_0]
use = call:neutron.auth:pipeline_factory
noauth = request_id catch_errors extensions neutronapiapp_v2_0
keystone = request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0

[filter:request_id]
paste.filter_factory = neutron.openstack.common.middleware.request_id:RequestIdMiddleware.factory

[filter:catch_errors]
paste.filter_factory = neutron.openstack.common.middleware.catch_errors:CatchErrorsMiddleware.factory

[filter:keystonecontext]
paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory

[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory

[filter:extensions]
paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory

[app:neutronversions]
paste.app_factory = neutron.api.versions:Versions.factory

[app:neutronapiapp_v2_0]
paste.app_factory = neutron.api.v2.router:APIRouter.factory

入口是 composite Section,用于将 HTTP Request URL 分发到指定的 Middleware 和 Application。

  • use:指定了根据 URL 分发 Request 的方式。
  • egg:Paste#urlmap:表示使用 paste/urlmap.py 模块来根据 URL 分发 Request。
  • /v2.0、/v3、/:作为传递给 urlmap_factory() 函数进行 Request 分发的实参。urlmap_factory() 返回下一个 composite Section 或 app Section。

这里以 /v2.0 为例,urlmap_factory() 返回了 neutronapi_v2_0 composite Section,它包含了一个 Middleware Pipeline。Pipeline 用于把一系列 Middleware 和最终的 Application 串联起来,并遵守以下 2 点要求:

  1. 最后一个名字对应的 Section,一定要是一个 app 类型的。
  2. 非最后一个名字对应的 Sections,一定要是一个 filter 类型的。

在 filter 和 app Section 中定义了 paste.filter_factory 实际的入口函数。

Routes

Routes 的常规用法是首先构建一个 Mapper 实例对象,然后调用 Mapper 对象的 connect() 或 collection() 方法把相应的 URL、HTTP Method 映射到一个 Controller 的某个 Action 上。

Controller 是一个自定义的类实例,每一种 API Resource 都实现了一个 Controller,内含了多个与 HTTP Methods 对应的 Actions 成员方法,例如:list、show、create、update、delete 等。

通过 Paste/PasteDeploy 的配置处理之后进入到 neutronapiapp_v2_0 Application 的 factory() 函数,Paste 最终会调用它。在 APIRouter 类的实例化过程中会完成 Routes Mapper 的映射工作。

class APIRouter(wsgi.Router):

    @classmethod
    def factory(cls, global_config, **local_config):
        return cls(**local_config)

    def __init__(self, **local_config):
        mapper = routes_mapper.Mapper()
        plugin = manager.NeutronManager.get_plugin()  # Core Plugin
        ext_mgr = extensions.PluginAwareExtensionManager.get_instance()  # Service Plugins
        ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)

        col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,  # 集合操作类型:index、create
                          member_actions=MEMBER_ACTIONS)  # 成员操作类型:show、update、delete

        def _map_resource(collection, resource, params, parent=None):
            allow_bulk = cfg.CONF.allow_bulk
            allow_pagination = cfg.CONF.allow_pagination
            allow_sorting = cfg.CONF.allow_sorting
            controller = base.create_resource(
                collection, resource, plugin, params, allow_bulk=allow_bulk,
                parent=parent, allow_pagination=allow_pagination,
                allow_sorting=allow_sorting)
            path_prefix = None
            if parent:
                path_prefix = "/%s/{%s_id}/%s" % (parent['collection_name'],
                                                  parent['member_name'],
                                                  collection)
            mapper_kwargs = dict(controller=controller,
                                 requirements=REQUIREMENTS,
                                 path_prefix=path_prefix,
                                 **col_kwargs)
            return mapper.collection(collection, resource,
                                     **mapper_kwargs)

		# 顶级资源
        mapper.connect('index', '/', controller=Index(RESOURCES))

        for resource in RESOURCES:
            _map_resource(RESOURCES[resource], resource,
                          attributes.RESOURCE_ATTRIBUTE_MAP.get(
                              RESOURCES[resource], dict()))

        for resource in SUB_RESOURCES:
            _map_resource(SUB_RESOURCES[resource]['collection_name'], resource,
                          attributes.RESOURCE_ATTRIBUTE_MAP.get(
                              SUB_RESOURCES[resource]['collection_name'],
                              dict()),
                          SUB_RESOURCES[resource]['parent'])

        # Certain policy checks require that the extensions are loaded
        # and the RESOURCE_ATTRIBUTE_MAP populated before they can be
        # properly initialized. This can only be claimed with certainty
        # once this point in the code has been reached. In the event
        # that the policies have been initialized before this point,
        # calling reset will cause the next policy check to
        # re-initialize with all of the required data in place.
        policy.reset()
        super(APIRouter, self).__init__(mapper)

WebOb

WebOb 有 3 个重要的对象:

  1. webob.Request:对 HTTP Request 中的 environ 参数进行封装。
  2. webob.Response:对 HTTP Response 的所有元素进行封装。
  3. webob.exc:对 HTTP Error Code 和 Msg 进行封装。

此外,WebOb 还提供了一个 webob.dec.wsgify 装饰器,以便我们可以不使用原始的 WSGI 参数传递和返回格式,而全部使用 WebOb 替代。

@wsgify
def myfunc(req):
    return webob.Response('Hey!')

WSGI 原始调用方式

app_iter = myfunc(environ, start_response)

WebOb 调用方式

resp = myfunc(req)

WSGI Server 启动

# /Users/fanguiju/workspace/neutron-icehouse-eol/neutron/service.py

def _run_wsgi(app_name):
    ......
    server = wsgi.Server("Neutron")
    server.start(app, cfg.CONF.bind_port, cfg.CONF.bind_host,
                 workers=cfg.CONF.api_workers)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/724284.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【UE 材质】磨砂玻璃材质

效果 步骤 新建一个材质,这里命名为“M_FrostedGlass” 打开“M_FrostedGlass”,设置混合模式为半透明 添加如下节点: 创建一个材质实例 将材质实例赋予到一个mesh 打开材质实例,调整一下玻璃颜色和模糊值 原视频链接&#xff1a…

微信小程序UV、PV量解释以及接口调用频率

微信小程序UV、PV量 浏览量(PV):即通常说的Page View(PV),用户每打开一个网站页面就被记录1次。用户多次打开同一页面,浏览量值累计。微信小程序中PV是打开小程序的打开次数。 访客数(UV):一天之内网站的独立访客数(以Cookie为依…

建立LIS系统需注意解决的问题

建立LIS需注意解决的问题 数据接收的可靠性:不管是采用硬件方式还是软件方式接收数据,保证接收过程数据不丢失或出现错误,是建立LIS首先要解决好的技术问题,否则就不是一个成功的LIS,缺乏实际的应用价值。 系统运行的…

vue3+vite使用particles.js

上效果: 1、安装vue3-particles 注意: 这里不能直接安装2.0以上的版本,否则界面无法出来。至于根本原因目前还没查到 # 通过以下命令可以发现当前所具有的版本 $ pnpm view vue3-particles versions [1.42.1, 1.42.2, 1.42.3, 1.42.4,1.43.…

【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(场景问题分析+性能影响因素)

一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(场景问题分析性能影响因素) 常见的几个场景问题问题1:过期还是不过期缓存数据保证最终一致性 问题2:维度化缓存与增量更新通过维度化缓存优点和好处 问题3…

总在谈流程,却又做不好,问题出在哪?

时至今日,流程这个概念无论是在理论层面还是实践层面,都已为大家所熟知。 特别是随着华为的崛起,流程的吸引力与日俱增,为数不少的企业都在服用流程这剂灵丹妙药。 可是,真正搞明白流程概念的企业还不算多&#xff0…

win10开启远程桌面,win10开启3389端口

文章目录 前言一、不能远程桌面的原因1.1、系统设置未开启远程桌面功能1.2、开启防火墙3389端口1.3、开启3389端口 二、开启3389端口2.1、查看是否开启端口2.2、启动远程桌面服务2.3、远程连接administrator账号提示由于账户限制无法登录 三、其他 前言 最近重装了一下小主机系…

Unity 非父子物体保持相对静止

非父子物体保持相对静止 🍔效果🥪食用 🍔效果 保持两个非父子关系的物体坐标、旋转相对静止 🥪食用 插件下载 using System.Collections; using System.Collections.Generic; using UnityEngine;namespace ZYF {public class…

IntelliJ IDEA路径里面的反斜杠变成了其他符号解决办法

IntelliJ IDEA里面配置任何路径的时候路径里面的反斜杠分隔符变成了其他符号解决办法。 问题如图: 路径都变成了W加删除线。 原因: 字体设置问题,字体把斜杠转义了。 解决: 别用Gothic的相关字体,换成其他字体即…

如何在可视化页面中保证数据安全?Sugar BI通过URL参数标识用户,灵活实现用户权限

公开分享之后的大屏/报表页面中,由于不需要用户登录账号,因此页面中 数据模型的行级别权限、SQL 建模中嵌入用户邮箱、API 后端获取当前登录用户 这些需要用户登录账号才能进行的权限限制功能都不可用。 但是在一些场景下,是期望这些权限功能…

滴水逆向三期笔记与作业——02C语言——04 IF语句逆向分析上

OneNote防丢失。 海哥牛逼。 IF语句逆向分析上 一、内存图二、如何判断函数的参数2.1 一般情况 三、if的汇编案例根据汇编还原if 四、作业 一、内存图 二、如何判断函数的参数 2.1 一般情况 三、if的汇编 案例 根据汇编还原if 四、作业 海哥牛逼。

macbook安装chatglm2-6b

1、前言 chatglm安装环境还是比较简单的,比起Stable diffusion安装轻松不少。   安装分两部分,一是github的源码,二是Hugging Face上的模型代码;安装过程跟着官方的readme文档就能顺利安装。以下安装内容,绝大部分是…

自然语言处理:多层感知机MLP和Word2vec

文章目录 多层感知机MLP1.神经网络2.反向传播算法3.激活函数4.损失函数5.神经网络的使用场景 Word2vec参考 多层感知机MLP 1.神经网络 神经网络是一种计算模型,它受到人脑神经元之间连接和信息处理方式的启发。它由许多简单的处理单元(称为神经元或节点…

初始Liunx线程

文章目录 前言1.初始Liunx下线程2.关于虚拟地址的补充知识3.线程的相关特点1.线程的优点2.线程的缺点3.线程异常4.线程和进程的比较 4.线程相关操作接口线程控制相关接口 5.关于线程id的理解 前言 本文主要是对Liunx之下线程的前置知识铺垫,同时也是对之前进程的相…

Spring Boot 中的认证是什么,如何使用

Spring Boot 中的认证是什么,如何使用 在 Web 应用程序中,认证是一项重要的安全措施。Spring Boot 提供了丰富的认证机制,可以帮助我们轻松地实现各种认证需求。本文将介绍 Spring Boot 中的认证是什么,以及如何使用 Spring Boot…

2023上半年软考系统分析师科目一整理-16

2023上半年软考系统分析师科目一整理-16 信息系统的性能评价指标是客观评价信息系统性能的依据,其中,( )是指系统在单位时间内处理请求的数量。 A.系统响应时间 B.吞吐量 C.资源利用率 D.并发用户数 运用互联网技术,在…

软件工程——第7章实现知识点整理

本专栏是博主个人笔记,主要目的是利用碎片化的时间来记忆软工知识点,特此声明! 文章目录 1.实现由哪两个部分组成? 2.编码是什么?所选用的程序设计语言对程序的哪些特性有着深远影响? 3.软件测试在软件生…

旅游卡系统招募城市合伙人

旅游业的不断发展,旅游卡系统作为一种新型的旅游消费模式也逐渐被越来越多的人所认可。现在,许多旅游卡系统开始招募城市合伙人,以进一步拓展其市场。 旅游卡系统是一种可以将不同景区、景点门票进行整合,并提供折扣优惠的旅游…

谈谈mysql——Binlog的复制方式和解析技巧

mysql 我们先来看一下MySQL的基本架构,从大的方面来讲,一个server层,一个引擎层。server层就像一个接口,可以对接任何符合规定的引擎。具体的细节可以参考我之前写过的文章mysql的这些坑你踩过吗?快来看看怎么优化mys…

vue3+pinia用户信息持久缓存(token)的问题

vue3pinia用户信息持久缓存(token)的问题 对博主来说,这是个相当复杂的问题。 当初在使用vue2vuex进行用户信息持久登录时,写了不下3篇博客,确实是解决了问题,博客链接如下 vue存储和使用后端传递过来的tokenvue中对…