1. 程式人生 > >rust web框架actix (快速入門文件譯)----基礎(第一節)

rust web框架actix (快速入門文件譯)----基礎(第一節)

入門

讓我們編寫第一個actix Web應用程式!

Hello, world!

首先建立一個新的基於二進位制的Cargo專案並切換到新目錄:

cargo new hello-world
cd hello-world

現在,通過確保您的Cargo.toml包含以下內容,將actix-web新增為專案的依賴項:

[dependencies]
actix-web = "0.7"

為了實現Web伺服器,我們首先需要建立一個請求處理程式。

請求處理程式是一個函式,它接受HttpRequest例項作為其唯一引數,並返回一個可以轉換為HttpResponse的型別:

extern crate actix_web;
use actix_web::{server, App, HttpRequest};

fn index(_req: &HttpRequest) -> &'static str {
    "Hello world!"
}

接下來,建立一個Application例項,並在特定的HTTP方法和路徑上使用應用程式的資源註冊請求處理程式,然後,應用程式例項可以與HttpServer一起用於偵聽傳入的連線。 伺服器接受應該返回HttpHandler例項的函式。 為簡單起見,可以使用server :: new,這個函式是HttpServer :: new的快捷方式:

fn main() {
    server::new(|| App::new().resource("/", |r| r.f(index)))
        .bind("127.0.0.1:8088")
        .unwrap()
        .run();
}

而已! 現在,使用貨運執行編譯並執行程式。 前往http:// localhost:8088 /檢視結果。

如果需要,您可以在開發期間使用自動重新載入伺服器,按需重新編譯。 要了解如何實現這一點,請檢視自動過載模式。

編寫應用程式

actix-web提供各種原語來使用Rust構建Web伺服器和應用程式。 它提供路由,中介軟體,請求的預處理,響應的後處理,websocket協議處理,多部分流等。

所有actix Web伺服器都圍繞App例項構建。 它用於註冊資源和中介軟體的路由。 它還儲存在同一應用程式中所有處理程式共享的應用程式狀態。

應用程式充當所有路由的名稱空間,即特定應用程式的所有路由具有相同的URL路徑字首。 應用程式字首始終包含前導“/”斜槓。 如果提供的字首不包含前導斜槓,則會自動插入。 字首應由值路徑段組成。

對於帶有字首/ app的應用程式,任何帶有路徑/ app,/ app /或/ app / test的請求都會匹配; 但是,路徑/應用程式不匹配。

fn index(req: &HttpRequest) -> impl Responder {
    "Hello world!"
}

let app = App::new()
    .prefix("/app")
    .resource("/index.html", |r| r.method(Method::GET).f(index))
    .finish()

在此示例中,將建立具有/ app字首和index.html資源的應用程式。 該資源可通過/app/index.html網址獲取。

有關更多資訊,請檢視URL Dispatch部分。

一臺伺服器可以提供多個應用程式:

let server = server::new(|| {
    vec![
        App::new()
            .prefix("/app1")
            .resource("/", |r| r.f(|r| HttpResponse::Ok())),
        App::new()
            .prefix("/app2")
            .resource("/", |r| r.f(|r| HttpResponse::Ok())),
        App::new().resource("/", |r| r.f(|r| HttpResponse::Ok())),
    ]
});

所有/ app1請求路由到第一個應用程式,/ app2到第二個應用程式,所有其他請求到第三個。 應用程式根據註冊順序進行匹配。 如果具有更通用字首的應用程式在較不通用的字首之前註冊,則它將有效地阻止較不通用的應用程式匹配。 例如,如果具有字首“/”的App被註冊為第一個應用程式,它將匹配所有傳入的請求。

State

應用程式狀態與同一應用程式中的所有路由和資源共享。 使用http actor時,可以使用HttpRequest :: state()作為只讀訪問狀態,但可以使用RefCell的內部可變性來實現狀態可變性。 狀態也可用於路由匹配謂詞和中介軟體。

讓我們編寫一個使用共享狀態的簡單應用程式。 我們將在state儲存請求計數:

use actix_web::{http, App, HttpRequest};
use std::cell::Cell;

// This struct represents state(這個結構代表狀態)
struct AppState {
    counter: Cell<usize>,
}

fn index(req: &HttpRequest<AppState>) -> String {
    let count = req.state().counter.get() + 1; // <- get count
    req.state().counter.set(count); // <- store new count in state

    format!("Request number: {}", count) // <- response with count
}

應用程式初始化時,需要傳遞初始狀態:

App::with_state(AppState { counter: Cell::new(0) })
    .resource("/", |r| r.method(http::Method::GET).f(index))
    .finish()

注意:http伺服器接受應用程式工廠而不是應用程式例項。 Http伺服器為每個執行緒構造一個應用程式例項,因此必須多次構造應用程式狀態。 如果要在不同執行緒之間共享狀態,則應使用共享物件,例如, 弧。 應用程式狀態不需要是“傳送和同步”,但應用程式工廠必須是“傳送+同步”。

要啟動上一個應用程式,請將其建立為一個閉包:

server::new(|| {
    App::with_state(AppState { counter: Cell::new(0) })
        .resource("/", |r| r.method(http::Method::GET).f(index))
}).bind("127.0.0.1:8080")
    .unwrap()
    .run()

將應用程式與不同狀態相結合

將多個應用程式與不同狀態組合也是可能的。

server :: new要求處理程式具有單一型別。

使用App :: boxed方法可以輕鬆克服此限制,該方法將App轉換為盒裝特徵物件。

struct State1;
struct State2;

fn main() {
    server::new(|| {
        vec![
            App::with_state(State1)
                .prefix("/app1")
                .resource("/", |r| r.f(|r| HttpResponse::Ok()))
                .boxed(),
            App::with_state(State2)
                .prefix("/app2")
                .resource("/", |r| r.f(|r| HttpResponse::Ok()))
                .boxed(),
                ]
    }).bind("127.0.0.1:8080")
        .unwrap()
        .run()
}

使用應用程式字首來編寫應用程式

App :: prefix()方法允許設定特定的應用程式字首。 此字首表示將新增到資源配置新增的所有資源模式的資源字首。 這可以用於幫助將一組路由安裝在與所包含的可呼叫作者的預期不同的位置,同時仍保持相同的資源名稱。

例如:

fn show_users(req: &HttpRequest) -> HttpResponse {
    unimplemented!()
}

fn main() {
    App::new()
        .prefix("/users")
        .resource("/show", |r| r.f(show_users))
        .finish();
}

在上面的示例中,show_users路由將具有/ users / show的有效路由模式而不是/ show,因為應用程式的字首引數將被新增到模式之前。 然後,路由將僅在URL路徑為/ users / show時匹配,並且當使用路由名稱show_users呼叫HttpRequest.url_for()函式時,它將生成具有相同路徑的URL。

應用程式謂詞和虛擬主機

您可以將謂詞視為接受請求物件引用並返回true或false的簡單函式。 形式上,謂詞是實現Predicate特徵的任何物件。 Actix提供了幾個謂詞,你可以檢視api文件的函式部分。

任何此謂詞都可以與App :: filter()方法一起使用。 其中一個提供的謂詞是Host,它可以根據請求的主機資訊用作應用程式的過濾器。

fn main() {
    let server = server::new(|| {
        vec![
            App::new()
                .filter(pred::Host("www.rust-lang.org"))
                .resource("/", |r| r.f(|r| HttpResponse::Ok())),
            App::new()
                .filter(pred::Host("users.rust-lang.org"))
                .resource("/", |r| r.f(|r| HttpResponse::Ok())),
            App::new().resource("/", |r| r.f(|r| HttpResponse::Ok())),
        ]
    });

    server.run();
}

HTTP伺服器

HttpServer型別負責提供http請求。

HttpServer接受應用程式工廠作為引數,應用程式工廠必須具有傳送+同步邊界。 有關多執行緒部分的更多資訊。

要繫結到特定的套接字地址,必須使用bind(),並且可以多次呼叫它。 要繫結ssl套接字,應使用bind_ssl()或bind_tls()。 要啟動http伺服器,請使用其中一種啟動方法。

使用start()作為伺服器

HttpServer是一個actix 角色。 必須在正確配置的actix系統中初始化它:

use actix_web::{server::HttpServer, App, HttpResponse};

fn main() {
    let sys = actix::System::new("guide");

    HttpServer::new(|| App::new().resource("/", |r| r.f(|_| HttpResponse::Ok())))
        .bind("127.0.0.1:59080")
        .unwrap()
        .start();

    let _ = sys.run();
}

可以使用run()方法在單獨的執行緒中啟動伺服器。 在這種情況下,伺服器會生成一個新執行緒並在其中建立一個新的actix系統。 要停止此伺服器,請傳送StopServer訊息。

HttpServer是作為actix actor實現的。 可以通過訊息傳遞系統與伺服器通訊。 啟動方法,例如 start(),返回啟動的http伺服器的地址。 它接受幾條訊息:

  • PauseServer - 暫停接受傳入連線
  • ResumeServer - 恢復接受傳入連線
  • StopServer - 停止傳入連線處理,停止所有工作並退出
use actix_web::{server, App, HttpResponse};
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let sys = actix::System::new("http-server");
        let addr = server::new(|| {
            App::new()
                .resource("/", |r| r.f(|_| HttpResponse::Ok()))
        })
            .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
            .shutdown_timeout(60)    // <- Set shutdown timeout to 60 seconds
            .start();
        let _ = tx.send(addr);
        let _ = sys.run();
    });

    let addr = rx.recv().unwrap();
    let _ = addr.send(server::StopServer { graceful: true }).wait(); // <- Send `StopServer` message to server.
}

多執行緒

HttpServer自動啟動許多http worker,預設情況下,此數字等於系統中的邏輯CPU數。 可以使用HttpServer :: workers()方法覆蓋此數字。

use actix_web::{server::HttpServer, App, HttpResponse};

fn main() {
    HttpServer::new(|| App::new().resource("/", |r| r.f(|_| HttpResponse::Ok())))
        .workers(4); // <- Start 4 workers
}

伺服器為每個建立的worker建立一個單獨的應用程式例項。 執行緒之間不共享應用程式狀態。 要共享狀態,可以使用Arc。

應用程式狀態不需要是傳送和同步,但工廠必須是傳送+同步。

SSL

ssl伺服器有兩個功能:tls和alpn。 tls功能用於native-tls整合,alpn用於openssl。

[dependencies]
actix-web = { version = "0.7", features = ["alpn"] }
use actix_web::{server, App, HttpRequest, Responder};
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};

fn index(req: &HttpRequest) -> impl Responder {
    "Welcome!"
}

fn main() {
    // load ssl keys
    let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
    builder
        .set_private_key_file("key.pem", SslFiletype::PEM)
        .unwrap();
    builder.set_certificate_chain_file("cert.pem").unwrap();

    server::new(|| App::new().resource("/index.html", |r| r.f(index)))
        .bind_ssl("127.0.0.1:8080", builder)
        .unwrap()
        .run();
}

注意:HTTP / 2.0協議需要tls alpn。 目前,只有openssl有alpn支援。 有關完整示例,請檢視examples / tls。

要建立key.pem和cert.pem,請使用該命令。 填寫你自己的主題

$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
  -days 365 -sha256 -subj "/C=CN/ST=Fujian/L=Xiamen/O=TVlinux/OU=Org/CN=muro.lxd"

要刪除密碼,請將nopass.pem複製到key.pem

$ openssl rsa -in key.pem -out nopass.pem

Keep-Alive

Actix可以等待保持連線的請求。

保持連線行為由伺服器設定定義。

75,Some(75),KeepAlive :: Timeout(75) - 啟用75秒保持活動計時器。

無或KeepAlive ::已禁用 - 禁用保持活動狀態。

KeepAlive :: Tcp(75) - 使用SO_KEEPALIVE套接字選項。

use actix_web::{server, App, HttpResponse};

fn main() {
    server::new(|| App::new().resource("/", |r| r.f(|_| HttpResponse::Ok())))
        .keep_alive(75); // <- Set keep-alive to 75 seconds

    server::new(|| App::new().resource("/", |r| r.f(|_| HttpResponse::Ok())))
        .keep_alive(server::KeepAlive::Tcp(75)); // <- Use `SO_KEEPALIVE` socket option.

    server::new(|| App::new().resource("/", |r| r.f(|_| HttpResponse::Ok())))
        .keep_alive(None); // <- Disable keep-alive
}

如果選擇了第一個選項,則根據響應的連線型別計算保持活動狀態。 預設情況下,未定義HttpResponse :: connection_type。 在這種情況下,keep alive由請求的http版本定義。

對於HTTP / 1.0,keep alive是關閉的,對於HTTP / 1.1和HTTP / 2.0,它是開啟的。

可以使用HttpResponseBuilder :: connection_type()方法更改連線型別。

use actix_web::{http, HttpRequest, HttpResponse};

fn index(req: HttpRequest) -> HttpResponse {
    HttpResponse::Ok()
        .connection_type(http::ConnectionType::Close) // <- Close connection
        .force_close()                                // <- Alternative method
        .finish()
}

優雅的關機

HttpServer支援正常關閉。 收到停止訊號後,工人有一定的時間來完成服務請求。 超時後仍然活著的所有工作人員都被強制撤銷。 預設情況下,關閉超時設定為30秒。 您可以使用HttpServer :: shutdown_timeout()方法更改此引數。

您可以使用伺服器地址向伺服器傳送停止訊息,並指定是否要正常關閉。 start()方法返回伺服器的地址。

HttpServer處理多個OS訊號。 CTRL-C適用於所有作業系統,其他訊號可在unix系統上使用。

  • SIGINT - 強制關機工作人員
  • SIGTERM - 優雅的關機工作人員
  • SIGQUIT - 強制關機工作人員 可以使用HttpServer :: disable_signals()方法禁用訊號處理。

請求處理程式

請求處理程式可以是實現Handler特徵的任何物件。

請求處理分兩個階段進行。 首先呼叫handler物件,返回實現Responder特徵的任何物件。 然後,在返回的物件上呼叫respond_to(),將自身轉換為AsyncResult或Error。

預設情況下,actix為某些標準型別提供Responder實現,例如&'static str,String等。

有關實現的完整列表,請檢視Responder文件。

有效處理程式的示例:

fn index(req: &HttpRequest) -> &'static str {
    "Hello world!"
}
fn index(req: &HttpRequest) -> String {
    "Hello world!".to_owned()
}

您還可以更改簽名以返回impl Responder,如果涉及更復雜的型別,它將很有效。

fn index(req: &HttpRequest) -> impl Responder {
    Bytes::from_static("Hello world!")
}
fn index(req: &HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
    ...
}

處理程式特徵在S上是通用的,它定義了應用程式狀態的型別。 可以使用HttpRequest :: state()方法從處理程式訪問應用程式狀態; 但是,可以將狀態作為只讀參考進行訪問。 如果您需要對狀態進行可變訪問,則必須實現它。

注意:或者,處理程式可以使用內部可變地訪問其自己的狀態。 請注意,actix會建立應用程式狀態和處理程式的多個副本,這些副本對於每個執行緒都是唯一的。 如果在多個執行緒中執行應用程式,actix將建立與應用程式狀態物件和處理程式物件的執行緒數相同的數量。

以下是儲存已處理請求數的處理程式示例:

use actix_web::{App, HttpRequest, HttpResponse, dev::Handler};

struct MyHandler(Cell<usize>);

impl<S> Handler<S> for MyHandler {
    type Result = HttpResponse;

    /// Handle request
    fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
        let i = self.0.get();
        self.0.set(i + 1);
        HttpResponse::Ok().into()
    }
}

fn main(){
    server::new(|| App::new()
                .resource("/", |r| r.h(MyHandler(Cell::new(0)))))  //use r.h() to bind handler, not the r.f()
    .bind("127.0.0.1:8080")
    .unwrap()
    .run();
}

雖然這個處理程式可以工作,但self.0會有所不同,具體取決於執行緒數和每個執行緒處理的請求數。 正確的實現將使用Arc和AtomicUsize。

use actix_web::{server, App, HttpRequest, HttpResponse, dev::Handler};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

struct MyHandler(Arc<AtomicUsize>);

impl<S> Handler<S> for MyHandler {
    type Result = HttpResponse;

    /// Handle request
    fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
        self.0.fetch_add(1, Ordering::Relaxed);
        HttpResponse::Ok().into()
    }
}

fn main() {
    let sys = actix::System::new("example");

    let inc = Arc::new(AtomicUsize::new(0));

    server::new(
        move || {
            let cloned = inc.clone();
            App::new()
                .resource("/", move |r| r.h(MyHandler(cloned)))
        })
        .bind("127.0.0.1:8088").unwrap()
        .start();

    println!("Started http server: 127.0.0.1:8088");
    let _ = sys.run();
}

注意Mutex或RwLock等同步原語。 actix-web框架非同步處理請求。 通過阻止執行緒執行,所有併發請求處理程序都將阻塞。 如果需要從多個執行緒共享或更新某些狀態,請考慮使用actix actor系統。