ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

frp Server Plugin 详解:用外部 HTTP 服务扩展 frps 的插件化能力

2026/9/5 20:47:16 拓冰建站 浏览量
frp Server Plugin 详解:用外部 HTTP 服务扩展 frps 的插件化能力 frp Server Plugin 详解用外部 HTTP 服务扩展 frps 的插件化能力【免费下载链接】frpA fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet.项目地址: https://gitcode.com/GitHub_Trending/fr/frpfrp 的 Server Plugin 机制允许开发者在不修改 Golang 源码的前提下通过一个独立的 HTTP 外部进程扩展 frps 的能力。frps 在执行关键操作如客户端登录、创建/关闭代理、心跳等之前会向插件服务发送基于 JSON over HTTP 的 RPC 请求并根据响应决定是否放行、拒绝或修改该操作的内容。读完本文你将掌握 Server Plugin 的完整 RPC 协议六种 Operation 的请求/响应格式、frps 侧的配置方法、元数据传递机制以及从源码角度理解插件链的执行语义从而可以编写自己的认证、端口分配、审计类插件服务。Server Plugin 的工作模型Server Plugin 的定位是“在 frps 关键操作前插入一个可编程的决策点”。其核心模型如下外部服务运行在独立进程中监听自己的 HTTP 端口接收来自 frps 的 RPC 调用frps 在执行某些操作之前先向外部 RPC 服务发送请求并根据其返回结果决定后续行为RPC 基于JSON over HTTP每次调用是一个带查询参数的POST请求。从源码结构看frps 在启动时遍历配置文件中的[[httpPlugins]]条目将每个条目包装为一个Plugin实例并注册进Manager配置结构体 HTTPPluginOptions 定义了name、addr、path、ops、tlsVerify五个字段注册逻辑位于 Service 初始化for _, p : range cfg.HTTPPlugins { svr.pluginManager.Register(plugin.NewHTTPPluginOptions(p)) }。Plugin接口本身只有三个方法见 plugin.gotype Plugin interface { Name() string IsSupport(op string) bool Handle(ctx context.Context, op string, content any) (res *Response, retContent any, err error) }其中IsSupport由配置中的ops字段决定——插件只处理自己声明关心的操作。协议版本常量APIVersion 0.1.0与文档中示例请求里的version0.1.0一致。RPC 请求与响应协议请求格式HTTP path 在每个插件上可独立配置下面以path /handler为例frps 发出的请求形如POST /handler?version0.1.0opLogin { version: 0.1.0, op: Login, content: { ... // 操作的具体内容 } } Request Header: X-Frp-Reqid: 用于链路追踪对照 httpPlugin.do 的实现可以确认几个细节请求体是Request{Version, Op, Content}的 JSON 序列化types.goversion与op同时作为 URL query 参数附带一份Header 中固定携带X-Frp-Reqid每次请求由 newPluginRequestContext 生成的随机 ID和Content-Type: application/json插件侧可以用 reqid 做日志关联追踪addr未带协议前缀时默认补http://以https://开头时会创建带 TLS 配置的客户端tlsVerify控制是否跳过证书校验。响应格式插件对每次操作请求可给出以下四类响应之一非 200 HTTP 状态码frps 直接视为请求失败。源码中 do 方法 对非 200 会返回do http request error code: %d错误。拒绝操作并返回原因{ reject: true, reject_reason: invalid user }允许操作且保持原内容不变{ reject: false, unchange: true }允许操作并返回修改后的内容用content整体替换原内容{ unchange: false, content: { ... // 替换后的内容 } }响应结构对应 Response 结构体。值得注意的一个源码细节当unchange为 false 时frps 会对插件返回的content做一次类型断言retContent.(*T)见 handleMutableContent即替换内容必须是与请求 content 相同结构的对象——插件返回的是对整个 content 的替换而不是差量合并。Manager 的插件链执行语义Manager 按操作类型分别维护六个插件列表loginPlugins、newProxyPlugins等注册时依据IsSupport(op)将插件分入对应列表。执行语义handleMutableContent同一操作的多个插件按配置顺序串行执行前一个插件返回的内容会作为后一个插件的输入形成内容改写链任一插件返回reject: true操作立即失败错误信息就是reject_reason任一插件请求出错网络失败/非 200操作同样失败frps 日志会记录send Op request to plugin [name] error并带 reqid 前缀便于追踪。唯一例外是CloseProxy它不返回可修改内容且插件出错时只聚合记录错误而不中断其他插件的执行——这与“代理已经要关闭插件通知失败不应阻塞清理”的语义相符。六种支持的 Operation 及其 Content 结构当前支持的操作为Login、NewProxy、CloseProxy、Ping、NewWorkConn、NewUserConn常量定义见 plugin.go。各操作的触发点与 content 字段如下。Login — 客户端登录操作frpc 向 frps 登录时触发触发点见 Service 处理登录。这是实现“外部用户认证”的主要入口{ content: { version: string, hostname: string, os: string, arch: string, user: string, timestamp: int64, privilege_key: string, run_id: string, pool_count: int, metas: mapstringstring, client_address: string } }对应结构体 LoginContent 内嵌msg.Login额外附加client_addressfrpc 建连来源地址。由于 Login 内容允许被插件替换unchange: false插件可以在这里改写user、补充metas等信息。NewProxy — 创建新代理frpc 请求创建代理时触发触发点见 control.go 中处理 NewProxy。content 携带了完整的代理定义插件可以在这里做端口冲突检测、动态端口分配、域名白名单等事{ content: { user: { user: string, metas: mapstringstring, run_id: string }, proxy_name: string, proxy_type: string, use_encryption: bool, use_compression: bool, bandwidth_limit: string, bandwidth_limit_mode: string, group: string, group_key: string, // 仅 tcp 和 udp remote_port: int, // 仅 http 和 https custom_domains: [string], subdomain: string, locations: [string], http_user: string, http_pwd: string, host_header_rewrite: string, headers: mapstringstring, // 仅 stcp sk: string, // 仅 tcpmux multiplexer: string, metas: mapstringstring } }对应结构体 NewProxyContent 由UserInfo与msg.NewProxy组合而成字段随代理类型不同而有所取舍如上注释所示。由于 NewProxy 的内容可被替换一个典型用法是客户端配置remotePort 0自动分配插件在收到 NewProxy 后从自己的端口池中挑一个可用端口写回remote_port实现集中式端口管理。CloseProxy — 代理被关闭先前创建的代理被关闭时触发触发点见 control.go 中关闭代理的 notify。注意每关闭一个代理都会单独发一次请求如果一个客户端绑定了大量代理逐个通知可能耗尽服务端资源文档对此有明确警告——如果你的场景客户端代理数量很多不要在这个操作上做重活。{ content: { user: { user: string, metas: mapstringstring, run_id: string }, proxy_name: string } }Ping — frpc 心跳frpc 周期性心跳时触发触发点见 control.go 中处理 Ping{ content: { user: { user: string, metas: mapstringstring, run_id: string }, timestamp: int64, privilege_key: string } }插件可以在心跳上实现“租约”式鉴权登录时放行但周期性在 Ping 上复核用户状态发现被吊销则返回reject: true使连接失效。NewWorkConn — 新工作连接frpc 建立新的 work 连接时触发在run_id与已有 frp 连接匹配成功后发送触发点见 Service 处理新工作连接。用于对“单客户端可建立的工作连接数”做限制或审计{ content: { user: { user: string, metas: mapstringstring, run_id: string }, run_id: string, timestamp: int64, privilege_key: string } }NewUserConn — 新用户连接有真实用户流量命中代理时触发支持tcp、stcp、https、tcpmux类型触发点见 proxy.go。注意从源码看该操作的插件调用是旁路式的——NewUserConn 调用 的结果不参与连接建立流程因此它适合做连接审计/计量而不是拦截流量拦截用户连接应使用支持面更广的机制或客户端侧方案{ content: { user: { user: string, metas: mapstringstring, run_id: string }, proxy_name: string, proxy_type: string, remote_addr: string } }frps 侧的插件配置在frps.toml中通过[[httpPlugins]]数组配置多个插件# frps.toml bindPort 7000 [[httpPlugins]] name user-manager addr 127.0.0.1:9000 path /handler ops [Login] [[httpPlugins]] name port-manager addr 127.0.0.1:9001 path /handler ops [NewProxy]参数说明对应 HTTPPluginOptions参数说明name插件名称用于日志标识如plugin [user-manager] has been registeredaddr外部 RPC 服务监听地址默认按 http 处理https 需显式写 schemaaddr https://127.0.0.1:9001pathPOST 请求的 URL pathops该插件需要处理的操作列表如[Login, NewProxy]未声明的操作不会路由到该插件tlsVerify仅对 https 有意义默认验证证书设为false可跳过校验见 http 客户端构建示例展示了“多插件分工”的典型用法user-manager只管登录鉴权port-manager只管代理创建。同一操作配置多个插件时按配置顺序链式执行见前文 Manager 语义。Metadata客户端自定义数据的透传通道客户端可以在配置中携带任意键值对元数据frps 会随每次 RPC 请求转发给插件这是插件识别客户端身份、做细粒度策略的关键通道。元数据分两类全局元数据在Login请求中位于content.metas下在其他所有请求中位于content.user.metas下代理级元数据仅出现在NewProxy请求中位于content.metas下。对应的 frpc 配置示例# frpc.toml serverAddr 127.0.0.1 serverPort 7000 user fake metadatas.token fake metadatas.version 1.0.0 [[proxies]] name ssh type tcp localPort 22 remotePort 6000 metadatas.id 123这样Login/Ping等请求里metas会包含token、version而NewProxy里除了user.metas携带全局值外metas还会带上id。插件侧例如对接 LDAP/内部 CMDB 的鉴权服务可以据此实现“用户 代理”两级粒度的策略控制。编写插件服务的实现要点综合以上协议与源码行为一个符合 0.1.0 协议的插件服务只需监听addr指定的端口在path上提供POST接口从 query 中读取version/op从 body 的content字段读取操作内容按ops声明处理对应操作返回三类 JSON 之一拒绝 / 放行不变 / 放行走替换内容替换内容时须返回与请求 content 同结构的完整对象出错时直接返回非 200 状态码即可让 frps 判定操作失败需要关联日志时读取X-Frp-Reqid请求头对CloseProxy保持轻量避免在单客户端多代理场景下成为瓶颈。相关实现文件汇总插件接口与操作常量pkg/plugin/server/plugin.goHTTP 客户端实现pkg/plugin/server/http.go插件管理与链式执行pkg/plugin/server/manager.go请求/响应与 content 结构pkg/plugin/server/types.gofrps 侧注册与各操作触发点server/service.go、server/control.go、server/proxy/proxy.go【免费下载链接】frpA fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet.项目地址: https://gitcode.com/GitHub_Trending/fr/frp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考