# Actix-web 基本使用

Actix-web 是基于`Actix` actor 异步并发框架以及`tokio`异步IO系统的web高性能框架，具有类型安全，易于扩展，特性丰富等优势。 是一个轻量且实用的web框架。Actix-web 不仅可以用作微服务部署于nginx等HTTP server之后，而且可以作为单独的HTTP server使用，支持HTTP 1， HTTP 2以及SSL/TSL。 Actix-web 需要在rust 1.39之后运行.

## 1. APP

### 1.1 启动

`actix-web`提供了各种原型构建server以及app。提供了routing，middleware， pre-processing request，post-processing response。一个`HttpServer`实例接受一个`web::app`作为参数，`server`用以绑定端口，运行异步服务，`app`用以注册资源路由、中间件、存储应用状态（可以被同一个命名域下`handlers`共享）。应用的`scope`扮演了所有路由的命名域，在此`scope`下的路由规则具有相同的资源前缀。

使用`actix-web`以及`actix-rt`，在`Cargo.toml`添加对应依赖.

```rust
use actix_web::{web, App, HttpResponse, HttpServer};

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(
        || App::new()
            .service(web::resource("/").to(|| HttpResponse::Ok())))
        .bind("127.0.0.1:59090")?
        .run()
        .await
}
```

使用`await`延迟执行，直到`Future`处于ready状态。

### 1.2 应用状态

应用状态可以被处于同一个`scope`下的所有router共享，使用`web::Data<T>`进行存储, 应用状态对于中间件来讲也是可用的。`HttpServer`接受一个app 工厂作为参数而不是实例，`HttpServer`为每一个线程构建一个`app`实例，因此应用数据一定被多次创建。**如果需要在多个线程间共享数据，必须使用共享对象，例如`Arc`**. 内部的`Data`类型使用了`Arc`，所以`Data`可以在app factory外创建，并且使用`clone`在`App::app_data()`方法调用中存储。

```rust
use std::cell::Cell;
use actix_web::{web, App, HttpResponse, Responder};

struct MyData {
    counter: Cell<usize>,
}

async fn index(data: web::Data<MyData>) -> impl Responder {
    data.counter.set(data.counter.get() + 1);
    HttpResponse::Ok()
}

let app = App::new()
    .data(MyData{ counter: Cell::new(0) })
    .service(
        web::resource("/index.html").route(
            web::get().to(index)));
```

`App::app_data()` 方法可以存放应用全局数据，可以使用`HttpRequest::app_data`在运行时获取 。

**`app_data()`与`data（）`的区别:**

* `data()`: 用于设置应用状态，数据由`Data<T>`类型包装，可用于同一个命名域下所有route、middleware 使用

  而且对于应用状态，默认**每一个线程都有一份数据拷贝**，所以需要同时使用时需要借助`Arc`或者原子变量
* `app_data()` : 用于存储应用级别的任意数据，也可以用于存储`web::Data<T>`用于应用状态提取

### 1.3 命名域

使用`web::scope`方法设定具有相同前缀的routers。

```rust
#[actix_rt::main]
async fn main() {
    App::new()
        .service(
            web::scope("/users")
                .route("/show", web::get().to(show_users)));
}
```

### 1.4 route guard

`guard`通过设定相应的条件，例如`method`，`headers`等，过滤不符合条件的请求。

```rust
 App::new()
            .app_data(data.clone()) // register data
            .configure(config)
            .service(
                web::scope("/api")
                // use guard to set route or scope filter
                .guard(guard::Header("Token-access", "justatoken"))
                .service(index)
                .service(sim_obj)
                // 由于Scope并没有指定唯一的资源实体，所以需要path参数指定
                // 唯一URI，然后指定相应的router
                .route("/hello", web::get().to(hello))
                .service(name)
            )
```

### 1.5 服务配置

为了提高`App`以及`web::scope`的可重用性，提供了`configure`方法,可以将配置部分移动至不同的模块或者不同的库中.

```rust
use actix_web::{web, App, HttpResponse, HttpServer};

// this function could be located in different module
fn scoped_config(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::resource("/test")
            .route(web::get().to(|| HttpResponse::Ok().body("test")))
            .route(web::head().to(|| HttpResponse::MethodNotAllowed())),
    );
}

// this function could be located in different module
fn config(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::resource("/app")
            .route(web::get().to(|| HttpResponse::Ok().body("app")))
            .route(web::head().to(|| HttpResponse::MethodNotAllowed())),
    );
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .configure(config)
            .service(web::scope("/api").configure(scoped_config))
            .route("/", web::get().to(|| HttpResponse::Ok().body("/")))
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}
```

响应请求结果:

```http
/         -> "/"
/app      -> "app"
/api/test -> "test"
```

## 2. Router

对于`app`或者`web::scope`对象配置路由时可以使用`route`或者`service`方法：

`route`方法接受两个参数，第一个指定url路径，第二个指定了一个`Router`对象，`Router`可以使用`method`方法指定请求的方法，使用`to`方法绑定到具体的`handler`。为了方便使用可以直接用`web::get, web::post, web::patch`等方法。其内部使用了`Router::method(actix_http::Method::Get)`等。

```rust
use actix_web::{web, App, HttpResponse};

let app = App::new().service(
    web::resource("/{project_id}")
       .route(web::get().to(|| HttpResponse::Ok()))
);
```

也可以使用`pub fn service<T: IntoPattern>(path: T) -> WebService`： 配合handler宏调用定义路由。

```rust
#[post("/users")]
async fn create_users(info: web::Json<User>) -> impl Responder {
    info.into_inner()
}

let app = App::new().service(
                web::scope("/app")
                    // use a fn_guard to filter
                    .guard(guard::fn_guard(
                        |req| req.headers()
                            .contains_key("content-type")
                    ))
                    .service(create_users)
            )
```

## 3. 请求处理

一个`handler`是一个异步方法接受可以从一个HTTP 请求中提取的，0个或多个参数（ie. *impl FromRequest*）,并且返回一个可以转化为`HttpRequest`的类型(ie. *impl Responder*)

Request handler 的处理过程可以分为两阶段，首先handler 对象被调用，返回一个实现了`Responder` trait的对象，接着调用`respond_to` 转化为`HttpResponse`或`Error`。默认很多标准类型都实现了`Responder` trait，例如:

* `&'a String`
* `&'static [u8]`
* `&'static str`
* `Option<T>`
* `Result<T, E>`
* `String`
* `(T, actix_web::http::StatusCode)`

### 3.1 自定义类型作为Responder

实现`Responder` trait 的类型都可以作为handler返回

```rust
use actix_web::{Error, HttpRequest, HttpResponse, Responder};
use serde::Serialize;
use futures::future::{ready, Ready};

#[derive(Serialize)]
struct MyObj {
    name: &'static str,
}

// Responder
impl Responder for MyObj {
    type Error = Error;
    type Future = Ready<Result<HttpResponse, Error>>;

    fn respond_to(self, _req: &HttpRequest) -> Self::Future {
        let body = serde_json::to_string(&self).unwrap();

        // Create response and set content type
        ready(Ok(HttpResponse::Ok()
            .content_type("application/json")
            .body(body)))
    }
}

async fn index() -> impl Responder {
    MyObj { name: "user" }
}
```

### 3.2 streaming

Response 的响应体可以异步生成，body必须实现`Stream<Item=Bytes, Error=Error>` trait

```rust
use actix_web::{web, App, HttpServer, Error, HttpResponse};
use bytes::Bytes;
use futures::stream::once;
use futures::future::ok;

async fn index() -> HttpResponse {
    let body = once(ok::<_, Error>(Bytes::from_static(b"test")));

    HttpResponse::Ok()
        .content_type("application/json")
        .streaming(body)
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().route("/async", web::to(index)))
        .bind("127.0.0.1:8088")?
        .run()
        .await
}
```

### 3.3 返回两种类型

可以使用`actix_web::Either`包装两种不同的类型作为返回值：

```rust
use actix_web::{Either, Error, HttpResponse};

type RegisterResult = Either<HttpResponse, Result<&'static str, Error>>;

fn index() -> RegisterResult {
    if is_a_variant() {
        // <- choose variant A
        Either::A(HttpResponse::BadRequest().body("Bad data"))
    } else {
        // <- variant B
        Either::B(Ok("Hello!"))
    }
}
```

## 4. 基于类型安全的信息提取

`actix-web`有基于类型安全的request信息的访问方式，被称为*extractor*（ie. `impl FromRequest`），默认提供了：

* Path

  `Path` 提供了任意可以从`Request‘s Path` 提取的信息，可以通过指定资源`url`格式中的变量（可以被反序列化），使用`Path` wraper 获取。变量段可以使用图tuple的数据格式，变量可以是基本类型或者任何实现了`serde::Deserialize` trait 的结构体
* Query

  `Query` 类型提供了可用于request query的提取功能
* Json

  `Json` 可以将一个请求体反序列化为一个结构体，该结构体必须实现了`Deserialize` trait
* Form

  当只可以使用`url-encodered` 格式数据时，需要使用`Form`类型

## 2. 增量更新

进行web开发时为了实现快速开发以及测试，可以借助`systemfd`， `cargo-watch` 进行自动加载。`systemfd`会开启一个`socket`连接，并传递给`cargo-watch`，`cargo-watch`负责监控代码变化，并且进行实时编译，并且将服务暴露于`systemfd`提供的`socket`端口下。

* 安装插件

```
cargo install systemfd cargo-watch
```

```
[dependencies]
listenfd = "0.3"
```

* 代码增添

  ```rust
  use actix_web::{web, App, HttpRequest, HttpServer, Responder};
  use listenfd::ListenFd;

  async fn index(_req: HttpRequest) -> impl Responder {
      "Hello World!"
  }

  #[actix_rt::main]
  async fn main() -> std::io::Result<()> {
      let mut listenfd = ListenFd::from_env();
      let mut server = HttpServer::new(|| App::new().route("/", web::get().to(index)));

      server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
          server.listen(l)?
      } else {
          server.bind("127.0.0.1:3000")?
      };

      server.run().await
  }
  ```
* 服务启动

```
systemfd --no-pid -s http::3000 -- cargo watch -x run
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://books.innohub.top/rustinfo/info/actix-web-base.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
