ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Dify 前端开发代理 @langgenius/dev-proxy 全解析:路由配置、热重载与 Cookie 重写机制

Dify 前端开发代理 @langgenius/dev-proxy 全解析:路由配置、热重载与 Cookie 重写机制 Dify 前端开发代理 langgenius/dev-proxy 全解析路由配置、热重载与 Cookie 重写机制【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify在 Dify 仓库中前端web/开发时常常需要把/console/api、/api等请求转发到线上或本地后端。packages/dev-proxy目录下的langgenius/dev-proxy就是为这类场景设计的通用 Hono 开发代理它不内置任何产品专属路由、Cookie 名称或环境变量约定所有代理路径与上游目标都在本地配置文件中显式声明。读完本文你将掌握该包的完整安装与 CLI 用法、配置结构与路由匹配规则、CORS 策略、Cookie 重写含__Host-/__Secure-前缀与多目标作用域隔离的实现细节并能直接参考 Dify 仓库自身的前端代理配置来搭建自己的开发链路。一、包定位与安装从 README 的描述看这个包的核心定位是「generic Hono-based development proxy」基于 Hono 中的hono与hono/node-server只做 HTTP/WebSocket 转发不包含任何 Dify 专属逻辑。产品相关的配置全部外置到使用方的配置文件里。安装与脚本接入方式pnpm add -D langgenius/dev-proxy在前端项目的package.json中添加脚本{ scripts: { dev:proxy: dev-proxy --config ./dev-proxy.config.ts --env-file ./.env.local } }然后运行pnpm dev:proxy。Dify 仓库自身就是这样使用的web/package.json 中声明了dev:proxy: dev-proxy --config ./dev-proxy.config.ts --env-file ./.env.local并把langgenius/dev-proxy作为workspace:*依赖引入配置文件即 web/dev-proxy.config.ts。需要注意运行环境package.json 中engines字段要求node ^24.20.0且构建走vite-plusvp pack/vp test。二、CLI 参数与热重载行为CLI 入口为dev-proxy支持以下选项与 cli.ts 中printUsage输出的帮助文本一致dev-proxy --config ./dev-proxy.config.ts--config、-c配置文件路径默认dev-proxy.config.ts--env-file在求值配置文件之前先加载环境变量文件--host覆盖配置中的server.host--port覆盖配置中的server.port--watch监听配置文件与环境变量文件变更并热重载默认开启--no-watch禁用配置与环境文件的热重载--help、-h打印帮助。README 特别强调不支持--target。上游目标必须写在配置文件里保证「路由 上游」的映射关系显式可查。从源码看热重载的完整机制在 cli.ts 的createDevProxyRuntime中默认通过watchDevProxyConfig基于c12的watchConfig监听配置文件若指定了--env-file再用chokidar单独监听该 env 文件变更到达后调用enqueueReload任务被串行排队reloadTask reloadTask.then(...)避免并发重载重载时先比较解析出的 host/port若不变仅调用runtime.updateConfig(nextConfig)在运行进程内替换 Hono 应用与 WebSocket 升级处理器日志输出reloaded config changes若 host 或 port 变了则关闭旧服务器并startDevProxyServer重新监听日志输出restarted on http://host:port after ...。这与 README「Behavior」一节的描述吻合路由、CORS、target、cookie 重写变更在运行进程中生效而监听地址变化会重建服务器。另外 CLI 注册了SIGINT/SIGTERM清理钩子退出前会关闭 env 监听、config 监听和所有已 upgrade 的 WebSocket socket。参数解析本身在 config.ts 的parseDevProxyCliArgs支持--namevalue内联写法遇到未识别的选项会直接抛错Unsupported dev proxy option端口经resolvePort校验必须是 1–65535 的整数。默认值也在这里定义DEFAULT_PROXY_HOST 127.0.0.1、DEFAULT_PROXY_PORT 5001。三、配置结构Config Shape配置文件支持.ts、.mts、.js、.mjs四种格式由c12加载。最小完整示例README 原文import { defineDevProxyConfig } from langgenius/dev-proxy export default defineDevProxyConfig({ server: { host: 127.0.0.1, port: 5001, }, routes: [ { paths: /api, target: https://example.com, }, ], cors: { allowedOrigins: local, }, })defineDevProxyConfig在 config.ts 中只是一个恒等函数作用是提供类型推导。真正的结构定义在 types.tsserver可选host字符串与port数字routes必填DevProxyRoute[]。每项包含paths单个字符串或字符串数组、target上游 URL、可选的cookieRewrite选项对象或false显式禁用cors可选allowedOrigins取值为local或显式 Origin 数组。加载完成后assertDevProxyConfig会做运行时断言配置必须是对象且必须包含routes数组否则启动即报错Dev proxy config must include a routes array.。路由匹配规则声明顺序优先 前缀包含README 明确了匹配语义routes按声明顺序匹配第一条命中的路由生效每个配置的 path 同时匹配精确路径及其所有子路径即paths: /api会命中/api、/api/apps、/api/apps/123。源码印证在 server.ts 的findProxyRouteroutes.find((route) normalizeRoutePaths(route.paths).some( (routePath) requestPath routePath || requestPath.startsWith(${routePath}/), ), )注意startsWith(routePath /)的写法/api-v2不会误命中/api路由避免了朴素前缀匹配的经典坑。Hono 侧则通过app.all(path)与app.all(path /*)两条路由注册来覆盖精确路径和子路径registerProxyRoute并对不以/开头的路径直接抛错。因此当两组路由存在包含关系时README 给出的实践就是「更具体的放前面」routes: [ { paths: /api/enterprise, target: http://127.0.0.1:5003 }, { paths: /api, target: http://127.0.0.1:5002 }, ]Dify 自身的 web/dev-proxy.config.ts 正是这一原则的现实案例把/console/api/enterprise、/api/enterprise、/admin-api、/mfa、/scim等更具体的路径指向企业版上游DEV_PROXY_ENTERPRISE_TARGET随后才是/console/api与通用/api路由且/socket.io也显式归入 console 路由以支持 WebSocket。上游 URL 拼接转发时的 URL 构造由buildUpstreamUrl(target, requestPath, search)完成若请求路径本身以 target 的 path 为前缀例如 target 是https://example.com/base、请求为/base/api则直接使用请求路径避免拼成/base/base/api否则把请求路径追加到 target path 之后。查询串原样保留。四、CORS 策略默认信任本地 Origin默认情况下allowedOrigins: local代理对来自本地开发 Origin 的带凭证 CORS 请求放行。源码中本地宿主名单是 server.ts 里的const LOCAL_DEV_HOSTS new Set([localhost, 127.0.0.1, [::1], ::1])命中本地 Origin 时响应会写入Access-Control-Allow-Origin: origin、Access-Control-Allow-Credentials: true并追加Vary: Origin。若要收紧到指定来源配置成数组即可cors: { allowedOrigins: [http://localhost:3000], }此时isAllowedDevOrigin只做精确的includes(origin)判断其余 Origin 的响应不带任何 CORS 头。预检请求由代理自身处理不转发给上游OPTIONS请求直接返回204并带上Access-Control-Allow-Methods: GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONSAccess-Control-Allow-Headers会回显客户端的Access-Control-Request-Headers缺省值为Authorization, Content-Type, X-CSRF-Token。源码中还有一个针对私有网络请求的细节若客户端带了Access-Control-Request-Private-Network: true代理会回Access-Control-Allow-Private-Network: true——这正是本地前端直连127.0.0.1上游时浏览器「private network access」预检所需。WebSocket Upgrade 请求同样受同一 Origin 策略约束createWebSocketUpgradeHandler在升级握手阶段就检查 Origin不合法直接回403 Forbidden未命中路由回404。五、两个典型场景场景一本地前端经单台代理访问线上后端前端请求http://127.0.0.1:5001/api/apps代理转发到https://cloud.example.com/api/appsimport { defineDevProxyConfig } from langgenius/dev-proxy const target process.env.DEV_PROXY_TARGET || https://cloud.example.com export default defineDevProxyConfig({ server: { host: process.env.DEV_PROXY_HOST || 127.0.0.1, port: Number(process.env.DEV_PROXY_PORT || 5001), }, routes: [ { paths: /api, target, }, ], })配合可选的.env文件DEV_PROXY_TARGEThttps://cloud.example.com DEV_PROXY_HOST127.0.0.1 DEV_PROXY_PORT5001启动命令dev-proxy --config ./dev-proxy.config.ts --env-file ./.env.local。这里--env-file的加载时机很关键从 config.ts 的createC12ConfigOptions看env 文件在 c12求值配置文件之前注入dotenv选项开启interpolate: true因此process.env.DEV_PROXY_TARGET在配置脚本执行时已经可用。并且该 env 文件被持续监听编辑后代理自动重载无需手动重启。场景二一个前端对接两个本地后端例如/console/api/*走本地 console 后端http://127.0.0.1:5001/api/*走本地公共 API 后端http://127.0.0.1:5002import { defineDevProxyConfig } from langgenius/dev-proxy const consoleApiTarget process.env.DEV_PROXY_CONSOLE_API_TARGET || http://127.0.0.1:5001 const publicApiTarget process.env.DEV_PROXY_PUBLIC_API_TARGET || http://127.0.0.1:5002 export default defineDevProxyConfig({ server: { host: process.env.DEV_PROXY_HOST || 127.0.0.1, port: Number(process.env.DEV_PROXY_PORT || 8082), }, routes: [ { paths: /console/api, target: consoleApiTarget, }, { paths: /api, target: publicApiTarget, }, ], })对应的.envDEV_PROXY_CONSOLE_API_TARGEThttp://127.0.0.1:5001 DEV_PROXY_PUBLIC_API_TARGEThttp://127.0.0.1:5002 DEV_PROXY_HOST127.0.0.1 DEV_PROXY_PORT8082六、Cookie 重写让线上安全 Cookie 在本地 HTTP 下工作这是该包最有技术含量的部分。问题背景线上后端如 HTTPS 站点通常会签发带__Host-或__Secure-前缀的安全 Cookie——浏览器要求这类 Cookie 必须是Secure、Path/且不带Domain属性。而本地开发走http://localhost浏览器根本不会保存SecureCookie登录态直接失效。cookieRewrite就是为此设计的 opt-in 机制且完全由配置驱动包本身不认识任何应用级 Cookie 名称。基本用法import type { CookieRewriteOptions } from langgenius/dev-proxy import { defineDevProxyConfig } from langgenius/dev-proxy const cookieRewrite: CookieRewriteOptions { hostPrefixCookies: [access_token, refresh_token, /^passport-/], } export default defineDevProxyConfig({ routes: [ { paths: /api, target: https://cloud.example.com, cookieRewrite, }, ], })hostPrefixCookies需要参与「Host 前缀转换」的 Cookie 名单支持字符串精确匹配和正则如/^passport-/匹配发生在去除安全前缀之后的名字上shouldUseHostPrefix先stripSecureCookiePrefix再比对设置cookieRewrite: false可对某条路由显式关闭重写。双向重写流程从 cookies.ts 的实现看重写是双向的本地 → 上游rewriteCookieHeaderForUpstream浏览器发出的普通 Cookie如access_tokenxxx会被加回__Host-前缀再发给上游——当上游 target 是 HTTPS 时useHostPrefix: true见 server.ts 中useHostPrefix: targetUrl.protocol https:。已有的__Host-名字保持原样__Secure-名字则统一改写为__Host-形态。这样上游按线上协议校验 Cookie 时不会拒绝。上游 → 本地rewriteSetCookieHeadersForLocal上游Set-Cookie里的安全 Cookie 在写给本地浏览器前会被「降级」剥离__Host-/__Secure-前缀toLocalCookieName删除Domain、Secure、Partitioned属性否则浏览器在http://localhost下会拒收SameSiteNone改写为SameSiteLaxSameSiteNone同样要求SecurePath统一归一为Path/。多目标作用域隔离localCookieScope: target-origin当一个本地代理同时指向多个线上目标时比如 Dify 前端同时代理公共云和企业版同一个access_token不能跨目标串用。此时启用目标作用域const cookieRewrite: CookieRewriteOptions { hostPrefixCookies: [access_token, csrf_token, refresh_token], localCookieScope: target-origin, csrfHeader: { cookieName: csrf_token, headerName: X-CSRF-Token, }, }从 cookies.ts 看其机制对每个 target 的 origin 做 FNV-1a 哈希得到作用域 keyhashScope本地 Cookie 以dev_proxy_hash_原cookie名的形式存储toScopedLocalCookieName即不同上游的登录 Cookie 在本地浏览器中天然隔离转发时只把「当前 target 作用域」的 Cookie 还原成上游名字发出去其余作用域的 Cookie 以及非作用域但命中hostPrefixCookies的旧 Cookie 一律剔除返回undefined后被 filter 掉防止把 A 站的 token 泄漏给 B 站若配置了csrfHeader代理会用当前作用域下的csrf_tokenCookie 值覆盖请求头中的X-CSRF-Token有值则 set无值则 delete解决前端残留了旧 CSRF 头导致上游校验失败的问题。Dify 前端的 web/dev-proxy.config.ts 就是这套机制的生产级示例hostPrefixCookies覆盖access_token、csrf_token、refresh_token、webapp_access_token及/^passport-/启用localCookieScope: target-origin并声明csrfHeader映射csrf_token→X-CSRF-Token三条路由enterprise 组、console 组、public/api全部挂载同一份difyCookieRewrite。七、转发行为与底层实现细节README「Behavior」一节列出的行为在 server.ts 中均有对应实现这里补充源码级的细节路径前缀保留匹配的路径前缀原样转发不做重写或剥离WebSocket 支持Upgrade 请求复用同一套路由、Origin 策略和 Cookie 重写逻辑走http/https原生的upgrade事件createWebSocketUpgradeHandler成功握手后把上下游 socket 双向pipe起来上游不可达时向客户端回502 Bad Gateway。Dify 配置里显式代理/socket.io即依赖此能力请求体流式转发非 GET/HEAD 请求直接把request.raw.body作为 fetch 的 body 并设duplex: half大文件上传不会先在内存中缓冲Hop-by-hop 头清理转发前删除connection、keep-alive、proxy-authenticate、proxy-authorization、te、trailer、transfer-encoding、upgrade等逐跳头并且额外尊重Connection:头中列出的自定义逐跳头createHopByHopHeaderNames同时删除host头、强制accept-encoding: identity并要求上游若携带origin则改写为目标 origin响应侧上游响应头同样清理逐跳头并丢弃content-encoding与content-length因为上游已被要求 identity 编码且 body 会重新流经 Hono 响应管线set-cookie单独提取以便重写后逐条重新 append错误处理Hono 全局onError捕获上游请求失败统一返回502与Upstream proxy request failed.文本并保留 CORS 头以便本地前端能读取错误。测试覆盖方面包内自带 config.spec.ts、server.spec.ts、cookies.spec.ts 与 cli.spec.ts 四组单元测试分别验证 CLI 参数解析、路由/URL 构造、Cookie 双向重写和服务器行为可作为修改或二次开发时的行为基线。八、小结langgenius/dev-proxy的设计哲学是「约定极少、配置即文档」零内置路由、零内置 Cookie 知识把复杂度集中在三处能力上——显式的路由/上游映射、面向本地开发的带凭证 CORS、以及把线上安全 Cookie 协议适配到本地 HTTP 环境的 Cookie 重写。其热重载host/port 不变时进程内热更新、变化时重建服务器让--env-file驱动的切换上游目标变得几乎无感。在 Dify 仓库中它是web/前端对接云端的标准开发通道如果你在自己的前端项目中遇到「本地前端 线上/多后端 安全 Cookie」的组合问题这个包的实现尤其是cookies.ts的双向重写与作用域隔离提供了可以直接借鉴的完整参考。【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表