net: Move listing of directories to async code (#42108)

Listing directory and constructing the list is another prime candidate
to switch from blocking code to async code.
This PR does this.

Of note is the change in type for ProtocolHandler::load which now has a
lifetime bounded by Request and self and the Response has a similar
lifetime bound.

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>

Testing: Looking at directories still works.

---------

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
This commit is contained in:
Narfinger
2026-01-24 22:14:16 +01:00
committed by GitHub
parent 4abe355010
commit 1dcb7588ba
3 changed files with 30 additions and 28 deletions

View File

@@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::fs::{DirEntry, Metadata, ReadDir};
use std::fs::Metadata;
use std::path::PathBuf;
use chrono::{DateTime, Local};
@@ -15,7 +15,7 @@ use servo_config::pref;
use servo_url::ServoUrl;
use url::Url;
pub fn fetch(request: &mut Request, url: ServoUrl, path_buf: PathBuf) -> Response {
pub(crate) async fn fetch(request: &mut Request, url: ServoUrl, path_buf: PathBuf) -> Response {
if !pref!(network_local_directory_listing_enabled) {
// If you want to be able to browse local directories, configure Servo prefs so that
// "network.local_directory_listing.enabled" is set to true.
@@ -29,7 +29,7 @@ pub fn fetch(request: &mut Request, url: ServoUrl, path_buf: PathBuf) -> Respons
return Response::network_error(NetworkError::LocalDirectoryError);
}
let directory_contents = match std::fs::read_dir(path_buf.clone()) {
let directory_contents = match tokio::fs::read_dir(path_buf.clone()).await {
Ok(directory_contents) => directory_contents,
Err(error) => {
return Response::network_error(NetworkError::ResourceLoadError(format!(
@@ -38,7 +38,7 @@ pub fn fetch(request: &mut Request, url: ServoUrl, path_buf: PathBuf) -> Respons
},
};
let output = build_html_directory_listing(url.as_url(), path_buf, directory_contents);
let output = build_html_directory_listing(url.as_url(), path_buf, directory_contents).await;
let mut response = Response::new(url, ResourceFetchTiming::new(request.timing_type()));
response.headers.typed_insert(ContentType::html());
@@ -55,10 +55,10 @@ pub fn fetch(request: &mut Request, url: ServoUrl, path_buf: PathBuf) -> Respons
/// * `url` - the original URL of the request that triggered this directory listing.
/// * `path` - the full path to the local directory.
/// * `directory_contents` - a [`ReadDir`] with the contents of the directory.
pub fn build_html_directory_listing(
pub(crate) async fn build_html_directory_listing(
url: &Url,
path: PathBuf,
directory_contents: ReadDir,
mut directory_contents: tokio::fs::ReadDir,
) -> String {
let mut page_html = String::with_capacity(1024);
page_html.push_str("<!DOCTYPE html>");
@@ -81,11 +81,8 @@ pub fn build_html_directory_listing(
parent_url_string
));
for directory_entry in directory_contents {
let Ok(directory_entry) = directory_entry else {
continue;
};
let Ok(metadata) = directory_entry.metadata() else {
while let Ok(Some(directory_entry)) = directory_contents.next_entry().await {
let Ok(metadata) = directory_entry.metadata().await else {
continue;
};
write_directory_entry(directory_entry, metadata, url, &mut page_html);
@@ -97,7 +94,12 @@ pub fn build_html_directory_listing(
page_html
}
fn write_directory_entry(entry: DirEntry, metadata: Metadata, url: &Url, output: &mut String) {
fn write_directory_entry(
entry: tokio::fs::DirEntry,
metadata: Metadata,
url: &Url,
output: &mut String,
) {
let Ok(name) = entry.file_name().into_string() else {
return;
};

View File

@@ -25,12 +25,12 @@ use crate::protocols::{
pub struct FileProtocolHander {}
impl ProtocolHandler for FileProtocolHander {
fn load(
&self,
request: &mut Request,
fn load<'a>(
&'a self,
request: &'a mut Request,
done_chan: &mut DoneChannel,
context: &FetchContext,
) -> Pin<Box<dyn Future<Output = Response> + Send>> {
) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>> {
let url = request.current_url();
if request.method != Method::GET {
@@ -38,9 +38,9 @@ impl ProtocolHandler for FileProtocolHander {
}
let response = if let Ok(file_path) = url.to_file_path() {
if file_path.is_dir() {
return Box::pin(ready(local_directory_listing::fetch(
request, url, file_path,
)));
return Box::pin(async move {
local_directory_listing::fetch(request, url, file_path).await
});
}
if let Ok(file) = File::open(file_path.clone()) {

View File

@@ -29,7 +29,7 @@ use blob::BlobProtocolHander;
use data::DataProtocolHander;
use file::FileProtocolHander;
type FutureResponse = Pin<Box<dyn Future<Output = Response> + Send>>;
type FutureResponse<'a> = Pin<Box<dyn Future<Output = Response> + Send + 'a>>;
// The set of schemes that can't be registered.
static FORBIDDEN_SCHEMES: [&str; 4] = ["http", "https", "chrome", "about"];
@@ -47,12 +47,12 @@ pub trait ProtocolHandler: Send + Sync {
/// http endpoint, it is recommended to a least provide:
/// - A relevant status code.
/// - A Content Type.
fn load(
&self,
request: &mut Request,
fn load<'a>(
&'a self,
request: &'a mut Request,
done_chan: &mut DoneChannel,
context: &FetchContext,
) -> FutureResponse;
) -> FutureResponse<'a>;
/// Specify if resources served by that protocol can be retrieved
/// with `fetch()` without no-cors mode to allow the caller direct
@@ -177,12 +177,12 @@ struct WebPageContentProtocolHandler {
impl ProtocolHandler for WebPageContentProtocolHandler {
/// <https://html.spec.whatwg.org/multipage/#protocol-handler-invocation>
fn load(
&self,
request: &mut Request,
fn load<'a>(
&'a self,
request: &'a mut Request,
_done_chan: &mut DoneChannel,
context: &FetchContext,
) -> FutureResponse {
) -> FutureResponse<'a> {
let mut url = request.current_url();
// Step 1. Assert: inputURL's scheme is normalizedScheme.
assert!(url.scheme() == self.scheme);