Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leearmstrong/b5e09b76df484fbdf1f7a47883d38c83 to your computer and use it in GitHub Desktop.
Save leearmstrong/b5e09b76df484fbdf1f7a47883d38c83 to your computer and use it in GitHub Desktop.
Golang reverse proxy
package main
import (
"bytes"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"sync"
"time"
)
type servers struct {
lastIndex int
servers []string
mutex sync.RWMutex
}
var loadBalancer servers
func (s *servers) addServer(serverAddress string) {
s.mutex.Lock()
s.servers = append(s.servers, serverAddress)
s.mutex.Unlock()
}
func (s *servers) getNextServer() string {
var currentServer string
s.mutex.Lock()
currentServer = s.servers[s.lastIndex]
s.lastIndex++
if s.lastIndex == len(s.servers) {
s.lastIndex = 0
}
s.mutex.Unlock()
return currentServer
}
func main() {
loadBalancer.addServer("10.0.1.81")
loadBalancer.addServer("10.0.1.82")
loadBalancer.addServer("10.0.1.83")
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = 1000
http.HandleFunc("/", handler)
http.ListenAndServe(":80", nil)
}
func handleDuplicatedRequest(req *http.Request) {
}
func handler(w http.ResponseWriter, req *http.Request) {
_, productionRequest := duplicateRequest(req)
//maxTries := 3
// Push the handling of the duplicated request off to another goroutine
//go handleDuplicatedRequest(alternateRequest)
err := proxyRequest(w, productionRequest)
if err != nil {
log.Println(err, productionRequest.URL)
}
}
func proxyRequest(w http.ResponseWriter, productionRequest *http.Request) error {
// Obtain the next server in the round robin list
currentServer := loadBalancer.getNextServer()
// Allow this to connect for 1 second!
var netTransport = &http.Transport{
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
IdleConnTimeout: 10 * time.Second,
}
netTransport.MaxIdleConnsPerHost = 1000
// Allow this to have 10 seconds to complete a request
var netClient = &http.Client{
Transport: netTransport,
}
productionRequest.URL.Host = currentServer
resp, err := netClient.Do(productionRequest)
if err != nil {
log.Printf("Failed to connect to %s\n", err)
return err
}
defer resp.Body.Close()
for k, v := range resp.Header {
w.Header()[k] = v
}
w.WriteHeader(resp.StatusCode)
body, _ := ioutil.ReadAll(resp.Body)
w.Write(body)
return nil
}
type nopCloser struct {
io.Reader
}
func (nopCloser) Close() error { return nil }
func duplicateRequest(request *http.Request) (request1 *http.Request, request2 *http.Request) {
b1 := new(bytes.Buffer)
b2 := new(bytes.Buffer)
w := io.MultiWriter(b1, b2)
io.Copy(w, request.Body)
defer request.Body.Close()
request1 = &http.Request{
Method: request.Method,
URL: request.URL,
Proto: request.Proto,
ProtoMajor: request.ProtoMajor,
ProtoMinor: request.ProtoMinor,
Header: request.Header,
Body: nopCloser{b1},
Host: request.Host,
ContentLength: request.ContentLength,
Close: true,
}
request2 = &http.Request{
Method: request.Method,
URL: request.URL,
Proto: request.Proto,
ProtoMajor: request.ProtoMajor,
ProtoMinor: request.ProtoMinor,
Header: request.Header,
Body: nopCloser{b2},
Host: request.Host,
ContentLength: request.ContentLength,
Close: true,
}
if request.URL.Scheme == "" {
request2.URL.Scheme = "http"
request1.URL.Scheme = "http"
}
request1.Header.Add("X-Forwarded-For", request.RemoteAddr)
request2.Header.Add("X-Forwarded-For", request.RemoteAddr)
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment