deno_runtime/ops/web_worker/
sync_fetch.rs1use std::sync::Arc;
4
5use crate::web_worker::WebWorkerInternalHandle;
6use crate::web_worker::WebWorkerType;
7use deno_core::futures::StreamExt;
8use deno_core::op2;
9use deno_core::url::Url;
10use deno_core::OpState;
11use deno_fetch::data_url::DataUrl;
12use deno_fetch::FetchError;
13use deno_web::BlobStore;
14use http_body_util::BodyExt;
15use hyper::body::Bytes;
16use serde::Deserialize;
17use serde::Serialize;
18
19fn mime_type_essence(mime_type: &str) -> String {
21 let essence = match mime_type.split_once(';') {
22 Some((essence, _)) => essence,
23 None => mime_type,
24 };
25 essence.trim().to_ascii_lowercase()
26}
27
28#[derive(Debug, thiserror::Error)]
29pub enum SyncFetchError {
30 #[error("Blob URLs are not supported in this context.")]
31 BlobUrlsNotSupportedInContext,
32 #[error("{0}")]
33 Io(#[from] std::io::Error),
34 #[error("Invalid script URL")]
35 InvalidScriptUrl,
36 #[error("http status error: {0}")]
37 InvalidStatusCode(http::StatusCode),
38 #[error("Classic scripts with scheme {0}: are not supported in workers")]
39 ClassicScriptSchemeUnsupportedInWorkers(String),
40 #[error("{0}")]
41 InvalidUri(#[from] http::uri::InvalidUri),
42 #[error("Invalid MIME type {0:?}.")]
43 InvalidMimeType(String),
44 #[error("Missing MIME type.")]
45 MissingMimeType,
46 #[error(transparent)]
47 Fetch(#[from] FetchError),
48 #[error(transparent)]
49 Join(#[from] tokio::task::JoinError),
50 #[error(transparent)]
51 Other(deno_core::error::AnyError),
52}
53
54#[derive(Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct SyncFetchScript {
57 url: String,
58 script: String,
59}
60
61#[op2]
62#[serde]
63pub fn op_worker_sync_fetch(
64 state: &mut OpState,
65 #[serde] scripts: Vec<String>,
66 loose_mime_checks: bool,
67) -> Result<Vec<SyncFetchScript>, SyncFetchError> {
68 let handle = state.borrow::<WebWorkerInternalHandle>().clone();
69 assert_eq!(handle.worker_type, WebWorkerType::Classic);
70
71 let options = state.borrow::<deno_fetch::Options>().clone();
74 let client = deno_fetch::create_client_from_options(&options)
75 .map_err(FetchError::ClientCreate)?;
76
77 let blob_store = state
81 .try_borrow::<Arc<BlobStore>>()
82 .ok_or(SyncFetchError::BlobUrlsNotSupportedInContext)?
83 .clone();
84
85 let thread = std::thread::spawn(move || {
89 let runtime = tokio::runtime::Builder::new_current_thread()
90 .enable_io()
91 .enable_time()
92 .build()?;
93
94 runtime.block_on(async move {
95 let mut futures = scripts
96 .into_iter()
97 .map(|script| {
98 let client = client.clone();
99 let blob_store = blob_store.clone();
100 deno_core::unsync::spawn(async move {
101 let script_url = Url::parse(&script)
102 .map_err(|_| SyncFetchError::InvalidScriptUrl)?;
103 let mut loose_mime_checks = loose_mime_checks;
104
105 let (body, mime_type, res_url) = match script_url.scheme() {
106 "http" | "https" => {
107 let mut req = http::Request::new(
108 http_body_util::Empty::new()
109 .map_err(|never| match never {})
110 .boxed(),
111 );
112 *req.uri_mut() = script_url.as_str().parse()?;
113
114 let resp =
115 client.send(req).await.map_err(FetchError::ClientSend)?;
116
117 if resp.status().is_client_error()
118 || resp.status().is_server_error()
119 {
120 return Err(SyncFetchError::InvalidStatusCode(resp.status()));
121 }
122
123 let mime_type = resp
125 .headers()
126 .get("Content-Type")
127 .and_then(|v| v.to_str().ok())
128 .map(mime_type_essence);
129
130 loose_mime_checks = false;
132
133 let body = resp
134 .collect()
135 .await
136 .map_err(SyncFetchError::Other)?
137 .to_bytes();
138
139 (body, mime_type, script)
140 }
141 "data" => {
142 let data_url =
143 DataUrl::process(&script).map_err(FetchError::DataUrl)?;
144
145 let mime_type = {
146 let mime = data_url.mime_type();
147 format!("{}/{}", mime.type_, mime.subtype)
148 };
149
150 let (body, _) =
151 data_url.decode_to_vec().map_err(FetchError::Base64)?;
152
153 (Bytes::from(body), Some(mime_type), script)
154 }
155 "blob" => {
156 let blob = blob_store
157 .get_object_url(script_url)
158 .ok_or(FetchError::BlobNotFound)?;
159
160 let mime_type = mime_type_essence(&blob.media_type);
161
162 let body = blob.read_all().await;
163
164 (Bytes::from(body), Some(mime_type), script)
165 }
166 _ => {
167 return Err(
168 SyncFetchError::ClassicScriptSchemeUnsupportedInWorkers(
169 script_url.scheme().to_string(),
170 ),
171 )
172 }
173 };
174
175 if !loose_mime_checks {
176 match mime_type.as_deref() {
178 Some("application/javascript" | "text/javascript") => {}
179 Some(mime_type) => {
180 return Err(SyncFetchError::InvalidMimeType(
181 mime_type.to_string(),
182 ))
183 }
184 None => return Err(SyncFetchError::MissingMimeType),
185 }
186 }
187
188 let (text, _) = encoding_rs::UTF_8.decode_with_bom_removal(&body);
189
190 Ok(SyncFetchScript {
191 url: res_url,
192 script: text.into_owned(),
193 })
194 })
195 })
196 .collect::<deno_core::futures::stream::FuturesUnordered<_>>();
197 let mut ret = Vec::with_capacity(futures.len());
198 while let Some(result) = futures.next().await {
199 let script = result??;
200 ret.push(script);
201 }
202 Ok(ret)
203 })
204 });
205
206 thread.join().unwrap()
207}