Build a Golang HTTP Proxy from Scratch: Full Code and Explanation

This article walks through the concepts of forward and reverse proxies, explains HTTP and HTTPS proxy protocols, and provides a complete, step‑by‑step Golang implementation with code samples and diagrams for building a functional HTTP proxy server.

Go Development Architecture Practice
Go Development Architecture Practice
Go Development Architecture Practice
Build a Golang HTTP Proxy from Scratch: Full Code and Explanation

This article introduces how to implement an HTTP proxy server using Go, covering both the theoretical background of proxy types and the practical code needed to run one.

Proxy Basics

A proxy acts as an intermediary that forwards requests from a client to a target server and returns the response. Forward proxies serve client requests, while reverse proxies serve server requests. Different protocols are used for each type, such as HTTP, HTTPS, SOCKS4/5 for forward proxies and TCP, UDP, HTTP, HTTPS for reverse proxies.

HTTP Proxy Overview

In a forward HTTP proxy the client sends a full URL in the request line and uses the Proxy-Connection header instead of Connection. This allows the proxy to identify the target server and remain compatible with older HTTP/1.0 proxies that do not understand the standard Connection header.

// Direct connection
GET / HTTP/1.1
Host: staight.github.io
Connection: keep-alive

// HTTP proxy request
GET http://staight.github.io/ HTTP/1.1
Host: staight.github.io
Proxy-Connection: keep-alive

The full URL is required because the proxy must know which server to contact, and the Proxy-Connection header prevents connection‑handling issues with legacy proxies.

HTTPS Proxy

HTTPS proxies use the CONNECT method. The client establishes a tunnel to the target server, after which all traffic is encrypted and the proxy simply forwards raw TCP data.

CONNECT staight.github.io:443 HTTP/1.1
Host: staight.github.io:443
Proxy-Connection: keep-alive

Unlike HTTP, the request line does not contain a full URL because the proxy does not need to parse the encrypted payload.

HTTP proxy flow diagram
HTTP proxy flow diagram
HTTPS proxy flow diagram
HTTPS proxy flow diagram

Code Implementation

The program creates a TCP listener on port 8080, accepts connections, and handles each in a separate goroutine.

// tcp listener on port 8080
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)
}

Inside handle, the client request is read into a buffer, the method and URL are parsed, and the target address is derived. For CONNECT (HTTPS) the address is built from the scheme and opaque part; otherwise the host is used, defaulting to port 80 when omitted.

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"
    }
}

The proxy then dials the target server. For HTTPS it sends a "200 Connection established" response to the client before tunneling data; for HTTP it forwards the original request bytes.

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])
}
// bidirectional copy
go io.Copy(server, client)
io.Copy(client, server)

The full source code is provided below, followed by screenshots of the proxy running.

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)
}
Running proxy screenshot
Running proxy screenshot
Proxy test result
Proxy test result

Reference: https://blog.csdn.net/weixin_43507410/article/details/124839308

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Gonetwork programmingHTTP proxyProxy server
Go Development Architecture Practice
Written by

Go Development Architecture Practice

Daily sharing of Golang-related technical articles, practical resources, language news, tutorials, real-world projects, and more. Looking forward to growing together. Let's go!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.