httpbin-rs 0.1.0

使用 Rust 实现 httpbin
use actix_web::{
    web::{delete, get, patch, post, put, resource, Bytes, Json, ServiceConfig},
    HttpRequest,
};
use serde::Serialize;
use std::collections::HashMap;
use utoipa::ToSchema;

const TAG: &str = "Http Methods";

#[derive(Serialize, ToSchema)]
pub(super) struct ApiOutput {
    args: Option<HashMap<String, String>>,
    data: String,
    files: Option<Vec<String>>,
    form: Option<Vec<String>>,
    headers: HashMap<String, String>,
    json: Option<String>,
    origin: String,
    url: String,
    method: String,
}

pub(crate) fn api(cfg: &mut ServiceConfig) {
    cfg.service(resource("/delete").route(delete().to(delete_api)))
        .service(resource("/get").route(get().to(get_api)))
        .service(resource("/patch").route(patch().to(patch_api)))
        .service(resource("/post").route(post().to(post_api)))
        .service(resource("/put").route(put().to(put_api)));
}

#[utoipa::path(
    tag = TAG,
    delete,
    path = "/delete",
    responses(
        (status=200, description="The request’s DELETE parameters.", body=ApiOutput)
    )
)]
pub(crate) async fn delete_api(req: HttpRequest, data: Bytes) -> Json<ApiOutput> {
    anything(req, data).await
}

#[utoipa::path(
    tag = TAG,
    get,
    path = "/get",
    responses(
        (status=200, description="The request’s query parameters.", body=ApiOutput)
    )
)]
pub(crate) async fn get_api(req: HttpRequest, data: Bytes) -> Json<ApiOutput> {
    anything(req, data).await
}

#[utoipa::path(
    tag = TAG,
    patch,
    path = "/patch",
    responses(
        (status=200, description="The request’s PATCH parameters.", body=ApiOutput)
    )
)]
pub(crate) async fn patch_api(req: HttpRequest, data: Bytes) -> Json<ApiOutput> {
    anything(req, data).await
}

#[utoipa::path(
    tag = TAG,
    post,
    path = "/post",
    responses(
        (status=200, description="The request’s POST parameters.", body=ApiOutput)
    )
)]
pub(crate) async fn post_api(req: HttpRequest, data: Bytes) -> Json<ApiOutput> {
    anything(req, data).await
}

#[utoipa::path(
    tag = TAG,
    put,
    path = "/put",
    responses(
        (status=200, description="The request’s PUT parameters.", body=ApiOutput)
    )
)]
pub(crate) async fn put_api(req: HttpRequest, data: Bytes) -> Json<ApiOutput> {
    anything(req, data).await
}

pub(crate) async fn anything(req: HttpRequest, data: Bytes) -> Json<ApiOutput> {
    let headers = req
        .headers()
        .iter()
        .map(|x| {
            (
                x.0.to_string(),
                x.1.to_str().unwrap_or_default().to_string(),
            )
        })
        .collect();

    let mut args = HashMap::new();
    if !req.query_string().is_empty() {
        for arg in req.query_string().split('&') {
            let mut kv = arg.split('=');
            let key = kv.next().unwrap_or_default().to_string();
            let mut value = kv.next().unwrap_or_default().to_string();
            if let Some(v) = args.get(&key) {
                value = format!("{},{}", v, value);
            }
            args.insert(key, value);
        }
    }

    Json(ApiOutput {
        args: (!args.is_empty()).then_some(args),
        data: String::from_utf8(data.to_vec()).unwrap_or_default(),
        files: None,
        form: None,
        headers,
        json: None,
        origin: req
            .connection_info()
            .realip_remote_addr()
            .unwrap_or_default()
            .to_string(),
        url: req.full_url().to_string(),
        method: req.method().to_string(),
    })
}