资讯详情

资讯详情

Channels 2.1.4 发布说明深度解读:中间件缓存、Origin 校验与 channel_layer_alias 关键修复

后端WebSocket异步编程【免费下载链接】channelsDeveloper-friendly asynchrony for Django项目地址https://gitcode.com/gh_mirrors/ch/channels点击查看免费下载Channels 2.1.4 是 2.1 系列的一个 bugfix 版本重点解决了 Django 中间件重复实例化带来的 HTTP 服务性能瓶颈同时修复了测试框架静态文件服务、Origin 头校验报错信息、runserver 日志体系等多个问题并首次允许通用 Consumer 通过channel_layer_alias属性使用非默认的 Channel Layer。本文以官方发布说明为主体结合当前仓库源码逐条剖析每个变更的实现原理与使用影响帮助读者理解 Channels 2.x 内部机制并掌握升级到 2.1.4 前后的行为差异。版本概况与升级兼容性2.1.4 属于 Channels 2.1 系列的维护性bugfix发布不包含新功能特性只针对 2.1 系列已有代码进行缺陷修复与小型改进。版本发布说明中明确列出了向后不兼容变更Backwards Incompatible ChangesNone即从 2.1.3 或更早的 2.1.x 版本升级到 2.1.4 不需要修改应用代码配置、路由、Consumer 的既有写法均保持兼容可以直接原地升级。对于仍停留在 2.0.x 的工程升级到 2.1.4 前仍需遵循 2.1 系列的迁移要求如ROUTING配置项在 Channels 2 中已被移除这一点在 channels/layers.py 中仍有明确体现ChannelLayerManager遇到旧格式配置中的ROUTING键会直接抛出InvalidChannelLayerError。Django 中间件改为缓存复用HTTP 服务性能显著提升本次发布最重要的一条是Django middleware 现在被缓存而不是在每个请求时重新实例化带来了显著的性能提升。此前某些中间件加载需要数秒导致 Channels 在处理 HTTP 请求时几乎不可用。问题根源中间件按请求实例化在早期实现中Channels 会在每个 ASGI 请求scope到来时重新创建 Django 中间件实例。中间件的__init__中如果包含耗时操作例如加载模板引擎、初始化数据库连接池、解析大型配置文件那么每个 HTTP 请求都要重复付出这些初始化成本部分中间件初始化耗时甚至高达数秒直接导致 Channels 的 HTTP 服务在负载下卡死、近乎不可用。修复后的行为修复后中间件实例被缓存并跨请求复用。这与 Django WSGI 体系中中间件在进程启动时一次性实例化、请求期间复用的模型一致。对使用者而言这意味着中间件类实例在整个进程生命周期内共享__init__中的初始化逻辑只执行一次不应在中间件实例属性中存放与单个请求绑定的状态否则会在请求间串扰。这恰好与 channels/middleware.py 中BaseMiddleware的类注释相呼应——它明确警告子类并非自安全not self-safe不要在实例上保存状态因为同一个实例会服务于多个应用实例请改用 scope 传递状态class BaseMiddleware: Base class for implementing ASGI middleware. Note that subclasses of this are not self-safe; dont store state on the instance, as it serves multiple application instances. Instead, use scope. 可见本次修复正是让实际实现与这一设计约束重新对齐中间件实例一旦被缓存复用实例级状态存储就必然成为 bug 温床。推荐做法是把请求级数据写入scope字典——BaseMiddleware.__call__也特意通过scope dict(scope)复制一份 scope防止修改向上游泄漏。Channels 官方自带的多层中间件栈正是基于BaseMiddleware构建的例如 channels/auth.py 提供的AuthMiddlewareStackdef AuthMiddlewareStack(inner): return CookieMiddleware(SessionMiddleware(AuthMiddleware(inner)))这些中间件均通过向 scope 注入session、user等键工作天然符合状态放 scope的缓存安全模式。ChannelServerLiveTestCase 恢复静态文件服务2.1.4 修复了ChannelServerLiveTestCase不再提供静态文件服务的问题。在 channels/testing/live.py 中可以看到ChannelsLiveServerTestCase继承 Django 的TransactionTestCase会在独立的 Daphne 进程中启动真实服务器供 Selenium 等外部测试框架连接class ChannelsLiveServerTestCase(TransactionTestCase): host localhost ProtocolServerProcess DaphneProcess static_wrapper ASGIStaticFilesHandler serve_static True关键字段解释serve_static True默认开启静态文件服务static_wrapper ASGIStaticFilesHandler静态文件通过 Django 的ASGIStaticFilesHandler包装host localhost测试服务器监听主机可通过live_server_urlhttp://host:port与live_server_ws_urlws://host:port属性获取地址。在_pre_setup中测试用例通过make_application把静态文件包装器套在默认 ASGI 应用外层见 channels/testing/live.pydef make_application(*, static_wrapper): # Module-level function for pickle-ability application get_default_application() if static_wrapper is not None: application static_wrapper(application) return application2.1.4 修复的就是该流程中serve_static配置被忽略、静态文件包装器未生效的问题。修复后基于该测试类做端到端测试时页面引用的 CSS/JS 静态资源可以正常从测试服务器加载不再出现 404。需要注意该测试类要求使用磁盘型数据库若检测到内存数据库会抛出ImproperlyConfigured见 channels/testing/live.py并且_post_teardown会负责终止 Daphne 进程、还原ALLOWED_HOSTS修改。Origin 头校验失败的错误信息改进2.1.4 改进了由于非法 Origin 头导致的错误提示。要理解这一改进需要先了解 Channels 的 Origin 校验机制——它由 channels/security/websocket.py 中的OriginValidator类实现。校验流程OriginValidator是 ASGI 中间件形态的 WebSocket 应用包装器其核心流程如下对应__call__实现校验 scope 类型必须是websocket否则抛出ValueError见 channels/security/websocket.py从 scope 的headers中提取origin头并解码latin1用urlparse解析见 channels/security/websocket.py调用valid_origin()判断是否允许若 Origin 缺失且允许列表中无*直接拒绝否则交给validate_origin()与允许列表匹配见 channels/security/websocket.py校验通过则放行到内层应用否则交给WebsocketDenier拒绝连接——WebsocketDenier是一个继承自AsyncWebsocketConsumer的简化应用connect时直接close()见 channels/security/websocket.py。允许列表匹配规则match_allowed_origin见 channels/security/websocket.py支持以下模式*匹配一切精确域名如example.com无 scheme 时按域名比较主机名带 scheme 的完整 Origin如https://example.com此时 scheme、端口、域名必须全部匹配以点开头的域名如.example.com匹配该域名及其全部子域借助 Django 的is_same_domain。端口比较存在默认值逻辑http/ws默认端口 80https/wss默认端口 443见 channels/security/websocket.py因此显式写 443 端口与不写端口在https下视为等价。便捷工厂与 DEBUG 行为AllowedHostsOriginValidator见 channels/security/websocket.py是常用便捷封装直接读取 Django 的ALLOWED_HOSTS设置作为允许列表若DEBUGTrue且ALLOWED_HOSTS为空则自动回退为[localhost, 127.0.0.1, [::1]]便于本地开发调试。与测试的对应关系仓库测试 tests/security/test_websocket.py 覆盖了允许/拒绝、通配子域、空 Origin、非法 Origin 等场景。其中空 Origin 头与非法 Origin 头的用例如(borigin, b)和(borigin, bsomething-invalid)正是针对 2.1.4 这类边界场景的回归验证此前这类输入可能产生晦涩难懂的异常或行为异常2.1.4 后校验逻辑与错误路径更清晰可控。2.1.4 中改进的错误信息主要落在校验被拒绝、校验过程异常时向开发者反馈的信息质量上配合上述流程开发者可以更快定位被拒连接到底是 Origin 缺失、域名不匹配还是格式非法。runserver 日志接入 Django 日志框架2.1.4 将runserver的日志输出切换到 Django 的标准日志框架logging以对齐现代 Django 的日志行为。在此之前runserver启动信息往往直接打印到 stdout/stderr无法与 Django 项目的LOGGING配置联动难以统一日志格式、级别过滤与收集。修复后运行输出通过 Django logging 框架发布开发者可以在LOGGING配置中按 logger 名称例如django.channels相关 logger自定义处理方式。这一点与仓库中 Channels worker 命令的日志实践一致channels/management/commands/runworker.py 中即使用logger logging.getLogger(django.channels.worker)记录 worker 启动与运行信息并通过--verbosity控制输出详略runserver采用同样的 logging 通道后两类常驻进程的日志可以统一纳入项目日志体系。若项目在升级后发现 runserver 日志样式变化或丢失应检查LOGGING配置中是否存在覆盖django.channels系列 logger 的规则。通用 Consumer 支持非默认 Channel Layerchannel_layer_alias2.1.4 允许通用 ConsumerWebsocketConsumer、AsyncWebsocketConsumer、AsyncHttpConsumer等使用非默认的 Channel Layer。此前通用 Consumer 固定绑定默认 layer需要同时配置多个后端例如生产用 Redis、部分连接走内存时只能通过自定义 Consumer 实现2.1.4 起只需在 Consumer 类上设置channel_layer_alias属性。底层实现该能力的根基在 channels/consumer.pyAsyncConsumer是全部通用 Consumer 的公共基类定义了类属性class AsyncConsumer: _sync False channel_layer_alias DEFAULT_CHANNEL_LAYER async def __call__(self, scope, receive, send): self.scope scope # Initialize channel layer self.channel_layer get_channel_layer(self.channel_layer_alias) ...Consumer 每次被调用时通过get_channel_layer(self.channel_layer_alias)按别名获取对应的 Channel Layer 实例。而get_channel_layer见 channels/layers.py从全局channel_layers管理器ChannelLayerManager中按别名取层ChannelLayerManager依据 Django 设置的CHANNEL_LAYERS字典按需惰性实例化后端并在CHANNEL_LAYERS设置变化setting_changed信号时清空缓存重新加载见 channels/layers.py。配置与使用示例首先在settings.py中配置多个命名后端default与自定义别名CHANNEL_LAYERS { default: { BACKEND: channels_redis.core.RedisChannelLayer, CONFIG: {hosts: [(127.0.0.1, 6379)]}, }, memory: { BACKEND: channels.layers.InMemoryChannelLayer, CONFIG: {capacity: 100, expiry: 60}, }, }然后在 Consumer 类中声明别名from channels.generic.websocket import AsyncWebsocketConsumer class MemoryChatConsumer(AsyncWebsocketConsumer): channel_layer_alias memory async def connect(self): await self.channel_layer.group_add(chat, self.channel_name) await self.accept()此时self.channel_layer指向CHANNEL_LAYERS[memory]对应的后端。需要说明channel_layer_alias是类属性默认值为DEFAULT_CHANNEL_LAYER即default不设置时行为与旧版本完全一致若指定的别名未在CHANNEL_LAYERS中配置get_channel_layer返回NoneConsumer 将以无 channel layer 模式运行不再创建专属接收通道这是从源码结构可以直接推断的边界行为见 channels/consumer.py仓库测试 tests/test_generic_websocket.py 中有多处以channel_layer_alias testlayer验证该特性说明别名机制在通用 WebSocket Consumer 场景下已被测试覆盖。提前访问 scope[user] 的错误信息改进2.1.4 改进了在用户对象尚未就绪时访问scope[user]的错误提示。背景异步环境下的懒加载Django 用户加载依赖数据库与会话必须在异步环境中执行。AuthMiddleware在populate_scope阶段先把scope[user]填为一个懒加载代理LazyObject真正的用户实例要等resolve_scope阶段通过get_user(scope)异步取回后才填充见 channels/auth.py。此前如果在 ASGI 应用的构造阶段而非请求处理阶段就访问scope[user]由于代理尚未解析只会抛出一个泛化的初始化错误让人难以判断问题原因——尤其是应用构造函数里拿不到 user这一 Channels 独有的约束构造阶段是同步上下文无法执行异步数据库加载。2.1.4 的改进修复在 channels/auth.py 中新增了UserLazyObjectclass UserLazyObject(LazyObject): Throw a more useful error message when scope[user] is accessed before its resolved def _setup(self): raise ValueError(Accessing scope user before it is ready.)现在一旦在解析完成前访问scope[user]会立即得到明确的ValueError: Accessing scope user before it is ready.直接点明用户尚未就绪。配套地AuthMiddleware.populate_scope在无 session 时也会给出清晰指引AuthMiddleware无法在 scope 中找到 sessionSessionMiddleware必须位于其上层见 channels/auth.py提示开发者按CookieMiddleware → SessionMiddleware → AuthMiddleware的正确顺序即AuthMiddlewareStack组织中间件。对应用开发者的实践意义用户对象只能在 Consumer 的异步方法如connect、receive内部访问不能在 ASGI 应用/Consumer 的__init__构造函数中访问scope[user]如需在连接时判断登录态应放在connect等异步处理器中。升级建议与版本总结性能如果此前因中间件初始化开销导致 Channels HTTP 服务响应缓慢2.1.4 的中间件缓存机制可以直接缓解但请确保自定义 ASGI 中间件不在实例上保存请求级状态遵循 channels/middleware.py 的约束状态放 scope。日志升级后检查项目LOGGING配置确认django.channels相关 logger 的处理器、格式与预期一致。多后端需要为部分 Consumer 单独指定 channel layer 时使用channel_layer_alias类属性并配套配置CHANNEL_LAYERS中的命名后端。测试依赖静态资源的 Live Server 测试Selenium 等在 2.1.4 可正常加载静态文件注意此类测试需使用磁盘数据库。兼容性本次发布无向后不兼容变更可安全升级若需继续深入了解 Channel Layer 配置语法与容量/过期参数可参阅 channels/layers.py 的BaseChannelLayer基类实现。Channels 2.1.4 虽然只是一个小版本维护发布却通过中间件缓存、日志体系对齐、错误信息优化与channel_layer_alias别名支持在性能、可观测性与多后端灵活性三个方向上为 2.1 系列补上了关键短板值得所有使用 Channels 2.1 的 Django 异步应用跟进升级。赞分享后端WebSocket异步编程【免费下载链接】channelsDeveloper-friendly asynchrony for Django项目地址https://gitcode.com/gh_mirrors/ch/channels点击查看免费下载相关推荐LND v0.20.1 发布说明深度解读关键修复、Postgres 并发控制与 BOLT 7 时间戳强制LND v0.20.1 发布说明深度解读关键修复、Postgres 并发控制与 BOLT 7 时间戳强制 本文是对 LNDLightning Network区块链Node.js 0.12.3 (Stable) 发布说明深度解析V8 与 libuv 升级、九项关键修复及发布物校验指南Node.js 0.12.3 Stable 发布说明深度解析V8 与 libuv 升级、九项关键修复及发布物校验指南 本篇技术指南以 apps/site/pa前端文档Robot Framework 4.0.1 版本发布说明与关键修复深度解析Robot Framework 4.0.1 版本发布说明与关键修复深度解析 Robot Framework 4.0.1 是 4.0.x 系列的首个缺陷修复版本测试RPA接口测试上一篇TVBoxOSC 新手指南从空白界面到流畅播放只需 3 步下一篇开源项目 neverthrow 使用教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →