使用Rust(Rocket)、Svelte实现一个前后端分离的短链接生成器

11 篇文章 0 订阅

最近学了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

这里使用了SvelteKitnpm 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>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值