最近学了Rust想找一个纯rust的项目,但是没找到合适的,在 Devto上看到了一个不错的系列文章URL Shortener with Rust, Svelte, & AWS (1/): Intro + Setup,原文戳此处。
这篇文章涉及的比较广泛、全面,是使用Rust的Web框架Rocket和前端框架Svelte,再结合Docker以及AWS提供线上服务。
在这里我只参考了Rocket和Svelte部分。
Rocket
创建项目
mkdir cargo-rocket
cd cargo-rocket
cargo init --bin
添加依赖
[dependencies]
rocket = "0.5.0-rc.1"
dashmap = "4.0.2"
rand = "0.8.4"
use dashmap::DashMap;
use rand::{thread_rng, Rng};
use rocket::{State, fs::FileServer, response::{
Redirect,
status::{
BadRequest,
NotFound
}
}};
#[macro_use]
extern crate rocket;
#[get("/<key>")]
fn redirect(key: u32, state: &State<DashMap<u32, String>>) -> Result<Redirect, NotFound<&str>> {
state
.get(&key)
.map(|url| Redirect::to(url.clone()))
.ok_or(NotFound("?"))
}
#[post("/api/shorten?<url>")]
fn shorten(url: String, state: &State<DashMap<u32, String>>) -> Result<String, BadRequest<&str>> {
if url.is_empty() {
Err(BadRequest(Some("URL is empty!")))
} else {
let key = thread_rng().gen::<u32>();
state.insert(key, url);
Ok(key.to_string())
}
}
#[launch]
fn rocket() -> _ {
rocket::build()
.manage(DashMap::<u32, String>::new())
.mount("/", routes![shorten, redirect])
.mount(
"/",
FileServer::from(rocket::fs::relative!("/svelte/build"))
)
}
#[cfg(test)]
mod test {
use rocket::{http::Status, local::blocking::Client};
use super::rocket;
#[test]
fn simple_demop_test() {
let x = 1 + 1;
assert_eq!(x, 2);
}
#[test]
fn vaild_requests() {
let client = Client::tracked(rocket()).expect("valid rocket instance");
let response = client.post("/api/shorten?url=https://duck.com").dispatch();
assert_eq!(response.status(), Status::Ok);
let key: u32 = response
.into_string()
.expect("body")
.parse()
.expect("valid u32");
let response = client.get(format!("/{}", key)).dispatch();
assert_eq!(response.status(), Status::SeeOther);
let redirect = response
.headers()
.get_one("Location")
.expect("location header");
assert_eq!(redirect, "https://duck.com")
}
#[test]
fn empty_url() {
let client = Client::tracked(rocket())
.expect("valid rocket instance");
let response = client.post("/api/shorten?url=").dispatch();
assert_eq!(response.status(), Status::BadRequest);
}
#[test]
fn invalid_url() {
let client = Client::tracked(rocket())
.expect("valid rocket instance");
let response = client.post("/123").dispatch();
assert_eq!(response.status(), Status::NotFound);
}
}
Svelte
这里使用了SvelteKit,npm init svelte@next svelte
。
先创建一个简单的模板,然后 npm install
,然后通过npm build
进行打包,可以使用于生产环境。
但是Svelte要打包到对应的环境需要对应的Adaptor,因此打包之前先安装adapter-static
<!-- /cargo-rocket/svelte/src/app.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
%svelte.head%
</head>
<body>
<div id="svelte">%svelte.body%</div>
</body>
</html>
// /cargo-rocket/svelte/src/route/index.svelte
<div class="box">
{#if request == null}
<div class="field has-addons">
<div class="control">
<input
class="input"
type="text"
bind:value={url}
placeholder="URL"
/>
</div>
<div class="control">
<button class="button is-info" on:click={click}>Shorten</button>
</div>
</div>
{:else}
{#await request}
<p>Loading...</p>
{:then response}
<div class="card">
<header class="card-header">
<p class="card-header-title">Done!</p>
</header>
<div class="card-content">
<a
class="content"
href={getUrl(response.text)}
target="_blank">{getUrl(response.text)}</a
>
</div>
<footer class="card-footer">
<button
class="card-footer-item button"
on:click={() => (request = null)}>Back</button
>
<button
class="card-footer-item button is-info"
on:click={() =>
navigator.clipboard.writeText(
getUrl(response.text)
)}>Copy</button
>
</footer>
</div>
{:catch}
<p>Something went wrong!</p>
{/await}
{/if}
</div>
<script>
import superagent from "superagent";
let url = "";
let request = null;
function click () {
request = superagent.post(`/api/shorten?url=${url}`);
}
function getUrl(key) {
return `http://${window.location.host}/${key}`;
}
</script>
// /cargo-rocket/svelte/src/route/__layout.svelte
<div id="svelte" class="container is-fluid my-5">
<nav class="navbar is-dark" role="navigation">
<div class="navbar-brand">
<div class="navbar-item ml-5 is-dark">
<img src="/favicon.png" width="32" height="32" alt="logo" />
</div>
<h1 class="title is-2 navbar-item">URL Shortener</h1>
</div>
</nav>
<slot />
</div>