Repository: denoland/deno
Pull request: fix(ext/fetch): read file urls via vfs

Closes #28129.


Changed files:
filename: Cargo.lock
patch: @@ -2060,6 +2060,7 @@ dependencies = [
  "deno_core",
  "deno_error",
  "deno_fs",
+ "deno_io",
  "deno_path_util",
  "deno_permissions",
  "deno_tls",
@@ -2083,7 +2084,6 @@ dependencies = [
  "tokio",
  "tokio-rustls",
  "tokio-socks",
- "tokio-util",
  "tokio-vsock",
  "tower 0.5.2",
  "tower-http",

filename: ext/fetch/Cargo.toml
patch: @@ -20,6 +20,7 @@ data-url.workspace = true
 deno_core.workspace = true
 deno_error.workspace = true
 deno_fs.workspace = true
+deno_io.workspace = true
 deno_path_util.workspace = true
 deno_permissions.workspace = true
 deno_tls.workspace = true
@@ -41,7 +42,6 @@ thiserror.workspace = true
 tokio.workspace = true
 tokio-rustls.workspace = true
 tokio-socks.workspace = true
-tokio-util = { workspace = true, features = ["io"] }
 tower.workspace = true
 tower-http.workspace = true
 tower-service.workspace = true

filename: ext/fetch/fs_fetch_handler.rs
patch: @@ -6,24 +6,21 @@ use std::rc::Rc;
 use deno_core::CancelFuture;
 use deno_core::OpState;
 use deno_core::futures::FutureExt;
-use deno_core::futures::TryFutureExt;
-use deno_core::futures::TryStreamExt;
 use deno_core::url::Url;
-use deno_error::JsErrorBox;
+use deno_fs::FileSystemRc;
 use deno_fs::OpenOptions;
-use deno_fs::open_options_for_checked_path;
+use deno_io::fs::FileResource;
+use deno_permissions::CheckedPath;
 use deno_permissions::OpenAccessKind;
 use deno_permissions::PermissionsContainer;
-use http::StatusCode;
-use http_body_util::BodyExt;
-use tokio_util::io::ReaderStream;
+use http_body_util::combinators::BoxBody;
 
 use crate::CancelHandle;
 use crate::CancelableResponseFuture;
 use crate::FetchHandler;
+use crate::ResourceToBodyAdapter;
 
-/// An implementation which tries to read file URLs from the file system via
-/// tokio::fs.
+/// An implementation which tries to read file URLs via `deno_fs::FileSystem`.
 #[derive(Clone)]
 pub struct FsFetchHandler;
 
@@ -34,52 +31,28 @@ impl FetchHandler for FsFetchHandler {
     url: &Url,
   ) -> (CancelableResponseFuture, Option<Rc<CancelHandle>>) {
     let cancel_handle = CancelHandle::new_rc();
-    let path = match url.to_file_path() {
-      Ok(path) => path,
-      Err(_) => {
-        let fut = async move { Err::<_, _>(()) };
-        return (
-          fut
-            .map_err(move |_| super::FetchError::NetworkError)
-            .or_cancel(&cancel_handle)
-            .boxed_local(),
-          Some(cancel_handle),
-        );
-      }
-    };
-    let path_and_opts_result = {
-      state
-        .borrow::<PermissionsContainer>()
-        .check_open(Cow::Owned(path), OpenAccessKind::Read, Some("fetch()"))
-        .map(|path| {
-          (
-            open_options_for_checked_path(
-              OpenOptions {
-                read: true,
-                ..Default::default()
-              },
-              &path,
-            ),
-            path.into_owned(),
-          )
-        })
+    let Ok(path) = url.to_file_path() else {
+      return (
+        async move { Err(super::FetchError::NetworkError) }
+          .or_cancel(&cancel_handle)
+          .boxed_local(),
+        Some(cancel_handle),
+      );
     };
+    let fs = state.borrow::<FileSystemRc>().clone();
+    let path = state
+      .borrow::<PermissionsContainer>()
+      .check_open(Cow::Owned(path), OpenAccessKind::Read, Some("fetch()"))
+      .map(CheckedPath::into_owned);
     let response_fut = async move {
-      let (opts, path) = path_and_opts_result?;
-      let file = tokio::fs::OpenOptions::from(opts)
-        .open(path)
+      let file = fs
+        .open_async(path?, OpenOptions::read())
         .await
         .map_err(|_| super::FetchError::NetworkError)?;
-      let stream = ReaderStream::new(file)
-        .map_ok(hyper::body::Frame::data)
-        .map_err(JsErrorBox::from_err);
-
-      let body = http_body_util::StreamBody::new(stream).boxed();
-      let response = http::Response::builder()
-        .status(StatusCode::OK)
-        .body(body)
-        .map_err(move |_| super::FetchError::NetworkError)?;
-      Ok::<_, _>(response)
+      let resource = Rc::new(FileResource::new(file, "".to_owned()));
+      let body = BoxBody::new(ResourceToBodyAdapter::new(resource));
+      let response = http::Response::new(body);
+      Ok(response)
     }
     .or_cancel(&cancel_handle)
     .boxed_local();

filename: tests/specs/compile/include/buffered_reads/main.ts
patch: @@ -55,3 +55,14 @@
     }
   }
 }
+
+// large amount of data via fetch
+{
+  const res = await fetch(import.meta.resolve("./data/2.dat"));
+  const bytes = await res.bytes();
+  for (let i = 0; i < bytes.length; i++) {
+    if (bytes[i] !== i % 256) {
+      throw new Error("Unexpected data.");
+    }
+  }
+}