Go combines Consul to implement dynamic reverse proxy
The core function of a proxy can be summed up in one sentence: accept a request from a client, forward it to a back-end server, get a response and return it to the client.
The reverse proxy
The actual operation mode of Reverse Proxy refers to that the Proxy server receives Internet connection requests, forwards the requests to the server on the internal network, and returns the results obtained from the server to the client requesting Internet connection. In this case, the proxy server behaves as a server.
Implementation logic
According to the description of the agent, there are several steps:
- The proxy receives the request from the client and copies the original request object
- Modify the request direction of a new request according to some rules
- Sends the new request to the base server and receives the response back from the server
- The response from the previous step is processed as required and returned to the client
Go language implementation
Native code
Because HTTP requests are received and forwarded, http.handler is implemented
type OriginReverseProxy struct {
servers []*url.URL
}
func NewOriginReverseProxy(targets []*url.URL) *OriginReverseProxy {
return &OriginReverseProxy{
servers: targets,
}
}
// Implement http.handler to receive all requests
func (proxy *OriginReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// 1. The original request object is copied
r2 := clone(req)
// 2. Change the requested IP address to the corresponding server address
target := proxy.servers[rand.Int()%len(proxy.servers)]
r2.URL.Scheme = target.Scheme
r2.URL.Host = target.Host
// 3. Send a new request for replication
transport := http.DefaultTransport
res, err := transport.RoundTrip(r2)
/ / 4. Process the response
iferr ! =nil {
rw.WriteHeader(http.StatusBadGateway)
return
}
for key, value := range res.Header {
for _, v := range value {
rw.Header().Add(key, v)
}
}
rw.WriteHeader(res.StatusCode)
io.Copy(rw, res.Body)
res.Body.Close()
}
// Copy the original request to generate a new request
func clone(req *http.Request) *http.Request {
r2 := new(http.Request)
*r2 = *req
r2.URL = cloneURL(req.URL)
ifreq.Header ! =nil {
r2.Header = req.Header.Clone()
}
ifreq.Trailer ! =nil {
r2.Trailer = req.Trailer.Clone()
}
ifs := req.TransferEncoding; s ! =nil {
s2 := make([]string.len(s))
copy(s2, s)
r2.TransferEncoding = s2
}
r2.Form = cloneURLValues(req.Form)
r2.PostForm = cloneURLValues(req.PostForm)
return r2
}
func cloneURLValues(v url.Values) url.Values {
if v == nil {
return nil
}
return url.Values(http.Header(v).Clone())
}
func cloneURL(u *url.URL) *url.URL {
if u == nil {
return nil
}
u2 := new(url.URL)
*u2 = *u
ifu.User ! =nil {
u2.User = new(url.Userinfo)
*u2.User = *u.User
}
return u2
}
Copy the code
test
// Start a Web project with gin for easy forwarding
func TestGin(t *testing.T) {
r := gin.Default()
r.GET("/ping".func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run(": 9091") // listen and serve on 0.0.0.0:9091
}
func main(a) {
proxy := proxy.NewOriginReverseProxy([]*url.URL{
{
Scheme: "http",
Host: "localhost:9091",
},
})
http.ListenAndServe(": 19090", proxy)
}
Copy the code
Request http://127.0.0.1:19090/ping returns {” message “:” pong “}
Implementation of Httputil. ReverseProxy
In the above example, we implemented the receiving, copying, forwarding, and processing of the request ourselves. The code I wrote is ok.
From the above example, we are mainly concerned with the second step: change the address of the request to the corresponding server address, the rest of the logic is common, but the official has already handled the logic for us, so let’s look at the official how to implement
In the Httputil. ReverseProxy source code, you can use the custom Director method to modify the original request after the original request is copied, but before the new request is sent. This is where we really change the request. You can modify the response by customizing ModifyResponse and customizing ErrorHandler to handle exceptions
type ReverseProxy struct {
// Director must be a function which modifies
// the request into a new request to be sent
// using Transport. Its response is then copied
// back to the original client unmodified.
// Director must not access the provided Request
// after returning.
Director func(*http.Request)
Transport http.RoundTripper
FlushInterval time.Duration
ErrorLog *log.Logger
BufferPool BufferPool
ModifyResponse func(*http.Response) error
ErrorHandler func(http.ResponseWriter, *http.Request, error)
}
Copy the code
Here we modify the request address by customizing the Director
func NewMultipleHostsReverseProxy(targets []*url.URL) *httputil.ReverseProxy {
director := func(req *http.Request) {
target := targets[rand.Int()%len(targets)]
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = target.Path
}
return &httputil.ReverseProxy{Director: director}
}
Copy the code
test
Gin’s project still needs to start. There’s no ambiguity here
func TestMultipleHostsReverseProxy(t *testing.T) {
proxy := proxy.NewMultipleHostsReverseProxy([]*url.URL{
{
Scheme: "http",
Host: "localhost:9091",
},
})
http.ListenAndServe(": 9090", proxy)
}
Copy the code
Access Consul to implement dynamic proxy
In the previous article, we discussed how to use Go to implement Consul service discovery. If you want to implement dynamic proxy in Consul, you need to consider how to map the requested address to the corresponding service. Here we need to add some functions on the basis of principle:
- Find the corresponding service based on the requested address
- Find the corresponding example based on the service
The simplest implementation for the first step is to start the rule with the request address
type LoadBalanceRoute interface {
ObtainInstance(path string) *url.URL
}
type Route struct {
Path string
ServiceName string
}
type DiscoveryLoadBalanceRoute struct {
DiscoveryClient DiscoveryClient
Routes []Route
}
func (d DiscoveryLoadBalanceRoute) ObtainInstance(path string) *url.URL {
for _, route := range d.Routes {
if strings.Index(path, route.Path) == 0 {
instances, _ := d.DiscoveryClient.GetInstances(route.ServiceName)
instance := instances[rand.Int()%len(instances)]
scheme := "http"
return &url.URL{
Scheme: scheme,
Host: instance.GetHost(),
}
}
}
return nil
}
func NewLoadBalanceReverseProxy(lb LoadBalanceRoute) *httputil.ReverseProxy {
director := func(req *http.Request) {
target := lb.ObtainInstance(req.URL.Path)
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
}
return &httputil.ReverseProxy{Director: director}
}
Copy the code
test
func main(a) {
registry, _ := proxy.NewConsulServiceRegistry("127.0.0.1".8500."")
reverseProxy := proxy.NewLoadBalanceReverseProxy(&proxy.DiscoveryLoadBalanceRoute{
DiscoveryClient: registry,
Routes: []proxy.Route{
{
Path: "abc",
ServiceName: "abc",
},
},
})
http.ListenAndServe(": 19090", reverseProxy)
}
Copy the code
reference
- Juejin. Cn/post / 688309…