在 Golang 中的 net/http/httputil 包中提供了 ReverseProxy 这么一个数据结构,它是实现整个反向代理的关键。

    1. // 反向代理
    2. type ReverseProxy struct {
    3. // Director这个函数传入的参数是新复制的一个请求,我们可以修改这个请求
    4. // 比如修改请求的请求Host或者请求URL等
    5. Director func(*http.Request)
    6. // Transport 代表底层的连接池设置,比如连接最长保持多久等
    7. // 如果不填的话,则使用默认的设置
    8. Transport http.RoundTripper
    9. // FlushInterval表示多久将下游的response的数据拷贝到proxy的response
    10. FlushInterval time.Duration
    11. // ErrorLog 表示错误日志打印的句柄
    12. ErrorLog *log.Logger
    13. // BufferPool表示将下游response拷贝到proxy的response的时候使用的缓冲池大小
    14. BufferPool BufferPool
    15. // ModifyResponse 函数表示,如果要将下游的response内容进行修改,再传递给proxy
    16. // 的response,这个函数就可以进行设置,但是如果这个函数返回了error,则将response
    17. // 传递进入ErrorHandler,否则使用默认设置
    18. ModifyResponse func(*http.Response) error
    19. // ErrorHandler 处理ModifyResponse返回的Error
    20. ErrorHandler func(http.ResponseWriter, *http.Request, error)
    21. }

    一个请求到了,直接先请求一下后端服务,如果后端发现请求不存在,返回 404 Not Found 之后, 我们再将请求再请求到前端服务

    1. // 重新启动一个proxy网关
    2. func (p *Proxy) newProxyReverseProxy(frontend, backend *url.URL) *httputil.ReverseProxy {
    3. ...
    4. // 先创建一个后端服务的directory
    5. director := func(req *http.Request) {
    6. req.URL.Scheme = backend.Scheme
    7. req.URL.Host = backend.Host
    8. }
    9. // 定义一个NotFoundErr
    10. NotFoundErr := errors.New("response is 404, need to redirect")
    11. return &httputil.ReverseProxy{
    12. Director: director, // 先转发到后端服务
    13. ModifyResponse: func(response *http.Response) error {
    14. // 如果后端服务返回了404,我们返回NotFoundErr 会进入到errorHandler中
    15. if response.StatusCode == 404 {
    16. return NotFoundErr
    17. }
    18. return nil
    19. },
    20. ErrorHandler: func(writer http.ResponseWriter, request *http.Request, err error) {
    21. // 判断 Error 是否为NotFoundError, 是的话则进行前端服务的转发,重新修改writer
    22. if errors.Is(err, NotFoundErr) {
    23. httputil.NewSingleHostReverseProxy(frontend).ServeHTTP(writer, request)
    24. }
    25. }}
    26. }

    其次是后端调试模式,先启动一个 Goroutine 监听后端文件,再启动一个只有后端的 proxy:

    1. // devBackendCommand 启动后端调试模式
    2. var devBackendCommand = &cobra.Command{
    3. ...
    4. RunE: func(c *cobra.Command, args []string) error {
    5. proxy := NewProxy(c.GetContainer())
    6. // 监听后端文件
    7. go proxy.monitorBackend()
    8. // 启动只有后端的proxy
    9. if err := proxy.startProxy(false, true); err != nil {
    10. return err
    11. }
    12. return nil
    13. },
    14. }

    而前后端同时调试,则是先启动一个 Goroutine 监听后端文件,再同时启动监听前后端的 proxy:

    1. var devAllCommand = &cobra.Command{
    2. ...
    3. RunE: func(c *cobra.Command, args []string) error {
    4. proxy := NewProxy(c.GetContainer())
    5. // 监听后端文件
    6. go proxy.monitorBackend()
    7. // 启动前后端同时监听的proxy
    8. if err := proxy.startProxy(true, true); err != nil {
    9. return err
    10. }
    11. return nil
    12. },
    13. }
    1. // 初始化Dev命令
    2. func initDevCommand() *cobra.Command {
    3. devCommand.AddCommand(devBackendCommand)
    4. devCommand.AddCommand(devFrontendCommand)
    5. devCommand.AddCommand(devAllCommand)
    6. return devCommand
    7. }
    8. // devCommand 为调试模式的一级命令
    9. var devCommand = &cobra.Command{
    10. Use: "dev",
    11. Short: "调试模式",
    12. RunE: func(c *cobra.Command, args []string) error {
    13. c.Help()
    14. return nil
    15. },
    16. }
    17. // devBackendCommand 启动后端调试模式
    18. var devBackendCommand = &cobra.Command{
    19. Use: "backend",
    20. Short: "启动后端调试模式",
    21. RunE: func(c *cobra.Command, args []string) error {
    22. ...
    23. },
    24. }
    25. // devFrontendCommand 启动前端调试模式
    26. var devFrontendCommand = &cobra.Command{
    27. Use: "frontend",
    28. Short: "前端调试模式",
    29. RunE: func(c *cobra.Command, args []string) error {
    30. ...
    31. },
    32. }
    33. // 同时启动前端和后端调试
    34. var devAllCommand = &cobra.Command{
    35. Use: "all",
    36. Short: "同时启动前端和后端调试",
    37. RunE: func(c *cobra.Command, args []string) error {
    38. ...
    39. },
    40. }