1
1
import asyncio
2
2
import glob
3
3
import hashlib
4
+ import json
4
5
import os
5
- import pickle
6
- from typing import Any , Callable , Dict , Optional
6
+ from typing import Callable , Dict , Optional
7
7
8
8
from backend .state import BackendRequest
9
9
from backend .state import BackendState
@@ -23,67 +23,67 @@ def __init__(self, app: ASGIApp, state: BackendState, cache_dir: str = "cache"):
23
23
os .makedirs (self .cache_dir )
24
24
25
25
async def dispatch (self , request : BackendRequest , call_next : Callable ):
26
- if request .url .path .startswith ("/api/snapshot" ):
27
- return await call_next (request )
28
- if request .url .path .startswith ("/api/price_shock" ):
29
- return await call_next (request )
30
26
if not request .url .path .startswith ("/api" ):
31
27
return await call_next (request )
32
- if self .state .current_pickle_path == "bootstrap" :
33
- return await call_next (request )
34
28
35
29
current_pickle = self .state .current_pickle_path
36
30
previous_pickle = self ._get_previous_pickle ()
37
31
38
- # Try to serve data from the current (latest) pickle first
39
32
current_cache_key = self ._generate_cache_key (request , current_pickle )
40
- current_cache_file = os .path .join (self .cache_dir , f"{ current_cache_key } .pkl " )
33
+ current_cache_file = os .path .join (self .cache_dir , f"{ current_cache_key } .json " )
41
34
42
35
if os .path .exists (current_cache_file ):
43
- print (f"Serving latest data for { request .url .path } " )
44
- with open (current_cache_file , "rb" ) as f :
45
- response_data = pickle .load (f )
46
-
47
- return Response (
48
- content = response_data ["content" ],
49
- status_code = response_data ["status_code" ],
50
- headers = dict (response_data ["headers" ], ** {"X-Cache-Status" : "Fresh" }),
51
- )
36
+ return self ._serve_cached_response (current_cache_file , "Fresh" )
52
37
53
- # If no data in current pickle, try the previous pickle
54
38
if previous_pickle :
55
39
previous_cache_key = self ._generate_cache_key (request , previous_pickle )
56
40
previous_cache_file = os .path .join (
57
- self .cache_dir , f"{ previous_cache_key } .pkl "
41
+ self .cache_dir , f"{ previous_cache_key } .json "
58
42
)
59
43
60
44
if os .path .exists (previous_cache_file ):
61
- print (f"Serving stale data for { request .url .path } " )
62
- with open (previous_cache_file , "rb" ) as f :
63
- response_data = pickle .load (f )
64
-
65
- # Prepare background task
66
- background_tasks = BackgroundTasks ()
67
- background_tasks .add_task (
68
- self ._fetch_and_cache ,
45
+ return await self ._serve_stale_response (
46
+ previous_cache_file ,
69
47
request ,
70
48
call_next ,
71
49
current_cache_key ,
72
50
current_cache_file ,
73
51
)
74
52
75
- response = Response (
76
- content = response_data ["content" ],
77
- status_code = response_data ["status_code" ],
78
- headers = dict (
79
- response_data ["headers" ], ** {"X-Cache-Status" : "Stale" }
80
- ),
81
- )
82
- response .background = background_tasks
83
- return response
53
+ return await self ._serve_miss_response (
54
+ request , call_next , current_cache_key , current_cache_file
55
+ )
84
56
85
- # If no data available, return an empty response and fetch fresh data in the background
86
- print (f"No data available for { request .url .path } " )
57
+ def _serve_cached_response (self , cache_file : str , cache_status : str ):
58
+ print (f"Serving { cache_status .lower ()} data" )
59
+ with open (cache_file , "r" ) as f :
60
+ response_data = json .load (f )
61
+
62
+ content = json .dumps (response_data ["content" ]).encode ("utf-8" )
63
+ headers = {
64
+ k : v
65
+ for k , v in response_data ["headers" ].items ()
66
+ if k .lower () != "content-length"
67
+ }
68
+ headers ["Content-Length" ] = str (len (content ))
69
+ headers ["X-Cache-Status" ] = cache_status
70
+
71
+ return Response (
72
+ content = content ,
73
+ status_code = response_data ["status_code" ],
74
+ headers = headers ,
75
+ media_type = "application/json" ,
76
+ )
77
+
78
+ async def _serve_stale_response (
79
+ self ,
80
+ cache_file : str ,
81
+ request : BackendRequest ,
82
+ call_next : Callable ,
83
+ current_cache_key : str ,
84
+ current_cache_file : str ,
85
+ ):
86
+ response = self ._serve_cached_response (cache_file , "Stale" )
87
87
background_tasks = BackgroundTasks ()
88
88
background_tasks .add_task (
89
89
self ._fetch_and_cache ,
@@ -92,12 +92,32 @@ async def dispatch(self, request: BackendRequest, call_next: Callable):
92
92
current_cache_key ,
93
93
current_cache_file ,
94
94
)
95
+ response .background = background_tasks
96
+ return response
97
+
98
+ async def _serve_miss_response (
99
+ self ,
100
+ request : BackendRequest ,
101
+ call_next : Callable ,
102
+ cache_key : str ,
103
+ cache_file : str ,
104
+ ):
105
+ print (f"No data available for { request .url .path } " )
106
+ background_tasks = BackgroundTasks ()
107
+ background_tasks .add_task (
108
+ self ._fetch_and_cache ,
109
+ request ,
110
+ call_next ,
111
+ cache_key ,
112
+ cache_file ,
113
+ )
114
+ content = json .dumps ({"result" : "miss" }).encode ("utf-8" )
95
115
96
- # Return an empty response immediately
97
116
response = Response (
98
- content = '{"result": "miss"}' ,
99
- status_code = 200 , # No Content
100
- headers = {"X-Cache-Status" : "Miss" },
117
+ content = content ,
118
+ status_code = 200 ,
119
+ headers = {"X-Cache-Status" : "Miss" , "Content-Length" : str (len (content ))},
120
+ media_type = "application/json" ,
101
121
)
102
122
response .background = background_tasks
103
123
return response
@@ -120,15 +140,21 @@ async def _fetch_and_cache(
120
140
response_body = b""
121
141
async for chunk in response .body_iterator :
122
142
response_body += chunk
143
+
144
+ body_content = json .loads (response_body .decode ())
123
145
response_data = {
124
- "content" : response_body ,
146
+ "content" : body_content ,
125
147
"status_code" : response .status_code ,
126
- "headers" : dict (response .headers ),
148
+ "headers" : {
149
+ k : v
150
+ for k , v in response .headers .items ()
151
+ if k .lower () != "content-length"
152
+ },
127
153
}
128
154
129
155
os .makedirs (os .path .dirname (cache_file ), exist_ok = True )
130
- with open (cache_file , "wb " ) as f :
131
- pickle .dump (response_data , f )
156
+ with open (cache_file , "w " ) as f :
157
+ json .dump (response_data , f )
132
158
print (f"Cached fresh data for { request .url .path } " )
133
159
else :
134
160
print (
0 commit comments