refactor(gateway): 重构反向代理实现以支持 WebSocket 和 HTTP 请求
- 添加 gclient 和 gstr 依赖包用于 HTTP 客户端操作 - 实现新的 BuildRequest 函数,使用 gclient 处理普通 HTTP 请求 - 分离 WebSocket 请求处理到独立的 proxyWebSocket 函数 - 移除旧的 hasProtocol 函数和相关逻辑 - 添加完整的请求头和响应头复制机制 - 实现响应状态码和响应体的正确传递 - 简化 WebSocket 代理逻辑,使用标准反向代理处理main v1.0.1017
parent
45262d8f88
commit
95623f3802
|
|
@ -1,14 +1,18 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.magicany.cc/black1552/gf-common/log"
|
"git.magicany.cc/black1552/gf-common/log"
|
||||||
|
"github.com/gogf/gf/v2/net/gclient"
|
||||||
"github.com/gogf/gf/v2/net/ghttp"
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
"github.com/gogf/gf/v2/os/gctx"
|
"github.com/gogf/gf/v2/os/gctx"
|
||||||
|
"github.com/gogf/gf/v2/text/gstr"
|
||||||
)
|
)
|
||||||
|
|
||||||
// hasProtocol 检查字符串是否包含协议前缀
|
// hasProtocol 检查字符串是否包含协议前缀
|
||||||
|
|
@ -22,17 +26,49 @@ func hasProtocol(s string) bool {
|
||||||
// BuildRequest 反向代理请求到指定主机
|
// BuildRequest 反向代理请求到指定主机
|
||||||
// 自动支持所有 HTTP 方法及 WebSocket 连接
|
// 自动支持所有 HTTP 方法及 WebSocket 连接
|
||||||
func BuildRequest(r *ghttp.Request, host string) {
|
func BuildRequest(r *ghttp.Request, host string) {
|
||||||
// 自动添加协议前缀
|
if gstr.Contains(r.RequestURI, "/ws") {
|
||||||
targetHost := host
|
proxyWebSocket(r, host)
|
||||||
if !hasProtocol(host) {
|
return
|
||||||
// 根据请求判断协议:WebSocket 用 ws,普通 HTTP 用 http
|
}
|
||||||
if r.Header.Get("Upgrade") == "websocket" {
|
client := gclient.New()
|
||||||
targetHost = "ws://" + host
|
// 构建目标URL而不是直接复制RequestURI
|
||||||
} else {
|
targetURL := host + r.URL.Path
|
||||||
targetHost = "http://" + host
|
if r.URL.RawQuery != "" {
|
||||||
|
targetURL += "?" + r.URL.RawQuery
|
||||||
|
}
|
||||||
|
// 复制请求头
|
||||||
|
for key, values := range r.Header {
|
||||||
|
for _, value := range values {
|
||||||
|
client.SetHeader(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
response, err := client.DoRequest(gctx.New(), r.Method, targetURL, r.GetBody())
|
||||||
|
if err != nil {
|
||||||
|
log.Error(gctx.New(), "request error:", err)
|
||||||
|
panic(fmt.Sprintf("request error: %v", err))
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
// 读取响应体
|
||||||
|
respBody, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(gctx.New(), "read response body error:", err)
|
||||||
|
panic(fmt.Sprintf("read response body error: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制响应头
|
||||||
|
for key, values := range response.Header {
|
||||||
|
for _, value := range values {
|
||||||
|
r.Response.Header().Add(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 设置响应状态码并写入响应体
|
||||||
|
r.Response.Status = response.StatusCode
|
||||||
|
r.Response.Write(respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxyWebSocket 处理 WebSocket 连接的代理
|
||||||
|
func proxyWebSocket(r *ghttp.Request, targetHost string) {
|
||||||
|
// 解析目标主机 URL
|
||||||
targetURL, err := url.Parse(targetHost)
|
targetURL, err := url.Parse(targetHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(gctx.New(), "parse target host error:", err)
|
log.Error(gctx.New(), "parse target host error:", err)
|
||||||
|
|
@ -41,33 +77,13 @@ func BuildRequest(r *ghttp.Request, host string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建反向代理
|
||||||
proxy := httputil.NewSingleHostReverseProxy(targetURL)
|
proxy := httputil.NewSingleHostReverseProxy(targetURL)
|
||||||
|
|
||||||
// 自定义 Director 来设置目标 URL,保留原始请求的所有参数
|
// 修改请求 URL,保留原始路径和查询参数
|
||||||
proxy.Director = func(req *http.Request) {
|
r.URL.Scheme = targetURL.Scheme
|
||||||
req.URL.Scheme = targetURL.Scheme
|
r.URL.Host = targetURL.Host
|
||||||
req.URL.Host = targetURL.Host
|
log.Info(gctx.New(), r.GetBodyString())
|
||||||
req.URL.Path = r.URL.Path
|
// 处理 WebSocket 连接
|
||||||
req.URL.RawQuery = r.URL.RawQuery
|
proxy.ServeHTTP(r.Response.Writer, r.Request)
|
||||||
// 保留原始 Host 头
|
|
||||||
req.Host = r.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
// 错误处理
|
|
||||||
proxy.ErrorHandler = func(w http.ResponseWriter, req *http.Request, err error) {
|
|
||||||
log.Error(gctx.New(), "proxy error:", err)
|
|
||||||
w.WriteHeader(http.StatusBadGateway)
|
|
||||||
w.Write([]byte("Bad Gateway"))
|
|
||||||
}
|
|
||||||
proxy.ModifyResponse = func(resp *http.Response) error {
|
|
||||||
// 获取原始响应的 Header
|
|
||||||
for k, v := range resp.Header {
|
|
||||||
r.Response.Header().Set(k, v[0])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// ServeHTTP 会自动处理 WebSocket 升级和所有 HTTP 方法
|
|
||||||
// 使用 RawWriter() 获取原始的 http.ResponseWriter,避免 gf 框架的封装影响
|
|
||||||
proxy.ServeHTTP(r.Response.RawWriter(), r.Request)
|
|
||||||
r.Response.WriteJson(r.Response.RawWriter())
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue