Skip to content

Instantly share code, notes, and snippets.

@amalshaji
Created October 9, 2023 18:24
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 amalshaji/ba2f03ebd5bd58b9f286e6819c678e62 to your computer and use it in GitHub Desktop.
Save amalshaji/ba2f03ebd5bd58b9f286e6819c678e62 to your computer and use it in GitHub Desktop.
Simple reverse proxy in go
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net"
"os"
"regexp"
"strings"
)
const HTTP_404 = `
HTTP/1.1 404 Not Found
Content-Type: text/plain
Unregistered subdomain
`
var proxyCache = make(map[string]string)
func parseConfig() *serverConfig {
configFile, err := os.Open("proxy.json")
if err != nil {
log.Fatal(err)
}
defer configFile.Close()
var config serverConfig
jsonParser := json.NewDecoder(configFile)
if err = jsonParser.Decode(&config); err != nil {
log.Fatal(err)
}
return &config
}
type serverConfig struct {
ServerURL string `json:"serverUrl"`
Proxies []struct {
Subdomain string `json:"subdomain"`
Target string `json:"target"`
} `json:"proxies"`
}
type Server struct {
config *serverConfig
}
func new() *Server {
return &Server{
config: parseConfig(),
}
}
func (s *Server) loadSubdomainCache() {
for _, v := range s.config.Proxies {
proxyCache[v.Subdomain] = v.Target
}
}
func (s *Server) extractHostHeader(request string) string {
hostPattern := `Host:\s+([^\s]+)`
re := regexp.MustCompile(hostPattern)
matches := re.FindStringSubmatch(request)
if len(matches) >= 2 {
return matches[1]
}
return ""
}
func (s *Server) extractSubdomain(host string) string {
return strings.Replace(host, fmt.Sprintf(".%s", s.config.ServerURL), "", 1)
}
func (s *Server) getTargetBySubdomain(subdomain string) string {
if target, ok := proxyCache[subdomain]; ok {
return target
}
if target, ok := proxyCache["*"]; ok {
return target
}
return ""
}
func (s *Server) createListener() net.Listener {
listener, err := net.Listen("tcp", s.config.ServerURL)
if err != nil {
log.Fatal(err)
}
return listener
}
func (s *Server) handleConnection(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
log.Fatal(err)
}
incomingData := buf[:n]
subdomain := s.extractSubdomain(s.extractHostHeader(string(incomingData)))
target := s.getTargetBySubdomain(subdomain)
if target == "" {
io.WriteString(conn, HTTP_404)
return
}
remote, err := net.Dial("tcp", target)
if err != nil {
log.Fatal(err)
}
defer remote.Close()
remote.Write(incomingData)
io.Copy(conn, remote)
}
func main() {
server := new()
server.loadSubdomainCache()
listener := server.createListener()
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go server.handleConnection(conn)
}
}
{
"serverUrl": "localhost:8080",
"proxies": [
{
"subdomain": "test",
"target": "localhost:8000"
},
{
"subdomain": "9000",
"target": "localhost:9000"
},
{
"subdomain": "*",
"target": "localhost:9999"
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment