Add ability to set 404 image

Fix imagemagick blur arguments
This commit is contained in:
asonix 2023-07-05 19:06:48 -05:00
parent e7e4876908
commit 3487cb0e30
4 changed files with 85 additions and 13 deletions

View File

@ -430,6 +430,21 @@ A secure API key can be generated by any password generator.
"identifier": "/path/to/object" "identifier": "/path/to/object"
} }
``` ```
- `POST /internal/set_not_found` Set the 404 image that is served from the original and process
endpoints. The image used must already be uploaded and have an alias. The request should look
like this:
```json
{
"alias": "asdf.png"
}
```
On success, the returned json should look like this:
```json
{
"msg": "ok"
}
```
Additionally, all endpoints support setting deadlines, after which the request will cease Additionally, all endpoints support setting deadlines, after which the request will cease
processing. To enable deadlines for your requests, you can set the `X-Request-Deadline` header to an processing. To enable deadlines for your requests, you can set the `X-Request-Deadline` header to an

View File

@ -1,6 +1,8 @@
[server] [server]
address = '0.0.0.0:8080' address = '0.0.0.0:8080'
worker_id = 'pict-rs-1' worker_id = 'pict-rs-1'
api_key = 'api-key'
[tracing.logging] [tracing.logging]
format = 'normal' format = 'normal'
targets = 'warn,tracing_actix_web=info,actix_server=info,actix_web=info' targets = 'warn,tracing_actix_web=info,actix_server=info,actix_web=info'

View File

@ -80,6 +80,8 @@ const MINUTES: u32 = 60;
const HOURS: u32 = 60 * MINUTES; const HOURS: u32 = 60 * MINUTES;
const DAYS: u32 = 24 * HOURS; const DAYS: u32 = 24 * HOURS;
const NOT_FOUND_KEY: &str = "404-alias";
static DO_CONFIG: OnceCell<(Configuration, Operation)> = OnceCell::new(); static DO_CONFIG: OnceCell<(Configuration, Operation)> = OnceCell::new();
static CONFIG: Lazy<Configuration> = Lazy::new(|| { static CONFIG: Lazy<Configuration> = Lazy::new(|| {
DO_CONFIG DO_CONFIG
@ -595,6 +597,21 @@ async fn process_details<R: FullRepo, S: Store>(
Ok(HttpResponse::Ok().json(&details)) Ok(HttpResponse::Ok().json(&details))
} }
async fn not_found_hash<R: FullRepo>(repo: &R) -> Result<Option<(Alias, R::Bytes)>, Error> {
let Some(not_found) = repo.get(NOT_FOUND_KEY).await? else {
return Ok(None);
};
let alias = String::from_utf8_lossy(not_found.as_ref())
.parse::<Alias>()
.expect("Infallible");
repo.hash(&alias)
.await
.map(|opt| opt.map(|hash| (alias, hash)))
.map_err(Error::from)
}
/// Process files /// Process files
#[tracing::instrument(name = "Serving processed image", skip(repo, store))] #[tracing::instrument(name = "Serving processed image", skip(repo, store))]
async fn process<R: FullRepo, S: Store + 'static>( async fn process<R: FullRepo, S: Store + 'static>(
@ -607,10 +624,15 @@ async fn process<R: FullRepo, S: Store + 'static>(
let (format, alias, thumbnail_path, thumbnail_args) = prepare_process(query, ext.as_str())?; let (format, alias, thumbnail_path, thumbnail_args) = prepare_process(query, ext.as_str())?;
let path_string = thumbnail_path.to_string_lossy().to_string(); let path_string = thumbnail_path.to_string_lossy().to_string();
let Some(hash) = repo.hash(&alias).await? else {
// Invalid alias let (hash, alias, not_found) = if let Some(hash) = repo.hash(&alias).await? {
// TODO: placeholder 404 image (hash, alias, false)
return Ok(HttpResponse::NotFound().finish()); } else {
let Some((alias, hash)) = not_found_hash(&repo).await? else {
return Ok(HttpResponse::NotFound().finish());
};
(hash, alias, true)
}; };
let identifier_opt = repo let identifier_opt = repo
@ -637,7 +659,7 @@ async fn process<R: FullRepo, S: Store + 'static>(
new_details new_details
}; };
return ranged_file_resp(&store, identifier, range, details).await; return ranged_file_resp(&store, identifier, range, details, not_found).await;
} }
let original_details = ensure_details(&repo, &store, &alias).await?; let original_details = ensure_details(&repo, &store, &alias).await?;
@ -674,6 +696,11 @@ async fn process<R: FullRepo, S: Store + 'static>(
} else { } else {
return Err(UploadError::Range.into()); return Err(UploadError::Range.into());
} }
} else if not_found {
(
HttpResponse::NotFound(),
Either::right(once(ready(Ok(bytes)))),
)
} else { } else {
(HttpResponse::Ok(), Either::right(once(ready(Ok(bytes))))) (HttpResponse::Ok(), Either::right(once(ready(Ok(bytes)))))
}; };
@ -785,15 +812,21 @@ async fn serve<R: FullRepo, S: Store + 'static>(
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let alias = alias.into_inner(); let alias = alias.into_inner();
let Some(identifier) = repo.identifier_from_alias::<S::Identifier>(&alias).await? else { let (hash, alias, not_found) = if let Some(hash) = repo.hash(&alias).await? {
// Invalid alias (hash, Serde::into_inner(alias), false)
// TODO: placeholder 404 image } else {
return Ok(HttpResponse::NotFound().finish()); let Some((alias, hash)) = not_found_hash(&repo).await? else {
return Ok(HttpResponse::NotFound().finish());
};
(hash, alias, true)
}; };
let identifier = repo.identifier(hash).await?;
let details = ensure_details(&repo, &store, &alias).await?; let details = ensure_details(&repo, &store, &alias).await?;
ranged_file_resp(&store, identifier, range, details).await ranged_file_resp(&store, identifier, range, details, not_found).await
} }
#[tracing::instrument(name = "Serving file headers", skip(repo, store))] #[tracing::instrument(name = "Serving file headers", skip(repo, store))]
@ -855,6 +888,7 @@ async fn ranged_file_resp<S: Store + 'static>(
identifier: S::Identifier, identifier: S::Identifier,
range: Option<web::Header<Range>>, range: Option<web::Header<Range>>,
details: Details, details: Details,
not_found: bool,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let (builder, stream) = if let Some(web::Header(range_header)) = range { let (builder, stream) = if let Some(web::Header(range_header)) = range {
//Range header exists - return as ranged //Range header exists - return as ranged
@ -887,7 +921,12 @@ async fn ranged_file_resp<S: Store + 'static>(
.to_stream(&identifier, None, None) .to_stream(&identifier, None, None)
.await? .await?
.map_err(Error::from); .map_err(Error::from);
(HttpResponse::Ok(), Either::right(stream))
if not_found {
(HttpResponse::NotFound(), Either::right(stream))
} else {
(HttpResponse::Ok(), Either::right(stream))
}
}; };
Ok(srv_response( Ok(srv_response(
@ -952,6 +991,21 @@ struct AliasQuery {
alias: Serde<Alias>, alias: Serde<Alias>,
} }
#[tracing::instrument(name = "Setting 404 Image", skip(repo))]
async fn set_not_found<R: FullRepo>(
json: web::Json<AliasQuery>,
repo: web::Data<R>,
) -> Result<HttpResponse, Error> {
let alias = json.into_inner().alias;
repo.set(NOT_FOUND_KEY, Vec::from(alias.to_string()).into())
.await?;
Ok(HttpResponse::Created().json(serde_json::json!({
"msg": "ok",
})))
}
#[tracing::instrument(name = "Purging file", skip(repo))] #[tracing::instrument(name = "Purging file", skip(repo))]
async fn purge<R: FullRepo>( async fn purge<R: FullRepo>(
query: web::Query<AliasQuery>, query: web::Query<AliasQuery>,
@ -1110,7 +1164,8 @@ fn configure_endpoints<R: FullRepo + 'static, S: Store + 'static>(
.service(web::resource("/variants").route(web::delete().to(clean_variants::<R>))) .service(web::resource("/variants").route(web::delete().to(clean_variants::<R>)))
.service(web::resource("/purge").route(web::post().to(purge::<R>))) .service(web::resource("/purge").route(web::post().to(purge::<R>)))
.service(web::resource("/aliases").route(web::get().to(aliases::<R>))) .service(web::resource("/aliases").route(web::get().to(aliases::<R>)))
.service(web::resource("/identifier").route(web::get().to(identifier::<R, S>))), .service(web::resource("/identifier").route(web::get().to(identifier::<R, S>)))
.service(web::resource("/set_not_found").route(web::post().to(set_not_found::<R>))),
); );
} }

View File

@ -295,7 +295,7 @@ impl Processor for Blur {
} }
fn command(&self, mut args: Vec<String>) -> Vec<String> { fn command(&self, mut args: Vec<String>) -> Vec<String> {
args.extend(["-gaussian-blur".to_string(), self.0.to_string()]); args.extend(["-gaussian-blur".to_string(), format!("0x{}", self.0)]);
args args
} }