让我们编写第一个actix Web应用程序!
首先创建一个新的基于二进制的Cargo项目并切换到新目录:
cargo new hello-world
cd hello-world
现在,通过确保您的Cargo.toml包含以下内容,将actix-web添加为项目的依赖项:
[dependencies]
actix-web = "0.7"
为了实现Web服务器,我们首先需要创建一个请求处理程序。
请求处理程序是一个函数,它接受HttpRequest实例作为其唯一参数,并返回一个可以转换为HttpResponse的类型:
文件名:src / main.rs
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被注册为第一个应用程序,它将匹配所有传入的请求。
应用程序状态与同一应用程序中的所有路由和资源共享。 使用http actor时,可以使用HttpRequest :: state()作为只读访问状态,但可以使用RefCell的内部可变性来实现状态可变性。 状态也可用于路由匹配谓词和中间件。
让我们编写一个使用共享状态的简单应用程序。 我们将在state存储请求计数:
use actix_web::{http, App, HttpRequest};
use std::cell::Cell;
// This struct represents state(这个结构代表状态)
struct AppState {
counter: Cell,
}
fn index(req: &HttpRequest) -> 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();
}
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服务器的地址。 它接受几条消息:
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服务器有两个功能: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
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系统上使用。
请求处理程序可以是实现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> {
...
}
处理程序特征在S上是通用的,它定义了应用程序状态的类型。 可以使用HttpRequest :: state()方法从处理程序访问应用程序状态; 但是,可以将状态作为只读参考进行访问。 如果您需要对状态进行可变访问,则必须实现它。
注意:或者,处理程序可以使用内部可变地访问其自己的状态。 请注意,actix会创建应用程序状态和处理程序的多个副本,这些副本对于每个线程都是唯一的。 如果在多个线程中运行应用程序,actix将创建与应用程序状态对象和处理程序对象的线程数相同的数量。
以下是存储已处理请求数的处理程序示例:
use actix_web::{App, HttpRequest, HttpResponse, dev::Handler};
struct MyHandler(Cell);
impl Handler for MyHandler {
type Result = HttpResponse;
/// Handle request
fn handle(&self, req: &HttpRequest) -> 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);
impl Handler for MyHandler {
type Result = HttpResponse;
/// Handle request
fn handle(&self, req: &HttpRequest) -> 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系统。