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

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

Reverse Proxy using Go to Cloud Run Instance

问题

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

我可能漏掉了什么?

package main

import (
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
)

const apiUrl = "MY_CLOUD_RUN.a.run.app"

func main() {
	http.HandleFunc("/", proxy)
	log.Fatal(http.ListenAndServe(":8081", nil))
}

func proxy(res http.ResponseWriter, req *http.Request) {
	// 绕过CORS检查
	if req.Method == http.MethodOptions {
		headers := res.Header()
		headers.Add("Access-Control-Allow-Origin", "*")
		headers.Add("Vary", "Origin")
		headers.Add("Vary", "Access-Control-Request-Method")
		headers.Add("Vary", "Access-Control-Request-Headers")
		headers.Add("Access-Control-Allow-Headers", "*")
		headers.Add("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
		res.WriteHeader(http.StatusOK)
		return
	}

	p := httputil.NewSingleHostReverseProxy(&url.URL{
		Scheme: "http",
		Host:   apiUrl,
	})
	p.Director = func(req *http.Request) {
		req.Header.Add("X-Forwarded-Host", req.Host)
		req.Header.Add("X-Origin-Host", apiUrl)
		req.Header.Add("Host", apiUrl)
		req.Header.Add("Access-Control-Allow-Origin", "*")
		req.URL.Scheme = "https"
		req.URL.Host = apiUrl
	}
	p.ModifyResponse = func(res *http.Response) error {
		res.Header.Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
		res.Header.Set("Access-Control-Allow-Credentials", "true")
		res.Header.Set("Access-Control-Allow-Origin", "*")
		res.Header.Set("Access-Control-Allow-Headers", "*")
		return nil
	}

	p.ServeHTTP(res, req)
}
英文:

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?

package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
)
const apiUrl = "MY_CLOUD_RUN.a.run.app"
func main() {
http.HandleFunc("/", proxy)
log.Fatal(http.ListenAndServe(":8081", nil))
}
func proxy(res http.ResponseWriter, req *http.Request) {
// gets past CORS checks
if req.Method == http.MethodOptions {
headers := res.Header()
headers.Add("Access-Control-Allow-Origin", "*")
headers.Add("Vary", "Origin")
headers.Add("Vary", "Access-Control-Request-Method")
headers.Add("Vary", "Access-Control-Request-Headers")
headers.Add("Access-Control-Allow-Headers", "*")
headers.Add("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
res.WriteHeader(http.StatusOK)
return
}
p := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "http",
Host:   apiUrl,
})
p.Director = func(req *http.Request) {
req.Header.Add("X-Forwarded-Host", req.Host)
req.Header.Add("X-Origin-Host", apiUrl)
req.Header.Add("Host", apiUrl)
req.Header.Add("Access-Control-Allow-Origin", "*")
req.URL.Scheme = "https"
req.URL.Host = apiUrl
}
p.ModifyResponse = func(res *http.Response) error {
res.Header.Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
res.Header.Set("Access-Control-Allow-Credentials", "true")
res.Header.Set("Access-Control-Allow-Origin", "*")
res.Header.Set("Access-Control-Allow-Headers", "*")
return nil
}
p.ServeHTTP(res, req)
}

答案1

得分: 1

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

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/signal"
	"time"

	"golang.org/x/oauth2"
	"google.golang.org/api/idtoken"
)

var port = ":8080"
var backend = "[CLOUD_RUN_INSTANCE_TO_PROXY].a.run.app"

func main() {
	logger := log.New(os.Stdout, "proxy: ", log.LstdFlags)
	logger.Println(fmt.Sprintf("代理服务器正在启动:%s,端口:%s", backend, port))

	router := http.NewServeMux()
	router.Handle("/", proxyHandler())

	server := &http.Server{
		Addr:         port,
		Handler:      logging(logger)(router),
		ErrorLog:     logger,
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
		IdleTimeout:  15 * time.Second,
	}

	done := make(chan bool)
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt)

	go func() {
		<-quit
		logger.Println("代理服务器正在关闭...")

		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()

		server.SetKeepAlivesEnabled(false)
		if err := server.Shutdown(ctx); err != nil {
			logger.Fatalf("无法正常关闭服务器:%v\n", err)
		}
		close(done)
	}()

	if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
		logger.Fatalf("无法监听端口%s:%v\n", port, err)
	}

	<-done
	logger.Println("服务器已停止")
}

func proxyHandler() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodOptions {
			headers := w.Header()
			headers.Add("Access-Control-Allow-Origin", "*")
			headers.Add("Access-Control-Allow-Headers", "*")
			headers.Add("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
			w.WriteHeader(http.StatusOK)
			return
		}

		path := fmt.Sprintf("https://%s%s", backend, r.RequestURI)
		at, _ := idTokenTokenSource(path)

		p := httputil.NewSingleHostReverseProxy(&url.URL{
			Scheme: "https",
			Host:   backend,
		})
		p.Director = func(r *http.Request) {
			if at != nil {
				at.SetAuthHeader(r)
			}
		}
		p.ModifyResponse = func(res *http.Response) error {
			res.Header.Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
			res.Header.Set("Access-Control-Allow-Credentials", "true")
			res.Header.Set("Access-Control-Allow-Origin", "*")
			res.Header.Set("Access-Control-Allow-Headers", "*")
			return nil
		}

		r.URL.Scheme = "https"
		r.URL.Host = backend
		r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
		r.Host = backend

		if at != nil {
			at.SetAuthHeader(r)
		}

		p.ServeHTTP(w, r)
	})
}

func logging(l *log.Logger) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			defer func() {
				requestId := r.Header.Get("X-Request-Id")
				if requestId == "" {
					requestId = fmt.Sprintf("%d", time.Now().UnixNano())
				}
				w.Header().Set("X-Request-Id", requestId)
				l.Println(requestId, r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent())
			}()

			next.ServeHTTP(w, r)
		})
	}
}

func idTokenTokenSource(audience string) (*oauth2.Token, error) {
	ts, err := idtoken.NewTokenSource(context.Background(), audience)
	if err != nil {
		return nil, err
	}

	t, err := ts.Token()
	if err != nil {
		return nil, err
	}

	return t, nil
}

其中一部分优雅关闭、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.

package main
import (
&quot;context&quot;
&quot;fmt&quot;
&quot;log&quot;
&quot;net/http&quot;
&quot;net/http/httputil&quot;
&quot;net/url&quot;
&quot;os&quot;
&quot;os/signal&quot;
&quot;time&quot;
&quot;golang.org/x/oauth2&quot;
&quot;google.golang.org/api/idtoken&quot;
)
var port = &quot;:8080&quot;
var backend = &quot;[CLOUD_RUN_INSTANCE_TO_PROXY].a.run.app&quot;
func main() {
logger := log.New(os.Stdout, &quot;proxy: &quot;, log.LstdFlags)
logger.Println(fmt.Sprintf(&quot;Proxy server is starting for: %s on port: %s&quot;, backend, port))
router := http.NewServeMux()
router.Handle(&quot;/&quot;, proxyHandler())
server := &amp;http.Server{
Addr:         port,
Handler:      logging(logger)(router),
ErrorLog:     logger,
ReadTimeout:  30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout:  15 * time.Second,
}
done := make(chan bool)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
go func() {
&lt;-quit
logger.Println(&quot;Proxy server is shutting down...&quot;)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
logger.Fatalf(&quot;Could not gracefully shutdown the server: %v\n&quot;, err)
}
close(done)
}()
if err := server.ListenAndServe(); err != nil &amp;&amp; err != http.ErrServerClosed {
logger.Fatalf(&quot;Could not listen on %s: %v\n&quot;, port, err)
}
&lt;-done
logger.Println(&quot;Server stopped&quot;)
}
func proxyHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions {
headers := w.Header()
headers.Add(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;)
headers.Add(&quot;Access-Control-Allow-Headers&quot;, &quot;*&quot;)
headers.Add(&quot;Access-Control-Allow-Methods&quot;, &quot;GET,HEAD,PUT,PATCH,POST,DELETE&quot;)
w.WriteHeader(http.StatusOK)
return
}
path := fmt.Sprintf(&quot;https://%s%s&quot;, backend, r.RequestURI)
at, _ := idTokenTokenSource(path)
p := httputil.NewSingleHostReverseProxy(&amp;url.URL{
Scheme: &quot;https&quot;,
Host:   backend,
})
p.Director = func(r *http.Request) {
if at != nil {
at.SetAuthHeader(r)
}
}
p.ModifyResponse = func(res *http.Response) error {
res.Header.Set(&quot;Access-Control-Allow-Methods&quot;, &quot;GET,HEAD,PUT,PATCH,POST,DELETE&quot;)
res.Header.Set(&quot;Access-Control-Allow-Credentials&quot;, &quot;true&quot;)
res.Header.Set(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;)
res.Header.Set(&quot;Access-Control-Allow-Headers&quot;, &quot;*&quot;)
return nil
}
r.URL.Scheme = &quot;https&quot;
r.URL.Host = backend
r.Header.Set(&quot;X-Forwarded-Host&quot;, r.Header.Get(&quot;Host&quot;))
r.Host = backend
if at != nil {
at.SetAuthHeader(r)
}
p.ServeHTTP(w, r)
})
}
func logging(l *log.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
requestId := r.Header.Get(&quot;X-Request-Id&quot;)
if requestId == &quot;&quot; {
requestId = fmt.Sprintf(&quot;%d&quot;, time.Now().UnixNano())
}
w.Header().Set(&quot;X-Request-Id&quot;, requestId)
l.Println(requestId, r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent())
}()
next.ServeHTTP(w, r)
})
}
}
func idTokenTokenSource(audience string) (*oauth2.Token, error) {
ts, err := idtoken.NewTokenSource(context.Background(), audience)
if err != nil {
return nil, err
}
t, err := ts.Token()
if err != nil {
return nil, err
}
return t, nil
}

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:

确定