用意したエンドポイントに共通した処理を噛ませたいな思う時があるかと思います。
例えば、apiのrequestのtokenをチェックしたり、ロギングしたり、特定のリクエストは変換したりなどなど。。
このような処理をするために全てのエンドポイントに1つづつ処理を記述するのはさすがにしんどいですよね。
そこで共通処理を必ず通すようにしてみようと思う。ようはactix-webの処理をラッピングしたProxy的な役割を用意してみたいと思います。
Rustのactix-webはその機能をwrapとmiddlewareという形式で実現できます。
まず、通常のエンドポイントを用意します
(actix-webの詳しい使い方はここでは割愛します)
(ごちゃごちゃ色々書いておりますが下記参考をもとにしています)
main.rs
use actix_cors::Cors;
use actix_web::{web, App, HttpServer};
use news_fetcher::{controllers::search_settings_controller};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let port = std::env::var("PORT").unwrap_or(String::from("8080"));
HttpServer::new(|| {
let cors = Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header();
App::new()
.wrap(cors)
.route("/sample", web::post().to(sample_controller::create))
.route("/sample/", web::get().to(sample_controller::read_all))
})
.bind(format!("0.0.0.0:{}", port))?
.run()
.await
}
次にMiddlewareとなる構造体を用意します
今回はプロキシなのでApiProxy
という名前で構造体を用意し、そいつにTransformを実装していく
次に
api_proxy.rs(前半)
use std::{future::{ready, Ready, Future}, pin::Pin};
use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
Error,
};
pub struct ApiProxy;
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S, ServiceRequest> for ApiProxy
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = ApiProxyMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(ApiProxyMiddleware { service }))
}
}
ミドルウェア本体になるApiProxyMiddleware
を定義していきます。
ここでcallの処理にリクエスト時の処理およびレスポンス時の処理を書くことができます
api_proxy.rs(後半)
pub struct ApiProxyMiddleware<S> {
/// The next service to call
service: S,
}
// This future doesn't have the requirement of being `Send`.
// See: futures_util::future::LocalBoxFuture
type LocalBoxFuture<T> = Pin<Box<dyn Future<Output = T> + 'static>>;
// `S`: type of the wrapped service
// `B`: type of the body - try to be generic over the body where possible
impl<S, B> Service<ServiceRequest> for ApiProxyMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<Result<Self::Response, Self::Error>>;
// This service is ready when its next service is ready
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
//ここにリクエストの前処理を記述
println!("Hi from start. You requested: {}", req.path());
// A more complex middleware, could return an error or an early response here.
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
//ここにレスポンスの後処理を記述
println!("Hi from response");
Ok(res)
})
}
}
そしてmain.rsで定義したApiProxy
をwrapして設定してやる
main.rs
use actix_cors::Cors;
use actix_web::{web, App, HttpServer};
use news_fetcher::{api_proxy, controllers::search_settings_controller};
use dotenv::dotenv;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
let port = std::env::var("PORT").unwrap_or(String::from("8080"));
HttpServer::new(|| {
let cors = Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header();
App::new()
.wrap(cors)
.wrap(api_proxy::ApiProxy) // ここに追加
.route("/sample", web::post().to(sample_controller::create))
.route("/sample/", web::get().to(sample_controller::read_all))
})
.bind(format!("0.0.0.0:{}", port))?
.run()
.await
}
うまく実装できたかcurlで確認してみます
curl http://127.0.0.1:8080/sample/
結果
Hi from start. You requested: /sample/
results: Ok([Sample { id: Some(1), category: "hoge"}, Sample { id: Some(2), category: "fuga"}])
Hi from response
レスポンスが Hi from ***で囲まれていますね、無事プロキシできました
あとはMiddlewareのcall処理をいい感じに組んであげれば色々なProxy処理が実装できるので色々変更してみてください