Skip to content

Instantly share code, notes, and snippets.

@yowu
Last active November 16, 2024 13:40
Show Gist options
  • Save yowu/f7dc34bd4736a65ff28d to your computer and use it in GitHub Desktop.
Save yowu/f7dc34bd4736a65ff28d to your computer and use it in GitHub Desktop.
A simple HTTP proxy by Golang
package main
import (
"flag"
"io"
"log"
"net"
"net/http"
"strings"
)
// 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)
}
}
func appendHostToXForwardHeader(header http.Header, host string) {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := header["X-Forwarded-For"]; ok {
host = strings.Join(prior, ", ") + ", " + host
}
header.Set("X-Forwarded-For", host)
}
type proxy struct {
}
func (p *proxy) ServeHTTP(wr http.ResponseWriter, req *http.Request) {
log.Println(req.RemoteAddr, " ", req.Method, " ", req.URL)
if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
msg := "unsupported protocal scheme "+req.URL.Scheme
http.Error(wr, msg, http.StatusBadRequest)
log.Println(msg)
return
}
client := &http.Client{}
//http: Request.RequestURI can't be set in client requests.
//http://golang.org/src/pkg/net/http/client.go
req.RequestURI = ""
delHopHeaders(req.Header)
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
appendHostToXForwardHeader(req.Header, clientIP)
}
resp, err := client.Do(req)
if err != nil {
http.Error(wr, "Server Error", http.StatusInternalServerError)
log.Fatal("ServeHTTP:", err)
}
defer resp.Body.Close()
log.Println(req.RemoteAddr, " ", resp.Status)
delHopHeaders(resp.Header)
copyHeader(wr.Header(), resp.Header)
wr.WriteHeader(resp.StatusCode)
io.Copy(wr, resp.Body)
}
func main() {
var addr = flag.String("addr", "127.0.0.1:8080", "The addr of the application.")
flag.Parse()
handler := &proxy{}
log.Println("Starting proxy server on", *addr)
if err := http.ListenAndServe(*addr, handler); err != nil {
log.Fatal("ListenAndServe:", err)
}
}
@aep
Copy link

aep commented Feb 16, 2022

hopHeaders needs Content-Length i think?

@jkopczyn
Copy link

jkopczyn commented Jun 6, 2022

The req.URL.Scheme is nonfunctional - req.URL will always have a blank Scheme, as discussed in golang/go#28940. So this will always error out.

@ppdouble
Copy link

VERSION = $(shell git describe --tags --always --dirty)
LDFLAGS=-ldflags "-X main.version=$(VERSION)"
OSARCH=$(shell go env GOHOSTOS)-$(shell go env GOHOSTARCH)

SIMPLEPROXYTOOL=\
	goproxytool-darwin-amd64 \
	goproxytool-linux-amd64 \
	goproxytool-windows-amd64.exe

proxytool: goproxytool-$(OSARCH)

$(SIMPLEPROXYTOOL):
	GOOS=$(word 2,$(subst -, ,$@)) GOARCH=$(word 3,$(subst -, ,$(subst .exe,,$@))) go build $(LDFLAGS) -o $@ ./$<

%-$(VERSION).zip: %.exe
	rm -f $@
	zip $@ $<

%-$(VERSION).zip: %
	rm -f $@
	zip $@ $<

clean:
	rm -f goproxytool-*


release:
	$(foreach bin,$(SIMPLEPROXYTOOL),$(subst .exe,,$(bin))-$(VERSION).zip)

rm:
	$(foreach bin,$(SIMPLEPROXYTOOL),$(subst .exe,,$(bin))-$(VERSION).zip)


build: proxytool $(SIMPLEPROXYTOOL) clean rm

.PHONY: build
all: build

A MakeFile following this link

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