用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

相关文章
|
7天前
计算机网络:思科实验【1-访问WEB服务器】
计算机网络:思科实验【1-访问WEB服务器】
计算机网络:思科实验【1-访问WEB服务器】
|
12天前
|
数据可视化 Shell Linux
shell+crontab+gitlab实现ecs服务器文件的web展示
本文通过把ecs服务器上的文件定时上传至gitlab,实现文件的页面可视化和修改历史。技术点:shell、crontab、gitlab。
38 3
|
1月前
|
负载均衡 应用服务中间件 API
什么是 Web 服务器领域的 openresty
什么是 Web 服务器领域的 openresty
31 0
|
2月前
|
IDE Linux 开发工具
如何在Linux运行RStudio Server并实现Web浏览器远程访问
如何在Linux运行RStudio Server并实现Web浏览器远程访问
43 0
|
2月前
|
存储 运维 应用服务中间件
[运维日志] Web 服务器日志依日期归档(Powershell 实现,附源代码)
[运维日志] Web 服务器日志依日期归档(Powershell 实现,附源代码)
57 0
|
1月前
|
应用服务中间件 nginx
【报错】Failed to start A high performance web server and a reverse proxy server.
【报错】Failed to start A high performance web server and a reverse proxy server.
|
6天前
|
安全 网络安全 开发者
如何在OpenWRT部署uhttpd搭建服务器实现远程访问本地web站点
如何在OpenWRT部署uhttpd搭建服务器实现远程访问本地web站点
20 0
|
7天前
|
存储 缓存 网络协议
Go语言并发编程实战:构建高性能Web服务器
【2月更文挑战第6天】本文将通过构建一个高性能的Web服务器实战案例,深入探讨如何在Go语言中运用并发编程技术。我们将利用goroutine和channel实现高效的请求处理、资源管理和并发控制,以提升Web服务器的性能和稳定性。通过这一实战,你将更好地理解和掌握Go语言在并发编程方面的优势和应用。
|
13天前
|
负载均衡 JavaScript 应用服务中间件
强大的WEB服务器-Nginx
强大的WEB服务器-Nginx
19 0
|
28天前
|
Kubernetes 应用服务中间件 nginx
K8S Pod Sidecar 应用场景之一 - 加入 NGINX Sidecar 做反代和 web 服务器
K8S Pod Sidecar 应用场景之一 - 加入 NGINX Sidecar 做反代和 web 服务器

相关产品

  • 云迁移中心