如何修改Golang反向代理的路径/路由以注入服务前缀
解决方案:修复路径注入与重定向处理
首先修正你代码里的两个明显bug:
req.url.path应为req.URL.Path(Go语言中结构体字段首字母大写才对外可见)TrimPrefix的参数应该是动态的服务名,而非固定字符串"/serviceName",改为"/"+serviceName
接下来针对核心需求,分两步解决路径注入问题:
一、处理重定向响应头
当remote1返回重定向(比如301/302状态码)时,Location 头中的路径是不带 /service1 前缀的,必须手动添加,否则浏览器会直接跳转到错误的路径。
在获取到 serverResponse 后添加以下逻辑:
import "net/url" import "strings" // 处理重定向Location头 if location := serverResponse.Header.Get("Location"); location != "" { u, err := url.Parse(location) if err == nil { // 如果是相对路径,直接添加服务前缀;如果是remote1的绝对路径,替换路径部分 if u.IsAbs() && u.Host == hostName { u.Path = "/" + serviceName + u.Path serverResponse.Header.Set("Location", u.String()) } else if !u.IsAbs() { serverResponse.Header.Set("Location", "/"+serviceName+location) } } }
二、修改响应体中的路径
页面内的链接、静态资源(CSS/JS/图片)路径都是相对路径,需要批量修改为带 /service1 前缀的路径。用HTML解析库(如goquery)比正则更可靠,避免误匹配。
步骤1:安装goquery
go get github.com/PuerkitoBio/goquery
步骤2:修改响应体处理逻辑
替换原代码中 io.Copy(res, serverResponse.Body) 的部分:
import "github.com/PuerkitoBio/goquery" import "strconv" import "strings" // 读取响应体到缓冲区 bodyBytes, err := io.ReadAll(serverResponse.Body) serverResponse.Body.Close() if err != nil { res.WriteHeader(http.StatusInternalServerError) fmt.Fprint(res, "Failed to read upstream response") return } bodyStr := string(bodyBytes) // 仅处理HTML类型的响应 if strings.Contains(serverResponse.Header.Get("Content-Type"), "text/html") { doc, err := goquery.NewDocumentFromReader(strings.NewReader(bodyStr)) if err == nil { // 修改所有href属性(链接) doc.Find("[href]").Each(func(i int, s *goquery.Selection) { href, exists := s.Attr("href") if exists && !strings.HasPrefix(href, "http") && !strings.HasPrefix(href, "#") && !strings.HasPrefix(href, "data:") { s.SetAttr("href", "/"+serviceName+href) } }) // 修改所有src属性(静态资源) doc.Find("[src]").Each(func(i int, s *goquery.Selection) { src, exists := s.Attr("src") if exists && !strings.HasPrefix(src, "http") && !strings.HasPrefix(src, "data:") { s.SetAttr("src", "/"+serviceName+src) } }) // 重新生成HTML内容 bodyStr, _ = doc.Html() } } // 更新Content-Length头(内容修改后长度变化) res.Header().Set("Content-Length", strconv.Itoa(len(bodyStr))) // 写入修改后的响应 res.Write([]byte(bodyStr))
三、不推荐iframe方案的原因
用iframe虽然能快速实现路径隔离,但会带来诸多问题:
- 跨域限制导致部分功能失效(如Cookie共享、页面跳转)
- 浏览器前进/后退按钮无法正常工作
- SEO不友好,搜索引擎无法正确抓取内容
- 嵌套层级带来的样式、布局兼容性问题
完整修正后的proxy函数示例
func proxy(serviceName string) func(res http.ResponseWriter, req *http.Request) { return func(res http.ResponseWriter, req *http.Request) { cnn, err := dialer.Dial("tcp", ":1250") // 按你的架构需求保留 if err != nil { res.WriteHeader(http.StatusInternalServerError) return } defer cnn.Close() // 记得关闭连接,避免资源泄漏 transport := &http.Transport{ Dial: func(network, addr string) (net.Conn, error) { return cnn, nil }, } client := &http.Client{ Transport: transport, } hostName := hostNameMap[serviceName] req.URL.Host = req.Host req.URL.Scheme = originSvr.Scheme req.RequestURI = "" // 修正:动态移除服务名前缀 req.URL.Path = strings.TrimPrefix(req.URL.Path, "/"+serviceName) serverResponse, err := client.Do(req) if err != nil { res.WriteHeader(http.StatusInternalServerError) fmt.Fprint(res, err.Error()) return } defer serverResponse.Body.Close() // 处理重定向Location头 if location := serverResponse.Header.Get("Location"); location != "" { u, err := url.Parse(location) if err == nil { if u.IsAbs() && u.Host == hostName { u.Path = "/" + serviceName + u.Path serverResponse.Header.Set("Location", u.String()) } else if !u.IsAbs() { serverResponse.Header.Set("Location", "/"+serviceName+location) } } } // 复制响应头到客户端 for k, v := range serverResponse.Header { res.Header()[k] = v } res.WriteHeader(serverResponse.StatusCode) // 读取并修改响应体 bodyBytes, err := io.ReadAll(serverResponse.Body) if err != nil { fmt.Fprint(res, "Failed to read upstream response") return } bodyStr := string(bodyBytes) if strings.Contains(serverResponse.Header.Get("Content-Type"), "text/html") { doc, err := goquery.NewDocumentFromReader(strings.NewReader(bodyStr)) if err == nil { doc.Find("[href]").Each(func(i int, s *goquery.Selection) { href, exists := s.Attr("href") if exists && !strings.HasPrefix(href, "http") && !strings.HasPrefix(href, "#") && !strings.HasPrefix(href, "data:") { s.SetAttr("href", "/"+serviceName+href) } }) doc.Find("[src]").Each(func(i int, s *goquery.Selection) { src, exists := s.Attr("src") if exists && !strings.HasPrefix(src, "http") && !strings.HasPrefix(src, "data:") { s.SetAttr("src", "/"+serviceName+src) } }) bodyStr, _ = doc.Html() } } res.Header().Set("Content-Length", strconv.Itoa(len(bodyStr))) res.Write([]byte(bodyStr)) } }
内容的提问来源于stack exchange,提问作者solonly
相关产品推荐
相关产品推荐

