juri logo


crates package


总览

Juri 是一个 Web 服务器框架。

Github 地址

安装

Crates 地址

cargo:

cargo add juri 
cargo add async-std

或者

在 Cargo.toml 文件里添加 juri = "0.4.0-alpha.2"

使用

最小 Demo:

use juri::{Request, Response, Router, handler};
use std::net::SocketAddr;

#[handler]
fn handle_index(_request: &Request) -> juri::Result<Response> {
    Ok(Response::html("Hello Juri"))
}

#[juri::main]
async fn main() {
    let mut router = Router::new();

    router.at("/").get(api::views::handle_index);

    let addr = SocketAddr::from(([127, 0, 0, 1], 7878));
    juri::Server::bind(addr).server(router).await.unwrap();
}

新建路由器

use juri::Router;

let router = Router::new();

添加处理函数

At 方式

只支持 getpost 两种请求方式

use juri::Router;

let mut router = Router::new();

router.at("/").get(handle_fn);

/// 路径一样时,可以链式调用
router.at("/").get(handle_fn).post(handle_fn);

函数定义

use juri::{handler, Request, Response};

/// 支持同步和异步。异步时在 `fn` 前添加 `async`
#[handler]
fn handle_fn(request: &Request) -> juri::Result<Response> {
    /// body
}

Route 方式

只支持 getpost 两种请求方式,另外支持 ws

use juri::Router;

let mut router = Router::new();

/// 由函数提供,请求方法和路径
router.route(handle_fn);

函数定义

use juri::{get, post, Request, Response, WSRequest, WSResponse};

/// 支持同步和异步。异步时在 `fn` 前添加 `async`
#[get("/")]
fn handle_fn(request: &Request) -> juri::Result<Response> {
    /// body
}

/// 使用 ws 时,只需在 `get` 宏中加 ws 参数即可,其他同 get 请求格式
#[get("/", ws)]
fn handle_fn_ws(request: &juri::Request) -> juri::Result<WSResponse> {
    /// body
}

/// 支持同步和异步。异步时在 `fn` 前添加 `async`
#[post("/")]
fn handle_fn_post(request: &Request) -> juri::Result<Response> {
    /// body
}

路由器分组


#![allow(unused)]
fn main() {
use juri::Router;

let mut router = Router::new();
router.at("/").get(handle_fn);

let mut child_router = Router::new();
child_router.at("/one").get(child_handle_fn);

/// 此时,`/` 和 `/one` 都可以处理
router.router(child_router);
}

子路由添加根路径


#![allow(unused)]
fn main() {
use juri::Router;

let mut router = Router::new();
router.at("/").get(handle_fn);

let mut child_router = Router::new();
child_router.root("/hi");
child_router.at("/one").get(child_handle_fn);

/// 此时处理 `/` 和 `/hi/one`
/// 也就是路径是 `/hi/one`,才执行 `child_handle_fn` 函数
router.router(child_router);
}

路由的匹配语法

普通参数

/// 匹配 /one
router.at("/one");

/// 匹配 /one/two
router.at("/one/two");

参数

通过加 : 来把一个路径节点,变成匹配参数。

/// 匹配 /one/two
/// 通过 `request.param("chapters")` 来获取 two
router.at("/one/:chapters");

/// 匹配 /one/two/three
router.get("/one/:chapters/three", handle_index);

可重复的参数

参数路径节点后面加 + 时可匹配 1 个或多个

/// 匹配 /one/two,/one/two/three 时。
/// /one/two 时 `request.param("chapters") == two
/// /one/two/three 时 `request.param("chapters") == two/three
router.get("/one/:chapters+", handle_index);

请求(Request)


#![allow(unused)]
fn main() {
/// 获取 header
pub fn header(&self, key: &str) -> Option<String>;

/// 获取多个 key 相同的值
pub fn header_multi_value(&self, key: &str) -> Option<HeaderValues>;
}

#![allow(unused)]
fn main() {
/// 获取 cookie
pub fn cookie(&self, key: &str) -> Option<String>;
}

#![allow(unused)]
fn main() {
/// 获取路径参数
pub fn param(&self, key: &str) -> Option<String>;

/// 获取查询参数
pub fn query(&self, key: &str) -> Option<String>;
}

FormData

FormData

FormData


#![allow(unused)]
fn main() {
/// 获取单个文件,返回 Option 类型
request.file("file");
/// 获取多个 name 值一样的文件 ,返回 Vec 类型
request.files("file");
}

#![allow(unused)]
fn main() {
let file = request.file("file").unwrap();

/// 打开缓存文件
file.open();

/// 获取缓存文件大小
file.file_size();

/// 获取文件类型
file.file_type();
}

复制缓存文件到指定路径


#![allow(unused)]
fn main() {
let file = request.file("file").unwrap();
let file_name = file.file_name.clone().unwrap();
let path = format!("/home/upload/{}", file_name);
let path = Path::new(&path);

file.copy(path).unwrap();
}

WebSocket 是啥

WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。具体可以参考 维基百科

JavaScript 的 WebSocket 使用可以参考 MDN 文档

使用


#![allow(unused)]
fn main() {
use juri::{get, Request, Response, web_socket::{Message, RequestExt, WSResponse}};

#[get("/ws", ws)]
pub fn handle_ws(&request: Request) -> juri::Result<WSResponse> {
    /// 升级为 ws, 成功时返回 WSResponse
    let mut ws = request.upgrader().unwrap();

    // 传入 ws 处理逻辑
    ws.on(|mut stream| async move {
        loop {
            let message = stream.read().await.unwrap();
            match message {
                Message::Text(text) => {
                    stream.send(WSMessage::Text("hi".to_string())).await.unwrap();
                },
                Message::Binary(_) => todo!(),
                Message::Ping(_) => todo!(),
                Message::Pong(_) => todo!(),
                Message::Close => {
                    return;
                }
            }
        }
    });

    /// 返回 WSResponse
    Ok(ws)

    /// 也可以自定义返回
    // Ok(WSResponse::new(Response::html("")))
}
}

插件

创建插件需要实现以下 JuriPlugin trait


#![allow(unused)]
fn main() {
pub trait JuriPlugin: Send + Sync + 'static {
    /// 请求拦截器
    fn request(&self, request: &mut Request) -> Option<Response>;
    /// 响应拦截器
    fn response(&self, request: &Request, response: &mut Response);
}
}

例:


#![allow(unused)]
fn main() {
use juri::plugin::JuriPlugin;

struct MyPlugin {

}

impl JuriPlugin for MyPlugin {
    fn request(&self, request: &mut Request) -> Option<Response> {
        /// 可修改请求内容

        /// 返回是 `None` 时,继续执行
        /// or
        /// 返回是 `Response` 时,拦截接下来的操作


        /// 例:插件有 `[1, 2, 3]`
        /// 插件 2 返回 `Response` 时,执行顺序为 request:1 - 2,response: 2 - 1
        /// 都为 `None` 时,执行顺序为 request:1 - 2 - 3,匹配路由,response: 3 - 2 - 1
    }

    fn response(&self, request: &Request, response: &mut Response) {
        /// 可修改请求内容和响应内容
    }
}
}

使用


#![allow(unused)]
fn main() {
let my_plugin = MyPlugin {};

/// 生成 `MyPlugin` 实例传入 `plugin` 函数
juri::Server::bind(addr).plugin(my_plugin);
}

内置插件

静态文件插件

静态文件插件

加载静态文件插件

使用签名如下


#![allow(unused)]
fn main() {
impl StaticFilePlugin {
    /// HashMap 的 key 代表匹配 URL 的前缀,value 代表匹配 的文件路径(为多个文件路径)
    pub fn new(config: HashMap<&str, Vec<PathBuf>>>) -> Self;
}
}

例:

key 为 /static, value 为伪代码 ["/home", "/www/site"],URL 为 /static/js/index.js

会先查找 /home/js/index.js,然后在查找 /www/site/js/index.js,最后匹配失败

使用


#![allow(unused)]
fn main() {
use juri::plugin::StaticFilePlugin;
use std::{env, collections::HashMap};

let current_dir = env::current_dir().unwrap();

let static_file_plugin = StaticFilePlugin::new(HashMap::from([(
        "/static",
        vec![current_dir.join("web-socket").join("static")],
    )]));

juri::Server::bind(addr).plugin(static_file_plugin);
}