How to Build a Caching Proxy in Go

Web
Build a Go caching proxy using an HTTP server and sync.Map to store and serve upstream responses.

Build a caching proxy in Go by creating an HTTP server that forwards requests to an upstream server and stores responses in memory using a sync.Map.

package main

import (
	"io"
	"log"
	"net/http"
	"sync"
)

var cache = sync.Map{}

func main() {
	upstream := "http://example.com"
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if resp, ok := cache.Load(r.URL.Path); ok {
			cached := resp.(*http.Response)
			copyHeaders(w.Header(), cached.Header)
			w.WriteHeader(cached.StatusCode)
			io.Copy(w, cached.Body)
			return
		}
		resp, err := http.Get(upstream + r.URL.Path)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadGateway)
			return
		}
		defer resp.Body.Close()
		body, _ := io.ReadAll(resp.Body)
		cached := &http.Response{
			StatusCode: resp.StatusCode,
			Header:     resp.Header,
			Body:       io.NopCloser(&bodyReader{body}),
		}
		cache.Store(r.URL.Path, cached)
		copyHeaders(w.Header(), resp.Header)
		w.WriteHeader(resp.StatusCode)
		w.Write(body)
	})
	log.Fatal(http.ListenAndServe(":8080", mux))
}

type bodyReader struct{ b []byte }
func (r *bodyReader) Read(p []byte) (int, error) { return copy(p, r.b), nil }
func copyHeaders(dst, src http.Header) { for k, v := range src { dst[k] = v } }
  1. Initialize a sync.Map to store cached responses keyed by URL path.
  2. Define an HTTP handler that checks the map for an existing response before making a new request.
  3. If a cache miss occurs, fetch the response from the upstream server, read the body, and store a new http.Response object in the map.
  4. Serve the cached or fresh response to the client, copying headers and status codes.
  5. Start the server on port 8080 to listen for incoming requests.