Serve both files and directories
This commit is contained in:
parent
053854ce72
commit
9fa09de8df
24
README.md
24
README.md
|
@ -29,9 +29,31 @@ Then just configure Apache or NGINX to proxy to the given port. For example:
|
||||||
|
|
||||||
# ... other options ...
|
# ... other options ...
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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
|
## Build and install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
71
src/main.rs
71
src/main.rs
|
@ -1,8 +1,6 @@
|
||||||
// SPDX-FileCopyrightText: © Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
// SPDX-FileCopyrightText: © Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
// SPDX-License-Identifier: EUPL-1.2
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
use s3::serde_types::ListBucketResult;
|
|
||||||
|
|
||||||
use {
|
use {
|
||||||
lazy_static::lazy_static,
|
lazy_static::lazy_static,
|
||||||
rocket::response::Responder,
|
rocket::response::Responder,
|
||||||
|
@ -80,61 +78,61 @@ enum Error {
|
||||||
|
|
||||||
#[rocket::get("/<path..>")]
|
#[rocket::get("/<path..>")]
|
||||||
async fn index(path: PathBuf) -> Result<FileView, Error> {
|
async fn index(path: PathBuf) -> Result<FileView, Error> {
|
||||||
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:
|
The way things work in S3, the following holds for us:
|
||||||
- we need to use a slash as separator
|
- we need to use a slash as separator
|
||||||
- folders need to be queried ending with a slash
|
- 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 try first to retrieve list an object as a file. If we fail,
|
||||||
we fallback to retrieving the object itself.
|
we fallback to retrieving the equivalent folder.
|
||||||
*/
|
*/
|
||||||
let s3_objects = BUCKET.list(s3_path, Some("/".into())).await;
|
|
||||||
|
|
||||||
let s3_objects = match s3_objects {
|
// FIXME: this can be big, we should use streaming,
|
||||||
Ok(s3_objects) => s3_objects,
|
// not loading in memory!
|
||||||
Err(_) => {
|
if !path.as_os_str().is_empty() {
|
||||||
// TODO: this can be big, we should use streaming,
|
let data = BUCKET
|
||||||
// not loading in memory.
|
.get_object(format!("{}", path.display()))
|
||||||
let data = BUCKET
|
.await
|
||||||
.get_object(format!("{}", path.display()))
|
.map_err(|_| Error::NotFound("Object not found".into()));
|
||||||
.await
|
|
||||||
.map_err(|_| Error::NotFound("Object not found".into()))?
|
if let Ok(contents) = data {
|
||||||
.bytes()
|
let bytes = contents.bytes().to_vec();
|
||||||
.to_vec();
|
return Ok(FileView::File(bytes));
|
||||||
return Ok(FileView::File(data));
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let objects = s3_fileview(&s3_objects);
|
let objects = s3_fileview(&path).await?;
|
||||||
let rendered = Template::render(
|
let rendered = Template::render(
|
||||||
"index",
|
"index",
|
||||||
context! {
|
context! {
|
||||||
path: format!("{}/", path.display()),
|
path: format!("{}/", path.display()),
|
||||||
has_parent: !path.as_os_str().is_empty(),
|
|
||||||
objects
|
objects
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Ok(FileView::Folder(rendered))
|
Ok(FileView::Folder(rendered))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn s3_fileview(s3_objects: &Vec<ListBucketResult>) -> Vec<&str> {
|
async fn s3_fileview(path: &PathBuf) -> Result<Vec<String>, Error> {
|
||||||
/*
|
/*
|
||||||
if listing a folder:
|
if listing a folder:
|
||||||
- folders will be under 'common_prefixes'
|
- folders will be under 'common_prefixes'
|
||||||
- files will be under the 'contents' property
|
- 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()
|
.iter()
|
||||||
.flat_map(|list| -> Vec<Option<&str>> {
|
.flat_map(|list| -> Vec<Option<&str>> {
|
||||||
let prefix = if let Some(p) = &list.prefix {
|
let prefix = if let Some(p) = &list.prefix {
|
||||||
|
@ -148,14 +146,19 @@ fn s3_fileview(s3_objects: &Vec<ListBucketResult>) -> Vec<&str> {
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|dir| dir.prefix.strip_prefix(&prefix));
|
.map(|dir| dir.prefix.strip_prefix(&prefix));
|
||||||
|
|
||||||
let files = list
|
let files = list
|
||||||
.contents
|
.contents
|
||||||
.iter()
|
.iter()
|
||||||
.map(|obj| obj.key.strip_prefix(&prefix));
|
.map(|obj| obj.key.strip_prefix(&prefix));
|
||||||
|
|
||||||
folders.chain(files).collect()
|
folders.chain(files).collect()
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect()
|
.map(str::to_owned)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::launch]
|
#[rocket::launch]
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<body>
|
<body>
|
||||||
<h1>{{ path }}</h1>
|
<h1>{{ path }}</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% if has_parent %}
|
{% if path != "/" %}
|
||||||
<li><a href="../">..</a></li>
|
<li><a href="../">..</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue