Skip to content

Instantly share code, notes, and snippets.

@dmage
Created June 23, 2021 12:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmage/02d68c7ac98810df3d974b30a2a9753a to your computer and use it in GitHub Desktop.
Save dmage/02d68c7ac98810df3d974b30a2a9753a to your computer and use it in GitHub Desktop.
HTTP proxy with CONNECT
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
)
// Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
var hopHeaders = []string{
"Connection",
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"Te", // canonicalized version of "TE"
"Trailers",
"Transfer-Encoding",
"Upgrade",
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
func delHopHeaders(header http.Header) {
for _, h := range hopHeaders {
header.Del(h)
}
}
type proxy struct {
}
func (p *proxy) handleConnect(w http.ResponseWriter, req *http.Request) error {
hj, ok := w.(http.Hijacker)
if !ok {
return fmt.Errorf("writer %T doesn't support hijacking", w)
}
conn, _, err := hj.Hijack()
if err != nil {
return err
}
defer conn.Close()
remoteConn, err := net.Dial("tcp", req.URL.Host)
if err != nil {
return err
}
defer remoteConn.Close()
_, err = conn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
if err != nil {
return err
}
done := make(chan error, 2)
go func() {
_, err := io.Copy(conn, remoteConn)
done <- err
}()
go func() {
_, err := io.Copy(remoteConn, conn)
done <- err
}()
return <-done
}
func (p *proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Printf("%s: %s %s", req.RemoteAddr, req.Method, req.URL)
if req.Method == "CONNECT" {
err := p.handleConnect(w, req)
if err != nil {
log.Printf("%s: %s", req.RemoteAddr, err)
}
return
}
if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
msg := "unsupported protocal scheme " + req.URL.Scheme
http.Error(w, msg, http.StatusBadRequest)
log.Println(msg)
return
}
delHopHeaders(req.Header)
req.RequestURI = "" // http: Request.RequestURI can't be set in client requests.
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
http.Error(w, "Server Error", http.StatusInternalServerError)
log.Fatal("ServeHTTP:", err)
}
defer resp.Body.Close()
log.Println(req.RemoteAddr, " ", resp.Status)
delHopHeaders(resp.Header)
copyHeader(w.Header(), resp.Header)
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}
func main() {
var addr = flag.String("addr", "127.0.0.1:8080", "The addr of the application.")
flag.Parse()
log.Println("Starting proxy server on", *addr)
log.Fatal(http.ListenAndServe(*addr, &proxy{}))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment