用Rust搭建React Server Components 的Web服务器(二)

简介: 用Rust搭建React Server Components 的Web服务器(二)

2. 服务端渲染

说一个事实,其实见到的网页都是通过网络传输的HTML构建而成(毕竟HTML构成了网页的骨架)。 只不过,有些网页是一股脑的所有HTML都返回了,而有的网页是通过SPA/SSR等资源分离技术通过Node/JS/React等各种眼花缭乱的技术来构建一个网站。

构建服务器

让我们使用Axum作为应用框架构建一个最简单的Web服务器

image.png

首先,我们先在Cargo.toml中引入axumtokio。这两具体干啥的,我们在前面介绍了,这里就不过赘述了。

[dependencies]
axum = { version = "0.6.20" }
tokio = { version = "1", features = ["full"] }

main.rs

下面这段代码是构建一个监听指定端口的服务器。并对指定路径的做出反应。

use axum::{  
  response::Html, 
  response::IntoResponse, 
  routing::get, Router
  };
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
   // 创建一个 Axum 应用程序
   let app = Router::new()
       .route("/page1", get(page1))
       .route("/page2", get(page2));
   // 指定服务器地址(这里监听本地的 127.0.0.1:3000)
   let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
   // 使用 Axum 的服务器绑定和服务方法启动服务器
   axum::Server::bind(&addr)
       .serve(app.into_make_service())
       .await
       .unwrap();
}
async fn page1() -> impl IntoResponse {
    Html("<h1>Page 1</h1>")
}
async fn page2() -> impl IntoResponse {
    Html("<h1>Page 2!!!</h1>")
}

我们对其中核心代码做简单的分析和解释:

  1. main函数:
  • #[tokio::main] 注解标识 main 函数是异步的,这意味着它可以在一个异步运行时环境中执行。
  • main 函数中,首先通过 Router::new() 创建了一个 Axum 应用程序 app,然后使用 .route() 方法定义了两个路由规则:一个是 /page1,另一个是 /page2,分别映射到 page1page2 函数。
  • 接着,通过 SocketAddr 创建了服务器地址对象,指定服务器监听的地址和端口。
  • 最后,使用 AxumServer::bind() 方法绑定服务器地址,并使用 .serve() 方法启动服务器。服务器将处理传入的请求,并根据路由规则调用相应的处理函数。
  1. page1page2函数:
  • 这两个函数是路由处理函数,它们接收请求并返回响应。
  • 这里的 page1 函数返回一个 Html 响应,其中包含一个简单的标题标签 <h1>Page 1</h1>
  • 同样,page2 函数返回一个 Html 响应,其中包含 <h1>Page 2!!!</h1>

使用cargo run后,我们可以在浏览器中,通过访问对应的页面地址进行页面展示。

image.png



页面共有逻辑抽离

在我们页面开发中,总是会有多个页面拥有共同的布局和样式,我们可以对其进行抽离。也就是消除重复的HTML,此时我们可以使用format!()来操作。

// 代码省略
async fn layout(content: &'static str) -> String {
    format!(
        "
        <h1>柒八九陪你一起学Rust</h1>
        {content}
        "
    )
}
async fn page1() -> impl IntoResponse {
    Html(layout("<h1>Page 1</h1>").await)
}
async fn page2() -> impl IntoResponse {
    Html(layout("<h1>Page 2!!!</h1>").await)
}

上面代码中最重要的是 content 参数的类型是 &'static str,这意味着它是一个静态字符串切片,其生命周期被标记为 'static,表示这个字符串切片在整个程序运行期间都有效,不会被销毁。这样做是为了确保在异步函数中使用字符串切片时,不会出现生命周期问题,因为异步函数可能会在较长的时间内挂起。

同时函数签名定义为async fn 表示这是一个异步函数,它可以在执行期间挂起而不会阻塞整个线程。

我们还是熟悉的配方,在浏览器中访问对应的页面地址。

image.png


在Rust中定义组件

熟悉前端开发的同学,感觉到这种逻辑或者页面结构抽离很熟悉,这不就是我们经常挂在嘴边的组件封装吗。既然页面结构可以进行抽离,那如果我们站在页面全链路角度来看,那是不是我们可以在服务器中定义我们页面中需要展示的组件,然后再输出到浏览器中。

咦,这个观点和概念是不是又感觉似曾相识。这就是我们之前讲过的RSC

例如,现在有一个React组件。

// 一个JSX组件
function Count({ count }) {
    if (count < 0) {
        return <p>数字为负数</p>;
    }
    return <h1>{count}</h1>;
}

等价于Rust服务器组件

fn count(count: i64) -> String {
    if count < 0 {
        "<p>数字为负数</p>".into()
    } else {
        format!("<h1>{count}</h1>")
    }
}

可能大家没注意到,这里做一下拓展,在React中我们定义组件的时候,我们有一个不成文的规定,组件首字母大写,例如上面的例子中Count。 而到了Rust中定义组件时候,组件名称变成了小写了(count)。其实这也是Rust的不成文规定。这是因为Rust代码使用{蛇形命名法|Snake Case} 来作为规范函数和变量名称的风格。而蛇形命名法只使用小写的字母进行命名,并以下画线分隔单词

use axum::{
  response::Html, 
  response::IntoResponse, 
  routing::get, Router
};
// 省略部分代码
async fn layout(content: String) -> String {
    format!(
        "
        <h1>柒八九陪你一起学Rust</h1>
        {content}
        "
    )
}
fn count(count: i64) -> String {
    if count < 0 {
        "<p>数字为负数</p>".into()
    } else {
        format!("<h1>{count}</h1>")
    }
}
async fn page1() -> impl IntoResponse {
    Html(layout(count(789)).await)
}
async fn page2() -> impl IntoResponse {
    Html(layout(count(-1)).await)
}

我们可以将count引入到我们的页面中。

image.png


注意:上面例子中,layout(content: String)的函数签名变成接受String了。(不过这里不是很重要)


优化组件定义方式

我们上面的代码示例中,是利用fromat!()对字符串进行页面结构的拼装,在一些简单的页面这种方式还是很有作用的,但是如果阁下遇到页面逻辑复杂,层级众多的时候,使用format!()就有点捉襟见肘了。

我们可以使用Maud crate,来重新处理上面的组件。

image.png

下面,我们做一次简单的改造。

首先,我们引入了一个新的依赖项。

[dependencies]
maud = { varsion = "0.25.0", features = ["axum"] } 

这里的features是适配各种Rust框架的,具体情况可以参考Web framework integration

使用MaudRSC

fn count(count: i64) -> Markup {
    // 我们仍然可以在这里放一些其他逻辑。
    // 但是,Maud模板方便地支持if、let、match和循环。
    html! {
        @if (count < 0) {
            p { "数字为负数" }
        } else {
            h1 { (count) }
        }
    }
}

我们现在将format!()maud替换。代码如下:

// ...省略
use maud::{html, Markup};
// 省略main 函数
async fn layout(content: Markup) -> Markup {
    html! {
        h1 {"柒八九陪你一起学Rust"}
        {(content)}
    }
}
fn count(count: i64) -> Markup {
    html! {
        @if count < 0 {
            p {"数字为负数"}
        }@ else {
            h1 {(count)}
        }
    }
}
async fn page1() -> impl IntoResponse {
    html! {
        {(layout(count(789)).await)}
    }
}
async fn page2() -> impl IntoResponse {
    html! {
        {(layout(count(-1)).await)}
    }
}

然后,我们继续访问对应的网址,可以查看一下效果。(亲测有效,这里就不贴图了)

细心的小伙伴,可能使用Maud时,函数签名中使用了Markup而不是String。这里简单的解释一下。

Markup是一个字符串,但它也是一种表示包含HTML的字符串的方式。默认情况下,Maud会转义字符串内容。直接返回Markup更容易嵌套Maud组件

Last but not least,最浓墨重彩的一笔是,MaudRender特性

默认情况下,Maud会使用标准的Display特性将组件呈现为HTML。类型可以通过实现Render来自定义其输出。

这对于创建自己的组件非常有用:

我们可以在各自页面或者共有页面中引入对应的样式信息。对应的代码如下。

struct Css(&'static str);
impl Render for Css {
    fn render(&self) -> Markup {
        html! {
            link rel="stylesheet" type="text/css" href=(self.0);
        }
    }
}
async fn layout(content: Markup) -> Markup {
    // 创建一个 Stylesheet 实例,传入样式表的链接地址
    let stylesheet = Stylesheet("./styles.css");
    // 使用 render 方法将 Stylesheet 实例转换为 Markup
    let stylesheet_markup: Markup = stylesheet.render();
    html! {
        head {
            title {"Rust 搭建RSC服务器"}
            // 插入样式表链接
            (stylesheet_markup)
        }
        body {
            h1 {"柒八九陪你一起学Rust"}
            {(content)}
        }
    }
}

image.png

相关文章
RUST游戏服务器搭建
本文介绍了搭建Rust服务器的详细步骤,涵盖硬件和软件要求、Linux和Windows环境下的安装配置、进阶设置如自定义模式和插件支持、端口转发、管理命令及常见问题解决方法。硬件方面推荐4核CPU、16GB内存、SSD硬盘及10Mbps上传带宽;操作系统建议使用Linux(Ubuntu 22.04 LTS)或Windows Server,并需安装SteamCMD等工具。通过这些步骤,用户可以顺利搭建并维护一个稳定高效的Rust服务器。
211 7
超聚变服务器2288H V6使用 iBMC 安装 Ubuntu Server 24.04 LTS及后续系统配置
【11月更文挑战第15天】本文档详细介绍了如何使用iBMC在超聚变服务器2288H V6上安装Ubuntu Server 24.04 LTS,包括连接iBMC管理口、登录iBMC管理界面、配置RAID、安装系统以及后续系统配置等步骤。
1096 4
gpg从公钥服务器接收失败(gpg: keyserver receive failed: Server indicated a failure)
通过上述步骤,大多数情况下应该能够解决GPG从公钥服务器接收失败的问题。如果问题依旧存在,可能需要进一步调查与公钥服务器相关的更深层次的技术问题,或者考虑在相关社区论坛寻求帮助。
1342 1
Windows Server 2019 DHCP服务器搭建
Windows Server 2019 DHCP服务器搭建
146 3
Windows Server 2019 Web服务器搭建
Windows Server 2019 Web服务器搭建
228 0
阿里云经济型e实例云服务器评测:企业官网搭建的性价比之选
阿里云服务器经济型e实例可以用来搭建企业网站吗?云服务器作为搭建企业官网的基础设施,其性能、稳定性、成本等因素直接影响着官网的运营效果。阿里云经济型e实例云服务器作为一款性价比较高的产品,备受用户关注。许多企业在选择云服务器搭建官网时,都会将其纳入考虑范围。本文将详细探讨阿里云经济型e实例云服务器的特点、性能表现、稳定性与可靠性,以及成本考量,最终解答是否适合用它来搭建企业官网。
阿里云特惠云服务器99元与199元配置与性能和适用场景解析:高性价比之选
2025年,阿里云长效特惠活动继续推出两款极具吸引力的特惠云服务器套餐:99元1年的经济型e实例2核2G云服务器和199元1年的通用算力型u1实例2核4G云服务器。这两款云服务器不仅价格亲民,而且性能稳定可靠,为入门级用户和普通企业级用户提供了理想的选择。本文将对这两款云服务器进行深度剖析,包括配置介绍、实例规格、使用场景、性能表现以及购买策略等方面,帮助用户更好地了解这两款云服务器,以供参考和选择。
DeepSeek服务器繁忙解决方法:使用阿里云一键部署DeepSeek个人网站!
通过阿里云一键部署DeepSeek个人网站,解决服务器繁忙问题。学生用户可领取300元代金券实现0成本部署,普通用户则可用99元/年的服务器。教程涵盖从选择套餐、设置密码到获取百炼API-KEY的全流程,助您快速搭建专属大模型主页,体验DeepSeek、Qwen-max、Llama等多款模型,无需代码,最快5分钟完成部署。支持绑定个人域名,共享亲友使用,日均成本仅约1元。
150 10

热门文章

最新文章