大家好,今天要來優化我們之前做的專案,並且替他加上和 DB 連線的功能,那麼首先我們先來調整一下原本的 main
這支程式還有我們的專案架構,
對於開發者而言 Log 是很重要的工具因此我們來添加一個 library 來幫助我們開發,首先添加依賴
[dependencies]
log = "0.4.8"
env_logger = "0.7.0"
用 巨集
的方式引入,這樣我們就可以直接調用裡面的程式,不過我其實還不是很懂,未來應該會再寫一篇文章來介紹。
#[macro_use]
extern crate log;
extern crate env_logger;
use actix_web::{web, App, HttpServer, Result, HttpResponse, get};
fn main() {
// init log
std::env::set_var("RUST_LOG", "actix_web=info,info");
env_logger::init();
// settings
let url = "127.0.0.1:3000";
info!("Running server on {}", url);
...
在 main 裡面初始化 log 之後就會看到 log 的資訊囉,
接著我們來調整一下啟動 server 的程式並且添加 middlewares,
use actix_web::middleware::errhandlers::{ErrorHandlerResponse, ErrorHandlers};
use actix_web::middleware::Logger;
use actix_web::{dev, http, web, App, HttpServer, Result, HttpResponse, get, middleware};
fn render_500<B>(mut res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
res.response_mut().headers_mut().insert(
http::header::CONTENT_TYPE,
http::HeaderValue::from_static("Error"),
);
Ok(ErrorHandlerResponse::Response(res))
}
let mut server = HttpServer::new(
|| App::new()
.wrap(Logger::default())
.wrap(Logger::new("%a %{User-Agent}i"))
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
.wrap(
ErrorHandlers::new()
.handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500),
)
.service(
web::scope("/api/v1").service(translate)
)
);
這樣只要有 request 進來時就會顯示使用者的資訊結果如下,
DefaultHeaders
會給每個 response 給預設的 header,但如果 api 有另外設定的會預設值就會無效。ErrorHandlers
則是在錯誤發生時處理 status code 的 middleware。
最後我們還要把 hello
這支 api 重新命名成 translate
讓他語意更清楚,
...
#[derive(Serialize, Deserialize)]
struct Chinese {
text: String,
}
/**
* Translate chinese to English with deep learning.
*/
fn exec_translate_from_python(value:&str) -> String {
let mut _translate = Command::new("python3");
_translate.arg("run_nn.py").arg("translate").arg(value);
_translate.current_dir("/Users/liyanxin/Life/myprojects/Chinese2English_Seq2Seq_Attention");
let output = _translate.output().expect("failed to execute process");
String::from_utf8_lossy(&output.stdout).to_string()
}
#[get("/translate")]
fn translate(info: web::Query<Chinese>) -> Result<HttpResponse> {
let output = exec_translate_from_python(&info.text);
Ok(HttpResponse::Ok().json(Chinese {
text: output,
}))
}
...
這樣呼叫時的 api 路徑就是 /api/v1/translate?text=我好餓
,帶參數就變成是用 query 的方式。
最後先附上目前的完整程式碼,
[package]
name = "actixweb"
version = "0.1.0"
authors = ["SP_Penguin <abrcdf1023@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "1.0.8"
listenfd = "0.3"
serde = { version = "1.0", features = ["derive"] }
log = "0.4.8"
env_logger = "0.7.0"
#[macro_use]
extern crate log;
extern crate env_logger;
use actix_web::middleware::errhandlers::{ErrorHandlerResponse, ErrorHandlers};
use actix_web::middleware::Logger;
use actix_web::{dev, http, web, App, HttpServer, Result, HttpResponse, get, middleware};
use listenfd::ListenFd;
use serde::{Deserialize, Serialize};
use std::process::Command;
#[derive(Serialize, Deserialize)]
struct Chinese {
text: String,
}
fn render_500<B>(mut res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
res.response_mut().headers_mut().insert(
http::header::CONTENT_TYPE,
http::HeaderValue::from_static("Error"),
);
Ok(ErrorHandlerResponse::Response(res))
}
/**
* Translate chinese to English with deep learning.
*/
fn exec_translate_from_python(value:&str) -> String {
let mut _translate = Command::new("python3");
_translate.arg("run_nn.py").arg("translate").arg(value);
_translate.current_dir("/Users/liyanxin/Life/myprojects/Chinese2English_Seq2Seq_Attention");
let output = _translate.output().expect("failed to execute process");
String::from_utf8_lossy(&output.stdout).to_string()
}
#[get("/translate")]
fn translate(info: web::Query<Chinese>) -> Result<HttpResponse> {
let output = exec_translate_from_python(&info.text);
Ok(HttpResponse::Ok().json(Chinese {
text: output,
}))
}
fn main() {
// init log
std::env::set_var("RUST_LOG", "actix_web=info,info");
env_logger::init();
// settings
let url = "127.0.0.1:3000";
info!("Running server on {}", url);
// auto reload
let mut listenfd = ListenFd::from_env();
// start server
let mut server = HttpServer::new(
|| App::new()
.wrap(Logger::default())
.wrap(Logger::new("%a %{User-Agent}i"))
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
.wrap(
ErrorHandlers::new()
.handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500),
)
.service(
web::scope("/api/v1").service(translate)
)
);
// auto reload listener
server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
server.listen(l).unwrap()
} else {
server.bind(url).unwrap()
};
server.run().unwrap();
}
接著我們將用 diesel 這個 ORM library 來處理我們的 CRUD,而官方教學要我們用 postgresql 所以必須先安裝他,用 homebrew 進行安裝。
安裝完接著我們要修改密碼,先找到自己的使用者名稱(筆者安裝完就是電腦的名稱)。
$ sudo -u your_user_name psql postgres
接著下 \password
postgres=# \password
兩次確認密碼之後完成。
先安裝 pgadmin4,
$ brew cask install pgadmin4
安裝完開啟 pgadmin4 設定 DB 連線,
並且新增 Database 命名為 diesel_demo,
完成!
首先新增依賴(除了新增專案外最熟的部分XD
[dependencies]
diesel = { version = "1.0.0", features = ["postgres"] }
dotenv = "0.9.0"
接著安裝 diesel cli 工具,
$ cargo install diesel_cli
然後回到我們的專案根目錄新增一個 .env 的檔案,裡面用來設定 DB 連線位址
$ echo DATABASE_URL=postgres://username:password@localhost/diesel_demo > .env
Note: 這邊記得要改成自己的 username 和 password
接著同樣在根目錄執行 setup 指令讓剛剛安裝的 cli 工具發威,
diesel setup
接著我們要來做一個簡單的 blog 先讓我們新增一個 migration,
$ diesel migration generate create_posts
migration 的作用似乎是讓你可以復原對 DB 所做的操作,這邊我是很不確定所以就把官方文件貼上來了,
Migrations allow us to evolve the database schema over time. Each migration can be applied (up.sql) or reverted (down.sql). Applying and immediately reverting a migration should leave your database schema unchanged.
然後來編輯一下剛剛產生的 sql 檔案
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title VARCHAR NOT NULL,
body TEXT NOT NULL,
published BOOLEAN NOT NULL DEFAULT 'f'
)
DROP TABLE posts
完成之後執行 migration run
diesel migration run
若是要復原的話就用 migration redo
指令
diesel migration redo
redo 就會執行 down.sql 來復原 DB,所以 down.sql 要確保是正確的。
如果一切順利的話應該會長這樣,
接著我們就可以開始來寫一點程式了,首先要先建立 DB 的連線,
pub mod schema;
pub mod models;
#[macro_use]
extern crate diesel;
extern crate dotenv;
use diesel::prelude::*;
use diesel::pg::PgConnection;
use dotenv::dotenv;
use std::env;
pub fn establish_connection() -> PgConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
PgConnection::establish(&database_url)
.expect(&format!("Error connecting to {}", database_url))
}
接著新增 Struct 定義 Post 格式,
#[derive(Queryable)]
pub struct Post {
pub id: i32,
pub title: String,
pub body: String,
pub published: bool,
}
另外確認一下有沒有 schema.rs,這個檔案在 setup 的時候應該就會自動產生。
table! {
posts (id) {
id -> Int4,
title -> Varchar,
body -> Text,
published -> Bool,
}
}
最後就是新增一個呼叫的程式拉,
extern crate diesel;
extern crate actixweb;
use actixweb::*;
use self::models::*;
use diesel::prelude::*;
fn main() {
// establish db connection
use self::schema::posts::dsl::*;
let connection = establish_connection();
let results = posts.filter(published.eq(true))
.limit(5)
.load::<Post>(&connection)
.expect("Error loading posts");
println!("Displaying {} posts", results.len());
for post in results {
println!("{}", post.title);
println!("----------\n");
println!("{}", post.body);
}
}
馬上來執行試試,
cargo run --bin show_posts
完成!到目前為止的結果,
由於我們還沒有資料所以這樣是正常的。
今天的內容還蠻多的,應該還要再一天把它完成,那今天就先告一個段落我們明天繼續!
actix-todo
benchmarks
actix docs