创建

一般使用

使用 httputil.NewSingleHostReverseProxy 即可

返回Response

当我们想实现获取通过ReverseProxy的请求结果时,可以使用自定义的 responsewriter 来实现。参考定义

func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    ....
}

type ResponseWriter interface {
	// Header returns the header map that will be sent by
	// WriteHeader. The Header map also is the mechanism with which
	// Handlers can set HTTP trailers.
	//
	// Changing the header map after a call to WriteHeader (or
	// Write) has no effect unless the modified headers are
	// trailers.
	//
	// There are two ways to set Trailers. The preferred way is to
	// predeclare in the headers which trailers you will later
	// send by setting the "Trailer" header to the names of the
	// trailer keys which will come later. In this case, those
	// keys of the Header map are treated as if they were
	// trailers. See the example. The second way, for trailer
	// keys not known to the Handler until after the first Write,
	// is to prefix the Header map keys with the TrailerPrefix
	// constant value. See TrailerPrefix.
	//
	// To suppress automatic response headers (such as "Date"), set
	// their value to nil.
	Header() Header

	// Write writes the data to the connection as part of an HTTP reply.
	//
	// If WriteHeader has not yet been called, Write calls
	// WriteHeader(http.StatusOK) before writing the data. If the Header
	// does not contain a Content-Type line, Write adds a Content-Type set
	// to the result of passing the initial 512 bytes of written data to
	// DetectContentType. Additionally, if the total size of all written
	// data is under a few KB and there are no Flush calls, the
	// Content-Length header is added automatically.
	//
	// Depending on the HTTP protocol version and the client, calling
	// Write or WriteHeader may prevent future reads on the
	// Request.Body. For HTTP/1.x requests, handlers should read any
	// needed request body data before writing the response. Once the
	// headers have been flushed (due to either an explicit Flusher.Flush
	// call or writing enough data to trigger a flush), the request body
	// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
	// handlers to continue to read the request body while concurrently
	// writing the response. However, such behavior may not be supported
	// by all HTTP/2 clients. Handlers should read before writing if
	// possible to maximize compatibility.
	Write([]byte) (int, error)

	// WriteHeader sends an HTTP response header with the provided
	// status code.
	//
	// If WriteHeader is not called explicitly, the first call to Write
	// will trigger an implicit WriteHeader(http.StatusOK).
	// Thus explicit calls to WriteHeader are mainly used to
	// send error codes.
	//
	// The provided code must be a valid HTTP 1xx-5xx status code.
	// Only one header may be written. Go does not currently
	// support sending user-defined 1xx informational headers,
	// with the exception of 100-continue response header that the
	// Server sends automatically when the Request.Body is read.
	WriteHeader(statusCode int)
}

例子

package main

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

	"github.com/gin-gonic/gin"
)

type customWriter struct {
	http.ResponseWriter
}

func NewCustomWriter(w http.ResponseWriter) *customWriter {
	return &customWriter{w}
}

func (c *customWriter) Header() http.Header {
	return c.ResponseWriter.Header()
}

func (c *customWriter) Write(data []byte) (int, error) {
	fmt.Println(string(data)) //get response here
	return c.ResponseWriter.Write(data)
}

func (c *customWriter) WriteHeader(i int) {
	c.ResponseWriter.WriteHeader(i)
}

func main() {
	router := gin.Default()
	router.GET("/", func(ctx *gin.Context) {
		targetURL, err := url.Parse("http://192.168.0.70:8090")
		if err != nil {
			log.Println(err)
		}
		proxy := httputil.NewSingleHostReverseProxy(targetURL)
		proxy.ServeHTTP(NewCustomWriter(ctx.Writer), ctx.Request)
	})
	router.Run(":8899")
}

封装个函数

type proxyResponseWriter struct {
	http.ResponseWriter
	timeOut  int64
	respChan chan []byte
}

func newProxyResponseWriter(w http.ResponseWriter, timeout int64) *proxyResponseWriter {
	return &proxyResponseWriter{w, timeout, make(chan []byte, 1)}
}

func (c *proxyResponseWriter) Header() http.Header {
	return c.ResponseWriter.Header()
}

func (c *proxyResponseWriter) Write(data []byte) (int, error) {
	c.respChan <- data
	return c.ResponseWriter.Write(data)
}

func (c *proxyResponseWriter) WriteHeader(i int) {
	c.ResponseWriter.WriteHeader(i)
}

func apiProxyWithResponse(target string, ctx *gin.Context) []byte {
	targetURL, _ := url.Parse(target)
	proxy := httputil.NewSingleHostReverseProxy(targetURL)
	respWriter := newProxyResponseWriter(ctx.Writer, 5)
	go proxy.ServeHTTP(respWriter, ctx.Request)
	timeoutChan := time.After(time.Duration(respWriter.timeOut) * time.Second)
	for {
		select {
		case <-timeoutChan:
			return nil
		case data := <-respWriter.respChan:
			return data

		}
	}

}

复用Requet对象

​ 某些情况下,需要对http request内容进行判断,然后再根据判断进行操作。写代码的时候发现通过 ioutil.ReadAll 读取以后,post进行发送,会报错 transport connection broken: http: ContentLength=272 with Body length 0 这样的错误。错误的原因大致是读取body’内容后,request body的Io关闭,不能再读取。

​ 解决办法很简单,读取完毕body以后,再将 ioutil.NopCloser(bytes.NewReader(data)) 的返回赋值给Request的body即可。

参考链接

https://stackoverflow.com/questions/31535569/golang-how-to-read-response-body-of-reverseproxy

https://stackoverflow.com/questions/42466491/golang-reverse-proxy-to-app-behind-nginx

https://stackoverflow.com/questions/54704420/how-to-retry-http-post-requests