引言
在 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)模块,探索其如何与服务端协同工作,实现内网穿透。