
本文将指导您如何使用Go语言的net/http/httputil.ReverseProxy包,构建一个能够根据请求域名将流量透明地路由到不同后端服务的反向代理。与客户端重定向不同,这种服务器端代理方案能确保用户体验和搜索引擎优化的连贯性,是实现多域名共用服务器、高效管理后端服务的关键技术实践。
在现代Web应用架构中,常常需要在同一台物理服务器上托管多个域名,并将这些域名的请求分发到运行在不同端口或不同机器上的后端服务。实现这一目标的关键在于构建一个高效且透明的请求路由机制。
许多开发者在初次尝试实现多域名路由时,可能会想到使用HTTP 302(Found)或301(Moved Permanently)等状态码进行重定向。例如,原始问题中提及的代码片段:
package main
import (
"net/http"
"log"
)
func main() {
mux := http.NewServeMux()
// 这种方式并非基于Host头路由,而是基于路径匹配。
// 即使是路径匹配,使用RedirectHandler也会导致客户端重定向。
mux.Handle("mydomainA.com", http.RedirectHandler("http://localhost:1234", 302))
mux.Handle("mydomainB.com", http.RedirectHandler("http://localhost:4567", 302))
log.Fatal(http.ListenAndServe(":8080", mux))
}这种方法存在以下问题:
立即学习“go语言免费学习笔记(深入)”;
为了解决这些问题,我们需要一个反向代理(Reverse Proxy)。反向代理在客户端和后端服务之间充当中间人,它接收客户端的请求,然后将请求转发给合适的后端服务,并将后端服务的响应返回给客户端。整个过程对客户端是完全透明的,客户端始终只与反向代理交互。
Go语言的标准库net/http/httputil提供了ReverseProxy类型,专门用于构建反向代理。它的核心在于一个Director函数,该函数负责修改传入的http.Request,使其指向正确的后端目标。
下面是一个使用httputil.ReverseProxy实现多域名透明路由的完整示例:
package main
import (
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
)
// domainTargets 定义了域名到后端服务URL的映射
var domainTargets = map[string]string{
"mydomainA.com": "http://localhost:1234", // 域名A的请求转发到本地1234端口
"mydomainB.com": "http://localhost:4567", // 域名B的请求转发到本地4567端口
}
// proxyHandler 是一个自定义的http.Handler,用于根据Host头分发请求
type proxyHandler struct {
proxies map[string]*httputil.ReverseProxy
}
// newProxyHandler 初始化并返回一个proxyHandler实例
func newProxyHandler() *proxyHandler {
proxies := make(map[string]*httputil.ReverseProxy)
for domain, targetURLStr := range domainTargets {
targetURL, err := url.Parse(targetURLStr)
if err != nil {
log.Fatalf("为域名 %s 解析目标URL失败: %v", domain, err)
}
// 创建一个针对特定目标URL的反向代理
proxy := httputil.NewSingleHostReverseProxy(targetURL)
// 核心:自定义Director函数来修改发往后端服务的请求
originalDirector := proxy.Director // 保存默认的Director函数
proxy.Director = func(req *http.Request) {
originalDirector(req) // 首先调用默认的Director,它会设置一些基本信息
// 重要:设置发往后端服务的Host头。
// 如果后端服务依赖于此Host头进行虚拟主机识别,则应设置为后端服务的Host。
// 如果后端服务需要知道原始请求的Host,则通过X-Forwarded-Host传递。
req.Host = targetURL.Host // 设置Host头为后端服务的Host (例如: localhost:1234)
// 添加X-Forwarded-* 头,将客户端的真实信息传递给后端服务
// 这对于后端服务的日志记录、分析和安全策略非常重要。
req.Header.Set("X-Forwarded-Host", req.Host) // 原始客户端请求的Host头
req.Header.Set("X-Forwarded-For", req.RemoteAddr) // 客户端IP地址
req.Header.Set("X-Forwarded-Proto", req.URL.Scheme) // 原始请求协议 (http/https)
}
proxies[domain] = proxy
}
return &proxyHandler{proxies: proxies}
}
// ServeHTTP 方法实现了http.Handler接口,负责处理所有传入请求
func (p *proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 从请求的Host头中获取域名
domain := r.Host
// 根据域名查找对应的反向代理实例
proxy, ok := p.proxies[domain]
if !ok {
log.Printf("未找到域名 %s 的代理配置", domain)
http.Error(w, "未找到: 未知主机", http.StatusNotFound)
return
}
// 使用找到的反向代理处理请求
proxy.ServeHTTP(w, r)
}
func main() {
// 启动两个模拟后端服务,用于测试
go startBackendServer(1234, "服务 A")
go startBackendServer(4567, "服务 B")
// 创建并注册自定义的代理处理器
handler := newProxyHandler()
// 将自定义处理器注册为所有路径的处理器,它会根据Host头进行分发
http.Handle("/", handler)
log.Println("多域名反向代理服务已启动,监听端口: 8080")
// 启动HTTP服务器,监听8080端口,使用默认的ServeMux (因为它已经注册了"/"处理器)
log.Fatal(http.ListenAndServe(":808以上就是Go语言实现多域名请求透明路由与反向代理的详细内容,更多请关注php中文网其它相关文章!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号