Skip to content

Instantly share code, notes, and snippets.

@arlandism
Created January 5, 2023 03:51
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 arlandism/5b5a3019f4bd5a7ee460b033b88546b1 to your computer and use it in GitHub Desktop.
Save arlandism/5b5a3019f4bd5a7ee460b033b88546b1 to your computer and use it in GitHub Desktop.
package main
import (
"fmt"
"os"
"golang.org/x/sys/unix"
"strings"
)
type cache struct {
monitoredPrefixes []string
routeCache map[string][]byte
}
func NewCache() *cache {
return &cache{
monitoredPrefixes: make([]string, 0), // list of prefixes to check
routeCache: make(map[string][]byte),
}
}
func (c *cache) AddPrefixToCache(prefix string) {
c.monitoredPrefixes = append(c.monitoredPrefixes, prefix)
}
func (c *cache) ShouldCache(requestedRoute string) bool {
for _, prefix := range c.monitoredPrefixes {
if strings.HasPrefix(requestedRoute, prefix) {
return true
}
}
return false
}
func (c *cache) Add(route string, data []byte) {
c.routeCache[route] = data
}
func (c *cache) Get(route string) ([]byte, bool) {
if data, ok := c.routeCache[route]; ok {
return data, true
}
return nil, false
}
const QueueLimit = 5
const BackendServerPort = 64000
const ProxyPort = 64001
func handleErr(context string, err error) {
if err != nil {
fmt.Printf("%s: %s ", context, err)
os.Exit(1)
}
}
func parseHTTPPath(rawRequest []byte) string {
reqAsStr := string(rawRequest)
// get first line
// assume a GET request
methodIndex := strings.Index(reqAsStr, "GET")
if methodIndex == -1 {
return ""
}
suffixIndex := strings.Index(reqAsStr, "HTTP")
if suffixIndex == -1 {
return ""
}
route := reqAsStr[methodIndex+4:suffixIndex]
return strings.TrimSpace(route)
}
func initBackendConnection() (int, error) {
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_STREAM, 0)
handleErr("init backend client", err)
err = unix.Bind(fd, &unix.SockaddrInet4{})
handleErr("binding backend client", err)
backendAddr := &unix.SockaddrInet4{
Port: BackendServerPort,
}
return fd, unix.Connect(fd, backendAddr)
}
// initialize the proxy socket, bind it, and start listening for connections
func initProxy() (int, error) {
serverSock, err := unix.Socket(unix.AF_INET, unix.SOCK_STREAM, 0)
handleErr("init socket", err)
sa := &unix.SockaddrInet4{Port: ProxyPort}
err = unix.Bind(serverSock, sa)
handleErr("binding socket", err)
err = unix.Listen(serverSock, QueueLimit)
return serverSock, err
}
func main() {
c := NewCache()
c.AddPrefixToCache("website/")
// start connection to server
backendClient, err := initBackendConnection()
handleErr("connecting to backend", err)
server, err := initProxy()
handleErr("initializing proxy socket", err)
fmt.Println("server booted")
for {
fmt.Println("waiting for client request")
client, _, err := unix.Accept(server)
clientBuf := make([]byte, 1024)
handleErr("accepting connection", err)
fmt.Println("calling recv from")
bytesRead, _, err := unix.Recvfrom(client, clientBuf, 0) // WAIT_ALL is the wrong flag here -- why?
handleErr("reading from client", err)
if bytesRead > 0 {
err = unix.Send(backendClient, clientBuf, 0)
handleErr("sending client request to backend", err)
requestedPath := parseHTTPPath(clientBuf)
fmt.Println("requested path is ", requestedPath)
if data, ok := c.Get(requestedPath); ok {
fmt.Println("cache hit")
err = unix.Send(client, data, 0)
} else {
fmt.Println("cache miss")
serverBuf := make([]byte, 1024)
serverBytesRead, _, err := unix.Recvfrom(backendClient, serverBuf, 0)
if serverBytesRead > 0 {
if c.ShouldCache(requestedPath) {
c.Add(requestedPath, serverBuf)
fmt.Println("caching")
}
err = unix.Send(client, serverBuf, 0)
handleErr("sending backend response back to client", err)
}
}
}
err = unix.Close(client)
handleErr("closing client", err)
}
err = unix.Close(server)
handleErr("closing server socket", err)
fmt.Println("Shutting down server")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment