• rust web 开发
    • iron
    • nickel

    rust web 开发

    rust既然是系统级的编程语言,所以当然也能用来开发 web,不过想我这样凡夫俗子,肯定不能从头自己写一个 web
    服务器,肯定要依赖已经存在的 rust web开发框架来完成 web 开发。

    rust目前比较有名的框架是iron和nickel,我们两个都写一下简单的使用教程。

    iron

    接上一篇,使用cargo获取第三方库。cargo new mysite --bin

    在cargo.toml中添加iron的依赖,

    1. [dependencies]
    2. iron = "*"

    然后build将依赖下载到本地 cargo build

    如果报ssl错误,那可能你需要安装linux的ssl开发库。

    首先还是从 hello world 开始吧,继续抄袭官方的例子:

    1. extern crate iron;
    2. use iron::prelude::*;
    3. use iron::status;
    4. fn main() {
    5. Iron::new(|_: &mut Request| {
    6. Ok(Response::with((status::Ok, "Hello World!")))
    7. }).http("localhost:3000").unwrap();
    8. }

    然后运行

    cargo run

    使用curl直接就可以访问你的网站了。

    curl localhost:3000

    Hello World!

    仔细一看,发现这个例子很无厘头啊,对于习惯了写python的我来说,确实不习惯。
    简单点看:

    iron::new().http("localhost:3000").unwrap()
    这句是服务器的基本的定义,new内部是一个rust lambda 表达式

    1. let plus_one = |x: i32| x + 1;
    2. assert_eq!(2, plus_one(1));

    具体的怎么使用 ,可以暂时不用理会,因为你只要知道如何完成web,因为我也不会。。
    结合之前一章节的json处理,我们来看看web接口怎么返回json,当然也要 rustc_serialize 放到 cargo.toml 中

    下面的代码直接参考开源代码地址

    1. extern crate iron;
    2. extern crate rustc_serialize;
    3. use iron::prelude::*;
    4. use iron::status;
    5. use rustc_serialize::json;
    6. #[derive(RustcEncodable)]
    7. struct Greeting {
    8. msg: String
    9. }
    10. fn main() {
    11. fn hello_world(_: &mut Request) -> IronResult<Response> {
    12. let greeting = Greeting { msg: "Hello, World".to_string() };
    13. let payload = json::encode(&greeting).unwrap();
    14. Ok(Response::with((status::Ok, payload)))
    15. }
    16. Iron::new(hello_world).http("localhost:3000").unwrap();
    17. println!("On 3000");
    18. }

    执行 cargo run 使用 curl 测试结果:

    1. curl localhost:3000
    2. {"msg":"Hello, World"}

    当然可以可以实现更多的业务需求,通过控制自己的json。

    既然有了json了,如果要多个路由什么的,岂不是完蛋了,所以不可能这样的,我们需要考虑一下怎么实现路由的定制

    不说话直接上代码,同一样要在你的cargo.toml文件中添加对router的依赖

    1. extern crate iron;
    2. extern crate router;
    3. extern crate rustc_serialize;
    4. use iron::prelude::*;
    5. use iron::status;
    6. use router::Router;
    7. use rustc_serialize::json;
    8. #[derive(RustcEncodable, RustcDecodable)]
    9. struct Greeting {
    10. msg: String
    11. }
    12. fn main() {
    13. let mut router = Router::new();
    14. router.get("/", hello_world);
    15. router.post("/set", set_greeting);
    16. fn hello_world(_: &mut Request) -> IronResult<Response> {
    17. let greeting = Greeting { msg: "Hello, World".to_string() };
    18. let payload = json::encode(&greeting).unwrap();
    19. Ok(Response::with((status::Ok, payload)))
    20. }
    21. // Receive a message by POST and play it back.
    22. fn set_greeting(request: &mut Request) -> IronResult<Response> {
    23. let payload = request.body.read_to_string();
    24. let request: Greeting = json::decode(payload).unwrap();
    25. let greeting = Greeting { msg: request.msg };
    26. let payload = json::encode(&greeting).unwrap();
    27. Ok(Response::with((status::Ok, payload)))
    28. }
    29. Iron::new(router).http("localhost:3000").unwrap();
    30. }

    这次添加了路由的实现和获取客户端发送过来的数据,有了get,post,所以现在一个基本的api网站已经完成了。不过
    并不是所有的网站都是api来访问,同样需要html模版引擎和直接返回静态页面。等等

    1. vagrant@ubuntu-14:~/tmp/test/rustprimer/mysite$ cargo build
    2. Compiling mysite v0.1.0 (file:///home/vagrant/tmp/test/rustprimer/mysite)
    3. src/main.rs:29:36: 29:52 error: no method named `read_to_string` found for type `iron::request::Body<'_, '_>` in the current scope
    4. src/main.rs:29 let payload = request.body.read_to_string();
    5. ^~~~~~~~~~~~~~~~
    6. src/main.rs:29:36: 29:52 help: items from traits can only be used if the trait is in scope; the following trait is implemented but not in scope, perhaps add a `use` for it:
    7. src/main.rs:29:36: 29:52 help: candidate #1: use `std::io::Read`
    8. error: aborting due to previous error
    9. Could not compile `mysite`.

    编译出错了,太糟糕了,提示说没有read_to_string这个方法,然后我去文档查了一下,发现有read_to_string方法
    再看提示信息

    1. src/main.rs:29:36: 29:52 help: items from traits can only be used if the trait is in scope; the following trait is implemented but not in scope, perhaps add a `use` for it:
    2. src/main.rs:29:36: 29:52 help: candidate #1: use `std::io::Read`

    让我们添加一个std::io::Read,这个如果操作过文件,你一定知道怎么写,添加一下,应该能过去了,还是继续出错了,看看报错

    1. Compiling mysite v0.1.0 (file:///home/vagrant/tmp/test/rustprimer/mysite)
    2. src/main.rs:30:36: 30:52 error: this function takes 1 parameter but 0 parameters were supplied [E0061]
    3. src/main.rs:30 let payload = request.body.read_to_string();
    4. ^~~~~~~~~~~~~~~~
    5. src/main.rs:30:36: 30:52 help: run `rustc --explain E0061` to see a detailed explanation
    6. src/main.rs:31:46: 31:53 error: mismatched types:
    7. expected `&str`,
    8. found `core::result::Result<usize, std::io::error::Error>`
    9. (expected &-ptr,
    10. found enum `core::result::Result`) [E0308]
    11. src/main.rs:31 let request: Greeting = json::decode(payload).unwrap();
    12. ^~~~~~~
    13. src/main.rs:31:46: 31:53 help: run `rustc --explain E0308` to see a detailed explanation
    14. src/main.rs:30:36: 30:52 error: cannot infer an appropriate lifetime for lifetime parameter `'b` due to conflicting requirements [E0495]
    15. src/main.rs:30 let payload = request.body.read_to_string();
    16. ^~~~~~~~~~~~~~~~
    17. src/main.rs:29:5: 35:6 help: consider using an explicit lifetime parameter as shown: fn set_greeting<'a>(request: &mut Request<'a, 'a>) -> IronResult<Response>
    18. src/main.rs:29 fn set_greeting(request: &mut Request) -> IronResult<Response> {
    19. src/main.rs:30 let payload = request.body.read_to_string();
    20. src/main.rs:31 let request: Greeting = json::decode(payload).unwrap();
    21. src/main.rs:32 let greeting = Greeting { msg: request.msg };
    22. src/main.rs:33 let payload = json::encode(&greeting).unwrap();
    23. src/main.rs:34 Ok(Response::with((status::Ok, payload)))
    24. ...
    25. error: aborting due to 3 previous errors
    26. Could not compile `mysite`.

    第一句提示我们,这个read_to_string(),至少要有一个参数,但是我们一个都没有提供。
    我们看看read_to_string的用法

    1. se std::io;
    2. use std::io::prelude::*;
    3. use std::fs::File;
    4. let mut f = try!(File::open("foo.txt"));
    5. let mut buffer = String::new();
    6. try!(f.read_to_string(&mut buffer));

    用法比较简单,我们修改一下刚刚的函数:

    1. fn set_greeting(request: &mut Request) -> IronResult<Response> {
    2. let mut payload = String::new();
    3. request.body.read_to_string(&mut payload);
    4. let request: Greeting = json::decode(&payload).unwrap();
    5. let greeting = Greeting { msg: request.msg };
    6. let payload = json::encode(&greeting).unwrap();
    7. Ok(Response::with((status::Ok, payload)))
    8. }

    从request中读取字符串,读取的结果存放到payload中,然后就可以进行操作了,编译之后运行,使用curl提交一个post数据

    1. $curl -X POST -d '{"msg":"Just trust the Rust"}' http://localhost:3000/set
    2. {"msg":"Just trust the Rust"}

    iron 基本告一段落
    当然还有如何使用html模版引擎,那就是直接看文档就行了。

    nickel

    当然既然是web框架肯定是iron能干的nicke也能干,所以那我们就看看如何做一个hello 和返回一个html
    的页面

    同样我们创建cargo new site --bin,然后添加nickel到cargo.toml中,cargo build

    1. #[macro_use] extern crate nickel;
    2. use nickel::Nickel;
    3. fn main() {
    4. let mut server = Nickel::new();
    5. server.utilize(router! {
    6. get "**" => |_req, _res| {
    7. "Hello world!"
    8. }
    9. });
    10. server.listen("127.0.0.1:6767");
    11. }

    简单来看,也就是这样回事。

    1. 引入了nickel的宏
    2. 初始化Nickel
    3. 调用utilize来定义路由模块。
    4. router! 宏,传入的参数是 get 方法和对应的路径,”**“是全路径匹配。
    5. listen启动服务器

    当然我们要引入关于html模版相关的信息

    1. #[macro_use] extern crate nickel;
    2. use std::collections::HashMap;
    3. use nickel::{Nickel, HttpRouter};
    4. fn main() {
    5. let mut server = Nickel::new();
    6. server.get("/", middleware! { |_, response|
    7. let mut data = HashMap::new();
    8. data.insert("name", "user");
    9. return response.render("site/assets/template.tpl", &data);
    10. });
    11. server.listen("127.0.0.1:6767");
    12. }

    上面的信息你可以编译,使用curl看看发现出现

    1. $ curl http://127.0.0.1:6767
    2. Internal Server Error

    看看文档,没发现什么问题,我紧紧更换了一个文件夹的名字,这个文件夹我也创建了。
    然后我在想难道是服务器将目录写死了吗?于是将上面的路径改正这个,问题解决。

    1. return response.render("examples/assets/template.tpl", &data);

    我们看一下目录结构

    1. .
    2. |-- Cargo.lock
    3. |-- Cargo.toml
    4. |-- examples
    5. | `-- assets
    6. | `-- template.tpl
    7. |-- src
    8. | `-- main.rs