Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
A simple HTTP proxy by Golang
package main
import (
// Hop-by-hop headers. These are removed when sent to the backend.
var hopHeaders = []string{
"Te", // canonicalized version of "TE"
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 {
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)
client := &http.Client{}
//http: Request.RequestURI can't be set in client requests.
req.RequestURI = ""
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)
copyHeader(wr.Header(), resp.Header)
io.Copy(wr, resp.Body)
func main() {
var addr = flag.String("addr", "", "The addr of the application.")
handler := &proxy{}
log.Println("Starting proxy server on", *addr)
if err := http.ListenAndServe(*addr, handler); err != nil {
log.Fatal("ListenAndServe:", err)
Copy link

g00nix commented Mar 19, 2021

TL;DR: https traffic is encrypted, a proxy is just a dumb intermediary between the client and server in this case.

https traffic is encrypted with the session key at the client (and server) side. Therefore the traffic in the different
sessions is always different, even if they are transferring the same content. In order to serve something from
the cache a proxy should have decrypted the traffic. Being able to decrypt the intercepted https traffic means
a successful attack on https. Sometimes this is possible (google for OpenSSL heartbleed).
However in general it's not feasible. And in most cases it's considered illegal.

So just do the HTTPS Proxy without cache.

Mind... blown...

Copy link

abdennour commented Jul 26, 2021

awesome guys!

Copy link

carnerito commented Oct 2, 2021

Great gist! I have one question. Why do you create a new instance of http.Client for each request? As stated in Golang documentation for http Client, it is concurrent safe and can be used concurrently by multiple goroutines?

Is there some downside of having a global http Client?

Copy link

Yapcheekian commented Nov 3, 2021

if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
		msg := "unsupported protocal scheme " + req.URL.Scheme
		http.Error(wr, msg, http.StatusBadRequest)

i think this part is redundant since you are using ListenAndServe

Copy link

aep commented Feb 16, 2022

hopHeaders needs Content-Length i think?

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.

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