简体   繁体   English

使用 Go 到 Cloud Run 实例的反向代理

[英]Reverse Proxy using Go to Cloud Run Instance

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.我觉得我快要完成这项工作了,但到目前为止,我遇到了一个问题,在 Go 中构建一个小型反向代理到 GCP Cloud Run 实例。请求“通过”,但请求的响应是默认值GCP Cloud Run 404。当请求返回到 Cloud Run 时,主机 header 被忽略,因此请求没有被路由更正。

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)
}

This is a bit more elaborate than the original initial write-up but what we wound up with was as follows.这比最初的最初写的要复杂一些,但我们最终得到的结果如下。

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("Proxy server is starting for: %s on port: %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("Proxy server is shutting down...")

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

        server.SetKeepAlivesEnabled(false)
        if err := server.Shutdown(ctx); err != nil {
            logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)
        }
        close(done)
    }()

    if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        logger.Fatalf("Could not listen on %s: %v\n", port, err)
    }

    <-done
    logger.Println("Server stopped")
}

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
}

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM