响应类型 #

什么是响应器? #

响应器(Responder)是 Actix Web 中生成 HTTP 响应的核心 trait。任何实现了 Responder trait 的类型都可以作为处理函数的返回值。

text
┌─────────────────────────────────────────────────────────────┐
│                      响应器工作流程                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  处理函数返回值                                               │
│         │                                                    │
│         ▼                                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Responder trait                         │   │
│  │  实现类型:                                           │   │
│  │  - HttpResponse                                      │   │
│  │  - String / &'static str                             │   │
│  │  - web::Json<T>                                      │   │
│  │  - web::Bytes                                        │   │
│  │  - 自定义类型                                         │   │
│  └─────────────────────────────────────────────────────┘   │
│         │                                                    │
│         ▼                                                    │
│  HTTP 响应                                                    │
│  Status: 200 OK                                              │
│  Headers: Content-Type: ...                                  │
│  Body: ...                                                   │
│                                                              │
└─────────────────────────────────────────────────────────────┘

内置响应类型 #

HttpResponse #

rust
use actix_web::{HttpResponse, Responder};

#[actix_web::get("/")]
async fn index() -> impl Responder {
    HttpResponse::Ok().body("Hello, World!")
}

字符串 #

rust
#[actix_web::get("/string")]
async fn string_response() -> impl Responder {
    "Plain text response"
}

#[actix_web::get("/static")]
async fn static_response() -> impl Responder {
    "Static string response"
}

JSON #

rust
use actix_web::web::Json;
use serde::Serialize;

#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
}

#[actix_web::get("/user")]
async fn get_user() -> impl Responder {
    Json(User {
        id: 1,
        name: "Alice".to_string(),
    })
}

Bytes #

rust
use actix_web::web::Bytes;

#[actix_web::get("/bytes")]
async fn bytes_response() -> impl Responder {
    Bytes::from_static(b"Raw bytes response")
}

HttpResponse 构建器 #

状态码 #

rust
use actix_web::HttpResponse;

#[actix_web::get("/status")]
async fn status_examples() -> impl Responder {
    HttpResponse::Ok()                    // 200
    HttpResponse::Created()               // 201
    HttpResponse::Accepted()              // 202
    HttpResponse::NoContent()             // 204
    HttpResponse::BadRequest()            // 400
    HttpResponse::Unauthorized()          // 401
    HttpResponse::Forbidden()             // 403
    HttpResponse::NotFound()              // 404
    HttpResponse::MethodNotAllowed()      // 405
    HttpResponse::Conflict()              // 409
    HttpResponse::InternalServerError()   // 500
    HttpResponse::ServiceUnavailable()    // 503
}

自定义状态码 #

rust
use actix_web::http::StatusCode;

#[actix_web::get("/custom")]
async fn custom_status() -> impl Responder {
    HttpResponse::build(StatusCode::from_u16(418).unwrap())
        .body("I'm a teapot")
}

响应头 #

rust
use actix_web::http::header;

#[actix_web::get("/headers")]
async fn with_headers() -> impl Responder {
    HttpResponse::Ok()
        .insert_header(("X-Custom-Header", "value"))
        .insert_header(("Content-Type", "text/plain"))
        .append_header(("Set-Cookie", "session=abc123"))
        .body("Response with headers")
}

响应体 #

rust
#[actix_web::get("/body")]
async fn body_types() -> impl Responder {
    HttpResponse::Ok()
        .body("String body")
}

#[actix_web::get("/json")]
async fn json_body() -> impl Responder {
    HttpResponse::Ok()
        .json(serde_json::json!({
            "message": "JSON response"
        }))
}

#[actix_web::get("/stream")]
async fn stream_body() -> impl Responder {
    use actix_web::web::Bytes;
    use futures::stream;
    
    HttpResponse::Ok()
        .streaming(stream::iter(vec![
            Ok::<_, actix_web::Error>(Bytes::from("Chunk 1")),
            Ok(Bytes::from("Chunk 2")),
        ]))
}

实现 Responder #

基本实现 #

rust
use actix_web::{HttpRequest, HttpResponse, Responder};
use serde::Serialize;

#[derive(Serialize)]
struct ApiResponse<T> {
    success: bool,
    data: T,
}

impl<T: Serialize> Responder for ApiResponse<T> {
    type Body = actix_web::body::BoxBody;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
        HttpResponse::Ok()
            .json(&self)
            .map_into_boxed_body()
    }
}

#[actix_web::get("/api/user")]
async fn api_user() -> impl Responder {
    ApiResponse {
        success: true,
        data: serde_json::json!({ "name": "Alice" }),
    }
}

带错误处理的响应 #

rust
use actix_web::{HttpRequest, HttpResponse, Responder, http::StatusCode};

enum AppResult<T> {
    Ok(T),
    Err(String),
}

impl<T: Serialize> Responder for AppResult<T> {
    type Body = actix_web::body::BoxBody;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
        match self {
            AppResult::Ok(data) => HttpResponse::Ok()
                .json(serde_json::json!({
                    "success": true,
                    "data": data
                }))
                .map_into_boxed_body(),
            AppResult::Err(msg) => HttpResponse::build(StatusCode::BAD_REQUEST)
                .json(serde_json::json!({
                    "success": false,
                    "error": msg
                }))
                .map_into_boxed_body(),
        }
    }
}

#[actix_web::get("/result")]
async fn result_response() -> impl Responder {
    AppResult::Ok(serde_json::json!({ "id": 1 }))
}

自定义响应类型 #

HTML 响应 #

rust
struct Html(String);

impl Responder for Html {
    type Body = String;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
        HttpResponse::Ok()
            .content_type("text/html; charset=utf-8")
            .body(self.0)
    }
}

#[actix_web::get("/html")]
async fn html_response() -> impl Responder {
    Html("<h1>Hello, HTML!</h1>".to_string())
}

XML 响应 #

rust
struct Xml(String);

impl Responder for Xml {
    type Body = String;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
        HttpResponse::Ok()
            .content_type("application/xml")
            .body(self.0)
    }
}

#[actix_web::get("/xml")]
async fn xml_response() -> impl Responder {
    Xml(r#"<?xml version="1.0"?><root><message>Hello</message></root>"#.to_string())
}

文件下载响应 #

rust
use actix_web::http::header::{ContentDisposition, DispositionParam, DispositionType};

struct FileDownload {
    filename: String,
    content: Vec<u8>,
}

impl Responder for FileDownload {
    type Body = actix_web::body::BoxBody;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
        HttpResponse::Ok()
            .insert_header(ContentDisposition {
                disposition: DispositionType::Attachment,
                parameters: vec![DispositionParam::Filename(self.filename)],
            })
            .content_type("application/octet-stream")
            .body(self.content)
            .map_into_boxed_body()
    }
}

#[actix_web::get("/download")]
async fn download_file() -> impl Responder {
    FileDownload {
        filename: "example.txt".to_string(),
        content: b"File content".to_vec(),
    }
}

响应构建器方法 #

链式调用 #

rust
#[actix_web::get("/chain")]
async fn chain_response() -> impl Responder {
    HttpResponse::Ok()
        .content_type("application/json")
        .insert_header(("X-Request-Id", "12345"))
        .insert_header(("X-Rate-Limit", "100"))
        .json(serde_json::json!({
            "status": "ok"
        }))
}
rust
use actix_web::cookie::{Cookie, SameSite};

#[actix_web::get("/set-cookie")]
async fn set_cookie() -> impl Responder {
    HttpResponse::Ok()
        .cookie(
            Cookie::build("session", "abc123")
                .path("/")
                .secure(true)
                .http_only(true)
                .same_site(SameSite::Strict)
                .finish()
        )
        .body("Cookie set")
}

#[actix_web::get("/clear-cookie")]
async fn clear_cookie() -> impl Responder {
    HttpResponse::Ok()
        .cookie(
            Cookie::build("session", "")
                .path("/")
                .max_age(actix_web::cookie::time::Duration::seconds(0))
                .finish()
        )
        .body("Cookie cleared")
}

流式响应 #

流式输出 #

rust
use actix_web::web::Bytes;
use futures::stream::{self, StreamExt};

#[actix_web::get("/stream")]
async fn stream_response() -> impl Responder {
    let stream = stream::iter(0..10)
        .map(|n| Ok::<_, actix_web::Error>(Bytes::from(format!("Chunk {}\n", n))));
    
    HttpResponse::Ok()
        .content_type("text/plain")
        .streaming(stream)
}

Server-Sent Events #

rust
use actix_web::web::Bytes;
use futures::stream::{Stream, StreamExt};
use std::time::Duration;

fn sse_stream() -> impl Stream<Item = Result<Bytes, actix_web::Error>> {
    stream::iter(0..)
        .map(|n| Ok(Bytes::from(format!("data: Message {}\n\n", n))))
        .throttle(Duration::from_secs(1))
}

#[actix_web::get("/sse")]
async fn sse() -> impl Responder {
    HttpResponse::Ok()
        .content_type("text/event-stream")
        .insert_header(("Cache-Control", "no-cache"))
        .insert_header(("Connection", "keep-alive"))
        .streaming(sse_stream())
}

重定向 #

rust
#[actix_web::get("/redirect")]
async fn redirect() -> impl Responder {
    HttpResponse::Found()
        .insert_header(("Location", "/target"))
        .finish()
}

#[actix_web::get("/redirect-permanent")]
async fn redirect_permanent() -> impl Responder {
    HttpResponse::MovedPermanently()
        .insert_header(("Location", "/new-location"))
        .finish()
}

完整示例 #

rust
use actix_web::{web, App, HttpResponse, HttpServer, Responder, HttpRequest};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct User {
    id: u32,
    name: String,
    email: String,
}

#[derive(Serialize)]
struct ApiResponse<T> {
    success: bool,
    data: T,
}

impl<T: Serialize> Responder for ApiResponse<T> {
    type Body = actix_web::body::BoxBody;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
        HttpResponse::Ok()
            .json(&self)
            .map_into_boxed_body()
    }
}

#[actix_web::get("/")]
async fn index() -> impl Responder {
    HttpResponse::Ok()
        .content_type("text/html")
        .body("<h1>Welcome</h1>")
}

#[actix_web::get("/user/{id}")]
async fn get_user(path: web::Path<u32>) -> impl Responder {
    ApiResponse {
        success: true,
        data: User {
            id: path.into_inner(),
            name: "Alice".to_string(),
            email: "alice@example.com".to_string(),
        },
    }
}

#[actix_web::post("/users")]
async fn create_user(user: web::Json<User>) -> impl Responder {
    HttpResponse::Created()
        .json(ApiResponse {
            success: true,
            data: user.into_inner(),
        })
}

#[actix_web::get("/redirect")]
async fn redirect_example() -> impl Responder {
    HttpResponse::Found()
        .insert_header(("Location", "/"))
        .finish()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(index)
            .service(get_user)
            .service(create_user)
            .service(redirect_example)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

下一步 #

现在你已经掌握了响应类型,继续学习 JSON 响应,深入了解 JSON 响应处理!

最后更新:2026-03-29