使用Go语言实现反向代理到Cloud Run实例

huangapple go评论135阅读模式
英文:

Reverse Proxy using Go to Cloud Run Instance

问题

我觉得我离成功很近了,但是到目前为止,我在使用Go构建一个小型反向代理到GCP Cloud Run实例时遇到了问题。请求“通过了”,但是请求的响应是默认的GCP Cloud Run 404。看起来在将请求发送回Cloud Run时,Host头被忽略了,因此请求没有被正确路由。

我可能漏掉了什么?

  1. package main
  2. import (
  3. "log"
  4. "net/http"
  5. "net/http/httputil"
  6. "net/url"
  7. )
  8. const apiUrl = "MY_CLOUD_RUN.a.run.app"
  9. func main() {
  10. http.HandleFunc("/", proxy)
  11. log.Fatal(http.ListenAndServe(":8081", nil))
  12. }
  13. func proxy(res http.ResponseWriter, req *http.Request) {
  14. // 绕过CORS检查
  15. if req.Method == http.MethodOptions {
  16. headers := res.Header()
  17. headers.Add("Access-Control-Allow-Origin", "*")
  18. headers.Add("Vary", "Origin")
  19. headers.Add("Vary", "Access-Control-Request-Method")
  20. headers.Add("Vary", "Access-Control-Request-Headers")
  21. headers.Add("Access-Control-Allow-Headers", "*")
  22. headers.Add("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
  23. res.WriteHeader(http.StatusOK)
  24. return
  25. }
  26. p := httputil.NewSingleHostReverseProxy(&url.URL{
  27. Scheme: "http",
  28. Host: apiUrl,
  29. })
  30. p.Director = func(req *http.Request) {
  31. req.Header.Add("X-Forwarded-Host", req.Host)
  32. req.Header.Add("X-Origin-Host", apiUrl)
  33. req.Header.Add("Host", apiUrl)
  34. req.Header.Add("Access-Control-Allow-Origin", "*")
  35. req.URL.Scheme = "https"
  36. req.URL.Host = apiUrl
  37. }
  38. p.ModifyResponse = func(res *http.Response) error {
  39. res.Header.Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
  40. res.Header.Set("Access-Control-Allow-Credentials", "true")
  41. res.Header.Set("Access-Control-Allow-Origin", "*")
  42. res.Header.Set("Access-Control-Allow-Headers", "*")
  43. return nil
  44. }
  45. p.ServeHTTP(res, req)
  46. }
英文:

I feel like I'm close to having this working but so far I"m running into an issue building a small reverse proxy in Go to a GCP Cloud Run instance. The request 'goes through' but the response from the request is the default GCP Cloud Run 404. It appears when making the request back to Cloud Run the Host header is being ignored and therefore the request is not being routed correction.

What might I be missing here?

  1. package main
  2. import (
  3. "log"
  4. "net/http"
  5. "net/http/httputil"
  6. "net/url"
  7. )
  8. const apiUrl = "MY_CLOUD_RUN.a.run.app"
  9. func main() {
  10. http.HandleFunc("/", proxy)
  11. log.Fatal(http.ListenAndServe(":8081", nil))
  12. }
  13. func proxy(res http.ResponseWriter, req *http.Request) {
  14. // gets past CORS checks
  15. if req.Method == http.MethodOptions {
  16. headers := res.Header()
  17. headers.Add("Access-Control-Allow-Origin", "*")
  18. headers.Add("Vary", "Origin")
  19. headers.Add("Vary", "Access-Control-Request-Method")
  20. headers.Add("Vary", "Access-Control-Request-Headers")
  21. headers.Add("Access-Control-Allow-Headers", "*")
  22. headers.Add("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
  23. res.WriteHeader(http.StatusOK)
  24. return
  25. }
  26. p := httputil.NewSingleHostReverseProxy(&url.URL{
  27. Scheme: "http",
  28. Host: apiUrl,
  29. })
  30. p.Director = func(req *http.Request) {
  31. req.Header.Add("X-Forwarded-Host", req.Host)
  32. req.Header.Add("X-Origin-Host", apiUrl)
  33. req.Header.Add("Host", apiUrl)
  34. req.Header.Add("Access-Control-Allow-Origin", "*")
  35. req.URL.Scheme = "https"
  36. req.URL.Host = apiUrl
  37. }
  38. p.ModifyResponse = func(res *http.Response) error {
  39. res.Header.Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
  40. res.Header.Set("Access-Control-Allow-Credentials", "true")
  41. res.Header.Set("Access-Control-Allow-Origin", "*")
  42. res.Header.Set("Access-Control-Allow-Headers", "*")
  43. return nil
  44. }
  45. p.ServeHTTP(res, req)
  46. }

答案1

得分: 1

这比最初的初步写作要复杂一些,但我们最终得到的结果如下所示。

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "net/http"
  7. "net/http/httputil"
  8. "net/url"
  9. "os"
  10. "os/signal"
  11. "time"
  12. "golang.org/x/oauth2"
  13. "google.golang.org/api/idtoken"
  14. )
  15. var port = ":8080"
  16. var backend = "[CLOUD_RUN_INSTANCE_TO_PROXY].a.run.app"
  17. func main() {
  18. logger := log.New(os.Stdout, "proxy: ", log.LstdFlags)
  19. logger.Println(fmt.Sprintf("代理服务器正在启动:%s,端口:%s", backend, port))
  20. router := http.NewServeMux()
  21. router.Handle("/", proxyHandler())
  22. server := &http.Server{
  23. Addr: port,
  24. Handler: logging(logger)(router),
  25. ErrorLog: logger,
  26. ReadTimeout: 30 * time.Second,
  27. WriteTimeout: 30 * time.Second,
  28. IdleTimeout: 15 * time.Second,
  29. }
  30. done := make(chan bool)
  31. quit := make(chan os.Signal, 1)
  32. signal.Notify(quit, os.Interrupt)
  33. go func() {
  34. <-quit
  35. logger.Println("代理服务器正在关闭...")
  36. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  37. defer cancel()
  38. server.SetKeepAlivesEnabled(false)
  39. if err := server.Shutdown(ctx); err != nil {
  40. logger.Fatalf("无法正常关闭服务器:%v\n", err)
  41. }
  42. close(done)
  43. }()
  44. if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
  45. logger.Fatalf("无法监听端口%s:%v\n", port, err)
  46. }
  47. <-done
  48. logger.Println("服务器已停止")
  49. }
  50. func proxyHandler() http.Handler {
  51. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  52. if r.Method == http.MethodOptions {
  53. headers := w.Header()
  54. headers.Add("Access-Control-Allow-Origin", "*")
  55. headers.Add("Access-Control-Allow-Headers", "*")
  56. headers.Add("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
  57. w.WriteHeader(http.StatusOK)
  58. return
  59. }
  60. path := fmt.Sprintf("https://%s%s", backend, r.RequestURI)
  61. at, _ := idTokenTokenSource(path)
  62. p := httputil.NewSingleHostReverseProxy(&url.URL{
  63. Scheme: "https",
  64. Host: backend,
  65. })
  66. p.Director = func(r *http.Request) {
  67. if at != nil {
  68. at.SetAuthHeader(r)
  69. }
  70. }
  71. p.ModifyResponse = func(res *http.Response) error {
  72. res.Header.Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
  73. res.Header.Set("Access-Control-Allow-Credentials", "true")
  74. res.Header.Set("Access-Control-Allow-Origin", "*")
  75. res.Header.Set("Access-Control-Allow-Headers", "*")
  76. return nil
  77. }
  78. r.URL.Scheme = "https"
  79. r.URL.Host = backend
  80. r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
  81. r.Host = backend
  82. if at != nil {
  83. at.SetAuthHeader(r)
  84. }
  85. p.ServeHTTP(w, r)
  86. })
  87. }
  88. func logging(l *log.Logger) func(http.Handler) http.Handler {
  89. return func(next http.Handler) http.Handler {
  90. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  91. defer func() {
  92. requestId := r.Header.Get("X-Request-Id")
  93. if requestId == "" {
  94. requestId = fmt.Sprintf("%d", time.Now().UnixNano())
  95. }
  96. w.Header().Set("X-Request-Id", requestId)
  97. l.Println(requestId, r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent())
  98. }()
  99. next.ServeHTTP(w, r)
  100. })
  101. }
  102. }
  103. func idTokenTokenSource(audience string) (*oauth2.Token, error) {
  104. ts, err := idtoken.NewTokenSource(context.Background(), audience)
  105. if err != nil {
  106. return nil, err
  107. }
  108. t, err := ts.Token()
  109. if err != nil {
  110. return nil, err
  111. }
  112. return t, nil
  113. }

其中一部分优雅关闭、HTTP设置和日志记录的代码来自:https://gist.github.com/enricofoltran/10b4a980cd07cb02836f70a4ab3e72d7

英文:

This is a bit more elaborate than the original initial write-up but what we wound up with was as follows.

  1. package main
  2. import (
  3. &quot;context&quot;
  4. &quot;fmt&quot;
  5. &quot;log&quot;
  6. &quot;net/http&quot;
  7. &quot;net/http/httputil&quot;
  8. &quot;net/url&quot;
  9. &quot;os&quot;
  10. &quot;os/signal&quot;
  11. &quot;time&quot;
  12. &quot;golang.org/x/oauth2&quot;
  13. &quot;google.golang.org/api/idtoken&quot;
  14. )
  15. var port = &quot;:8080&quot;
  16. var backend = &quot;[CLOUD_RUN_INSTANCE_TO_PROXY].a.run.app&quot;
  17. func main() {
  18. logger := log.New(os.Stdout, &quot;proxy: &quot;, log.LstdFlags)
  19. logger.Println(fmt.Sprintf(&quot;Proxy server is starting for: %s on port: %s&quot;, backend, port))
  20. router := http.NewServeMux()
  21. router.Handle(&quot;/&quot;, proxyHandler())
  22. server := &amp;http.Server{
  23. Addr: port,
  24. Handler: logging(logger)(router),
  25. ErrorLog: logger,
  26. ReadTimeout: 30 * time.Second,
  27. WriteTimeout: 30 * time.Second,
  28. IdleTimeout: 15 * time.Second,
  29. }
  30. done := make(chan bool)
  31. quit := make(chan os.Signal, 1)
  32. signal.Notify(quit, os.Interrupt)
  33. go func() {
  34. &lt;-quit
  35. logger.Println(&quot;Proxy server is shutting down...&quot;)
  36. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  37. defer cancel()
  38. server.SetKeepAlivesEnabled(false)
  39. if err := server.Shutdown(ctx); err != nil {
  40. logger.Fatalf(&quot;Could not gracefully shutdown the server: %v\n&quot;, err)
  41. }
  42. close(done)
  43. }()
  44. if err := server.ListenAndServe(); err != nil &amp;&amp; err != http.ErrServerClosed {
  45. logger.Fatalf(&quot;Could not listen on %s: %v\n&quot;, port, err)
  46. }
  47. &lt;-done
  48. logger.Println(&quot;Server stopped&quot;)
  49. }
  50. func proxyHandler() http.Handler {
  51. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  52. if r.Method == http.MethodOptions {
  53. headers := w.Header()
  54. headers.Add(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;)
  55. headers.Add(&quot;Access-Control-Allow-Headers&quot;, &quot;*&quot;)
  56. headers.Add(&quot;Access-Control-Allow-Methods&quot;, &quot;GET,HEAD,PUT,PATCH,POST,DELETE&quot;)
  57. w.WriteHeader(http.StatusOK)
  58. return
  59. }
  60. path := fmt.Sprintf(&quot;https://%s%s&quot;, backend, r.RequestURI)
  61. at, _ := idTokenTokenSource(path)
  62. p := httputil.NewSingleHostReverseProxy(&amp;url.URL{
  63. Scheme: &quot;https&quot;,
  64. Host: backend,
  65. })
  66. p.Director = func(r *http.Request) {
  67. if at != nil {
  68. at.SetAuthHeader(r)
  69. }
  70. }
  71. p.ModifyResponse = func(res *http.Response) error {
  72. res.Header.Set(&quot;Access-Control-Allow-Methods&quot;, &quot;GET,HEAD,PUT,PATCH,POST,DELETE&quot;)
  73. res.Header.Set(&quot;Access-Control-Allow-Credentials&quot;, &quot;true&quot;)
  74. res.Header.Set(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;)
  75. res.Header.Set(&quot;Access-Control-Allow-Headers&quot;, &quot;*&quot;)
  76. return nil
  77. }
  78. r.URL.Scheme = &quot;https&quot;
  79. r.URL.Host = backend
  80. r.Header.Set(&quot;X-Forwarded-Host&quot;, r.Header.Get(&quot;Host&quot;))
  81. r.Host = backend
  82. if at != nil {
  83. at.SetAuthHeader(r)
  84. }
  85. p.ServeHTTP(w, r)
  86. })
  87. }
  88. func logging(l *log.Logger) func(http.Handler) http.Handler {
  89. return func(next http.Handler) http.Handler {
  90. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  91. defer func() {
  92. requestId := r.Header.Get(&quot;X-Request-Id&quot;)
  93. if requestId == &quot;&quot; {
  94. requestId = fmt.Sprintf(&quot;%d&quot;, time.Now().UnixNano())
  95. }
  96. w.Header().Set(&quot;X-Request-Id&quot;, requestId)
  97. l.Println(requestId, r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent())
  98. }()
  99. next.ServeHTTP(w, r)
  100. })
  101. }
  102. }
  103. func idTokenTokenSource(audience string) (*oauth2.Token, error) {
  104. ts, err := idtoken.NewTokenSource(context.Background(), audience)
  105. if err != nil {
  106. return nil, err
  107. }
  108. t, err := ts.Token()
  109. if err != nil {
  110. return nil, err
  111. }
  112. return t, nil
  113. }

A good chunk of some of the graceful shutdown, http setup, and logging came from: https://gist.github.com/enricofoltran/10b4a980cd07cb02836f70a4ab3e72d7

huangapple
  • 本文由 发表于 2021年12月3日 07:47:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/70207857.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定