$ rustc --version
rustc 1.39.0 (4560ea788 2019-11-04)
$ cargo --version
cargo 1.39.0 (1c6ec66d5 2019-09-30)
gotham や hyper はまだ async/await ( futures 0.3 ) に対応していません。
そこで async fn を適切な形に整形する必要があります。
下記のように書けるようにします。
src/main.rs
pub type HandlerResult = Result<(State, Response<Body>), (State, HandlerError)>;
# [compat_handler]
async fn piyo_handler(mut state: State) -> HandlerResult {
let piyo = extract_json::<NewPiyo>(&mut state).compat().await;
let body = serde_json::to_string(&piyo).expect("Failed to serialise to json");
let res = create_response(&state, StatusCode::OK, mime::APPLICATION_JSON, body);
Ok((state, res))
}
fn router() -> Router {
let (chain, pipeline) = single_pipeline(new_pipeline().build());
build_router(chain, pipeline, |route| {
route.post("/piyo").to(piyo_handler);
})
}
extract_json の定義は gotham のレポジトリにあった例から貰っています。
gotham が対応している非同期ハンドラ
(mut state: gotham::state::State) -> Box<gotham::handler::HandlerFuture>
であるハンドラには対応しているようです。
futures 0.3 の互換性レイヤーを利用して下記のように書くことができます。
// futures = { version = "0.3", features = ["compat"] }
use futures::compat::Future01CompatExt;
use futures::future::{FutureExt, TryFutureExt};
fn piyo_handler(mut state: State) -> Box<HandlerFuture> {
async fn piyo_handler(mut state: State) -> Result<(State, Response<Body>), (State, HandlerError)> {
// 省略
Ok((state, res))
}
#[allow(deprecated)]
piyo_handler(state).boxed().compat().boxed()
}
属性マクロの作成
上記の書き方では少しコードが長くなってしまうため属性マクロを作成します。
新しくディレクトリを切ります。
compat_handler/Cargo.toml
[lib]
proc-macro = true
compat_handler/src/lib.rs
extern crate proc_macro;
use proc_macro::{TokenStream, TokenTree};
# [proc_macro_attribute]
pub fn compat_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
let tokens: Vec<TokenTree> = item.clone().into_iter().collect();
if "async" != tokens[0].to_string() {
panic!("compat_handler require async fn");
}
let fnname = tokens[2].to_string();
let code = item.to_string();
let s = format!(
r#"
fn {}(mut state: State) -> Box<HandlerFuture> {{
{}
#[allow(deprecated)]
{}(state).boxed().compat().boxed()
}}
"#,
fnname, code, fnname
);
// println!("{}", s);
s.parse().expect("failed to parse")
}
use 宣言の追加
Cargo.toml
[dependencies]
gotham = "0.4.0"
gotham_derive = "0.4.0"
hyper = "0.12"
futures = { version = "0.3", features = ["compat"] }
futures01 = "0.1"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
chrono = { version = "0.4.9", features = ["serde"] }
+compat_handler = { path = "./compat_handler" }
src/main.rs
+use compat_handler::compat_handler;
use futures::compat::Future01CompatExt;
use futures::future::{FutureExt, TryFutureExt};
use futures01::{future, Future, Stream};
use gotham::handler::{HandlerError, HandlerFuture, IntoHandlerError};
use gotham::helpers::http::response::create_response;
use gotham::pipeline::{new_pipeline, single::single_pipeline};
use gotham::router::{builder::*, Router};
use gotham::state::{FromState, State};
use hyper::{Body, Response, StatusCode};
use serde_derive::Serialize;
use std::str::from_utf8;
参考にした記事
Rust の非同期処理はランタイムが標準ではいっていないなどつまずくところが多かったです。
下記の記事がとても分かりやすく理解の助けになりました。