Skip to content

Commit 6918544

Browse files
committed
moved server from echo to http, added config support
1 parent ef7ad94 commit 6918544

File tree

18 files changed

+523
-42
lines changed

18 files changed

+523
-42
lines changed

api/handlers/cache_handlers.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package handlers
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"graphql_cache/config"
7+
"graphql_cache/graphcache"
8+
"io"
9+
"log"
10+
"net/http"
11+
"net/url"
12+
"time"
13+
)
14+
15+
func GetCacheHandler(cache *graphcache.GraphCache, cfg *config.Config) http.HandlerFunc {
16+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17+
w.Header().Add("Content-Type", "application/json")
18+
19+
// Create a new HTTP request with the same method, URL, and body as the original request
20+
targetURL, err := url.Parse(cfg.Origin)
21+
if err != nil {
22+
log.Println(err)
23+
http.Error(w, "Error parsing target URL", http.StatusInternalServerError)
24+
}
25+
26+
proxyReq, err := http.NewRequest(r.Method, targetURL.String(), r.Body)
27+
if err != nil {
28+
log.Println(err)
29+
http.Error(w, "Error creating proxy request", http.StatusInternalServerError)
30+
}
31+
32+
// Copy the headers from the original request to the proxy request
33+
for name, values := range r.Header {
34+
for _, value := range values {
35+
proxyReq.Header.Add(name, value)
36+
}
37+
}
38+
39+
requestBody, err := io.ReadAll(proxyReq.Body)
40+
if err != nil {
41+
log.Println(err)
42+
http.Error(w, "error reading request body", http.StatusInternalServerError)
43+
}
44+
45+
request := graphcache.GraphQLRequest{}
46+
request.FromBytes(requestBody)
47+
48+
start := time.Now()
49+
50+
astQuery, err := graphcache.GetASTFromQuery(request.Query)
51+
if err != nil {
52+
log.Println(err)
53+
http.Error(w, "error parsing query", http.StatusInternalServerError)
54+
}
55+
56+
transformedBody, err := graphcache.AddTypenameToQuery(request.Query)
57+
if err != nil {
58+
log.Println(err)
59+
http.Error(w, "error transforming body", http.StatusInternalServerError)
60+
}
61+
62+
log.Println("time taken to transform body ", time.Since(start))
63+
64+
transformedRequest := request
65+
transformedRequest.Query = transformedBody
66+
67+
if len(astQuery.Operations) > 0 && astQuery.Operations[0].Operation == "mutation" {
68+
// if the operation is a mutation, we don't cache it
69+
70+
proxyReq.Body = io.NopCloser(bytes.NewBuffer(transformedRequest.Bytes()))
71+
proxyReq.ContentLength = -1
72+
73+
client := http.Client{}
74+
// Send the proxy request using the custom transport
75+
resp, err := client.Do(proxyReq)
76+
if err != nil {
77+
http.Error(w, "Error sending proxy request", http.StatusInternalServerError)
78+
79+
}
80+
defer resp.Body.Close()
81+
82+
// Copy the headers from the proxy response to the original response
83+
for name, values := range resp.Header {
84+
for _, value := range values {
85+
w.Header().Add(name, value)
86+
}
87+
}
88+
89+
// Set the status code of the original response to the status code of the proxy response
90+
w.WriteHeader(resp.StatusCode)
91+
92+
// Copy the body of the proxy response to the original response
93+
io.Copy(w, resp.Body)
94+
95+
responseBody := new(bytes.Buffer)
96+
io.Copy(responseBody, resp.Body)
97+
98+
responseMap := make(map[string]interface{})
99+
err = json.Unmarshal(responseBody.Bytes(), &responseMap)
100+
if err != nil {
101+
log.Println("Error unmarshalling response:", err)
102+
}
103+
104+
cache.InvalidateCache("data", responseMap, nil)
105+
return
106+
}
107+
108+
cachedResponse, err := cache.ParseASTBuildResponse(astQuery, request)
109+
if err == nil && cachedResponse != nil {
110+
log.Println("serving response from cache...")
111+
br, err := json.Marshal(cachedResponse)
112+
if err != nil {
113+
http.Error(w, "error marshalling response", http.StatusInternalServerError)
114+
}
115+
log.Println("time taken to serve response from cache ", time.Since(start))
116+
graphqlresponse := graphcache.GraphQLResponse{Data: json.RawMessage(br)}
117+
res, err := cache.RemoveTypenameFromResponse(&graphqlresponse)
118+
if err != nil {
119+
http.Error(w, "error removing __typename", http.StatusInternalServerError)
120+
}
121+
w.Write(res.Bytes())
122+
return
123+
}
124+
125+
proxyReq.Body = io.NopCloser(bytes.NewBuffer(transformedRequest.Bytes()))
126+
proxyReq.ContentLength = -1
127+
128+
client := http.Client{}
129+
130+
// Send the proxy request using the custom transport
131+
resp, err := client.Do(proxyReq)
132+
if err != nil {
133+
log.Println(err)
134+
http.Error(w, "error sending proxy request", http.StatusInternalServerError)
135+
}
136+
defer resp.Body.Close()
137+
138+
// Copy the headers from the proxy response to the original response
139+
for name, values := range resp.Header {
140+
for _, value := range values {
141+
w.Header().Add(name, value)
142+
}
143+
}
144+
145+
responseBody := new(bytes.Buffer)
146+
io.Copy(responseBody, resp.Body)
147+
148+
responseMap := make(map[string]interface{})
149+
err = json.Unmarshal(responseBody.Bytes(), &responseMap)
150+
if err != nil {
151+
log.Println("Error unmarshalling response:", err)
152+
}
153+
154+
log.Println("time taken to get response from API ", time.Since(start))
155+
156+
astWithTypes, err := graphcache.GetASTFromQuery(transformedRequest.Query)
157+
if err != nil {
158+
log.Println(err)
159+
http.Error(w, "error parsing query", http.StatusInternalServerError)
160+
}
161+
162+
log.Println("time taken to generate AST with types ", time.Since(start))
163+
164+
reqVariables := transformedRequest.Variables
165+
variables := make(map[string]interface{})
166+
if reqVariables != nil {
167+
variables = reqVariables
168+
}
169+
170+
for _, op := range astWithTypes.Operations {
171+
// for the operation op we need to traverse the response and build the relationship map where key is the requested field and value is the key where the actual response is stored in the cache
172+
responseKey := cache.GetQueryResponseKey(op, responseMap, variables)
173+
for key, value := range responseKey {
174+
if value != nil {
175+
cache.SetQueryCache(key, value)
176+
}
177+
}
178+
}
179+
180+
log.Println("time taken to build response key ", time.Since(start))
181+
182+
// go through the response. Every object that has a __typename field, and an id field cache it in the format of typename:id
183+
// for example, if the response has an object with __typename: "Organisation" and id: "1234", cache it as Organisation:1234
184+
// if the object has a nested object with __typename: "User" and id: "5678", cache
185+
// it as User:5678
186+
187+
cache.CacheResponse("data", responseMap, nil)
188+
189+
log.Println("time taken to cache response ", time.Since(start))
190+
191+
// Copy the body of the proxy response to the original response
192+
// io.Copy(w, resp.Body)
193+
194+
resBody, err := io.ReadAll(resp.Body)
195+
if err != nil {
196+
http.Error(w, "error reading response body", http.StatusInternalServerError)
197+
}
198+
199+
newResponse := &graphcache.GraphQLResponse{}
200+
newResponse.FromBytes(resBody)
201+
res, err := cache.RemoveTypenameFromResponse(newResponse)
202+
if err != nil {
203+
http.Error(w, "error removing __typename", http.StatusInternalServerError)
204+
}
205+
w.Write(res.Bytes())
206+
// w.WriteHeader(http.StatusOK)
207+
})
208+
}

api/handlers/cache_purge_handlers.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package handlers
2+
3+
import (
4+
"encoding/json"
5+
"graphql_cache/config"
6+
"graphql_cache/graphcache"
7+
"net/http"
8+
)
9+
10+
func GetFlushCacheHandler(Cache *graphcache.GraphCache, cfg *config.Config) http.HandlerFunc {
11+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
12+
Cache.Flush()
13+
w.WriteHeader(http.StatusOK)
14+
w.Write([]byte("success"))
15+
})
16+
}
17+
18+
func GetFlushCacheByTypeHandler(Cache *graphcache.GraphCache, cfg *config.Config) http.HandlerFunc {
19+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20+
flushByTypeRequest := struct {
21+
Type string `json:"type"`
22+
ID string `json:"id"`
23+
}{}
24+
err := json.NewDecoder(r.Body).Decode(&flushByTypeRequest)
25+
if err != nil {
26+
http.Error(w, "error decoding request", http.StatusBadRequest)
27+
}
28+
Cache.FlushByType(flushByTypeRequest.Type, flushByTypeRequest.ID)
29+
w.WriteHeader(http.StatusOK)
30+
w.Write([]byte("success"))
31+
})
32+
}

api/handlers/debug_handlers.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package handlers
2+
3+
import (
4+
"graphql_cache/config"
5+
"graphql_cache/graphcache"
6+
"net/http"
7+
)
8+
9+
func GetDebugHandler(Cache *graphcache.GraphCache, cfg *config.Config) http.HandlerFunc {
10+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
11+
Cache.Debug()
12+
w.WriteHeader(http.StatusOK)
13+
w.Write([]byte("success"))
14+
})
15+
}

api/handlers/handlers.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package handlers
2+
3+
import (
4+
"graphql_cache/config"
5+
"graphql_cache/graphcache"
6+
"net/http"
7+
)
8+
9+
func GetHandlers(cache *graphcache.GraphCache, cfg *config.Config) *http.ServeMux {
10+
api := http.NewServeMux()
11+
api.Handle(cfg.Handlers.DebugPath, GetDebugHandler(cache, cfg))
12+
api.Handle(cfg.Handlers.FlushAllPath, GetFlushCacheHandler(cache, cfg))
13+
api.Handle(cfg.Handlers.FlushByTypePath, GetFlushCacheByTypeHandler(cache, cfg))
14+
api.Handle(cfg.Handlers.GraphQLPath, GetCacheHandler(cache, cfg))
15+
return api
16+
}

api/server.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package api
2+
3+
import (
4+
"graphql_cache/api/handlers"
5+
"graphql_cache/config"
6+
"graphql_cache/graphcache"
7+
"net/http"
8+
"strconv"
9+
)
10+
11+
type Server struct {
12+
httpServer *http.Server
13+
cache *graphcache.GraphCache
14+
cfg *config.Config
15+
}
16+
17+
func NewGraphCache(cfg *config.Config) *graphcache.GraphCache {
18+
return graphcache.NewGraphCache(cfg.CacheBackend)
19+
}
20+
21+
func NewServer(cfg *config.Config) *Server {
22+
cache := NewGraphCache(cfg)
23+
return &Server{
24+
cache: cache,
25+
cfg: cfg,
26+
httpServer: &http.Server{
27+
Addr: ":" + strconv.Itoa(cfg.Port),
28+
Handler: handlers.GetHandlers(cache, cfg),
29+
},
30+
}
31+
}
32+
33+
func (s *Server) Start() error {
34+
return s.httpServer.ListenAndServe()
35+
}

config.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
# First thing to configure is what endpoints should our cache forward requests to.
3+
# If your cache is running on https://api.acme.com/api/graphql then you should set the following:
4+
# origin="https://api.acme.com/api/graphql"
5+
6+
origin="http://localhost:8080"
7+
8+
9+
# Next, we need to configure the port that our cache will run on.
10+
# If you want to run the cache on port 8080, set the following:
11+
# port=8080
12+
13+
port=9090
14+
15+
16+
# We also need to configure the cache backend, do you want to cache the values in memory or in redis. Currently only in memory and redis is supported. Here is a list of supported values for cache_backend:
17+
# redis
18+
# in_memory
19+
20+
cache_backend="redis"
21+
22+
23+
# If you want to override the API paths for the cache server, you can configure them here.
24+
25+
[handlers]
26+
# flush_all_path="/flush"
27+
# flush_by_type_path="/flush.type"
28+
# debug_path="/debug"
29+
# health_path="/health"
30+
# graphql_path="/graphql"

0 commit comments

Comments
 (0)