Skip to content

Commit

Permalink
feat: added caching and security middlewares
Browse files Browse the repository at this point in the history
  • Loading branch information
hokamsingh committed Aug 28, 2024
1 parent 9818054 commit adbfdb2
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 29 deletions.
73 changes: 45 additions & 28 deletions internal/core/middleware/cacher.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package middleware

import (
"bytes"
"compress/gzip"
"context"
"encoding/gob"
"io"
"log"
"net/http"
Expand All @@ -23,13 +23,10 @@ func NewCaching(redisAddr string, ttl time.Duration, cacheControl bool) *Caching
client := redis.NewClient(&redis.Options{
Addr: redisAddr, // e.g., "localhost:6379"
})

// Attempt to ping Redis
if _, err := client.Ping(ctx).Result(); err != nil {
log.Printf("Could not connect to Redis: %v. Caching will be disabled.", err)
client = nil // Mark Redis as unavailable
_, err := client.Ping(ctx).Result()
if err != nil {
log.Fatalf("Could not connect to Redis: %v", err)
}

return &Caching{
client: client,
ttl: ttl,
Expand All @@ -47,53 +44,68 @@ func (c *Caching) Handle(next http.Handler) http.Handler {
return
}

// Proceed without caching if Redis is unavailable
if c.client != nil && r.Method == http.MethodGet {
if r.Method == http.MethodGet {
data, err := c.client.Get(ctx, r.RequestURI).Result()
if err == nil {
// Cache hit: decompress data
reader, err := gzip.NewReader(bytes.NewReader([]byte(data)))
// Cache hit: deserialize cached response
var cachedResponse cachedResponse
decoder := gob.NewDecoder(bytes.NewReader([]byte(data)))
err := decoder.Decode(&cachedResponse)
if err != nil {
log.Printf("Error decompressing cache data: %v. Proceeding without cache.", err)
log.Printf("Error decoding cached response: %v", err)
next.ServeHTTP(w, r)
return
}
defer reader.Close()

// Write decompressed data to response
// Write cached headers
for key, values := range cachedResponse.Headers {
for _, value := range values {
w.Header().Add(key, value)
}
}

// Write cached body
w.Header().Set("X-Cache-Hit", "true")
w.Header().Set("Content-Encoding", "gzip")
io.Copy(w, reader)
io.WriteString(w, cachedResponse.Body)
return
} else if err != redis.Nil {
log.Printf("Error retrieving from cache: %v. Proceeding without cache.", err)
log.Printf("Error retrieving from cache: %v", err)
}
}

// Capture and compress response
// Capture response
rec := &responseRecorder{ResponseWriter: w, statusCode: http.StatusOK, body: new(bytes.Buffer)}
next.ServeHTTP(rec, r)

// Cache only successful responses (status codes 200-299) if Redis is available
if c.client != nil && r.Method == http.MethodGet && rec.statusCode >= http.StatusOK && rec.statusCode < 300 {
var compressedData bytes.Buffer
gzipWriter := gzip.NewWriter(&compressedData)
_, err := gzipWriter.Write(rec.body.Bytes())
// Cache only successful responses (status code 200)
if r.Method == http.MethodGet && rec.statusCode == http.StatusOK {
cachedResponse := cachedResponse{
Headers: rec.Header(),
Body: rec.body.String(),
}

var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer)
err := encoder.Encode(cachedResponse)
if err != nil {
log.Printf("Error compressing cache data: %v. Proceeding without cache.", err)
log.Printf("Error encoding cached response: %v", err)
return
}
gzipWriter.Close()

// Store compressed data in Redis
err = c.client.Set(ctx, r.RequestURI, compressedData.Bytes(), c.ttl).Err()
err = c.client.Set(ctx, r.RequestURI, buffer.Bytes(), c.ttl).Err()
if err != nil {
log.Printf("Error setting cache: %v. Proceeding without cache.", err)
log.Printf("Error setting cache: %v", err)
}
}
})
}

// cachedResponse stores both headers and body
type cachedResponse struct {
Headers http.Header
Body string
}

type responseRecorder struct {
http.ResponseWriter
statusCode int
Expand All @@ -116,3 +128,8 @@ func (rec *responseRecorder) Flush() {
flusher.Flush()
}
}

func init() {
// Register the cachedResponse type with gob so it can be encoded/decoded
gob.Register(cachedResponse{})
}
5 changes: 4 additions & 1 deletion internal/core/middleware/json_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"io"
"log"
"net/http"
)

Expand Down Expand Up @@ -57,6 +58,7 @@ func (jp *JSONParser) Handle(next http.Handler) http.Handler {
// Read the body into a byte slice
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
log.Print(err)
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
Expand All @@ -65,8 +67,9 @@ func (jp *JSONParser) Handle(next http.Handler) http.Handler {
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

// Decode the body into a map
var body map[string]interface{}
var body interface{} // map[string]interface{} global
if err := json.Unmarshal(bodyBytes, &body); err != nil {
log.Print(err)
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
Expand Down

0 comments on commit adbfdb2

Please sign in to comment.