1use std::borrow::Cow;
4use std::cell::RefCell;
5use std::path::PathBuf;
6use std::pin::Pin;
7use std::rc::Rc;
8use std::sync::Arc;
9
10use async_trait::async_trait;
11use bytes::Bytes;
12use deno_core::AsyncRefCell;
13use deno_core::AsyncResult;
14use deno_core::ByteString;
15use deno_core::OpState;
16use deno_core::Resource;
17use deno_core::ResourceId;
18use deno_core::op2;
19use deno_core::serde::Deserialize;
20use deno_core::serde::Serialize;
21use deno_error::JsErrorBox;
22use futures::Stream;
23use tokio::io::AsyncRead;
24use tokio::io::AsyncReadExt;
25
26mod lsc_shard;
27mod lscache;
28mod sqlite;
29
30pub use lsc_shard::CacheShard;
31pub use lscache::LscBackend;
32pub use sqlite::SqliteBackedCache;
33use tokio_util::io::StreamReader;
34
35#[derive(Debug, thiserror::Error, deno_error::JsError)]
36pub enum CacheError {
37 #[class(type)]
38 #[error("CacheStorage is not available in this context")]
39 ContextUnsupported,
40 #[class(type)]
41 #[error("Cache name cannot be empty")]
42 EmptyName,
43 #[class(type)]
44 #[error("Cache is not available")]
45 NotAvailable,
46 #[class(type)]
47 #[error("Cache not found")]
48 NotFound,
49 #[class(type)]
50 #[error("Cache deletion is not supported")]
51 DeletionNotSupported,
52 #[class(type)]
53 #[error("Content-Encoding is not allowed in response headers")]
54 ContentEncodingNotAllowed,
55 #[class(generic)]
56 #[error(transparent)]
57 Sqlite(#[from] rusqlite::Error),
58 #[class(generic)]
59 #[error(transparent)]
60 JoinError(#[from] tokio::task::JoinError),
61 #[class(inherit)]
62 #[error(transparent)]
63 Resource(#[from] deno_core::error::ResourceError),
64 #[class(inherit)]
65 #[error(transparent)]
66 Other(JsErrorBox),
67 #[class(inherit)]
68 #[error("{0}")]
69 Io(#[from] std::io::Error),
70 #[class(type)]
71 #[error(transparent)]
72 InvalidHeaderName(#[from] hyper::header::InvalidHeaderName),
73 #[class(type)]
74 #[error(transparent)]
75 InvalidHeaderValue(#[from] hyper::header::InvalidHeaderValue),
76 #[class(type)]
77 #[error(transparent)]
78 Hyper(#[from] hyper::Error),
79 #[class(generic)]
80 #[error(transparent)]
81 ClientError(#[from] hyper_util::client::legacy::Error),
82 #[class(generic)]
83 #[error("Failed to create cache storage directory {}", .dir.display())]
84 CacheStorageDirectory {
85 dir: PathBuf,
86 #[source]
87 source: std::io::Error,
88 },
89 #[class(generic)]
90 #[error("cache {method} request failed: {status}")]
91 RequestFailed {
92 method: &'static str,
93 status: hyper::StatusCode,
94 },
95}
96
97#[derive(Clone)]
98pub struct CreateCache(pub Arc<dyn Fn() -> Result<CacheImpl, CacheError>>);
99
100deno_core::extension!(deno_cache,
101 deps = [ deno_webidl, deno_web, deno_fetch ],
102 ops = [
103 op_cache_storage_open,
104 op_cache_storage_has,
105 op_cache_storage_delete,
106 op_cache_put,
107 op_cache_match,
108 op_cache_delete,
109 ],
110 esm = [ "01_cache.js" ],
111 options = {
112 maybe_create_cache: Option<CreateCache>,
113 },
114 state = |state, options| {
115 if let Some(create_cache) = options.maybe_create_cache {
116 state.put(create_cache);
117 }
118 },
119);
120
121#[derive(Deserialize, Serialize, Debug, Clone)]
122#[serde(rename_all = "camelCase")]
123pub struct CachePutRequest {
124 pub cache_id: i64,
125 pub request_url: String,
126 pub request_headers: Vec<(ByteString, ByteString)>,
127 pub response_headers: Vec<(ByteString, ByteString)>,
128 pub response_status: u16,
129 pub response_status_text: String,
130 pub response_rid: Option<ResourceId>,
131}
132
133#[derive(Deserialize, Serialize, Debug)]
134#[serde(rename_all = "camelCase")]
135pub struct CacheMatchRequest {
136 pub cache_id: i64,
137 pub request_url: String,
138 pub request_headers: Vec<(ByteString, ByteString)>,
139}
140
141#[derive(Debug, Deserialize, Serialize)]
142#[serde(rename_all = "camelCase")]
143pub struct CacheMatchResponse(CacheMatchResponseMeta, Option<ResourceId>);
144
145#[derive(Debug, Deserialize, Serialize)]
146#[serde(rename_all = "camelCase")]
147pub struct CacheMatchResponseMeta {
148 pub response_status: u16,
149 pub response_status_text: String,
150 pub request_headers: Vec<(ByteString, ByteString)>,
151 pub response_headers: Vec<(ByteString, ByteString)>,
152}
153
154#[derive(Deserialize, Serialize, Debug)]
155#[serde(rename_all = "camelCase")]
156pub struct CacheDeleteRequest {
157 pub cache_id: i64,
158 pub request_url: String,
159}
160
161#[async_trait(?Send)]
162pub trait Cache: Clone + 'static {
163 type CacheMatchResourceType: Resource;
164
165 async fn storage_open(&self, cache_name: String) -> Result<i64, CacheError>;
166 async fn storage_has(&self, cache_name: String) -> Result<bool, CacheError>;
167 async fn storage_delete(
168 &self,
169 cache_name: String,
170 ) -> Result<bool, CacheError>;
171
172 async fn put(
174 &self,
175 request_response: CachePutRequest,
176 resource: Option<Rc<dyn Resource>>,
177 ) -> Result<(), CacheError>;
178
179 async fn r#match(
180 &self,
181 request: CacheMatchRequest,
182 ) -> Result<
183 Option<(CacheMatchResponseMeta, Option<Self::CacheMatchResourceType>)>,
184 CacheError,
185 >;
186 async fn delete(
187 &self,
188 request: CacheDeleteRequest,
189 ) -> Result<bool, CacheError>;
190}
191
192#[derive(Clone)]
193pub enum CacheImpl {
194 Sqlite(SqliteBackedCache),
195 Lsc(LscBackend),
196}
197
198#[async_trait(?Send)]
199impl Cache for CacheImpl {
200 type CacheMatchResourceType = CacheResponseResource;
201
202 async fn storage_open(&self, cache_name: String) -> Result<i64, CacheError> {
203 match self {
204 Self::Sqlite(cache) => cache.storage_open(cache_name).await,
205 Self::Lsc(cache) => cache.storage_open(cache_name).await,
206 }
207 }
208
209 async fn storage_has(&self, cache_name: String) -> Result<bool, CacheError> {
210 match self {
211 Self::Sqlite(cache) => cache.storage_has(cache_name).await,
212 Self::Lsc(cache) => cache.storage_has(cache_name).await,
213 }
214 }
215
216 async fn storage_delete(
217 &self,
218 cache_name: String,
219 ) -> Result<bool, CacheError> {
220 match self {
221 Self::Sqlite(cache) => cache.storage_delete(cache_name).await,
222 Self::Lsc(cache) => cache.storage_delete(cache_name).await,
223 }
224 }
225
226 async fn put(
227 &self,
228 request_response: CachePutRequest,
229 resource: Option<Rc<dyn Resource>>,
230 ) -> Result<(), CacheError> {
231 match self {
232 Self::Sqlite(cache) => cache.put(request_response, resource).await,
233 Self::Lsc(cache) => cache.put(request_response, resource).await,
234 }
235 }
236
237 async fn r#match(
238 &self,
239 request: CacheMatchRequest,
240 ) -> Result<
241 Option<(CacheMatchResponseMeta, Option<Self::CacheMatchResourceType>)>,
242 CacheError,
243 > {
244 match self {
245 Self::Sqlite(cache) => cache.r#match(request).await,
246 Self::Lsc(cache) => cache.r#match(request).await,
247 }
248 }
249
250 async fn delete(
251 &self,
252 request: CacheDeleteRequest,
253 ) -> Result<bool, CacheError> {
254 match self {
255 Self::Sqlite(cache) => cache.delete(request).await,
256 Self::Lsc(cache) => cache.delete(request).await,
257 }
258 }
259}
260
261pub enum CacheResponseResource {
262 Sqlite(AsyncRefCell<tokio::fs::File>),
263 Lsc(AsyncRefCell<Pin<Box<dyn AsyncRead>>>),
264}
265
266impl CacheResponseResource {
267 fn sqlite(file: tokio::fs::File) -> Self {
268 Self::Sqlite(AsyncRefCell::new(file))
269 }
270
271 fn lsc(
272 body: impl Stream<Item = Result<Bytes, std::io::Error>> + 'static,
273 ) -> Self {
274 Self::Lsc(AsyncRefCell::new(Box::pin(StreamReader::new(body))))
275 }
276
277 async fn read(
278 self: Rc<Self>,
279 data: &mut [u8],
280 ) -> Result<usize, std::io::Error> {
281 let nread = match &*self {
282 CacheResponseResource::Sqlite(_) => {
283 let resource = deno_core::RcRef::map(&self, |r| match r {
284 Self::Sqlite(r) => r,
285 _ => unreachable!(),
286 });
287 let mut file = resource.borrow_mut().await;
288 file.read(data).await?
289 }
290 CacheResponseResource::Lsc(_) => {
291 let resource = deno_core::RcRef::map(&self, |r| match r {
292 Self::Lsc(r) => r,
293 _ => unreachable!(),
294 });
295 let mut file = resource.borrow_mut().await;
296 file.read(data).await?
297 }
298 };
299
300 Ok(nread)
301 }
302}
303
304impl Resource for CacheResponseResource {
305 deno_core::impl_readable_byob!();
306
307 fn name(&self) -> Cow<'_, str> {
308 "CacheResponseResource".into()
309 }
310}
311
312#[op2]
313#[number]
314pub async fn op_cache_storage_open(
315 state: Rc<RefCell<OpState>>,
316 #[string] cache_name: String,
317) -> Result<i64, CacheError> {
318 let cache = get_cache(&state)?;
319 cache.storage_open(cache_name).await
320}
321
322#[op2]
323pub async fn op_cache_storage_has(
324 state: Rc<RefCell<OpState>>,
325 #[string] cache_name: String,
326) -> Result<bool, CacheError> {
327 let cache = get_cache(&state)?;
328 cache.storage_has(cache_name).await
329}
330
331#[op2]
332pub async fn op_cache_storage_delete(
333 state: Rc<RefCell<OpState>>,
334 #[string] cache_name: String,
335) -> Result<bool, CacheError> {
336 let cache = get_cache(&state)?;
337 cache.storage_delete(cache_name).await
338}
339
340#[op2]
341pub async fn op_cache_put(
342 state: Rc<RefCell<OpState>>,
343 #[serde] request_response: CachePutRequest,
344) -> Result<(), CacheError> {
345 let cache = get_cache(&state)?;
346 let resource = match request_response.response_rid {
347 Some(rid) => Some(
348 state
349 .borrow_mut()
350 .resource_table
351 .take_any(rid)
352 .map_err(CacheError::Resource)?,
353 ),
354 None => None,
355 };
356 cache.put(request_response, resource).await
357}
358
359#[op2]
360#[serde]
361pub async fn op_cache_match(
362 state: Rc<RefCell<OpState>>,
363 #[serde] request: CacheMatchRequest,
364) -> Result<Option<CacheMatchResponse>, CacheError> {
365 let cache = get_cache(&state)?;
366 match cache.r#match(request).await? {
367 Some((meta, None)) => Ok(Some(CacheMatchResponse(meta, None))),
368 Some((meta, Some(resource))) => {
369 let rid = state.borrow_mut().resource_table.add(resource);
370 Ok(Some(CacheMatchResponse(meta, Some(rid))))
371 }
372 None => Ok(None),
373 }
374}
375
376#[op2]
377pub async fn op_cache_delete(
378 state: Rc<RefCell<OpState>>,
379 #[serde] request: CacheDeleteRequest,
380) -> Result<bool, CacheError> {
381 let cache = get_cache(&state)?;
382 cache.delete(request).await
383}
384
385pub fn get_cache(
386 state: &Rc<RefCell<OpState>>,
387) -> Result<CacheImpl, CacheError> {
388 let mut state = state.borrow_mut();
389 if let Some(cache) = state.try_borrow::<CacheImpl>() {
390 Ok(cache.clone())
391 } else if let Some(create_cache) = state.try_borrow::<CreateCache>() {
392 let cache = create_cache.0()?;
393 state.put(cache);
394 Ok(state.borrow::<CacheImpl>().clone())
395 } else {
396 Err(CacheError::ContextUnsupported)
397 }
398}
399
400pub fn vary_header_matches(
403 vary_header: &ByteString,
404 query_request_headers: &[(ByteString, ByteString)],
405 cached_request_headers: &[(ByteString, ByteString)],
406) -> bool {
407 let vary_header = match std::str::from_utf8(vary_header) {
408 Ok(vary_header) => vary_header,
409 Err(_) => return false,
410 };
411 let headers = get_headers_from_vary_header(vary_header);
412 for header in headers {
413 let query_header = get_header(&header, query_request_headers);
414 let cached_header = get_header(&header, cached_request_headers);
415 if query_header != cached_header {
416 return false;
417 }
418 }
419 true
420}
421
422#[test]
423fn test_vary_header_matches() {
424 let vary_header = ByteString::from("accept-encoding");
425 let query_request_headers = vec![(
426 ByteString::from("accept-encoding"),
427 ByteString::from("gzip"),
428 )];
429 let cached_request_headers = vec![(
430 ByteString::from("accept-encoding"),
431 ByteString::from("gzip"),
432 )];
433 assert!(vary_header_matches(
434 &vary_header,
435 &query_request_headers,
436 &cached_request_headers
437 ));
438 let vary_header = ByteString::from("accept-encoding");
439 let query_request_headers = vec![(
440 ByteString::from("accept-encoding"),
441 ByteString::from("gzip"),
442 )];
443 let cached_request_headers =
444 vec![(ByteString::from("accept-encoding"), ByteString::from("br"))];
445 assert!(!vary_header_matches(
446 &vary_header,
447 &query_request_headers,
448 &cached_request_headers
449 ));
450}
451
452pub fn get_headers_from_vary_header(vary_header: &str) -> Vec<String> {
454 vary_header
455 .split(',')
456 .map(|s| s.trim().to_lowercase())
457 .collect()
458}
459
460#[test]
461fn test_get_headers_from_vary_header() {
462 let headers = get_headers_from_vary_header("accept-encoding");
463 assert_eq!(headers, vec!["accept-encoding"]);
464 let headers = get_headers_from_vary_header("accept-encoding, user-agent");
465 assert_eq!(headers, vec!["accept-encoding", "user-agent"]);
466}
467
468pub fn get_header(
470 name: &str,
471 headers: &[(ByteString, ByteString)],
472) -> Option<ByteString> {
473 headers
474 .iter()
475 .find(|(k, _)| {
476 if let Ok(k) = std::str::from_utf8(k) {
477 k.eq_ignore_ascii_case(name)
478 } else {
479 false
480 }
481 })
482 .map(|(_, v)| v.to_owned())
483}
484
485#[test]
486fn test_get_header() {
487 let headers = vec![
488 (
489 ByteString::from("accept-encoding"),
490 ByteString::from("gzip"),
491 ),
492 (
493 ByteString::from("content-type"),
494 ByteString::from("application/json"),
495 ),
496 (
497 ByteString::from("vary"),
498 ByteString::from("accept-encoding"),
499 ),
500 ];
501 let value = get_header("accept-encoding", &headers);
502 assert_eq!(value, Some(ByteString::from("gzip")));
503 let value = get_header("content-type", &headers);
504 assert_eq!(value, Some(ByteString::from("application/json")));
505 let value = get_header("vary", &headers);
506 assert_eq!(value, Some(ByteString::from("accept-encoding")));
507}
508
509pub fn serialize_headers(headers: &[(ByteString, ByteString)]) -> Vec<u8> {
511 let mut serialized_headers = Vec::new();
512 for (name, value) in headers {
513 serialized_headers.extend_from_slice(name);
514 serialized_headers.extend_from_slice(b"\r\n");
515 serialized_headers.extend_from_slice(value);
516 serialized_headers.extend_from_slice(b"\r\n");
517 }
518 serialized_headers
519}
520
521pub fn deserialize_headers(
523 serialized_headers: &[u8],
524) -> Vec<(ByteString, ByteString)> {
525 let mut headers = Vec::new();
526 let mut piece = None;
527 let mut start = 0;
528 for (i, byte) in serialized_headers.iter().enumerate() {
529 if byte == &b'\r' && serialized_headers.get(i + 1) == Some(&b'\n') {
530 if piece.is_none() {
531 piece = Some(start..i);
532 } else {
533 let name = piece.unwrap();
534 let value = start..i;
535 headers.push((
536 ByteString::from(&serialized_headers[name]),
537 ByteString::from(&serialized_headers[value]),
538 ));
539 piece = None;
540 }
541 start = i + 2;
542 }
543 }
544 assert!(piece.is_none());
545 assert_eq!(start, serialized_headers.len());
546 headers
547}