Build a Full-Featured HTTP/HTTPS Proxy in Go: Step‑by‑Step Guide
This article walks through the concepts, protocols, and complete Go code needed to implement both HTTP and HTTPS forward proxies, explaining request handling, header transformations, and TCP forwarding while providing clear diagrams and runnable examples.
This article provides a detailed guide to implementing an HTTP/HTTPS forward proxy using Go, covering both theory and practical code.
Proxy servers act as intermediaries: a forward proxy serves client requests by fetching resources from target servers, while a reverse proxy presents itself as the server to clients.
Forward proxy: client configures proxy, proxy forwards to server, transparent to server.
Reverse proxy: server configures proxy, proxy forwards to client, transparent to client.
Common proxy protocols include HTTP, HTTPS, SOCKS4/5, and VPN for forward proxies, and TCP, UDP, HTTP, HTTPS for reverse proxies.
HTTP Proxy Overview
An HTTP proxy is a simple forward proxy that uses the HTTP protocol between client and proxy.
It can forward HTTP, HTTPS, FTP, and other protocols, with slight variations in request formatting.
HTTP Protocol
Typical request headers sent from client to proxy:
// Direct connection
GET / HTTP/1.1
Host: staight.github.io
Connection: keep-alive
// HTTP proxy
GET http://staight.github.io/ HTTP/1.1
Host: staight.github.io
Proxy-Connection: keep-aliveKey differences when using an HTTP proxy:
URL becomes a full absolute path (e.g., http://staight.github.io/).
The Connection header is replaced by Proxy-Connection.
Other headers remain unchanged.
Why use the full URL?
Because the proxy needs the full target address; without it and without a Host header, the proxy cannot determine the destination server.
Why replace Connection with Proxy-Connection ?
This maintains compatibility with older HTTP/1.0 proxy servers that do not understand the persistent‑connection semantics of HTTP/1.1.
HTTPS Protocol
When using HTTPS, the client sends a CONNECT request:
CONNECT staight.github.io:443 HTTP/1.1
Host: staight.github.io:443
Proxy-Connection: keep-aliveDifferences from HTTP:
Method changes from GET to CONNECT.
The URL does not include a protocol scheme.
After the initial handshake, the proxy simply tunnels encrypted TCP traffic without modifying the payload.
Interaction diagrams:
Code Implementation
First, create a TCP listener on port 8080 and handle each incoming connection:
l, err := net.Listen("tcp", ":8080")
if err != nil {
log.Panic(err)
}
for {
client, err := l.Accept()
if err != nil {
log.Panic(err)
}
go handle(client)
}In handle, read the client request into a buffer, parse the method and URL, and determine the target address:
var b [1024]byte
n, err := client.Read(b[:])
if err != nil { log.Println(err); return }
var method, URL string
fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '
')]), "%s%s", &method, &URL)
hostPortURL, err := url.Parse(URL)
if err != nil { log.Println(err); return }
if method == "CONNECT" {
address = hostPortURL.Scheme + ":" + hostPortURL.Opaque
} else {
address = hostPortURL.Host
if strings.Index(hostPortURL.Host, ":") == -1 {
address = hostPortURL.Host + ":80"
}
}Establish a TCP connection to the target server and forward data appropriately:
server, err := net.Dial("tcp", address)
if err != nil { log.Println(err); return }
if method == "CONNECT" {
fmt.Fprint(client, "HTTP/1.1 200 Connection established
")
} else {
server.Write(b[:n])
}
go io.Copy(server, client)
io.Copy(client, server)The complete source code is provided below:
package main
import (
"bytes"
"fmt"
"io"
"log"
"net"
"net/url"
"strings"
)
func main() {
l, err := net.Listen("tcp", ":8080")
if err != nil { log.Panic(err) }
for {
client, err := l.Accept()
if err != nil { log.Panic(err) }
go handle(client)
}
}
func handle(client net.Conn) {
if client == nil { return }
defer client.Close()
log.Printf("remote addr: %v
", client.RemoteAddr())
var b [1024]byte
n, err := client.Read(b[:])
if err != nil { log.Println(err); return }
var method, URL, address string
fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '
')]), "%s%s", &method, &URL)
hostPortURL, err := url.Parse(URL)
if err != nil { log.Println(err); return }
if method == "CONNECT" {
address = hostPortURL.Scheme + ":" + hostPortURL.Opaque
} else {
address = hostPortURL.Host
if strings.Index(hostPortURL.Host, ":") == -1 {
address = hostPortURL.Host + ":80"
}
}
server, err := net.Dial("tcp", address)
if err != nil { log.Println(err); return }
if method == "CONNECT" {
fmt.Fprint(client, "HTTP/1.1 200 Connection established
")
} else {
server.Write(b[:n])
}
go io.Copy(server, client)
io.Copy(client, server)
}After adding the proxy configuration and running the program, the proxy listens on port 8080 and can forward both HTTP and HTTPS traffic.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
