How to implement HTTP forwarding Proxy in Go

Viewed 1050

I would like to implement an HTTP request and response system of this for

client ------> A ----------> B --------> (request) HTTPserver
client <------ A <---------- B <-------- (response) HTTPserver
  1. client send HTTP request with any HTTP method (POST,PUT, etc) to A
  2. A then reads the request body encrypt it and then forward it to B 3 B then decrypt the reads the HTTP request body
  3. B then with the received decrypt body as payload prepare an HTTP request and and send to the HTTP server. 5 The HTTP server then respond to B.
  4. B then encrypt the response from the HTTP server and then forward to A.
  5. A also decrypt the response from B and then and send the response back to the client.

I have implements the following base on earlier suggestions.

ProxyA:

const (
  ServerB = "<address of B>"
  Port = "<proxy A port>"
)

func main() {
  // start server
  http.HandleFunc("/", proxyPass)
  log.Fatal(http.ListenAndServe(":" + Port, nil))
}

func proxyPass(res http.ResponseWriter, req  *http.Request) {
 
 // read request body from client
bodybytes, _ := ioutil.ReadAll(req.Body)

defer req.Body.Close()

// encrypt req.Body
object, _ := enc.Encrypt(bodybytes)

// serialize object
serialized, _ := object.CompactSerialize()

// prepare forwarding message
msg := message{reformatedData: serialized}

// encode message 
msgbytes, _ := json.Marshal(&msg)

req.ContentLength = int64(len(msgbytes))
req.Body = ioutil.NopCloser(bytes.NewBuffer(msgbytes))


// How do I read the response data from proxy server B and then send
// response to the client
....
 
  url, _ := url.Parse(ServerB)
  proxy := httputil.NewSingleHostReverseProxy(url)
  proxy.ServeHTTP(res, req)
}

For proxy B:

const (
  Server = "<address of server>"
  Port = "<proxy B port>"
)

func main() {
  // start server
  http.HandleFunc("/", proxyPass)
  log.Fatal(http.ListenAndServe(":" + Port, nil))
}

func proxyPass(res http.ResponseWriter, req  *http.Request) {

  var msg message
  HTTPServerurl := http://xxxxx

  // read request body
  bodybytes, _ := ioutil.ReadAll(req.Body)
  
  req.ContentLength = int64(len(bodybytes))
  req.Body = ioutil.NopCloser(bytes.NewBuffer(bodybytes))


// decode message 
err = json.Unmarshal(bodybytes, &msg)

// decrypt message
object, _ := jose.ParseEncrypted(msg)
decrypted, _ := object.Decrypt("phrasetodecryptmessage")

//send HTTP request to HTTP server
resp, _ := HandlemessageToHTTPServer(decrypted, "POST", HTTPServerurl)

//read response body
RespBody, _ := ioutil.ReadAll(resp.Body)

// encrypt response body
object, _ = enc.Encrypt(producerRespBody)
serialized, _ := object.CompactSerialize()

// prepare response JSON message 
resmsg := resmessage {resmessage: serialized}

// marshall response message 
respmsgbytes, _ := json.Marshal(&resmsg)


// How do I write the "respmsgbytes" to proxyServHTTP "res" back to proxy A


  url, _ := url.Parse(Server)
  proxy := httputil.NewSingleHostReverseProxy(url)
  proxy.ServeHTTP(res, req)
} 

My question is

  1. How do I write the "respmsgbytes" to proxyServHTTP "res" in proxy B back to proxy A ?

  2. How do I read the response data from proxy server B and then send response to the client?

Any help? I have left error checking to make the code short.

1 Answers

You can use httputil
You can do something like following.
For proxy A:

const (
  ServerB = "<address of B>"
  Port = "<proxy A port>"
)

func main() {
  // start server
  http.HandleFunc("/", proxyPass)
  log.Fatal(http.ListenAndServe(":" + Port, nil))
}

func proxyPass(res http.ResponseWriter, req  *http.Request) {
  // Encrypt Request here
  // ...

  url, _ := url.Parse(ServerB)
  proxy := httputil.NewSingleHostReverseProxy(url)
  proxy.ServeHTTP(res, req)
} 

For proxy B:

const (
  Server = "<address of server>"
  Port = "<proxy B port>"
)

func main() {
  // start server
  http.HandleFunc("/", proxyPass)
  log.Fatal(http.ListenAndServe(":" + Port, nil))
}

func proxyPass(res http.ResponseWriter, req  *http.Request) {
  // Decrypt Request here
  // ...

  url, _ := url.Parse(Server)
  proxy := httputil.NewSingleHostReverseProxy(url)
  proxy.ServeHTTP(res, req)
} 

EDIT:
To handle the request body at each proxy, you can have a look at this. Alternatively, I think there should be no harm in construction of new req based on current req as following:

func proxyPass(res http.ResponseWriter, req  *http.Request) {
    body, _ := ioutil.ReadAll(req.Body)
    data := string(body)

    // process data here

    req, _ = http.NewRequest(req.Method, req.URL.String(), strings.NewReader(data))
    
    u, _ := url.Parse(Server)
    proxy := httputil.NewSingleHostReverseProxy(u)
    proxy.ServeHTTP(res, req)
}

This can be done at both proxies.

EDIT:
The proxy response can be updated using ReverseProxy.ModifyResponse.
You can use it like this:

func proxyPass(res http.ResponseWriter, req *http.Request) {
    ....
    
    proxy := httputil.NewSingleHostReverseProxy(url)
    
    proxy.ModifyResponse = func(response *http.Response) error {
        // Read and update the response here

        // The response here is response from server (proxy B if this is at proxy A)
        // It is a pointer, so can be modified to update in place
        // It will not be called if Proxy B is unreachable
    }

    proxy.ServeHTTP(res, req)
}
Related