在 Golang 中的 net/http/httputil 包中提供了 ReverseProxy 这么一个数据结构,它是实现整个反向代理的关键。
// 反向代理
type ReverseProxy struct {
// Director这个函数传入的参数是新复制的一个请求,我们可以修改这个请求
// 比如修改请求的请求Host或者请求URL等
Director func(*http.Request)
// Transport 代表底层的连接池设置,比如连接最长保持多久等
// 如果不填的话,则使用默认的设置
Transport http.RoundTripper
// FlushInterval表示多久将下游的response的数据拷贝到proxy的response
FlushInterval time.Duration
// ErrorLog 表示错误日志打印的句柄
ErrorLog *log.Logger
// BufferPool表示将下游response拷贝到proxy的response的时候使用的缓冲池大小
BufferPool BufferPool
// ModifyResponse 函数表示,如果要将下游的response内容进行修改,再传递给proxy
// 的response,这个函数就可以进行设置,但是如果这个函数返回了error,则将response
// 传递进入ErrorHandler,否则使用默认设置
ModifyResponse func(*http.Response) error
// ErrorHandler 处理ModifyResponse返回的Error
ErrorHandler func(http.ResponseWriter, *http.Request, error)
}
一个请求到了,直接先请求一下后端服务,如果后端发现请求不存在,返回 404 Not Found 之后, 我们再将请求再请求到前端服务
// 重新启动一个proxy网关
func (p *Proxy) newProxyReverseProxy(frontend, backend *url.URL) *httputil.ReverseProxy {
...
// 先创建一个后端服务的directory
director := func(req *http.Request) {
req.URL.Scheme = backend.Scheme
req.URL.Host = backend.Host
}
// 定义一个NotFoundErr
NotFoundErr := errors.New("response is 404, need to redirect")
return &httputil.ReverseProxy{
Director: director, // 先转发到后端服务
ModifyResponse: func(response *http.Response) error {
// 如果后端服务返回了404,我们返回NotFoundErr 会进入到errorHandler中
if response.StatusCode == 404 {
return NotFoundErr
}
return nil
},
ErrorHandler: func(writer http.ResponseWriter, request *http.Request, err error) {
// 判断 Error 是否为NotFoundError, 是的话则进行前端服务的转发,重新修改writer
if errors.Is(err, NotFoundErr) {
httputil.NewSingleHostReverseProxy(frontend).ServeHTTP(writer, request)
}
}}
}
其次是后端调试模式,先启动一个 Goroutine 监听后端文件,再启动一个只有后端的 proxy:
// devBackendCommand 启动后端调试模式
var devBackendCommand = &cobra.Command{
...
RunE: func(c *cobra.Command, args []string) error {
proxy := NewProxy(c.GetContainer())
// 监听后端文件
go proxy.monitorBackend()
// 启动只有后端的proxy
if err := proxy.startProxy(false, true); err != nil {
return err
}
return nil
},
}
而前后端同时调试,则是先启动一个 Goroutine 监听后端文件,再同时启动监听前后端的 proxy:
var devAllCommand = &cobra.Command{
...
RunE: func(c *cobra.Command, args []string) error {
proxy := NewProxy(c.GetContainer())
// 监听后端文件
go proxy.monitorBackend()
// 启动前后端同时监听的proxy
if err := proxy.startProxy(true, true); err != nil {
return err
}
return nil
},
}
// 初始化Dev命令
func initDevCommand() *cobra.Command {
devCommand.AddCommand(devBackendCommand)
devCommand.AddCommand(devFrontendCommand)
devCommand.AddCommand(devAllCommand)
return devCommand
}
// devCommand 为调试模式的一级命令
var devCommand = &cobra.Command{
Use: "dev",
Short: "调试模式",
RunE: func(c *cobra.Command, args []string) error {
c.Help()
return nil
},
}
// devBackendCommand 启动后端调试模式
var devBackendCommand = &cobra.Command{
Use: "backend",
Short: "启动后端调试模式",
RunE: func(c *cobra.Command, args []string) error {
...
},
}
// devFrontendCommand 启动前端调试模式
var devFrontendCommand = &cobra.Command{
Use: "frontend",
Short: "前端调试模式",
RunE: func(c *cobra.Command, args []string) error {
...
},
}
// 同时启动前端和后端调试
var devAllCommand = &cobra.Command{
Use: "all",
Short: "同时启动前端和后端调试",
RunE: func(c *cobra.Command, args []string) error {
...
},
}