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 } }
- Initialize a
sync.Mapto store cached responses keyed by URL path. - Define an HTTP handler that checks the map for an existing response before making a new request.
- If a cache miss occurs, fetch the response from the upstream server, read the body, and store a new
http.Responseobject in the map. - Serve the cached or fresh response to the client, copying headers and status codes.
- Start the server on port 8080 to listen for incoming requests.