From 9fa09de8dff547f37c5526acdd84af32bde3260a Mon Sep 17 00:00:00 2001 From: Matteo Settenvini Date: Sun, 2 Jul 2023 02:06:27 +0200 Subject: [PATCH] Serve both files and directories --- README.md | 24 ++++++++++++- src/main.rs | 71 ++++++++++++++++++++------------------- templates/index.html.tera | 2 +- 3 files changed, 61 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 45b6c87..6383b10 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,31 @@ Then just configure Apache or NGINX to proxy to the given port. For example: # ... other options ... - ``` +You probably also want a systemd unit file, for instance `/etc/systemd/system/serves3@.service`: + +```ini +[Unit] +Description=ServeS3, a S3 proxy +StartLimitInterval=100 +StartLimitBurst=10 + +[Service] +Type=simple +ExecStart=/usr/local/bin/serves3 +WorkingDirectory=/etc/serves3/%i/ +Environment=ROCKET_PORT=%i + +Restart=always +RestartSec=5s + +[Install] +WantedBy=multi-user.target +``` + +Then, e.g. for running on port 8000, you would put the corresponding configuration file in `/etc/serves3/8000/` and install the unit with `systemctl enable --now servers3@8000.service`. + ## Build and install ```bash diff --git a/src/main.rs b/src/main.rs index b6bad3a..f3e74ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: © Matteo Settenvini // SPDX-License-Identifier: EUPL-1.2 -use s3::serde_types::ListBucketResult; - use { lazy_static::lazy_static, rocket::response::Responder, @@ -80,61 +78,61 @@ enum Error { #[rocket::get("/")] async fn index(path: PathBuf) -> Result { - let parent = path.parent(); - - let s3_path = format!( - "{}{}", - path.display(), - match parent { - Some(_) => "/", - None => "", - } - ); - /* The way things work in S3, the following holds for us: - we need to use a slash as separator - folders need to be queried ending with a slash + - getting the bucket address will return an XML file + with all properties; we don't want that. - We try first to retrieve list an object as a folder. If we fail, - we fallback to retrieving the object itself. + We try first to retrieve list an object as a file. If we fail, + we fallback to retrieving the equivalent folder. */ - let s3_objects = BUCKET.list(s3_path, Some("/".into())).await; - let s3_objects = match s3_objects { - Ok(s3_objects) => s3_objects, - Err(_) => { - // TODO: this can be big, we should use streaming, - // not loading in memory. - let data = BUCKET - .get_object(format!("{}", path.display())) - .await - .map_err(|_| Error::NotFound("Object not found".into()))? - .bytes() - .to_vec(); - return Ok(FileView::File(data)); + // FIXME: this can be big, we should use streaming, + // not loading in memory! + if !path.as_os_str().is_empty() { + let data = BUCKET + .get_object(format!("{}", path.display())) + .await + .map_err(|_| Error::NotFound("Object not found".into())); + + if let Ok(contents) = data { + let bytes = contents.bytes().to_vec(); + return Ok(FileView::File(bytes)); } - }; + } - let objects = s3_fileview(&s3_objects); + let objects = s3_fileview(&path).await?; let rendered = Template::render( "index", context! { path: format!("{}/", path.display()), - has_parent: !path.as_os_str().is_empty(), objects }, ); Ok(FileView::Folder(rendered)) } -fn s3_fileview(s3_objects: &Vec) -> Vec<&str> { +async fn s3_fileview(path: &PathBuf) -> Result, Error> { /* if listing a folder: - folders will be under 'common_prefixes' - files will be under the 'contents' property */ - s3_objects + + let parent = path.parent(); + let s3_folder_path = match parent { + Some(_) => format!("{}/", path.display()), + None => "".into(), + }; + + let s3_objects = BUCKET + .list(s3_folder_path, Some("/".into())) + .await + .map_err(|_| Error::NotFound("Object not found".into()))?; + + let objects = s3_objects .iter() .flat_map(|list| -> Vec> { let prefix = if let Some(p) = &list.prefix { @@ -148,14 +146,19 @@ fn s3_fileview(s3_objects: &Vec) -> Vec<&str> { .iter() .flatten() .map(|dir| dir.prefix.strip_prefix(&prefix)); + let files = list .contents .iter() .map(|obj| obj.key.strip_prefix(&prefix)); + folders.chain(files).collect() }) .flatten() - .collect() + .map(str::to_owned) + .collect(); + + Ok(objects) } #[rocket::launch] diff --git a/templates/index.html.tera b/templates/index.html.tera index e702c72..5a83df0 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -8,7 +8,7 @@

{{ path }}

    - {% if has_parent %} + {% if path != "/" %}
  • ..
  • {% endif %}