1
- use std:: sync:: Arc ;
2
- use std:: io:: { Error as IoError , ErrorKind } ;
3
1
use chrono:: { DateTime , Utc } ;
2
+ use std:: io:: { Error as IoError , ErrorKind } ;
3
+ use std:: sync:: Arc ;
4
4
5
5
use futures:: StreamExt ;
6
6
use opentelemetry:: global;
@@ -37,14 +37,21 @@ pub async fn serve(state: State) {
37
37
}
38
38
39
39
/// Attempt an SPA fallback by serving `index.html` from the same deployment.
40
- async fn spa_fallback ( deployment_id : & str , last_modified : DateTime < Utc > , cid : Option < String > , state : & State ) -> Response {
40
+ async fn spa_fallback (
41
+ deployment_id : & str ,
42
+ last_modified : DateTime < Utc > ,
43
+ cid : Option < String > ,
44
+ state : & State ,
45
+ ) -> Response {
41
46
let spa_key = format ! ( "{}:index.html" , deployment_id) ;
42
47
let deployment_str = deployment_id. to_string ( ) ;
43
48
let spa_entry: Option < DeploymentFileEntry > = state
44
49
. cache
45
50
. file_entry
46
51
. get_with ( spa_key. clone ( ) , async move {
47
- DeploymentFile :: get_file_by_path ( & state. database , & deployment_str, "index.html" ) . await . ok ( )
52
+ DeploymentFile :: get_file_by_path ( & state. database , & deployment_str, "index.html" )
53
+ . await
54
+ . ok ( )
48
55
} )
49
56
. await ;
50
57
if let Some ( entry) = spa_entry {
@@ -61,7 +68,10 @@ async fn spa_fallback(deployment_id: &str, last_modified: DateTime<Utc>, cid: Op
61
68
async fn resolve_http ( request : & Request , state : Data < & State > ) -> impl IntoResponse {
62
69
// extract host and path
63
70
let headers = request. headers ( ) ;
64
- let host = headers. get ( "host" ) . and_then ( |h| h. to_str ( ) . ok ( ) ) . unwrap_or ( "localhost" ) ;
71
+ let host = headers
72
+ . get ( "host" )
73
+ . and_then ( |h| h. to_str ( ) . ok ( ) )
74
+ . unwrap_or ( "localhost" ) ;
65
75
let raw_path = request. uri ( ) . path ( ) ;
66
76
let path = raw_path. trim_start_matches ( '/' ) . to_string ( ) ;
67
77
let state_ref = * state;
@@ -71,10 +81,16 @@ async fn resolve_http(request: &Request, state: Data<&State>) -> impl IntoRespon
71
81
// 1) domain -> Deployment
72
82
let domain_key = host. to_string ( ) ;
73
83
let state_for_domain = state_ref. clone ( ) ;
74
- let maybe_dep = state_ref. cache . domain . get_with ( domain_key. clone ( ) , async move {
75
- get_last_deployment ( host, & state_for_domain) . await . ok ( )
76
- } ) . await ;
77
- let deployment = if let Some ( dep) = maybe_dep { dep } else {
84
+ let maybe_dep = state_ref
85
+ . cache
86
+ . domain
87
+ . get_with ( domain_key. clone ( ) , async move {
88
+ get_last_deployment ( host, & state_for_domain) . await . ok ( )
89
+ } )
90
+ . await ;
91
+ let deployment = if let Some ( dep) = maybe_dep {
92
+ dep
93
+ } else {
78
94
return Response :: builder ( )
79
95
. status ( StatusCode :: NOT_FOUND )
80
96
. body ( Body :: from_string ( include_str ! ( "./404.html" ) . to_string ( ) ) ) ;
@@ -89,18 +105,30 @@ async fn resolve_http(request: &Request, state: Data<&State>) -> impl IntoRespon
89
105
let state_for_file = state_ref. clone ( ) ;
90
106
let deployment_str = deployment_id. to_string ( ) ;
91
107
let state_for_file_str = state_for_file. clone ( ) ;
92
- let maybe_file = state_ref. cache . file_entry . get_with ( entry_key. clone ( ) , async move {
93
- DeploymentFile :: get_file_by_path ( & state_for_file_str. database , & deployment_str, & path) . await . ok ( )
94
- } ) . await ;
108
+ let maybe_file = state_ref
109
+ . cache
110
+ . file_entry
111
+ . get_with ( entry_key. clone ( ) , async move {
112
+ DeploymentFile :: get_file_by_path ( & state_for_file_str. database , & deployment_str, & path)
113
+ . await
114
+ . ok ( )
115
+ } )
116
+ . await ;
95
117
if let Some ( deployment_file) = maybe_file {
96
118
return serve_deployment_file ( deployment_file, last_modified, cid, state_ref) . await ;
97
119
}
98
120
99
121
// 3) SPA fallback -> index.html
100
122
let spa_key = format ! ( "{}:index.html" , deployment_id) ;
101
- let maybe_spa = state_ref. cache . file_entry . get_with ( spa_key. clone ( ) , async move {
102
- DeploymentFile :: get_file_by_path ( & state_for_file. database , & deployment_id, "index.html" ) . await . ok ( )
103
- } ) . await ;
123
+ let maybe_spa = state_ref
124
+ . cache
125
+ . file_entry
126
+ . get_with ( spa_key. clone ( ) , async move {
127
+ DeploymentFile :: get_file_by_path ( & state_for_file. database , & deployment_id, "index.html" )
128
+ . await
129
+ . ok ( )
130
+ } )
131
+ . await ;
104
132
if let Some ( deployment_file) = maybe_spa {
105
133
return serve_deployment_file ( deployment_file, last_modified, cid, state_ref) . await ;
106
134
}
@@ -143,13 +171,25 @@ async fn get_last_deployment(host: &str, state: &State) -> Result<Deployment, Ht
143
171
}
144
172
145
173
/// Serve a deployment file entry, using full in-memory cache for eligible files or streaming otherwise.
146
- async fn serve_deployment_file ( deployment_file : DeploymentFileEntry , last_modified : DateTime < Utc > , cid : Option < String > , state : & State ) -> Response {
174
+ async fn serve_deployment_file (
175
+ deployment_file : DeploymentFileEntry ,
176
+ last_modified : DateTime < Utc > ,
177
+ cid : Option < String > ,
178
+ state : & State ,
179
+ ) -> Response {
147
180
let mime = deployment_file. deployment_file_mime_type . clone ( ) ;
148
181
let file_key = deployment_file. file_hash . clone ( ) ;
149
182
// let cid_path = format!("{}/{}", cid.unwrap_or("".to_string()), deployment_file.deployment_file_file_path);
150
183
151
184
// full cache eligibility
152
- if ( mime == "text/html" || HTML_CACHE_FILE_EXTENSIONS . contains ( & deployment_file. deployment_file_file_path . split ( '.' ) . last ( ) . unwrap_or ( "" ) ) )
185
+ if ( mime == "text/html"
186
+ || HTML_CACHE_FILE_EXTENSIONS . contains (
187
+ & deployment_file
188
+ . deployment_file_file_path
189
+ . split ( '.' )
190
+ . last ( )
191
+ . unwrap_or ( "" ) ,
192
+ ) )
153
193
&& deployment_file. file_size . unwrap_or ( 0 ) <= HTML_CACHE_SIZE_LIMIT as i64
154
194
{
155
195
// in-memory cache hit
@@ -162,22 +202,31 @@ async fn serve_deployment_file(deployment_file: DeploymentFileEntry, last_modifi
162
202
. header ( "Last-Modified" , last_modified. to_rfc2822 ( ) ) ;
163
203
// optionally include IPFS path
164
204
if let Some ( cid_val) = & cid {
165
- let ipfs_path = format ! ( "{}/{}" , cid_val, deployment_file. deployment_file_file_path) ;
205
+ let ipfs_path = format ! (
206
+ "/ipfs/{}/{}" ,
207
+ cid_val, deployment_file. deployment_file_file_path
208
+ ) ;
166
209
resp = resp. header ( "x-ipfs-path" , ipfs_path) ;
167
210
}
168
211
return resp. body ( Body :: from_bytes ( bytes. clone ( ) ) ) ;
169
212
}
170
213
// fetch and cache
171
214
if let Ok ( data) = state. storage . bucket . get_object ( & file_key) . await {
172
215
let bytes = data. into_bytes ( ) ;
173
- state. cache . file_bytes . insert ( file_key. clone ( ) , bytes. clone ( ) ) ;
216
+ state
217
+ . cache
218
+ . file_bytes
219
+ . insert ( file_key. clone ( ) , bytes. clone ( ) ) ;
174
220
let mut resp = Response :: builder ( )
175
221
. status ( StatusCode :: OK )
176
222
. header ( "content-type" , mime. clone ( ) )
177
223
. header ( "ETag" , format ! ( "\" {}\" " , file_key) )
178
224
. header ( "Last-Modified" , last_modified. to_rfc2822 ( ) ) ;
179
225
if let Some ( cid_val) = & cid {
180
- let ipfs_path = format ! ( "{}/{}" , cid_val, deployment_file. deployment_file_file_path) ;
226
+ let ipfs_path = format ! (
227
+ "/ipfs/{}/{}" ,
228
+ cid_val, deployment_file. deployment_file_file_path
229
+ ) ;
181
230
resp = resp. header ( "x-ipfs-path" , ipfs_path) ;
182
231
}
183
232
return resp. body ( Body :: from_bytes ( bytes) ) ;
@@ -204,13 +253,19 @@ async fn serve_deployment_file(deployment_file: DeploymentFileEntry, last_modifi
204
253
. header ( "ETag" , format ! ( "\" {}\" " , file_key) )
205
254
. header ( "Last-Modified" , last_modified. to_rfc2822 ( ) ) ;
206
255
if let Some ( cid_val) = & cid {
207
- let ipfs_path = format ! ( "{}/{}" , cid_val, deployment_file. deployment_file_file_path) ;
256
+ let ipfs_path = format ! (
257
+ "/ipfs/{}/{}" ,
258
+ cid_val, deployment_file. deployment_file_file_path
259
+ ) ;
208
260
resp = resp. header ( "x-ipfs-path" , ipfs_path) ;
209
261
}
210
- return resp. body ( body) ;
262
+
263
+ resp. body ( body)
264
+ }
265
+ Err ( _) => {
266
+ Response :: builder ( )
267
+ . status ( StatusCode :: INTERNAL_SERVER_ERROR )
268
+ . body ( Body :: from_string ( "Failed to stream file" . to_string ( ) ) )
211
269
}
212
- Err ( _) => return Response :: builder ( )
213
- . status ( StatusCode :: INTERNAL_SERVER_ERROR )
214
- . body ( Body :: from_string ( "Failed to stream file" . to_string ( ) ) ) ,
215
270
}
216
271
}
0 commit comments