Final application
You should have this file tree layout:
$ tree
.
├── Cargo.lock
├── Cargo.toml
├── fastly.toml
└── src
├── app.js
├── index.html
└── main.rs
To recap your final Rust code should look something like this:
use std::io::Cursor;
use fastly::http::{Method, StatusCode};
use fastly::{mime, Error, Request, Response};
use rustagram::image;
use rustagram::image::io::Reader;
use rustagram::RustagramFilter;
#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
// Pattern match on the path...
match (req.get_method(), req.get_path()) {
// If request is to the `/` path...
(&Method::GET, "/") => Ok(Response::from_status(StatusCode::OK)
.with_content_type(mime::TEXT_HTML_UTF_8)
.with_body(include_str!("index.html"))),
(&Method::GET, "/app.js") => Ok(Response::from_status(StatusCode::OK)
.with_content_type(mime::APPLICATION_JAVASCRIPT)
.with_body(include_str!("app.js"))),
(&Method::POST, "/image") => convert_image(req),
// Catch all other requests and return a 404.
_ => Ok(Response::from_status(StatusCode::NOT_FOUND)
.with_body_text_plain("The page you requested could not be found\n")),
}
}
pub fn convert_image(mut req: Request) -> Result<Response, Error> {
let filter_str = req.get_query_parameter("filter").unwrap();
let filter = filter_str.parse().unwrap();
if !req.has_body() {
return Ok(
Response::from_status(StatusCode::BAD_REQUEST)
.with_body_text_plain("missing image")
);
}
let body = req.take_body();
let body = body.into_bytes();
let img = Reader::new(Cursor::new(body))
.with_guessed_format()
.unwrap();
let img = img.decode().unwrap();
let img = img.thumbnail(500, 500);
let out = img.to_rgba8().apply_filter(filter);
let mut bytes: Vec<u8> = Vec::new();
out.write_to(&mut Cursor::new(&mut bytes), image::ImageOutputFormat::Png)?;
Ok(Response::from_status(StatusCode::OK)
.with_body(bytes)
.with_content_type(mime::IMAGE_PNG))
}
The frontend in HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Rust WASM Demo</title>
</head>
<body>
<input type="file" id="files" name="file" accept="image/png, image/jpeg" />
<select name="filter">
<option value="None">none</option>
<option value="1977" selected>1977</option>
<option value="Aden">Aden</option>
<option value="Brannan">Brannan</option>
<option value="Brooklyn">Brooklyn</option>
<option value="Clarendon">Clarendon</option>
<option value="Earlybird">Earlybird</option>
<option value="Gingham">Gingham</option>
<option value="Hudson">Hudson</option>
<option value="Inkwell">Inkwell</option>
<option value="Kelvin">Kelvin</option>
<option value="Lark">Lark</option>
<option value="Lofi">Lofi</option>
<option value="Maven">Maven</option>
<option value="Mayfair">Mayfair</option>
<option value="Moon">Moon</option>
<option value="Nashville">Nashville</option>
<option value="Reyes">Reyes</option>
<option value="Rise">Rise</option>
<option value="Slumber">Slumber</option>
<option value="Stinson">Stinson</option>
<option value="Toaster">Toaster</option>
<option value="Valencia">Valencia</option>
<option value="Walden">Walden</option>
</select>
<span></span>
<br>
<img />
<script src="app.js"></script>
</body>
</html>
And the JavaScript frontend code:
document.querySelector('input[type=file]').onchange = (evt) => {
postImage();
};
document.querySelector('select').onchange = (evt) => {
postImage();
};
async function postImage() {
let files = document.getElementById('files').files;
if (!files.length) {
return;
}
let file = files[0];
let span = document.querySelector('span');
span.innerText = "working...";
let imgEl = document.querySelector('img');
imgEl.src = URL.createObjectURL(file);
imgEl.width = "500";
let filter = document.querySelector("select").value.toLowerCase();
if (filter == "none") {
span.innerText = "done.";
return;
}
let img = await file.arrayBuffer();
let url = `/image?filter=${filter}`;
let response = await fetch(url, {
method: "POST",
body: img,
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
let blob = await response.blob();
imgEl.src = URL.createObjectURL(blob);
span.innerText = "done.";
}
You can build and serve your application locally like this:
fastly compute serve
Some ideas on what to do next:
- Did you even notice that this was compiled to WebAssembly?
- What happens if you compile it natively?
- Can you return different image formats? Different sizes?
- What other task could be suitable for edge computing?