引言
在 NPS 系列文章中,我们已经深入探讨了 NPS 的多种代理模式。本篇文章将聚焦于 NPS 的 HTTP/HTTPS 域名解析功能,这是 NPS 实现 Web 服务穿透的核心。我们将通过分析 nps/server/proxy/http.go 文件,揭示 NPS 如何处理 HTTP 和 HTTPS 请求,并实现缓存、认证、动态后端切换等高级功能。
HTTP/HTTPS 域名解析的需求
在实际应用中,我们经常需要将内网的 Web 服务通过域名暴露到公网。这涉及到:
- 域名到内网 IP 的映射:根据请求的域名,将流量转发到内网中对应的 Web 服务器。
- HTTP/HTTPS 协议处理:NPS 需要能够处理标准的 HTTP 请求,以及加密的 HTTPS 请求。
- 高级功能:为了提升性能、安全性和灵活性,通常还需要支持缓存、认证、负载均衡、自动 HTTPS 等功能。
http.go:HTTP/HTTPS 域名解析的实现
http.go 文件定义了 httpServer 结构体,它是 NPS 实现 HTTP/HTTPS 域名解析的核心。
httpServer 结构体
httpServer 结构体继承了 BaseServer,并包含了 HTTP/HTTPS 代理所需的关键字段:
type httpServer struct {
BaseServer
httpPort int
httpsPort int
httpServer *http.Server
httpsServer *http.Server
httpsListener net.Listener
useCache bool
addOrigin bool
cache *cache.Cache
cacheLen int
}
BaseServer:继承了base.go中定义的通用功能。httpPort int/httpsPort int:HTTP 和 HTTPS 监听端口。httpServer *http.Server/httpsServer *http.Server:Go 标准库的 HTTP 服务器实例。httpsListener net.Listener:HTTPS 监听器。useCache bool/cache *cache.Cache/cacheLen int:用于控制和管理 HTTP 缓存。addOrigin bool:是否添加X-Forwarded-For等原始请求头。
NewHttp() 函数用于创建并初始化一个 httpServer 实例。
Start() 方法:启动 HTTP/HTTPS 服务
httpServer 的 Start() 方法负责启动 HTTP 和 HTTPS 监听服务:
- 加载错误页面:从文件中加载 404 错误页面内容。
- 启动 HTTP 服务:如果
httpPort大于 0,则创建一个http.Server实例,并在独立的 goroutine 中启动 HTTP 监听。所有 HTTP 请求都会通过s.handleTunneling()方法处理。 - 启动 HTTPS 服务:如果
httpsPort大于 0,则创建一个http.Server实例,并在独立的 goroutine 中启动 HTTPS 监听。这里会调用NewHttpsServer()来创建并启动一个HttpsServer实例(我们在上一篇文章中详细分析过https.go),负责处理 HTTPS 流量和 SNI 证书管理。
handleTunneling():HTTP 请求的入口与分发
handleTunneling() 方法是所有 HTTP 请求(包括 WebSocket 升级请求)进入 httpServer 的入口点。它负责根据请求类型进行分发:
- 获取主机信息:根据请求的 Host 头获取对应的
file.Host配置信息。 - 自动 HTTPS 重定向:如果主机配置启用了
AutoHttps并且当前请求是 HTTP,则会进行 301 重定向到 HTTPS。 - WebSocket 升级请求:如果请求是 WebSocket 升级请求(通过检查
Upgrade头),则调用NewHttpReverseProxy(s).ServeHTTP(w, r),将请求委托给websocket.go中定义的HttpReverseProxy进行处理。 - 普通 HTTP 请求:如果不是 WebSocket 升级请求,则通过
http.Hijacker劫持连接,然后调用s.handleHttp(conn.NewConn(c), r)处理普通 HTTP 请求。
handleHttp():普通 HTTP 请求的处理
handleHttp() 方法是处理普通 HTTP 请求的核心逻辑。它负责将客户端的 HTTP 请求转发给后端服务,并将响应返回给客户端:
- 黑名单检查:检查客户端 IP 是否在黑名单中。
- 获取主机信息:根据请求的 Host 头获取对应的
file.Host配置信息。 - 流量和连接数检查:检查客户端的流量和连接数是否超出限制。
- 认证检查:对请求进行认证。
- 获取目标地址:从主机配置中获取一个随机的目标地址。
- 建立隧道连接:通过
s.bridge.SendLinkInfo()将连接请求发送给 NPS 的bridge模块,由bridge负责与客户端建立实际的隧道连接。 - 数据转发循环:
- 请求转发:将客户端的 HTTP 请求写入到隧道连接。
- 响应读取与转发:从隧道连接读取后端响应,并将其写入到客户端连接。
- 缓存处理:如果启用了缓存 (
s.useCache) 并且请求在缓存中,则直接从缓存返回响应。 - 动态后端切换:在循环中,如果检测到请求的 Host 发生变化,NPS 会动态地切换到新的后端服务。
- 流量统计:在数据转发过程中,会进行流量统计。
NewServer() 和 NewServerWithTls():HTTP/HTTPS 服务器的创建
这两个函数用于创建 Go 标准库的 http.Server 实例。
NewServer():创建普通的 HTTP 服务器。NewServerWithTls():创建支持 TLS 的 HTTPS 服务器,并加载证书。
resetReqMethod():修复请求方法
这个辅助函数用于修复某些情况下 HTTP 请求方法可能被截断的问题(例如 “GET” 变成 “ET”)。
总结
nps/server/proxy/http.go 文件是 NPS 实现 HTTP/HTTPS 域名解析和高级功能的核心。它通过 httpServer 结构体,结合 Go 标准库的 HTTP 服务器和 NPS 内部的隧道机制,实现了强大的 Web 服务穿透能力。其对缓存、认证、动态后端切换以及自动 HTTPS 等功能的支持,使得 NPS 成为一个功能全面、灵活高效的 HTTP/HTTPS 代理解决方案。
至此,我们已经详细剖析了 NPS 服务端 proxy 目录下所有主要的代理模式实现。在下一篇文章中,我们将转向 NPS 的客户端(Client)模块,探索其如何与服务端协同工作,实现内网穿透。