Skip to content

Commit c376754

Browse files
committed
added cache proxy api and separate package for caching functions
1 parent c47f2e6 commit c376754

File tree

11 files changed

+842
-13
lines changed

11 files changed

+842
-13
lines changed

cache/cache.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ type Cache interface {
77
Exists(key string) (bool, error)
88
Map() (map[string]interface{}, error)
99
JSON() ([]byte, error)
10+
Debug(identifier string) error
1011
}

cache/redis_cache.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type RedisCache struct {
2424
// })
2525
// }
2626

27-
func NewRedisCache() *RedisCache {
27+
func NewRedisCache() Cache {
2828
c := redis.NewClient(&redis.Options{
2929
Addr: "localhost:6379",
3030
Password: "", // no password set

cache_proxy/balancer/balancer.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package balancer
2+
3+
import (
4+
"github.com/labstack/echo/v4"
5+
"github.com/labstack/echo/v4/middleware"
6+
)
7+
8+
type DefaultProxyBalancer struct {
9+
target *middleware.ProxyTarget
10+
}
11+
12+
func NewDefaultProxyBalancer(target *middleware.ProxyTarget) middleware.ProxyBalancer {
13+
return &DefaultProxyBalancer{
14+
target: target,
15+
}
16+
}
17+
18+
func (b *DefaultProxyBalancer) AddTarget(target *middleware.ProxyTarget) bool {
19+
b.target = target
20+
return true
21+
}
22+
23+
func (b *DefaultProxyBalancer) RemoveTarget(targetURL string) bool {
24+
b.target = nil
25+
return false
26+
}
27+
28+
func (b *DefaultProxyBalancer) Next(echo.Context) *middleware.ProxyTarget {
29+
if b.target != nil {
30+
return b.target
31+
}
32+
return nil
33+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package cache_middleware
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"encoding/json"
7+
"fmt"
8+
"graphql_cache/graphcache"
9+
"graphql_cache/transformer"
10+
"graphql_cache/utils/ast_utils"
11+
"io"
12+
"net"
13+
"net/http"
14+
"time"
15+
16+
"github.com/labstack/echo/v4"
17+
)
18+
19+
var gc = graphcache.NewGraphCache()
20+
21+
func CacheMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
22+
return func(c echo.Context) error {
23+
start := time.Now()
24+
25+
requestBytes, err := io.ReadAll(c.Request().Body)
26+
if err != nil {
27+
fmt.Println("Error reading request body:", err)
28+
return nil
29+
}
30+
31+
fmt.Println("request content length ", c.Request().ContentLength)
32+
fmt.Println("request body length", len(requestBytes))
33+
34+
var request graphcache.GraphQLRequest
35+
err = json.Unmarshal(requestBytes, &request)
36+
if err != nil {
37+
fmt.Println("Error unmarshalling request:", err)
38+
return nil
39+
}
40+
41+
astQuery, err := ast_utils.GetASTFromQuery(request.Query)
42+
if err != nil {
43+
fmt.Println("Error parsing query:", err)
44+
return nil
45+
}
46+
47+
if astQuery.Operations[0].Operation == "mutation" {
48+
// if the operation is a mutation, we don't cache it
49+
c.Request().Body = io.NopCloser(bytes.NewBuffer(requestBytes))
50+
c.Request().ContentLength = -1
51+
return next(c)
52+
}
53+
54+
cachedResponse, err := gc.ParseASTBuildResponse(astQuery, request)
55+
if err == nil && cachedResponse != nil {
56+
fmt.Println("serving response from cache...")
57+
br, err := json.Marshal(cachedResponse)
58+
if err != nil {
59+
return err
60+
}
61+
fmt.Println("time taken to serve response from cache ", time.Since(start))
62+
graphqlresponse := graphcache.GraphQLResponse{Data: json.RawMessage(br)}
63+
res, err := gc.RemoveTypenameFromResponse(&graphqlresponse)
64+
if err != nil {
65+
fmt.Println("Error removing __typename:", err)
66+
return nil
67+
}
68+
return c.JSON(200, res)
69+
}
70+
71+
transformedBody, err := transformer.TransformBody(request.Query, astQuery)
72+
if err != nil {
73+
fmt.Println("Error transforming body:", err)
74+
return nil
75+
}
76+
77+
fmt.Println("time taken to transform body ", time.Since(start))
78+
79+
transformedRequest := request
80+
transformedRequest.Query = transformedBody
81+
82+
c.Request().Body = io.NopCloser(bytes.NewBuffer(transformedRequest.Bytes()))
83+
c.Request().ContentLength = -1
84+
85+
resBody := new(bytes.Buffer)
86+
mw := io.MultiWriter(c.Response().Writer, resBody)
87+
writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer}
88+
c.Response().Writer = writer
89+
err = next(c)
90+
if err != nil {
91+
return err
92+
}
93+
responseMap := make(map[string]interface{})
94+
err = json.Unmarshal(resBody.Bytes(), &responseMap)
95+
if err != nil {
96+
fmt.Println("Error unmarshalling response:", err)
97+
}
98+
99+
fmt.Println("time taken to get response from API ", time.Since(start))
100+
101+
astWithTypes, err := ast_utils.GetASTFromQuery(transformedRequest.Query)
102+
if err != nil {
103+
fmt.Println("Error parsing query:", err)
104+
return nil
105+
}
106+
107+
fmt.Println("time taken to generate AST with types ", time.Since(start))
108+
109+
reqVariables := transformedRequest.Variables
110+
variables := make(map[string]interface{})
111+
if reqVariables != nil {
112+
variables = reqVariables
113+
}
114+
115+
for _, op := range astWithTypes.Operations {
116+
// for the operation op we need to traverse the response and the ast together to build a graph of the relations
117+
118+
// build the relation graph
119+
responseKey := gc.GetQueryResponseKey(op, responseMap, variables)
120+
for key, value := range responseKey {
121+
if value != nil {
122+
gc.SetQueryCache(key, value)
123+
}
124+
}
125+
}
126+
127+
fmt.Println("time taken to build response key ", time.Since(start))
128+
129+
// go through the response. Every object that has a __typename field, and an id field cache it in the format of typename:id
130+
// for example, if the response has an object with __typename: "Organisation" and id: "1234", cache it as Organisation:1234
131+
// if the object has a nested object with __typename: "User" and id: "5678", cache
132+
// it as User:5678
133+
134+
gc.CacheResponse("data", responseMap, nil)
135+
136+
fmt.Println("time taken to cache response ", time.Since(start))
137+
138+
// cacheStore.Debug("cacheStore")
139+
// recordCacheStore.Debug("recordCacheStore")
140+
// queryCacheStore.Debug("queryCacheStore")
141+
142+
// cacheState, _ := cacheStore.JSON()
143+
// fmt.Println(string(cacheState))
144+
145+
// recordCacheState, _ := recordCacheStore.JSON()
146+
// fmt.Println(string(recordCacheState))
147+
148+
// queryCacheState, _ := queryCacheStore.JSON()
149+
// fmt.Println(string(queryCacheState))
150+
151+
fmt.Println("time taken to finish completely ", time.Since(start))
152+
newResponse := &graphcache.GraphQLResponse{}
153+
newResponse.FromBytes(resBody.Bytes())
154+
gc.RemoveTypenameFromResponse(newResponse)
155+
c.Response().Header().Set("X-Proxy", "GraphQL Cache")
156+
return nil
157+
}
158+
}
159+
160+
type bodyDumpResponseWriter struct {
161+
io.Writer
162+
http.ResponseWriter
163+
}
164+
165+
func (w *bodyDumpResponseWriter) WriteHeader(code int) {
166+
w.ResponseWriter.WriteHeader(code)
167+
}
168+
169+
func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) {
170+
return w.Writer.Write(b)
171+
}
172+
173+
func (w *bodyDumpResponseWriter) Flush() {
174+
w.ResponseWriter.(http.Flusher).Flush()
175+
}
176+
177+
func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
178+
return w.ResponseWriter.(http.Hijacker).Hijack()
179+
}

cache_proxy/main.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import (
4+
"graphql_cache/cache_proxy/balancer"
5+
"graphql_cache/cache_proxy/cache_middleware"
6+
"net/url"
7+
8+
"github.com/labstack/echo/v4"
9+
"github.com/labstack/echo/v4/middleware"
10+
)
11+
12+
const API_URL = "http://127.0.0.1:8080"
13+
14+
func main() {
15+
16+
e := echo.New()
17+
e.Use(middleware.Recover())
18+
e.Use(middleware.Logger())
19+
20+
e.Use(cache_middleware.CacheMiddleware)
21+
apiSever, err := url.Parse(API_URL)
22+
if err != nil {
23+
e.Logger.Fatal(err)
24+
}
25+
balancer := balancer.NewDefaultProxyBalancer(&middleware.ProxyTarget{
26+
URL: apiSever,
27+
})
28+
29+
e.Use(middleware.Proxy(balancer))
30+
31+
e.Logger.Fatal(e.Start(":9090"))
32+
}

0 commit comments

Comments
 (0)