package main import ( "bufio" "crypto/tls" "io" "log" "net" "net/url" "os" "reflect" "unsafe" "github.com/valyala/fasthttp" ) var ( upstreamURL string hopHeaders = []string{ "Connection", "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "Te", // canonicalized version of "TE" "Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522 "Transfer-Encoding", "Upgrade", } client = &fasthttp.Client{} ) func init() { upstream, err := url.Parse(os.Getenv("UPSTREAM")) if err != nil { panic(err) } if upstream.Scheme == "https" { client.TLSConfig = &tls.Config{ InsecureSkipVerify: true, } } upstreamURL = upstream.String() } func handler(requestCtx *fasthttp.RequestCtx) { sourceReq := fasthttp.AcquireRequest() defer fasthttp.ReleaseRequest(sourceReq) sourceResp := fasthttp.AcquireResponse() requestCtx.Request.Header.CopyTo(&sourceReq.Header) for _, h := range hopHeaders { sourceReq.Header.Del(h) } ip := requestCtx.RemoteAddr().(*net.TCPAddr).IP.String() sourceReq.Header.Set("Cf-Connecting-Ip", ip) sourceReq.Header.Add("X-Forwarded-For", ip) sourceReq.Header.Set("X-Real-Ip", ip) sourceReq.SetBody(requestCtx.PostBody()) sourceReq.SetRequestURIBytes(append(s2b(upstreamURL[:len(upstreamURL)-1]), requestCtx.RequestURI()...)) sourceResp.ReturnBodyReader = true if err := client.Do(sourceReq, sourceResp); err != nil { requestCtx.Error(err.Error(), fasthttp.StatusBadGateway) return } sourceResp.Header.CopyTo(&requestCtx.Response.Header) for _, h := range hopHeaders { requestCtx.Response.Header.Del(h) } requestCtx.Response.SetBodyStreamWriter(func(w *bufio.Writer) { _, err := io.Copy(w, sourceResp.BodyReader) if err != nil { if err != io.EOF { return } log.Println(err) return } if err := w.Flush(); err != nil { log.Println(err) } if err := sourceResp.BodyReader.Close(); err != nil { return } fasthttp.ReleaseBodyReader(sourceResp.BodyReader) fasthttp.ReleaseResponse(sourceResp) }) } func s2b(s string) []byte { sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) bh := reflect.SliceHeader{ Data: sh.Data, Len: sh.Len, Cap: sh.Len, } return *(*[]byte)(unsafe.Pointer(&bh)) } func main() { listenAddr := os.Getenv("LISTEN_ADDR") listenPort := os.Getenv("LISTEN_PORT") if err := fasthttp.ListenAndServe(listenAddr+":"+listenPort, handler); err != nil { panic(err) } }