1. 项目概述与核心价值最近在折腾个人服务器和家庭网络时我一直在寻找一个能帮我高效管理、监控和自动化处理网络请求的工具。市面上成熟的方案很多但要么太重要么不够灵活要么就是配置起来让人头大。直到我遇到了Noirewinter/hplan这个项目它像一把瑞士军刀精准地切中了网络代理、流量管理和自动化处理这个细分领域的痛点。简单来说hplan是一个用 Go 语言编写的高性能、可编程的 HTTP/Socks5 代理服务器但它远不止于此。它的核心魅力在于其“可编程”特性允许你通过编写 Lua 脚本对经过的每一个网络请求进行深度定制和干预实现诸如请求重写、流量复制、动态路由、访问控制、数据脱敏等一系列高级功能。对于开发者、运维工程师、安全研究员甚至是那些对网络技术有浓厚兴趣的极客来说hplan提供了一个绝佳的 playground。它不像那些开箱即用但黑盒化的商业软件而是将控制权完全交还给你。你可以清晰地看到流量是如何流入、经过哪些处理、最终流向何处。这种透明度和灵活性对于构建复杂的测试环境、模拟线上流量、进行安全审计或者实现精细化的网络策略都是不可或缺的。我自己就用它来分流开发环境和生产环境的 API 调用复制线上流量到测试服务器进行压测甚至定制了一些简单的爬虫规则。接下来我就结合自己的使用经验带你彻底拆解hplan从设计思路到实操细节再到避坑指南让你也能快速上手这把利器。2. 核心架构与设计哲学解析2.1 为什么选择 Go Lua 的组合hplan选择 Go 语言作为主体这背后有非常务实的考量。Go 语言以高并发、高性能和部署简单著称其原生的 goroutine 和 channel 机制非常适合处理大量并发的网络连接这正是代理服务器的核心场景。用 Go 编写的网络服务通常能轻松达到万级甚至十万级的并发连接资源占用却相对温和。这使得hplan在作为常驻服务运行时非常稳定和高效不会成为系统资源的负担。而 Lua 的引入则是点睛之笔。Lua 是一门轻量级、嵌入式的脚本语言它的解释器非常小巧执行效率高特别适合作为应用程序的扩展语言。hplan将流量处理的核心逻辑通过 Lua 脚本暴露出来实现了业务逻辑与核心引擎的解耦。这意味着你不需要为了修改一个路由规则而去重新编译整个 Go 项目只需要热更新一个 Lua 脚本文件即可。这种设计极大地提升了开发和运维的效率。你可以想象一下当需要紧急拦截某个异常请求或者临时添加一个流量标记规则时直接修改脚本并 reload 是多么便捷。注意虽然 Lua 很高效但在脚本中执行过于复杂的操作如大规模字符串处理、频繁的 IO 操作仍可能成为性能瓶颈。hplan的最佳实践是将核心的、性能敏感的逻辑如协议解析、连接复用放在 Go 层而将灵活多变的业务逻辑如 URL 重写、Header 修改放在 Lua 层。2.2 核心工作流程与模块拆解理解hplan的工作流程是灵活运用它的基础。其核心流程可以抽象为以下几个阶段监听与接入hplan启动后会在配置的端口上监听 HTTP 或 Socks5 协议的客户端连接。这部分由 Go 语言高效处理。请求预处理当一个客户端连接建立后hplan会先将原始的请求信息如目标地址、端口、协议封装成一个上下文对象准备传递给 Lua 脚本。Lua 脚本执行这是最核心的环节。hplan会调用你预先配置的 Lua 脚本并将上下文对象传入。你的脚本可以读取、修改这个上下文里的几乎所有信息。决策与转发基于 Lua 脚本处理后的上下文例如脚本可能修改了最终的目标地址hplan会与真正的目标服务器建立连接。数据流桥接建立双向的数据通道在客户端和目标服务器之间转发数据。在这个过程中Lua 脚本仍然可以介入对传输的数据内容进行读写和修改如果配置了相关钩子函数。连接清理通信结束后关闭连接释放资源。从模块角度看hplan主要包含网络引擎用 Go 实现的底层网络 IO 和多路复用保证高并发性能。协议解析器负责解析 HTTP 和 Socks5 协议。Lua 虚拟机管理器负责加载、执行 Lua 脚本并管理脚本的生命周期。配置管理解析 YAML 或 JSON 格式的配置文件设定监听端口、脚本路径、日志级别等。插件/钩子系统定义了一系列标准接口如on_request,on_response让 Lua 脚本能在特定阶段挂载执行。2.3 与同类工具的差异化优势你可能用过Squid、Nginx的proxy_pass或者一些云服务商提供的网关。hplan与它们相比定位非常不同。vs 传统代理如 SquidSquid 功能强大但配置主要依靠复杂的 ACL 和配置文件灵活性不足。hplan通过 Lua 脚本可以实现比 ACL 更复杂、更动态的逻辑比如基于请求内容而不仅仅是 URL做路由。vs 反向代理如 NginxNginx 的核心是高性能的静态内容服务和反向代理其可编程性主要通过 Lua 模块如 OpenResty实现但整体生态和配置方式更偏向 Web 服务器。hplan更纯粹地专注于“可编程代理”这一角色设计更轻量上手处理代理相关的逻辑更直接。vs API 网关成熟的 API 网关如 Kong, Apinto功能全面集成了限流、鉴权、监控等大量企业级功能但也因此比较重。hplan更像一个乐高积木的基础板它提供了最核心的流量拦截和脚本执行能力具体的功能需要你自己用 Lua 去搭建。这带来了极高的定制化自由度但同时也要求使用者具备一定的开发能力。简而言之如果你需要的是一个开箱即用、功能全面的企业级解决方案可能 API 网关更合适。但如果你需要的是一个高度灵活、可以随心所欲控制流量、用于实验、测试或构建特定工具的平台hplan几乎是目前最优雅的选择之一。3. 从零开始部署与基础配置3.1 环境准备与安装hplan是 Go 语言项目因此安装它最直接的方式是通过go install。确保你的系统已经安装了 Go 开发环境版本 1.16 以上推荐。# 安装最新版本的 hplan go install github.com/Noirewinter/hplanlatest安装完成后hplan二进制文件会出现在你的$GOPATH/bin目录下通常是~/go/bin。你可以将其移动到系统路径或者直接使用全路径运行。另一种方式是直接从项目的 Release 页面 下载预编译好的二进制文件这对于没有 Go 环境的服务器来说更方便。# 例如下载 Linux amd64 版本 wget https://github.com/Noirewinter/hplan/releases/download/vx.x.x/hplan-linux-amd64 chmod x hplan-linux-amd64 sudo mv hplan-linux-amd64 /usr/local/bin/hplan3.2 编写第一个配置文件hplan的运行依赖于一个配置文件默认会寻找当前目录下的config.yaml或通过-c参数指定。我们来创建一个最基础的配置让它监听本地的 8080 端口并启用一个简单的 Lua 脚本。创建一个名为config.yaml的文件# config.yaml log: level: info # 日志级别: debug, info, warn, error output: stdout # 输出到标准输出也可以是文件路径 servers: - protocol: http # 代理协议支持 http 和 socks5 listen: :8080 # 监听地址和端口 script: example.lua # 关联的 Lua 脚本路径相对于配置文件或绝对路径这个配置定义了一个 HTTP 代理服务器运行在 8080 端口所有流量都会经过example.lua脚本的处理。3.3 你的第一个 Lua 脚本记录请求现在我们来编写那个example.lua脚本。这个脚本将演示如何在请求被转发前打印出请求的基本信息。创建example.lua文件-- example.lua -- _G 是全局变量表hplan 会将一些内置对象和方法注入到这里 local log _G.log local ctx _G.ctx -- 当前请求的上下文对象 -- 这个函数会在 hplan 决定如何转发请求前被调用 function on_request() -- 从上下文中获取请求信息 local method ctx.req.method local host ctx.req.host local path ctx.req.path -- 使用 log 对象记录信息日志级别为 info log.info(拦截到请求: %s %s%s, method, host, path) -- 你可以在这里修改 ctx.req 的各个字段来改变请求行为 -- 例如重写目标主机 -- if host old.example.com then -- ctx.req.host new.example.com -- log.info(已将请求重定向至 new.example.com) -- end -- 返回 nil 或 true 表示继续处理返回 false 表示终止该请求 return true end -- 这个函数会在收到后端响应后被调用如果需要 -- function on_response() -- local status_code ctx.res.status -- log.info(收到响应状态码: %d, status_code) -- return true -- end3.4 启动与测试在同一目录下你已经有了config.yaml和example.lua。现在启动hplanhplan -c config.yaml如果一切正常你会看到类似下面的日志输出表明服务已启动INFO[0000] server is running... protocolhttp listen:8080现在你可以配置你的浏览器或系统代理指向http://127.0.0.1:8080。然后访问任意网站观察hplan终端的输出。你应该能看到它打印出你访问的每个 URL。实操心得在首次测试时建议将日志级别log.level设置为debug。这会输出更详细的连接和处理信息对于排查脚本是否被正确加载和执行非常有帮助。在生产环境再调回info或warn。4. Lua 脚本编程深度指南掌握了基础之后我们来深入hplan的灵魂——Lua 脚本编程。hplan通过ctx上下文对象为你提供了强大的操控能力。4.1 核心上下文对象详解ctx对象是脚本与hplan核心引擎交互的桥梁。它主要包含两个部分ctx.req请求对象和ctx.res响应对象。并非所有字段在所有阶段都可用例如ctx.res在on_request阶段是空的。ctx.req常用字段字段名类型描述示例/说明methodstringHTTP 请求方法GET,POSThoststring请求的目标主机名www.example.comportnumber请求的目标端口80,443pathstring请求的路径含查询参数/api/v1/user?id123headerstable请求头表键值对ctx.req.headers[User-Agent]bodystring请求体如果有对于 POST/PUT 请求src_ipstring客户端源 IP 地址192.168.1.100ctx.res常用字段在on_response阶段可用字段名类型描述statusnumberHTTP 响应状态码headerstable响应头表bodystring响应体4.2 实战场景一动态请求路由与重写这是hplan最常用的场景之一。假设我们有一个内部开发环境需要将特定域名的请求自动转发到内部的测试服务器。-- dynamic_router.lua local log _G.log local ctx _G.ctx -- 定义一个路由表可以是静态的也可以从外部文件/API读取 local ROUTE_MAP { [api.myapp.com] dev-api-internal.mycompany.net, [static.myapp.com] 192.168.10.20, } function on_request() local original_host ctx.req.host local target_host ROUTE_MAP[original_host] if target_host then log.info(路由重写: %s - %s, original_host, target_host) ctx.req.host target_host -- 注意如果端口不同也需要修改 ctx.req.port -- ctx.req.port 8080 end -- 另一个常见场景路径重写 -- 将所有 /old-api/ 开头的请求重写到 /new-api/ local path ctx.req.path if path:find(^/old-api/) then local new_path path:gsub(^/old-api/, /new-api/) ctx.req.path new_path log.info(路径重写: %s - %s, path, new_path) end return true end4.3 实战场景二请求/响应内容修改与注入你可以在流量中注入自定义头、修改请求参数甚至篡改响应内容。这在模拟测试、故障注入或安全测试中非常有用。-- modifier.lua local log _G.log local ctx _G.ctx local json require(cjson) -- 假设需要处理 JSONhplan 可能内置或你需要管理 Lua 依赖 function on_request() -- 1. 注入自定义请求头用于标识流量来源或传递认证信息 ctx.req.headers[X-Forwarded-By] hplan-gateway/1.0 ctx.req.headers[X-Internal-Debug] true -- 2. 修改查询参数 (简单字符串处理示例) if ctx.req.path:find(?) then -- 将参数 sourceweb 改为 sourcemobile ctx.req.path ctx.req.path:gsub(sourceweb, sourcemobile) end -- 3. 修改 JSON 请求体 (以 POST /api/login 为例) if ctx.req.method POST and ctx.req.path:find(/api/login) then local ok, req_body pcall(json.decode, ctx.req.body) if ok and type(req_body) table then -- 强制使用测试账号 req_body[username] test_user req_body[password] test_pass_123 ctx.req.body json.encode(req_body) log.info(已修改登录请求体为测试账号) else log.warn(无法解析或不是 JSON 请求体) end end return true end function on_response() -- 4. 修改响应头例如添加 CORS 头 ctx.res.headers[Access-Control-Allow-Origin] * ctx.res.headers[Access-Control-Allow-Methods] GET, POST, OPTIONS -- 5. 修改响应体内容 (谨慎操作可能破坏数据) local content_type ctx.res.headers[Content-Type] or if ctx.res.status 200 and content_type:find(application/json) then local ok, res_body pcall(json.decode, ctx.res.body) if ok and type(res_body) table then -- 在返回的 JSON 中注入一个额外字段 res_body[_injected_by_hplan] os.date(%Y-%m-%d %H:%M:%S) ctx.res.body json.encode(res_body) -- 注意修改了 body 后需要更新 Content-Length 头hplan 可能会自动处理但最好确认 ctx.res.headers[Content-Length] tostring(#ctx.res.body) end end return true end重要注意事项修改响应体是一项高风险操作。它会改变客户端接收到的原始数据可能导致前端解析失败。务必确保你的修改符合数据格式规范如 JSON 合法性并且只在明确的测试场景下使用。同时修改body后Content-Length头部必须同步更新否则会导致连接错误。有些版本的hplan或底层库会自动处理但最好在脚本中显式更新以确保兼容性。4.4 实战场景三流量复制与镜像将线上流量复制一份发送到另一台服务器用于监控分析、压力测试或数据归档这是运维的常见需求。hplan可以轻松实现。-- traffic_mirror.lua local log _G.log local ctx _G.ctx local http require(socket.http) -- 可能需要额外引入 LuaSocket 库这里仅为示例思路 -- 注意在 hplan 的 Lua 环境中直接发起 HTTP 请求可能受限制或需要特定方式。 -- 更可靠的做法是利用 hplan 可能提供的异步任务功能或者简单记录到日志/队列由外部程序消费。 function on_request() -- 主要逻辑正常转发请求 -- 我们在这里只做标记和记录真正的镜像发送可能在 on_response 之后或通过其他异步机制 local mirror_data { timestamp os.time(), src_ip ctx.req.src_ip, method ctx.req.method, host ctx.req.host, path ctx.req.path, headers ctx.req.headers, } -- 将需要镜像的请求信息序列化例如为 JSON并存入一个全局队列或直接写入文件 -- 这里简化处理仅打印日志 log.info([MIRROR] Request captured: %s %s, mirror_data.method, mirror_data.host .. mirror_data.path) -- 实际场景中这里可以将 mirror_data 发送到 Kafka、Redis 或写入本地文件 return true -- 继续正常处理 end -- 更完整的镜像需要在 on_response 后获取到完整的响应信息再一起存储或转发。实现完整的、低延迟的流量镜像需要更精细的设计可能涉及使用hplan的异步机制或协程避免阻塞主请求。将镜像数据推送到高性能的消息队列如 Redis Pub/Sub, Kafka。由独立的镜像消费者服务从队列中读取数据并发送到目标服务器。4.5 Lua 依赖管理与脚本热更新对于复杂的脚本你可能会用到第三方 Lua 库如cjson用于 JSON 解析luasocket用于网络操作。hplan通常会在其可执行文件同级目录或特定路径下搜索 Lua 库。你需要将对应的.soLinux或.dllWindows文件以及 Lua 模块文件放在正确的目录。热更新是hplan的一大优势。当你修改了 Lua 脚本后不需要重启hplan服务。大多数情况下hplan会在每次请求或定期重新加载脚本文件。但为了确保绝对生效你可以向hplan进程发送一个SIGHUP信号触发配置和脚本的重载。# 找到 hplan 的进程 ID ps aux | grep hplan # 发送重载信号 kill -HUP pid_of_hplan在脚本中你也可以通过package.loaded表来强制重新加载某个模块但这需要更精细的控制。5. 高级配置与性能调优5.1 多服务器与复杂路由配置一个hplan实例可以同时监听多个端口运行不同协议的服务并应用不同的脚本。# advanced_config.yaml log: level: info output: /var/log/hplan.log # 输出到文件 servers: - protocol: http listen: :8080 script: router.lua # 可以设置特定服务器的独立参数 read_timeout: 30 # 读超时秒 write_timeout: 30 # 写超时秒 - protocol: socks5 listen: :1080 script: socks_auth.lua # 可以为 SOCKS5 代理编写专门的认证脚本 # SOCKS5 可能不需要处理 HTTP 语义脚本逻辑更简单 - protocol: http listen: 192.168.1.100:8888 # 绑定到特定 IP script: internal_traffic.lua # 此服务仅处理来自内网的流量5.2 性能调优要点虽然 Go 语言和 Lua 本身性能不错但不当的使用仍会导致瓶颈。连接池与超时设置在配置文件中合理设置dial_timeout、read_timeout、write_timeout和idle_timeout避免僵死连接占用资源。hplan自身可能维护到后端服务器的连接池确保其大小适中。Lua 脚本优化避免全局变量在on_request/on_response函数内尽量使用局部变量local。缓存重复计算例如路由表、正则表达式模式串应该在脚本最外层函数外部定义和编译避免每次请求都重新创建。谨慎操作字符串和表大规模的字符串拼接或表操作在 Lua 中可能产生大量临时对象影响性能。对于复杂的文本处理考虑是否能在 Go 层实现。减少阻塞操作不要在 Lua 脚本中执行同步的网络 IO、读取大文件等操作这会严重阻塞整个代理进程。如果需要应使用异步模式或交给外部系统处理。系统资源限制在 Linux 下可以通过ulimit调整hplan进程可打开的文件描述符数量以支持更高并发。也可以考虑使用systemd等工具来管理进程和资源限制。监控与指标hplan可能内置或可以通过插件暴露 Prometheus 格式的指标如连接数、请求速率、延迟。结合 Grafana 进行监控是生产环境运维的关键。5.3 安全加固建议访问控制在 Lua 脚本的on_request最开始处加入 IP 白名单或黑名单检查。local ALLOWED_IPS {[192.168.1.0/24] true, 10.0.0.1} function on_request() local client_ip ctx.req.src_ip if not is_ip_allowed(client_ip, ALLOWED_IPS) then log.warn(拒绝来自 %s 的访问, client_ip) -- 可以返回一个自定义的错误响应或者直接断开连接 ctx.res {status 403, body Forbidden} return false -- 终止请求处理 end return true end脚本沙箱确保 Lua 脚本运行在受限的环境中。避免从不可信的来源加载脚本或者使用 Lua 的沙箱机制如setfenv限制脚本能访问的 API防止恶意脚本执行系统命令或访问敏感文件。最小权限原则运行hplan的系统用户不应具有过高权限如 root。使用非特权用户运行服务。日志审计确保关键操作如路由修改、访问拒绝都被妥善记录并定期审查日志。6. 常见问题排查与实战技巧6.1 问题排查清单现象可能原因排查步骤hplan启动失败1. 端口被占用2. 配置文件语法错误3. 缺少依赖1.netstat -tlnp | grep :端口号2. 使用yamllint检查 YAML 语法3. 查看启动错误日志确认是否缺少 Lua 模块代理连接超时或无响应1. 脚本中存在死循环或长时间阻塞2. 网络防火墙规则3. 上游服务器问题1. 将日志级别调至debug观察请求卡在哪一步2. 临时禁用 Lua 脚本测试基础代理功能3. 使用curl -v或telnet测试上游服务器可达性Lua 脚本修改未生效1. 脚本路径配置错误2. 脚本语法错误导致加载失败3. 缓存未更新1. 检查config.yaml中的script路径2. 查看hplan日志通常会有 Lua 语法错误提示3. 向hplan进程发送SIGHUP信号强制重载修改请求/响应后客户端报错1. 修改后的数据格式错误如无效 JSON2. 未更新Content-Length头3. 修改了关键协议头如Host导致后端服务器异常1. 在脚本中增加pcall进行错误捕获和日志记录2. 确保修改body后同步更新ctx.res.headers[Content-Length]3. 使用调试工具如 mitmproxy对比修改前后的原始流量性能低下吞吐量不高1. Lua 脚本过于复杂2. 连接池配置不当3. 系统资源不足1. 对脚本进行性能分析优化热点代码2. 调整配置文件中的超时和连接池参数3. 监控服务器 CPU、内存、网络 IO6.2 实战技巧与心得脚本模块化不要把所有逻辑都写在一个巨大的.lua文件里。将通用的功能如 IP 检查、日志工具、路由表查询抽象成独立的 Lua 模块通过require引入。这大大提升了代码的可维护性和复用性。善用日志分级在脚本中灵活使用log.debug,log.info,log.warn,log.error。在开发调试时开启debug在生产环境关闭。对于关键的业务逻辑分支记录info日志对于异常情况使用warn或error。防御性编程永远不要假设请求数据是规范的。在访问ctx.req.headers、解析ctx.req.body之前先判断其是否存在和类型是否正确。使用pcall来安全地调用可能出错的操作如 JSON 解码。测试驱动为复杂的 Lua 脚本编写独立的测试用例。你可以创建一个模拟的ctx表在 Lua 交互环境或单独的测试框架中运行你的处理函数验证其逻辑是否正确而不必每次都启动完整的hplan服务。与现有生态集成hplan可以很好地与其他工具配合。例如与 Prometheus/Grafana 集成在脚本中通过暴露的 API 或自定义日志格式输出 metrics 数据用于监控流量特征和脚本性能。与 CI/CD 集成将 Lua 脚本像代码一样管理纳入版本控制如 Git。通过 CI 流水线进行语法检查和基础测试然后自动部署到hplan服务器。与配置中心集成对于动态路由规则可以不写死在脚本里而是让脚本从 Consul、Etcd 或数据库定期拉取最新配置实现路由的热更新。hplan的强大源于它将复杂网络代理的控制权以一种优雅、可编程的方式交给了使用者。它可能不是解决所有网络问题的银弹但在需要精细化、定制化流量管理的场景下它无疑是一把得心应手的利器。从我个人的使用经验来看初期需要花些时间熟悉 Lua 和其编程模式但一旦掌握你会发现构建网络中间件、测试工具和自动化流程的效率得到了质的提升。开始可能会踩一些坑比如脚本性能问题或协议细节处理但社区和文档通常能提供帮助。建议从一个小而具体的需求开始实践例如简单地记录某个特定 API 的请求逐步扩展到更复杂的逻辑这样学习曲线会平滑很多。