pub fn serve(
ctx: &Ctx,
attachment: &Attachment,
headers: &HeaderMap,
disposition: Disposition,
) -> impl Future<Output = Result<Response>> + Send + use<>Expand description
Streams a stored file to the client, with the headers a browser needs for caching, seeking and saving it.
The response carries Content-Type, Content-Length,
Content-Disposition (the original file name; inline only for types
safe to display, see Disposition),
ETag and Cache-Control (CACHE_CONTROL).
A matching If-None-Match answers 304 Not Modified without a body; a
single Range (video seeking, resumed downloads) answers 206 with
Content-Range, or 416 when it lies outside the file (without calling
R2). Several ranges, other units and malformed values send the whole
file, as RFC 9110 allows. The headers come from attachment, so it must
be the row saved for that key.
Check that the user may see the record before calling it: the route is the only protection.
Free plan: one R2 class B operation per call, 304s included (10M free
per month). The bytes never pass through WebAssembly: the R2 stream is
attached to the response and crate::serve answers with it directly,
so a download costs almost no CPU whatever its size.
§Errors
Error::NotFound(404) when no object hasattachment.key.Error::Internal(500) when theSTORAGEbinding is missing (the message shows theSTORAGEentry to add to cloudflare.config.ts) or R2 fails.
§Examples
use axum::{extract::{Path, Query, State}, http::HeaderMap, response::Response};
use ocre::storage::{self, Attachment, Disposition};
use ocre::{Ctx, OptionExt, Result, params};
use serde::Deserialize;
#[derive(Deserialize)]
struct Download {
#[serde(default)]
download: bool,
}
// GET /photos/{id}/image, or /photos/{id}/image?download=true for "Save as".
async fn image(
State(ctx): State<Ctx>,
Path(id): Path<i64>,
Query(query): Query<Download>,
headers: HeaderMap,
) -> Result<Response> {
let sql = "SELECT image_key AS key, image_filename AS filename, image_content_type AS content_type, \
image_size AS size FROM photos WHERE id = ?1";
let image: Attachment = ctx.db()?.first(sql, params![id]).await?.or_404()?;
let disposition = if query.download { Disposition::Download } else { Disposition::Inline };
storage::serve(&ctx, &image, &headers, disposition).await
}