用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

相关文章
|
5天前
|
Rust 监控 Ubuntu
RUST游戏服务器搭建
本文介绍了搭建Rust服务器的详细步骤,涵盖硬件和软件要求、Linux和Windows环境下的安装配置、进阶设置如自定义模式和插件支持、端口转发、管理命令及常见问题解决方法。硬件方面推荐4核CPU、16GB内存、SSD硬盘及10Mbps上传带宽;操作系统建议使用Linux(Ubuntu 22.04 LTS)或Windows Server,并需安装SteamCMD等工具。通过这些步骤,用户可以顺利搭建并维护一个稳定高效的Rust服务器。
39 7
|
3月前
|
Ubuntu 网络协议 关系型数据库
超聚变服务器2288H V6使用 iBMC 安装 Ubuntu Server 24.04 LTS及后续系统配置
【11月更文挑战第15天】本文档详细介绍了如何使用iBMC在超聚变服务器2288H V6上安装Ubuntu Server 24.04 LTS,包括连接iBMC管理口、登录iBMC管理界面、配置RAID、安装系统以及后续系统配置等步骤。
486 4
|
3月前
|
XML 前端开发 JavaScript
PHP与Ajax在Web开发中的交互技术。PHP作为服务器端脚本语言,处理数据和业务逻辑
本文深入探讨了PHP与Ajax在Web开发中的交互技术。PHP作为服务器端脚本语言,处理数据和业务逻辑;Ajax则通过异步请求实现页面无刷新更新。文中详细介绍了两者的工作原理、数据传输格式选择、具体实现方法及实际应用案例,如实时数据更新、表单验证与提交、动态加载内容等。同时,针对跨域问题、数据安全与性能优化提出了建议。总结指出,PHP与Ajax的结合能显著提升Web应用的效率和用户体验。
84 3
|
4月前
|
网络协议 Ubuntu Linux
gpg从公钥服务器接收失败(gpg: keyserver receive failed: Server indicated a failure)
通过上述步骤,大多数情况下应该能够解决GPG从公钥服务器接收失败的问题。如果问题依旧存在,可能需要进一步调查与公钥服务器相关的更深层次的技术问题,或者考虑在相关社区论坛寻求帮助。
838 1
|
4月前
|
网络协议 Windows
Windows Server 2019 DHCP服务器搭建
Windows Server 2019 DHCP服务器搭建
|
4月前
|
网络协议 定位技术 Windows
Windows Server 2019 DNS服务器搭建
Windows Server 2019 DNS服务器搭建
155 1
|
4月前
|
网络协议 文件存储 Windows
Windows Server 2019 FTP服务器搭建
Windows Server 2019 FTP服务器搭建
142 0
|
4月前
|
网络协议 Windows
Windows Server 2019 Web服务器搭建
Windows Server 2019 Web服务器搭建
142 0
|
4天前
|
存储 机器学习/深度学习 人工智能
2025年阿里云GPU服务器租用价格、选型策略与应用场景详解
随着AI与高性能计算需求的增长,阿里云提供了多种GPU实例,如NVIDIA V100、A10、T4等,适配不同场景。2025年重点实例中,V100实例GN6v单月3830元起,适合大规模训练;A10实例GN7i单月3213.99元起,适用于混合负载。计费模式有按量付费和包年包月,后者成本更低。针对AI训练、图形渲染及轻量级推理等场景,推荐不同配置以优化成本和性能。阿里云还提供抢占式实例、ESSD云盘等资源优化策略,支持eRDMA网络加速和倚天ARM架构,助力企业在2025年实现智能计算的效率与成本最优平衡。 (该简介为原文内容的高度概括,符合要求的字符限制。)
|
6天前
|
存储 弹性计算 人工智能
2025年阿里云企业云服务器ECS选购与配置全攻略
本文介绍了阿里云服务器的核心配置选择方法论,涵盖算力需求分析、网络与存储设计、地域部署策略三大维度。针对不同业务场景,如初创企业官网和AI模型训练平台,提供了具体配置方案。同时,详细讲解了购买操作指南及长期运维优化建议,帮助用户快速实现业务上云并确保高效运行。访问阿里云官方资源聚合平台可获取更多最新产品动态和技术支持。