Skip to content

Instantly share code, notes, and snippets.

@aerialcombat
Last active May 12, 2024 19:28
Show Gist options
  • Save aerialcombat/ba8396e1dc4890a1ecb041645ef94d17 to your computer and use it in GitHub Desktop.
Save aerialcombat/ba8396e1dc4890a1ecb041645ef94d17 to your computer and use it in GitHub Desktop.
http/https proxy in Go
package main
import (
"flag"
"io"
"log"
"net"
"net/http"
"strings"
)
// hopHeaders are headers that should be removed when sent to the backend.
var hopHeaders = []string{
"Connection",
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"Te", // TE as per canonicalization
"Trailers",
"Transfer-Encoding",
"Upgrade",
}
// Proxy represents the proxy handler.
type Proxy struct{}
// NewProxy creates a new instance of a Proxy.
func NewProxy() *Proxy {
return &Proxy{}
}
// ServeHTTP handles HTTP requests to the proxy.
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL)
if r.Method == "CONNECT" {
p.handleConnect(w, r)
} else {
p.forwardRequest(w, r)
}
}
// handleConnect deals with the CONNECT method by establishing a tunnel.
func (p *Proxy) handleConnect(w http.ResponseWriter, r *http.Request) {
log.Printf("Handling CONNECT method for %s", r.Host)
destConn, err := net.Dial("tcp", r.Host)
if err != nil {
log.Printf("Failed to connect to host %v: %v", r.Host, err)
http.Error(w, "Failed to connect to target host", http.StatusInternalServerError)
return
}
hijacker, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "HTTP Server does not support hijacking", http.StatusInternalServerError)
return
}
clientConn, _, err := hijacker.Hijack()
if err != nil {
http.Error(w, "Hijacking failed", http.StatusInternalServerError)
return
}
clientConn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
go transfer(destConn, clientConn)
go transfer(clientConn, destConn)
}
// forwardRequest forwards an HTTP request to the destination server.
func (p *Proxy) forwardRequest(w http.ResponseWriter, r *http.Request) {
delHopHeaders(r.Header)
appendHostToXForwardHeader(r.Header, clientIP(r))
client := &http.Client{}
r.RequestURI = ""
resp, err := client.Do(r)
if err != nil {
log.Printf("Error forwarding request: %v", err)
http.Error(w, "Server Error", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
copyResponse(w, resp)
}
// copyResponse copies the response from the destination server to the client.
func copyResponse(w http.ResponseWriter, resp *http.Response) {
delHopHeaders(resp.Header)
copyHeader(w.Header(), resp.Header)
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}
// transfer relays data between source and destination.
func transfer(destination, source net.Conn) {
defer destination.Close()
defer source.Close()
io.Copy(destination, source)
}
// clientIP extracts the client IP address from the request.
func clientIP(r *http.Request) string {
if clientIP, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
return clientIP
}
return "unknown"
}
// appendHostToXForwardHeader appends the host to the X-Forwarded-For header.
func appendHostToXForwardHeader(header http.Header, host string) {
if prior, ok := header["X-Forwarded-For"]; ok {
host = strings.Join(prior, ", ") + ", " + host
}
header.Set("X-Forwarded-For", host)
}
// copyHeader copies headers from source to destination, except hop-by-hop headers.
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
// delHopHeaders deletes hop-by-hop headers.
func delHopHeaders(header http.Header) {
for _, h := range hopHeaders {
header.Del(h)
}
}
func main() {
addr := flag.String("addr", "localhost:8080", "The address of the application.")
flag.Parse()
proxy := NewProxy()
log.Printf("Starting proxy server on %s", *addr)
if err := http.ListenAndServe(*addr, proxy); err != nil {
log.Fatalf("ListenAndServe: %v", err)
}
}
@aerialcombat
Copy link
Author

revised version of https://gist.github.com/yowu/f7dc34bd4736a65ff28d for supporting https

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment