Tyk Coprocess 插件框架实战:使用 Python、Lua 与 gRPC 编写自定义 API 中间件
【免费下载链接】tykOpen Source API and AI Gateway supporting REST, GraphQL, TCP, gRPC and MCP (Model Context Protocol)项目地址: https://gitcode.com/gh_mirrors/ty/tyk
Coprocess(Co-Process)是 Tyk API 网关提供的"富插件"(rich plugin)机制,它允许开发者使用自己熟悉的语言编写中间件,挂载到 Tyk 的标准请求处理链路上。本文基于 coprocess/README.md 及仓库源码,系统讲解 Coprocess 的进程内消息传递架构、Dispatcher 接口、五类 Hook 的语义、Python/Lua/gRPC 三种驱动的接入方式、ID Extractor 认证缓存、Coprocess Gateway API 以及构建与测试方法,读完即可在自有 API 定义中配置并调试自定义中间件。
Coprocess 是什么:为什么需要它
Tyk 网关本身以 Go 编写,原生支持 JavaScript 插件(goja/otto)等脚本化中间件。但很多团队更希望在 Python、Lua、Ruby、Java 等语言中复用已有的认证、限流、转换逻辑。Coprocess 正是为此设计:它通过进程内消息传递把 Tyk 的请求对象、会话对象和 API 定义"翻译"成目标语言可处理的数据结构,让任意语言都能以 Hook 的形式参与 Tyk 的中间件链路。
从源码结构看,整个特性被组织在 coprocess/ 目录下,包含:
proto/:定义消息格式的 Protocol Buffers 文件及其生成脚本;bindings/:预生成的多语言绑定(Python、Ruby、Java、C++);python/、lua/、grpc/:三种驱动的实现与文档;dispatcher.go:Dispatcher 接口定义;api.h:C 语言桥接头文件。
核心架构:基于 Protocol Buffers 的进程内消息传递
Coprocess 的互操作性建立在两层消息结构之上:
- 传输层:目标语言的 C 桥接函数接收/返回一个
CoProcessMessage结构,其中p_data指向序列化数据的指针,length表示数据长度。该结构在 coprocess/api.h 中定义:
struct CoProcessMessage { void* p_data; int length; };- 业务层:
p_data解包后是完整的CoProcessObject数据,它携带 Hook 类型、HTTP 请求、Tyk 会话和 API 定义等信息。原文档以 Go 结构描述其形态,对应 Protobuf 消息coprocess.Object(见 coprocess/proto/coprocess_object.proto):
type CoProcessObject struct { HookType string Request CoProcessMiniRequestObject Session SessionState Metadata map[string]string Spec map[string]string }其中:
HookType:本次调用触发的 Hook 类型(见下文 Hook 语义);Request:MiniRequestObject,包含 headers、params、body、url、method 等请求字段;Session:当前 key/用户对应的 Tyk 会话状态;Metadata:动态元数据;Spec:API 定义信息,如 APIID、OrgID 等,是插件识别"属于哪个 API"的关键依据。
Go 侧与目标语言之间的桥接通过 cgo)。这也解释了为什么 Coprocess 被称为"进程内"插件:插件代码运行在 Tyk 进程内部,通过 C 层直接交换内存数据,不引入额外的网络往返(gRPC 驱动除外)。
Protobuf 消息的字段细节
从 coprocess/proto/coprocess_mini_request_object.proto 可以看到MiniRequestObject的完整字段,这对编写中间件至关重要:
headers(只读):此前中间件注入的请求头;set_headers/delete_headers:要追加/删除的请求头;body/raw_body:请求体(字符串与原始字节两种形式);url/request_uri/scheme/method:请求的 URL、原始 URI(含查询串)、协议与 HTTP 方法;params(只读)、add_params、delete_params:查询参数的读取与修改;return_overrides:直接覆盖 Tyk 对当前请求的响应(状态码、响应体、响应头),常用于在中间件内直接终止请求并返回自定义错误。
Proto 文件与绑定生成
Coprocess 的所有消息格式由 coprocess/proto/ 下的.proto文件定义,包括:
coprocess_common.proto:HookType枚举与StringSlice;coprocess_object.proto:Object、Event、EventReply与DispatchergRPC 服务;coprocess_mini_request_object.proto:请求对象;coprocess_response_object.proto:响应对象(用于 Response Hook);coprocess_return_overrides.proto:响应覆盖结构;coprocess_session_state.proto:会话状态。
原文档提示:如需修改 proto 并更新绑定,参考proto/目录下的生成脚本。当前仓库中该目录包含 codegen.py 与 Taskfile.yml,负责为各语言重新生成绑定代码。仓库内 coprocess/bindings/ 已预置生成结果:Python(*_pb2.py)、Ruby(*_pb.rb)、Java(*.java)、C++(*.pb.cc/.pb.h),gRPC 驱动可直接复用这些文件。
HookType 枚举:五种 Hook 的完整语义
coprocess/proto/coprocess_common.proto 中定义了HookType枚举,原文档描述了其中四类,源码则给出了完整集合与精确语义:
| 枚举值 | 名称 | 执行时机 |
|---|---|---|
1 | Pre | 在从请求头/参数中提取任何认证信息之前执行,同时适用于 keyless 与受保护 API |
2 | Post | 在认证、校验、限流、配额等中间件执行完毕后、请求被代理到上游之前执行 |
3 | PostKeyAuth | 紧随认证流程之后执行 |
4 | CustomKeyCheck | 作为自定义认证中间件整体替代 Tyk 内置认证 |
5 | Response | 上游 API 响应后执行,可修改返回给客户端的 HTTP 响应 |
Coprocess Dispatcher 接口
coprocess/dispatcher.go 定义了coprocess.Dispatcher接口,它是所有驱动(PythonDispatcher、gRPC 等)必须实现的门面:
type Dispatcher interface { Dispatch(*Object) (*Object, error) DispatchWithContext(context.Context, *Object) (*Object, error) DispatchEvent([]byte) DispatchObject(*Object) (*Object, error) LoadModules() HandleMiddlewareCache(*apidef.BundleManifest, string) Reload() }各方法职责如下:
Dispatch:核心分发入口。接收指向CoProcessObject的指针并返回同类型对象。它会在每个已配置的 Hook、每次请求时被调用。传统上该方法在目标语言侧只做一次函数调用(如 Python 驱动的dispatch_hook),真正的中间件查找与执行逻辑放在语言内部处理——因为不同语言加载、引用和调用中间件的方式差异很大。DispatchWithContext:携带context.Context的分发入口,用于链路追踪(trace propagation)。DispatchEvent:把 Tyk 事件分发给目标语言。注意它不使用 Protocol Buffers,入参是 JSON 编码的[]byte(目标语言侧按char*接收后自行做 JSON 解码),这样做是为了保持扩展性。DispatchObject:gRPC 驱动使用的分发入口(直接传递反序列化后的coprocess.Object)。LoadModules:CP 绑定首次启动时调用(Lua 驱动使用)。HandleMiddlewareCache:bundle 加载后用于缓存中间件清单(Lua 驱动使用)。Reload:触发热重载时调用,典型用途是重载目标语言中的脚本或模块。
以 Python 为例,gateway/coprocess_python.go 中的PythonDispatcher.DispatchWithContext展示了完整调用链:将coprocess.Object用proto.Marshal序列化 → 通过 CPython C API 获取dispatcherInstance.dispatch_hook属性 → 传入字节串并调用 → 从返回值中取回新对象字节 →proto.Unmarshal回 Go 结构。整个调用被pythonLock互斥锁保护,避免多 goroutine 并发操作 Python 解释器。
Python 侧的TykDispatcher(见 coprocess/python/dispatcher.py)则负责按 bundle 维护 hook 表:load_bundle把中间件打包进hook_table,dispatch_hook依据object.spec['bundle_hash']与object.hook_name找到对应的 Python 函数执行,dispatch_event依据事件的 APIID 与 handler 名称分发事件,reload留作重载钩子。
Hook 语义详解:中间件在请求链中的位置
原文档明确指出 Dispatcher "遵循标准中间件链逻辑",为自定义中间件行为提供简单的挂载机制。以 Python 装饰器为例(coprocess/python/tyk/decorators.py),Pre/Post/PostKeyAuth/CustomKeyCheck/Event五个装饰器分别对应相应 Hook,装饰器内部按函数参数个数(3 或 4 个)自动适配调用签名:
Pre:在认证之前运行,可对请求做任意预处理(如注入请求头、改写请求体);Post:在认证、校验、限流、配额之后、代理到上游之前运行,用于把请求"后处理"后再发给上游 API;PostKeyAuth:认证流程刚结束即运行,适合做基于认证结果的附加检查;CustomAuthCheck(即枚举中的CustomKeyCheck):完全取代 Tyk 内置认证,实现自有认证机制;调用签名为(request, session, metadata, spec);Response:上游响应返回后运行,可改写响应。
需要特别强调的是:所有 Hook 类型都支持链式挂载(chaining),唯独自定义认证auth_check例外——每个 API 只能配置一个CustomAuthCheck。
三种驱动:Python、Lua 与 gRPC
Python 驱动
Python 是文档中描述最详细的驱动,支持 Python 3.x。Tyk 会加载middleware/python目录下的全部模块,custom_middleware中的name字段即 Python 函数名。一个完整的 Python 中间件(参考 middleware/python 目录与 coprocess/python/README.md):
from tyk.decorators import * @Pre def MyPreMiddleware(request, session, spec): print("my_middleware: MyPreMiddleware") return request, session @Post def MyPostMiddleware(request, session, spec): print("my_middleware: MyPostMiddleware") return request, sessionPython 驱动的构建依赖(详见 coprocess/python/README.md):
- Python 3.x;
- Go 工具链;
- Cython(如需修改并重编译 Gateway API 绑定);
- protobuf Python 模块:
pip3 install protobuf==3.20.2; - gRPC 模块:
pip3 install grpcio。
Cython 的引入原因值得说明:原文档指出,Cython 将.pyx文件编译为 C 源文件(.c/.h)后直接参与 cgo 构建,作为cffi的替代方案——cffi需要用户先安装模块,增加了部署步骤;而 Cython 方案在构建结束后绑定已成为 Tyk 二进制的一部分,运行时无需.pyx文件。cythonize脚本会给生成的 C 文件打上// +build coprocess与// +build python标签,确保普通构建时 Go 编译器忽略这些 C 绑定文件。
Lua 驱动
Lua 支持见 coprocess/lua/README.md,唯一的硬性依赖是lua-cjson(用于 JSON 编解码),推荐通过 luarocks 安装:
% luarocks install lua-cjsonLua 驱动的核心代码位于 coprocess/lua/,包括绑定头文件 binding.h、Bundle 加载器 bundle.lua 以及tyk/core.lua、tyk/request.lua等运行时库。
gRPC 驱动
gRPC 驱动允许把中间件逻辑放到任意 gRPC 后端进程中,官方支持的语言非常广泛(C++、Java、Python、Go、Ruby、C#、Node.js、Objective-C、PHP 等)。由于 gRPC 本身基于 Protocol Buffers,Tyk 的 coprocess/proto/coprocess_object.proto 直接定义了 Dispatcher 服务契约:
service Dispatcher { rpc Dispatch (Object) returns (Object) {} rpc DispatchEvent (Event) returns (EventReply) {} }工作流程为:Tyk 启动时按tyk.conf全局配置连接你的 gRPC 服务器(支持本地 UNIX socket 或网络 TCP);Tyk 收到请求后调用 gRPC 服务器,由后者执行实际的中间件任务(转换、认证等)。仓库提供了 Ruby 示例 coprocess/grpc/ruby/sample_server.rb 与 Python 示例 coprocess/bindings/python/sample_server.py。
gRPC 全局配置(tyk.conf)
"coprocess_options": { "enable_coprocess": true, "coprocess_grpc_server": "tcp://127.0.0.1:5555" }, "enable_bundle_downloader": true, "bundle_base_url": "http://my-bundle-server.com/bundles/", "public_key_path": "/path/to/my/pubkey",enable_coprocess:开启富插件特性;coprocess_grpc_server:gRPC 服务器地址(仅 gRPC 插件需要);enable_bundle_downloader:开启 bundle 下载器;bundle_base_url:bundle 基础 URL。若 API 设置中指定了test-bundle,Tyk 会拉取http://my-bundle-server.com/bundles/test-bundle;public_key_path:用于校验已签名 bundle 的公钥,使用未签名 bundle 时可省略。
gRPC API 配置
"enable_coprocess_auth": true, "custom_middleware": { "pre": [ { "name": "MyPreMiddleware", "require_session": false } ], "auth_check": { "name": "MyAuthCheck" }, "driver": "grpc" },enable_coprocess_auth开启 Coprocess 认证,custom_middleware中driver: "grpc"指定驱动,auth_check配置自定义认证处理器。
ID Extractor 与认证缓存
ID Extractor 是一个非常有用的机制:它允许 Tyk 缓存认证 ID,从而让部分请求不必触达 Coprocess 后端即可完成认证,显著降低插件调用开销。提取规则在 API 定义的custom_middleware中按 API 配置:
"custom_middleware": { "pre": [ { "name": "MyPreMiddleware", "require_session": false } ], "id_extractor": { "extract_from": "header", "extract_with": "value", "extractor_config": { "header_name": "Authorization" } }, "driver": "grpc" },配置项说明:
extract_from:从请求的哪个位置提取认证 ID(如header、url等);extract_with:采用哪种提取策略,最简单的是value(直接取整个值作为 ID);extractor_config:提取策略的附加参数,如上例的header_name: "Authorization"指明从哪个请求头取值。
Tyk 提供了一组 ID Extractor 以覆盖最常见的用例,value extractor是最简单的一种;Python 驱动的对应测试见 coprocess/python/coprocess_id_extractor_python_test.go。
Coprocess Gateway API:从插件回调 Go 能力
gateway/coprocess_api.go 提供了网关 API 与 C 语言之间的桥接。任何需要导出的函数必须带//export注释,例如触发 Tyk 系统事件:
//export TykTriggerEvent func TykTriggerEvent( CEventName *C.char, CPayload *C.char ) { eventName := C.GoString(CEventName) payload := C.GoString(CPayload) FireSystemEvent(tykcommon.TykEvent(eventName), EventMetaDefault{ Message: payload, }) }对应的 C 头文件声明位于 coprocess/api.h:
#ifndef TYK_COPROCESS_API #define TYK_COPROCESS_API extern void TykTriggerEvent(char* event_name, char* payload); #endif各语言绑定通过包含该头文件(或内联声明)并借助类似ffi的机制以适当参数调用它。仓库中实际导出的完整 API 集合包括四个函数(见 coprocess/api.h 与 gateway/coprocess_api.go):
| 函数 | 用途 | 实现要点 |
|---|---|---|
TykStoreData(key, value, ttl) | 向 Redis 写入键值(带过期时间),键前缀为coprocess-data: | 使用带 1 秒超时的 context 写存储 |
TykGetData(key) | 从 Redis 读取键值 | 读取失败时返回空串 |
TykTriggerEvent(event_name, payload) | 触发 Tyk 系统事件 | 封装为EventMetaDefault{Message: payload} |
CoProcessLog(msg, level) | 以指定级别(debug/error/warning/info)写入 Tyk 日志 | 日志带python前缀字段 |
如果正在构建 Cython 模块,参考调用方式如下:
cdef extern: void TykTriggerEvent(char* event_name, char* payload); def call(): event_name = 'my event'.encode('utf-8') payload = 'my payload'.encode('utf-8') TykTriggerEvent( event_name, payload )Python 侧对 Gateway API 的封装是 coprocess/python/tyk/gateway.pyx(Cython 源码),它暴露了TykGateway模块。示例用法(从 Python 中间件中读写 Redis 键):
from tyk.decorators import * from gateway import TykGateway as tyk @Pre def SetKeyOnRequest(request, session, spec): tyk.store_data( "my_key", "expiring_soon", 15 ) val = tyk.get_data("cool_key") return request, session这些函数的端到端行为由 coprocess/coprocess_test.go 中的TestCoProcessGetSetData、TestCoProcessTykTriggerEvent等测试用例验证。
基本用法:在 API 定义中挂载 Coprocess 中间件
使用 Coprocess 中间件的标准方式是在 API 定义中声明custom_middleware字段。原文档给出的完整示例涵盖全部 Hook 类型:
"custom_middleware": { "pre": [ { "name": "MyPreMiddleware", "require_session": false }, { "name": "AnotherPreMiddleware", "require_session": false } ], "post": [ { "name": "MyPostMiddleware", "require_session": false } ], "post_key_auth": [ { "name": "MyPostKeyAuthMiddleware", "require_session": true } ], "auth_check": { "name": "MyAuthCheck" }, "driver": "python" }要点归纳:
pre、post、post_key_auth均为数组,支持链式挂载多个中间件,Tyk 按数组顺序执行;auth_check为对象(非数组),只能配置一个自定义认证处理器;require_session:该 Hook 是否要求请求已认证(即能否访问session对象)。post_key_auth中的中间件通常设为true;driver:指定驱动类型,取值为python、lua或grpc。
仓库中的完整可运行示例见 apps/coprocess_app_sample.json(含pre/post中间件的 Python 示例 API)与 apps/coprocess_app_sample_protected.json(受保护 API 示例)。
用 Python 编写事件处理器
除了请求中间件,还可以用 Python 编写 Tyk 事件监听器。第一步是在 API 定义中配置自定义事件处理器(handler_name固定为cp_dynamic_handler,handler_meta.name指向 Python 函数名):
... "event_handlers": { "events": { "AuthFailure": [ { "handler_name": "cp_dynamic_handler", "handler_meta": { "name": "my_handler" } } ] } }, ...第二步,在event_handlers目录中编写对应函数(参考 event_handlers/my_handler.py):
from tyk.decorators import Event @Event def my_handler(event, spec): print("-- my_handler:") print(" Event:", event) print(" Spec:", spec)上例监听AuthFailure事件(每次认证失败都会触发)。事件触发时,Tyk 会把形如下面的 Python 对象传给处理器:
{ "TimeStamp": "2016-08-19 11:13:31.537047694 -0400 PYT", "Meta":{ "Path":"/coprocess-auth-tyk-api-test/", "Origin":"127.0.0.1", "Message":"Auth Failure", "OriginatingRequest":"R0VUIC9jb3Byb2Nlc3MtYXV0aC10eWstYXBpLXRlc3QvIEhUVFAvMS4xDQpIb3N0OiAxMjcuMC4wLjE6ODA4MA0KVXNlci1BZ2VudDogY3VybC83LjQzLjANCkFjY2VwdDogKi8qDQpBdXRob3JpemF0aW9uOiAxDQoNCg==", "Key":"" }, "Type": "AuthFailure" }可通过向受保护的 Coprocess API 发送非法认证头来验证:
curl http://127.0.0.1:8080/coprocess-auth-tyk-api-test/ -H 'Authorization: invalidtoken'对应的事件分发链路在 coprocess/coprocess_test.go 的TestCoProcessDispatchEvent中有端到端验证:测试触发EventAuthFailure后,通过CoProcessDispatchEvent通道接收 JSON 包装对象,并校验事件类型与 Meta 字段。
构建说明:Build Tags 与多语言编译
Coprocess 通过 Go 的 build tag 机制按需编译,避免普通构建引入 C 依赖:
go build -tags 'coprocess python'go build -tags 'coprocess somelanguage'规则说明:
- 每种语言必须实现一个
CoProcessInit函数,在使用coprocessbuild tag 时从main函数调用(Python 侧对应 gateway/coprocess_python.go 的PythonInit/PythonLoadDispatcher/PythonNewDispatcher初始化流程,完整封装在NewPythonDispatcher中); - 仅使用
coprocesstag 而不指定任何语言 tag 会构建失败; - 标准构建依然可用:
go build。此时 coprocess/coprocess_dummy.go 提供哑CoProcessInit实现,该文件在启用coprocesstag 时被忽略(因为预期由具体语言实现)。
测试
运行 Coprocess 相关测试必须带上coprocessbuild tag:
go test -tags 'coprocess' go test -run CoProcess -tags 'coprocess'测试覆盖范围(见 coprocess/coprocess_test.go):
TestCoProcessDispatch/TestCoProcessDispatchEvent/TestCoProcessReload:Dispatcher 三个核心方法;TestCoProcessSerialization:coprocess.Object序列化长度校验;TestCoProcessGetSetData/TestCoProcessTykTriggerEvent:Gateway API 的存取与事件触发;TestCoProcessMiddleware/TestCoProcessObjectPostProcess:中间件链执行与请求头/参数的增删改;TestCoProcessAuth:自定义认证(CustomKeyCheck)的 403 拒绝路径;TestCoProcessReturnOverrides:通过ReturnOverrides直接覆盖响应(自定义状态码、响应体与响应头)。
小结
Coprocess 是 Tyk 将请求处理链开放给外部语言的关键机制:以 Protocol Buffers 定义消息契约、以 cgo 完成进程内桥接(gRPC 驱动则为进程间通信)、以 Dispatcher 接口统一分发逻辑、以五类 Hook 精确定位执行时机。无论你希望用 Python 快速编写预处理中间件、用 Lua 做轻量转换,还是把认证逻辑下沉到独立的 gRPC 服务,coprocess/README.md 与本文梳理的源码路径(dispatcher.go、api.h、coprocess_api.go、coprocess_object.proto)都能帮助你快速定位实现并开始二次开发。
【免费下载链接】tykOpen Source API and AI Gateway supporting REST, GraphQL, TCP, gRPC and MCP (Model Context Protocol)项目地址: https://gitcode.com/gh_mirrors/ty/tyk
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考