|
| 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 | +} |
0 commit comments