/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/cache/lib.rs
568 строк
15 KB
crowlbot
fix(ext/cache): implement Cache.keys() (#35455)
06 июл 2026, 18:04
Не верифицирован
06 июл 2026, 18:04
ed8c592
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::borrow::Cow; use std::cell::RefCell; use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; use bytes::Bytes; use deno_core::AsyncRefCell; use deno_core::AsyncResult; use deno_core::FromV8; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; use deno_core::ToV8; use deno_core::convert::ByteString; use deno_core::op2; use deno_error::JsErrorBox; use futures::Stream; use tokio::io::AsyncRead; use tokio::io::AsyncReadExt; mod lsc_shard; mod lscache; mod sqlite; pub use lsc_shard::CacheShard; pub use lscache::LscBackend; pub use sqlite::SqliteBackedCache; use tokio_util::io::StreamReader; #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum CacheError { #[class(type)] #[error("CacheStorage is not available in this context")] ContextUnsupported, #[class(type)] #[error("Cache name cannot be empty")] EmptyName, #[class(type)] #[error("Cache is not available")] NotAvailable, #[class(type)] #[error("Cache not found")] NotFound, #[class(type)] #[error("Cache deletion is not supported")] DeletionNotSupported, #[class(type)] #[error("Cache.keys() is not supported with the remote cache backend")] KeysNotSupported, #[class(type)] #[error("Content-Encoding is not allowed in response headers")] ContentEncodingNotAllowed, #[class(generic)] #[error(transparent)] Sqlite(#[from] rusqlite::Error), #[class(generic)] #[error(transparent)] JoinError(#[from] tokio::task::JoinError), #[class(inherit)] #[error(transparent)] Resource(#[from] deno_core::error::ResourceError), #[class(inherit)] #[error(transparent)] Other(JsErrorBox), #[class(inherit)] #[error("{0}")] Io(#[from] std::io::Error), #[class(type)] #[error(transparent)] InvalidHeaderName(#[from] hyper::header::InvalidHeaderName), #[class(type)] #[error(transparent)] InvalidHeaderValue(#[from] hyper::header::InvalidHeaderValue), #[class(type)] #[error(transparent)] Hyper(#[from] hyper::Error), #[class(generic)] #[error(transparent)] ClientError(#[from] hyper_util::client::legacy::Error), #[class(generic)] #[error("Failed to create cache storage directory {}", .dir.display())] CacheStorageDirectory { dir: PathBuf, #[source] source: std::io::Error, }, #[class(generic)] #[error("cache {method} request failed: {status}")] RequestFailed { method: &'static str, status: hyper::StatusCode, }, } #[derive(Clone)] pub struct CreateCache(pub Arc<dyn Fn() -> Result<CacheImpl, CacheError>>); deno_core::extension!(deno_cache, deps = [ deno_webidl, deno_web, deno_fetch ], ops = [ op_cache_storage_open, op_cache_storage_has, op_cache_storage_delete, op_cache_storage_keys, op_cache_put, op_cache_match, op_cache_keys, op_cache_delete, ], lazy_loaded_js = [ "01_cache.js" ], options = { maybe_create_cache: Option<CreateCache>, }, state = |state, options| { if let Some(create_cache) = options.maybe_create_cache { state.put(create_cache); } }, ); #[derive(FromV8, Debug, Clone)] pub struct CachePutRequest { pub cache_id: i64, pub request_url: String, pub request_headers: Vec<(ByteString, ByteString)>, pub response_headers: Vec<(ByteString, ByteString)>, pub response_status: u16, pub response_status_text: String, pub response_rid: Option<ResourceId>, } #[derive(FromV8, Debug)] pub struct CacheMatchRequest { pub cache_id: i64, pub request_url: String, pub request_headers: Vec<(ByteString, ByteString)>, } #[derive(Debug, ToV8)] pub struct CacheMatchResponse(CacheMatchResponseMeta, Option<ResourceId>); #[derive(Debug, ToV8)] pub struct CacheMatchResponseMeta { pub response_status: u16, pub response_status_text: String, pub request_headers: Vec<(ByteString, ByteString)>, pub response_headers: Vec<(ByteString, ByteString)>, } #[derive(FromV8, Debug)] pub struct CacheDeleteRequest { pub cache_id: i64, pub request_url: String, } #[derive(FromV8, Debug)] pub struct CacheKeysRequest { pub cache_id: i64, /// When set, only the key matching this (already normalized) request URL is /// returned. This lets the backend filter instead of materializing every /// entry in the cache just to return a single key. pub request_url: Option<String>, } #[derive(Debug, ToV8)] pub struct CacheKeyEntry { pub request_url: String, pub request_headers: Vec<(ByteString, ByteString)>, } #[derive(Clone)] pub enum CacheImpl { Sqlite(SqliteBackedCache), Lsc(LscBackend), } impl CacheImpl { pub async fn storage_open( &self, cache_name: String, ) -> Result<i64, CacheError> { match self { Self::Sqlite(cache) => cache.storage_open(cache_name).await, Self::Lsc(cache) => cache.storage_open(cache_name).await, } } pub async fn storage_has( &self, cache_name: String, ) -> Result<bool, CacheError> { match self { Self::Sqlite(cache) => cache.storage_has(cache_name).await, Self::Lsc(cache) => cache.storage_has(cache_name).await, } } pub async fn storage_delete( &self, cache_name: String, ) -> Result<bool, CacheError> { match self { Self::Sqlite(cache) => cache.storage_delete(cache_name).await, Self::Lsc(cache) => cache.storage_delete(cache_name).await, } } pub async fn storage_keys(&self) -> Result<Vec<String>, CacheError> { match self { Self::Sqlite(cache) => cache.storage_keys().await, Self::Lsc(cache) => cache.storage_keys().await, } } pub async fn put( &self, request_response: CachePutRequest, resource: Option<Rc<dyn Resource>>, ) -> Result<(), CacheError> { match self { Self::Sqlite(cache) => cache.put(request_response, resource).await, Self::Lsc(cache) => cache.put(request_response, resource).await, } } pub async fn r#match( &self, request: CacheMatchRequest, ) -> Result< Option<(CacheMatchResponseMeta, Option<CacheResponseResource>)>, CacheError, > { match self { Self::Sqlite(cache) => cache.r#match(request).await, Self::Lsc(cache) => cache.r#match(request).await, } } pub async fn delete( &self, request: CacheDeleteRequest, ) -> Result<bool, CacheError> { match self { Self::Sqlite(cache) => cache.delete(request).await, Self::Lsc(cache) => cache.delete(request).await, } } pub async fn keys( &self, cache_id: i64, request_url: Option<String>, ) -> Result<Vec<CacheKeyEntry>, CacheError> { match self { Self::Sqlite(cache) => cache.keys(cache_id, request_url).await, Self::Lsc(cache) => cache.keys(cache_id, request_url).await, } } } pub enum CacheResponseResource { Sqlite(AsyncRefCell<tokio::fs::File>), Lsc(AsyncRefCell<Pin<Box<dyn AsyncRead>>>), } impl CacheResponseResource { fn sqlite(file: tokio::fs::File) -> Self { Self::Sqlite(AsyncRefCell::new(file)) } fn lsc( body: impl Stream<Item = Result<Bytes, std::io::Error>> + 'static, ) -> Self { Self::Lsc(AsyncRefCell::new(Box::pin(StreamReader::new(body)))) } async fn read( self: Rc<Self>, data: &mut [u8], ) -> Result<usize, std::io::Error> { let nread = match &*self { CacheResponseResource::Sqlite(_) => { let resource = deno_core::RcRef::map(&self, |r| match r { Self::Sqlite(r) => r, _ => unreachable!(), }); let mut file = resource.borrow_mut().await; file.read(data).await? } CacheResponseResource::Lsc(_) => { let resource = deno_core::RcRef::map(&self, |r| match r { Self::Lsc(r) => r, _ => unreachable!(), }); let mut file = resource.borrow_mut().await; file.read(data).await? } }; Ok(nread) } } impl Resource for CacheResponseResource { deno_core::impl_readable_byob!(); fn name(&self) -> Cow<'_, str> { "CacheResponseResource".into() } } #[op2] #[number] pub async fn op_cache_storage_open( state: Rc<RefCell<OpState>>, #[string] cache_name: String, ) -> Result<i64, CacheError> { let cache = get_cache(&state)?; cache.storage_open(cache_name).await } #[op2] pub async fn op_cache_storage_has( state: Rc<RefCell<OpState>>, #[string] cache_name: String, ) -> Result<bool, CacheError> { let cache = get_cache(&state)?; cache.storage_has(cache_name).await } #[op2] pub async fn op_cache_storage_delete( state: Rc<RefCell<OpState>>, #[string] cache_name: String, ) -> Result<bool, CacheError> { let cache = get_cache(&state)?; cache.storage_delete(cache_name).await } #[op2] #[serde] pub async fn op_cache_storage_keys( state: Rc<RefCell<OpState>>, ) -> Result<Vec<String>, CacheError> { let cache = get_cache(&state)?; cache.storage_keys().await } #[op2] pub async fn op_cache_put( state: Rc<RefCell<OpState>>, #[scoped] request_response: CachePutRequest, ) -> Result<(), CacheError> { let cache = get_cache(&state)?; let resource = match request_response.response_rid { Some(rid) => Some( state .borrow_mut() .resource_table .take_any(rid) .map_err(CacheError::Resource)?, ), None => None, }; cache.put(request_response, resource).await } #[op2] pub async fn op_cache_match( state: Rc<RefCell<OpState>>, #[scoped] request: CacheMatchRequest, ) -> Result<Option<CacheMatchResponse>, CacheError> { let cache = get_cache(&state)?; match cache.r#match(request).await? { Some((meta, None)) => Ok(Some(CacheMatchResponse(meta, None))), Some((meta, Some(resource))) => { let rid = state.borrow_mut().resource_table.add(resource); Ok(Some(CacheMatchResponse(meta, Some(rid)))) } None => Ok(None), } } #[op2] pub async fn op_cache_delete( state: Rc<RefCell<OpState>>, #[scoped] request: CacheDeleteRequest, ) -> Result<bool, CacheError> { let cache = get_cache(&state)?; cache.delete(request).await } #[op2] pub async fn op_cache_keys( state: Rc<RefCell<OpState>>, #[scoped] request: CacheKeysRequest, ) -> Result<Vec<CacheKeyEntry>, CacheError> { let cache = get_cache(&state)?; cache.keys(request.cache_id, request.request_url).await } pub fn get_cache( state: &Rc<RefCell<OpState>>, ) -> Result<CacheImpl, CacheError> { let mut state = state.borrow_mut(); if let Some(cache) = state.try_borrow::<CacheImpl>() { Ok(cache.clone()) } else if let Some(create_cache) = state.try_borrow::<CreateCache>() { let cache = create_cache.0()?; state.put(cache); Ok(state.borrow::<CacheImpl>().clone()) } else { Err(CacheError::ContextUnsupported) } } /// Check if headers, mentioned in the vary header, of query request /// and cached request are equal. pub fn vary_header_matches( vary_header: &ByteString, query_request_headers: &[(ByteString, ByteString)], cached_request_headers: &[(ByteString, ByteString)], ) -> bool { let vary_header = match std::str::from_utf8(vary_header) { Ok(vary_header) => vary_header, Err(_) => return false, }; let headers = get_headers_from_vary_header(vary_header); for header in headers { let query_header = get_header(&header, query_request_headers); let cached_header = get_header(&header, cached_request_headers); if query_header != cached_header { return false; } } true } #[test] fn test_vary_header_matches() { let vary_header = ByteString::from("accept-encoding"); let query_request_headers = vec![( ByteString::from("accept-encoding"), ByteString::from("gzip"), )]; let cached_request_headers = vec![( ByteString::from("accept-encoding"), ByteString::from("gzip"), )]; assert!(vary_header_matches( &vary_header, &query_request_headers, &cached_request_headers )); let vary_header = ByteString::from("accept-encoding"); let query_request_headers = vec![( ByteString::from("accept-encoding"), ByteString::from("gzip"), )]; let cached_request_headers = vec![(ByteString::from("accept-encoding"), ByteString::from("br"))]; assert!(!vary_header_matches( &vary_header, &query_request_headers, &cached_request_headers )); } /// Get headers from the vary header. pub fn get_headers_from_vary_header(vary_header: &str) -> Vec<String> { vary_header .split(',') .map(|s| s.trim().to_lowercase()) .collect() } #[test] fn test_get_headers_from_vary_header() { let headers = get_headers_from_vary_header("accept-encoding"); assert_eq!(headers, vec!["accept-encoding"]); let headers = get_headers_from_vary_header("accept-encoding, user-agent"); assert_eq!(headers, vec!["accept-encoding", "user-agent"]); } /// Get value for the header with the given name. pub fn get_header( name: &str, headers: &[(ByteString, ByteString)], ) -> Option<ByteString> { headers .iter() .find(|(k, _)| { if let Ok(k) = std::str::from_utf8(k) { k.eq_ignore_ascii_case(name) } else { false } }) .map(|(_, v)| v.to_owned()) } #[test] fn test_get_header() { let headers = vec![ ( ByteString::from("accept-encoding"), ByteString::from("gzip"), ), ( ByteString::from("content-type"), ByteString::from("application/json"), ), ( ByteString::from("vary"), ByteString::from("accept-encoding"), ), ]; let value = get_header("accept-encoding", &headers); assert_eq!(value, Some(ByteString::from("gzip"))); let value = get_header("content-type", &headers); assert_eq!(value, Some(ByteString::from("application/json"))); let value = get_header("vary", &headers); assert_eq!(value, Some(ByteString::from("accept-encoding"))); } /// Serialize headers into bytes. pub fn serialize_headers(headers: &[(ByteString, ByteString)]) -> Vec<u8> { let mut serialized_headers = Vec::new(); for (name, value) in headers { serialized_headers.extend_from_slice(name); serialized_headers.extend_from_slice(b"\r\n"); serialized_headers.extend_from_slice(value); serialized_headers.extend_from_slice(b"\r\n"); } serialized_headers } /// Deserialize bytes into headers. pub fn deserialize_headers( serialized_headers: &[u8], ) -> Vec<(ByteString, ByteString)> { let mut headers = Vec::new(); let mut piece = None; let mut start = 0; for (i, byte) in serialized_headers.iter().enumerate() { if byte == &b'\r' && serialized_headers.get(i + 1) == Some(&b'\n') { if piece.is_none() { piece = Some(start..i); } else { let name = piece.unwrap(); let value = start..i; headers.push(( ByteString::from(&serialized_headers[name]), ByteString::from(&serialized_headers[value]), )); piece = None; } start = i + 2; } } assert!(piece.is_none()); assert_eq!(start, serialized_headers.len()); headers }