rust web 開發
rust既然是系統級的編程語言,所以當然也能用來開發 web,不過想我這樣凡夫俗子,肯定不能從頭自己寫一個 web 服務器,肯定要依賴已經存在的 rust web開發框架來完成 web 開發。
rust目前比較有名的框架是iron和nickel,我們兩個都寫一下簡單的使用教程。
iron
接上一篇,使用cargo獲取第三方庫。cargo new mysite --bin
在cargo.toml中添加iron的依賴,
[dependencies]
iron = "*"
然後build將依賴下載到本地 cargo build
如果報ssl錯誤,那可能你需要安裝linux的ssl開發庫。
首先還是從 hello world 開始吧,繼續抄襲官方的例子:
extern crate iron;
use iron::prelude::*;
use iron::status;
fn main() {
Iron::new(|_: &mut Request| {
Ok(Response::with((status::Ok, "Hello World!")))
}).http("localhost:3000").unwrap();
}
然後運行
cargo run
使用curl直接就可以訪問你的網站了。
curl localhost:3000
Hello World!
仔細一看,發現這個例子很無厘頭啊,對於習慣了寫python的我來說,確實不習慣。 簡單點看:
iron::new().http("localhost:3000").unwrap()
這句是服務器的基本的定義,new內部是一個rust lambda 表達式
let plus_one = |x: i32| x + 1;
assert_eq!(2, plus_one(1));
具體的怎麼使用 ,可以暫時不用理會,因為你只要知道如何完成web,因為我也不會。。 結合之前一章節的json處理,我們來看看web接口怎麼返回json,當然也要 rustc_serialize 放到 cargo.toml 中
下面的代碼直接參考開源代碼地址
extern crate iron;
extern crate rustc_serialize;
use iron::prelude::*;
use iron::status;
use rustc_serialize::json;
#[derive(RustcEncodable)]
struct Greeting {
msg: String
}
fn main() {
fn hello_world(_: &mut Request) -> IronResult<Response> {
let greeting = Greeting { msg: "Hello, World".to_string() };
let payload = json::encode(&greeting).unwrap();
Ok(Response::with((status::Ok, payload)))
}
Iron::new(hello_world).http("localhost:3000").unwrap();
println!("On 3000");
}
執行 cargo run 使用 curl 測試結果:
curl localhost:3000
{"msg":"Hello, World"}
當然可以可以實現更多的業務需求,通過控制自己的json。
既然有了json了,如果要多個路由什麼的,豈不是完蛋了,所以不可能這樣的,我們需要考慮一下怎麼實現路由的定製
不說話直接上代碼,同一樣要在你的cargo.toml文件中添加對router的依賴
extern crate iron;
extern crate router;
extern crate rustc_serialize;
use iron::prelude::*;
use iron::status;
use router::Router;
use rustc_serialize::json;
#[derive(RustcEncodable, RustcDecodable)]
struct Greeting {
msg: String
}
fn main() {
let mut router = Router::new();
router.get("/", hello_world);
router.post("/set", set_greeting);
fn hello_world(_: &mut Request) -> IronResult<Response> {
let greeting = Greeting { msg: "Hello, World".to_string() };
let payload = json::encode(&greeting).unwrap();
Ok(Response::with((status::Ok, payload)))
}
// Receive a message by POST and play it back.
fn set_greeting(request: &mut Request) -> IronResult<Response> {
let payload = request.body.read_to_string();
let request: Greeting = json::decode(payload).unwrap();
let greeting = Greeting { msg: request.msg };
let payload = json::encode(&greeting).unwrap();
Ok(Response::with((status::Ok, payload)))
}
Iron::new(router).http("localhost:3000").unwrap();
}
這次添加了路由的實現和獲取客戶端發送過來的數據,有了get,post,所以現在一個基本的api網站已經完成了。不過 並不是所有的網站都是api來訪問,同樣需要html模版引擎和直接返回靜態頁面。等等
vagrant@ubuntu-14:~/tmp/test/rustprimer/mysite$ cargo build
Compiling mysite v0.1.0 (file:///home/vagrant/tmp/test/rustprimer/mysite)
src/main.rs:29:36: 29:52 error: no method named `read_to_string` found for type `iron::request::Body<'_, '_>` in the current scope
src/main.rs:29 let payload = request.body.read_to_string();
^~~~~~~~~~~~~~~~
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:
src/main.rs:29:36: 29:52 help: candidate #1: use `std::io::Read`
error: aborting due to previous error
Could not compile `mysite`.
編譯出錯了,太糟糕了,提示說沒有read_to_string這個方法,然後我去文檔查了一下,發現有read_to_string方法 再看提示信息
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:
src/main.rs:29:36: 29:52 help: candidate #1: use `std::io::Read`
讓我們添加一個std::io::Read
,這個如果操作過文件,你一定知道怎麼寫,添加一下,應該能過去了,還是繼續出錯了,看看報錯
Compiling mysite v0.1.0 (file:///home/vagrant/tmp/test/rustprimer/mysite)
src/main.rs:30:36: 30:52 error: this function takes 1 parameter but 0 parameters were supplied [E0061]
src/main.rs:30 let payload = request.body.read_to_string();
^~~~~~~~~~~~~~~~
src/main.rs:30:36: 30:52 help: run `rustc --explain E0061` to see a detailed explanation
src/main.rs:31:46: 31:53 error: mismatched types:
expected `&str`,
found `core::result::Result<usize, std::io::error::Error>`
(expected &-ptr,
found enum `core::result::Result`) [E0308]
src/main.rs:31 let request: Greeting = json::decode(payload).unwrap();
^~~~~~~
src/main.rs:31:46: 31:53 help: run `rustc --explain E0308` to see a detailed explanation
src/main.rs:30:36: 30:52 error: cannot infer an appropriate lifetime for lifetime parameter `'b` due to conflicting requirements [E0495]
src/main.rs:30 let payload = request.body.read_to_string();
^~~~~~~~~~~~~~~~
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>
src/main.rs:29 fn set_greeting(request: &mut Request) -> IronResult<Response> {
src/main.rs:30 let payload = request.body.read_to_string();
src/main.rs:31 let request: Greeting = json::decode(payload).unwrap();
src/main.rs:32 let greeting = Greeting { msg: request.msg };
src/main.rs:33 let payload = json::encode(&greeting).unwrap();
src/main.rs:34 Ok(Response::with((status::Ok, payload)))
...
error: aborting due to 3 previous errors
Could not compile `mysite`.
第一句提示我們,這個read_to_string(),至少要有一個參數,但是我們一個都沒有提供。 我們看看read_to_string的用法
se std::io;
use std::io::prelude::*;
use std::fs::File;
let mut f = try!(File::open("foo.txt"));
let mut buffer = String::new();
try!(f.read_to_string(&mut buffer));
用法比較簡單,我們修改一下剛剛的函數:
fn set_greeting(request: &mut Request) -> IronResult<Response> {
let mut payload = String::new();
request.body.read_to_string(&mut payload);
let request: Greeting = json::decode(&payload).unwrap();
let greeting = Greeting { msg: request.msg };
let payload = json::encode(&greeting).unwrap();
Ok(Response::with((status::Ok, payload)))
}
從request中讀取字符串,讀取的結果存放到payload中,然後就可以進行操作了,編譯之後運行,使用curl提交一個post數據
$curl -X POST -d '{"msg":"Just trust the Rust"}' http://localhost:3000/set
{"msg":"Just trust the Rust"}
iron 基本告一段落 當然還有如何使用html模版引擎,那就是直接看文檔就行了。
nickel
當然既然是web框架肯定是iron能幹的nicke也能幹,所以那我們就看看如何做一個hello 和返回一個html 的頁面
同樣我們創建cargo new site --bin
,然後添加nickel到cargo.toml中,cargo build
#[macro_use] extern crate nickel;
use nickel::Nickel;
fn main() {
let mut server = Nickel::new();
server.utilize(router! {
get "**" => |_req, _res| {
"Hello world!"
}
});
server.listen("127.0.0.1:6767");
}
簡單來看,也就是這樣回事。
- 引入了nickel的宏
- 初始化Nickel
- 調用utilize來定義路由模塊。
router!
宏,傳入的參數是 get 方法和對應的路徑,"**"是全路徑匹配。- listen啟動服務器
#[macro_use] extern crate nickel;
use std::collections::HashMap;
use nickel::{Nickel, HttpRouter};
fn main() {
let mut server = Nickel::new();
server.get("/", middleware! { |_, response|
let mut data = HashMap::new();
data.insert("name", "user");
return response.render("site/assets/template.tpl", &data);
});
server.listen("127.0.0.1:6767");
}
上面的信息你可以編譯,使用curl看看發現出現
$ curl http://127.0.0.1:6767
Internal Server Error
看看文檔,沒發現什麼問題,我緊緊更換了一個文件夾的名字,這個文件夾我也創建了。 然後我在想難道是服務器將目錄寫死了嗎?於是將上面的路徑改正這個,問題解決。
return response.render("examples/assets/template.tpl", &data);
我們看一下目錄結構
.
|-- Cargo.lock
|-- Cargo.toml
|-- examples
| `-- assets
| `-- template.tpl
|-- src
| `-- main.rs