Rustのactix-webによるAPIサーバ実装例

Rustを触ってなさすぎて記憶が怪しくなってきたので復習がてら以下の機能をもつAPIサーバを書いた:

  • HTMLを返す
  • プレーンテキストを返す
  • Todo管理API
    • GETに対してTodo構造体をJSONとして返す
    • POSTされたJSONをTodo構造体に変換
  • パラメタに基づいて動的に生成した画像を返す
use serde::{Serialize, Deserialize};
use image::{
    codecs::png::PngEncoder,
    ImageEncoder, ColorType, RgbImage
};
use actix_web::{
    get, post, web, App, HttpServer, Responder, HttpResponse,
    http::{
        header::{ContentType},
    }
};

#[derive(Serialize, Deserialize, Debug)]
struct Todo {
    id: Option<u32>,
    content: String,
    done: bool,
}

#[get("/todo/{id}")]
async fn get_todo(id: web::Path<u32>) -> impl Responder {
    let id_option: Option<u32> = Some(id.into_inner());
    HttpResponse::Ok().json(Todo {
        id: id_option,
        content: String::from("Develop Todo App"),
        done: false,
    })
}

#[post("/todo")]
async fn post_todo(todo: web::Json<Todo>) -> impl Responder {
    println!("Posted: {:?}", todo);
    HttpResponse::Ok().body("ok")
}

#[get("/image/{r}")]
async fn fetch_image(r: web::Path<u8>) -> impl Responder {
    let r = *r;
    let mut img = RgbImage::new(512, 512);
    for x in 0..img.width() {
        for y in 0..img.height() {
            let g = 255 - (x as f32 / img.width() as f32 * 255.0) as u8;
            let b = (y as f32 / img.width() as f32 * 255.0) as u8;
            img.put_pixel(x, y, image::Rgb([r, g, b]));
        }
    }
    let mut buf = std::io::Cursor::new(Vec::new());
    let encoder = PngEncoder::new(&mut buf);
    encoder.write_image(&img, img.width(), img.height(), ColorType::Rgb8.into()).unwrap();
    let image_content = buf.into_inner();
    HttpResponse::Ok()
        .content_type(ContentType::png())
        .body(image_content)
}

#[get("/greet/{name}")]
async fn greet(name: web::Path<String>) -> impl Responder {
    HttpResponse::Ok()
        .content_type(ContentType::plaintext())
        .body(format!("Hello {name}"))
}

#[get("/")]
async fn index() -> impl Responder {
    HttpResponse::Ok()
        .content_type(ContentType::html())
        .body("<h1>It works!</h1>")
}

#[actix_web::main]
async fn main() -> Result<(), std::io::Error> {
    HttpServer::new(|| {
        App::new()
            .service(get_todo)
            .service(post_todo)
            .service(fetch_image)
            .service(greet)
            .service(index)
    })
    .bind(("127.0.0.1", 4134))?
    .run()
    .await
}

curl/todoにPOSTを投げる際は以下のように書く:

curl -X POST http://localhost:4134/todo \
    -d '{"content": "aaa", "done": false}' \
    -H "Content-Type: application/json"

また,/image/255にGETアクセスしたときの戻り値は以下のような画像になる:

An example return value of image api