Golang ReverseProxy返回空白页面问题排查
Golang ReverseProxy返回空白页面问题排查 你好!我正在尝试为我在 Ubuntu 20.04 服务器上运行的几个 docker-compose 应用创建一个统一的 Web 界面。我希望通过反向代理来访问这些服务,并在我的代码中实现该系统。Go Playground - The Go Programming Language
我可以看到存在一些连接性,对于服务 1,curl 显示 200,但在浏览器中却显示一个完全空白的页面。如果我直接 ping 服务 1,输出结果也是一样的。
$ curl -I -L sub1.example.com:8080
HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0
Content-Length: 522375
Content-Type: text/html; charset=utf-8
Etag: "9b42b********************21be3733876e3b1"
Expires: -1
Last-Modified: Thu, 20 Jan 2022 11:26:56 GMT
Pragma: no-cache
X-Clacks-Overhead: GNU Terry Pratchett
X-Content-Type-Options: nosniff
X-Frame-Options: sameorigin
X-Robots-Tag: noindex, nofollow, noimageindex
Date: Sat, 05 Feb 2022 22:16:01 GMT
sub2 显示一个 404 消息 - 但它确实将 URL 重定向到了 “Location”(这就是为什么我认为存在某种连接)
$ curl -I -L sub2.example.com:8080
HTTP/1.1 302 Found
Content-Language: en
Content-Length: 0
Content-Type: text/html; charset=utf-8
Date: Sat, 05 Feb 2022 22:18:54 GMT
Location: /accounts/login/?next=/
Server: nginx/1.18.0 (Ubuntu)
Vary: Accept-Language, Cookie
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Sat, 05 Feb 2022 22:18:54 GMT
Content-Length: 19
如果我直接 ping 服务 2,我得到的是 200
$ curl -I -L 172.23.0.4
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Date: Sat, 05 Feb 2022 22:21:53 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: keep-alive
Location: /accounts/login/?next=/
Vary: Accept-Language, Cookie
Content-Language: en
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Sat, 05 Feb 2022 22:21:53 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 12060
Connection: keep-alive
Expires: Sat, 05 Feb 2022 22:21:53 GMT
Cache-Control: max-age=0, no-cache, no-store, must-revalidate
Vary: Cookie, Accept-Language
Content-Language: en
Set-Cookie: sfcsrftoken=DAgNIckiQcyvy4********************8KHrYcSpvHf11HOfNu6x01bgiybpNH; expires=Sat, 04 Feb 2023 22:21:53 GMT; Max-Age=31449600; Path=/; SameSite=Lax
Set-Cookie: sessionid=yzj9cjvh3thhx8lqudqj0tf90dqxcu1i; expires=Sun, 06 Feb 2022 22:21:53 GMT; HttpOnly; Max-Age=86400; Path=/; SameSite=Lax
任何帮助都将不胜感激!!谢谢!
更多关于Golang ReverseProxy返回空白页面问题排查的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang ReverseProxy返回空白页面问题排查的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
从你的描述和代码来看,ReverseProxy返回空白页面通常是由于响应处理或代理配置问题导致的。以下是几个常见原因及解决方案:
1. 响应体未正确复制
ReverseProxy默认会复制响应体,但如果目标服务使用了分块传输编码或特殊的内容编码,可能需要自定义Director或ModifyResponse。
package main
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
// 创建目标URL
target, _ := url.Parse("http://localhost:8080")
// 创建反向代理
proxy := httputil.NewSingleHostReverseProxy(target)
// 自定义Director处理请求头
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
// 设置必要的请求头
req.Header.Set("X-Forwarded-Host", req.Host)
req.Header.Set("X-Forwarded-Proto", "http")
// 移除可能引起问题的头
req.Header.Del("Accept-Encoding")
}
// 修改响应处理
proxy.ModifyResponse = func(resp *http.Response) error {
// 确保响应头正确
resp.Header.Set("X-Proxy", "Go-Reverse-Proxy")
return nil
}
// 错误处理
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
fmt.Printf("代理错误: %v\n", err)
http.Error(w, "代理错误", http.StatusBadGateway)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
proxy.ServeHTTP(w, r)
})
http.ListenAndServe(":8081", nil)
}
2. 处理重定向问题
从你的curl输出看,服务2返回了302重定向。浏览器和代理对重定向的处理可能不同。
package main
import (
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
target, _ := url.Parse("http://localhost:8080")
proxy := httputil.NewSingleHostReverseProxy(target)
// 禁用自动重定向,手动处理
proxy.Transport = &http.Transport{
DisableKeepAlives: false,
}
// 修改响应,处理重定向
proxy.ModifyResponse = func(resp *http.Response) error {
// 如果是重定向,修改Location头
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
if location := resp.Header.Get("Location"); location != "" {
// 这里可以修改重定向URL
resp.Header.Set("Location", location)
}
}
return nil
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
proxy.ServeHTTP(w, r)
})
http.ListenAndServe(":8081", nil)
}
3. 完整的代理示例,包含调试信息
package main
import (
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
)
func main() {
// 设置日志
log.SetOutput(os.Stdout)
target, err := url.Parse("http://localhost:8080")
if err != nil {
log.Fatal(err)
}
proxy := httputil.NewSingleHostReverseProxy(target)
// 自定义传输层以添加日志
proxy.Transport = &loggingTransport{}
// 自定义错误处理
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf("代理请求错误: %v", err)
w.WriteHeader(http.StatusBadGateway)
w.Write([]byte("代理服务错误"))
}
// 修改响应
proxy.ModifyResponse = func(resp *http.Response) error {
log.Printf("响应状态: %d", resp.StatusCode)
log.Printf("响应头: %v", resp.Header)
return nil
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Printf("收到请求: %s %s", r.Method, r.URL.Path)
proxy.ServeHTTP(w, r)
})
log.Println("代理服务器启动在 :8081")
http.ListenAndServe(":8081", nil)
}
type loggingTransport struct{}
func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// 记录请求信息
log.Printf("代理请求: %s %s", req.Method, req.URL.String())
log.Printf("请求头: %v", req.Header)
// 使用默认传输
resp, err := http.DefaultTransport.RoundTrip(req)
if err != nil {
log.Printf("传输错误: %v", err)
return nil, err
}
// 记录响应信息
log.Printf("原始响应状态: %d", resp.StatusCode)
// 如果需要,可以读取响应体进行调试
if os.Getenv("DEBUG") == "true" {
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
log.Printf("响应体: %s", string(body))
resp.Body = io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(io.NopCloser(

