Building a URL Shortener With Spin
A Simple URL Shortener Built With Spin
This tutorial will walk you through building a Spin component that redirects short URLs to their configured destinations. In essence, this is a simple HTTP component that returns a response that contains redirect information based on the user-defined routes.
This is an evolving tutorial. As Spin allows building more complex components (through supporting access to services like databases), this tutorial will be updated to reflect that.
Here Is One We Prepared Earlier
The complete implementation for this tutorial can be found on GitHub.
First, our URL shortener allows users to configure their own final URLs —
currently, that is done through a configuration file that contains multiple
[[route]]
entries, each containing the shortened path as source
, and
the destination
URL:
[[route]]
source = "/spin"
destination = "https://github.com/fermyon/spin"
[[route]]
source = "/hype"
destination = "https://www.fermyon.com/blog/how-to-think-about-wasm"
Whenever a request for https://<domain>/spin
is sent, our component will
redirect to https://github.com/fermyon/spin
. Now that we have a basic
understanding of how the component should behave, let’s see how to implement it
using Spin.
First, we start with a new Spin component written in Rust:
/// A Spin HTTP component that redirects requests
/// based on the router configuration.
#[http_component]
fn redirect(req: Request) -> Result<Response> {
let router = Router::default()?;
router.redirect(req)
}
All the component does is create a new router based on the default configuration, then use it to redirect the request. Let’s see how the router is defined:
#[derive(Debug, Deserialize, Serialize)]
pub struct Route {
pub source: String,
pub destination: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Router {
#[serde(rename = "route")]
pub routes: Vec<Route>,
}
The Router
structure is a Rust representation of the TOML configuration above.
pub fn redirect(self, req: Request) -> Result<Response> {
// read the request path from the `spin-path-info` header
let path_info = req
.headers()
.get("spin-path-info")
.expect("cannot get path info from request headers");
// if the path is not present in the router configuration,
// return 404 Not Found.
let route = match self.path(path_info.to_str()?) {
Some(r) => r,
None => return not_found(),
};
// otherwise, return the redirect to the destination
let res = http::Response::builder()
.status(http::StatusCode::PERMANENT_REDIRECT)
.header(http::header::LOCATION, route.destination)
.body(None)?;
Ok(res)
}
The redirect
function is straightforward — it reads the request path from the
spin-path-info
header (make sure to read the document about the HTTP trigger
for an overview of the HTTP headers present in Spin components), selects the
corresponding destination from the router configuration, then sends the
HTTP redirect to the new location.
At this point, we can build and run the module with Spin:
$ spin build
$ spin up
And the component can now handle incoming requests:
# based on the configuration file, a request
# to /spin should be redirected
$ curl -i localhost:3000/spin
HTTP/1.1 308 Permanent Redirect
location: https://github.com/fermyon/spin
content-length: 0
# based on the configuration file, a request
# to /hype should be redirected
$ curl -i localhost:3000/hype
HTTP/1.1 308 Permanent Redirect
location: https://www.fermyon.com/blog/how-to-think-about-wasm
content-length: 0
# /abc is not present in the router configuration,
# so this returns a 404.
$ curl -i localhost:3000/abc
HTTP/1.1 404 Not Found
content-length: 9
Not Found
Notice that you can use the
--listen
option forspin up
to start the web server on a specific host and port, which you can then bind to a domain.
We can now publish the application to an OCI registry (together with router configuration file):
$ spin registry push ghcr.io/alyssa-p-hacker/url-shortener:v1
Pushed with digest sha256:3c408a1b29b3098286c7ea5ab22f47248ccfadcc63ad5596ca0d85e3f522c43d
And now we can run the application directly from the registry:
$ spin up -f ghcr.io/alyssa-p-hacker/url-shortener:v1
Serving http://127.0.0.1:3000
Available Routes:
shortener: http://127.0.0.1:3000 (wildcard)
Conclusion
In this tutorial we built a simple URL shortener as a Spin component. In the future we will expand this tutorial by storing the router configuration in a database supported by Spin, and potentially create another component that can be used to add new routes to the configuration.