axum crate の FromRequest の標準で提供される実装
前回は axum crate の FromRequestParts の標準で提供される実装を見ました。
今回は axum crate のビルトイン Extractor の実装、特に FromRequst のものを見ていきます。
今回も axum crate のバージョンは 0.8.6 です。
axum crate のビルトイン Extractor とは
axum における Extractor とは FromRequestParts trait または FromRequest trait を実装しているものです。ビルトイン Extractor は axum crate 本体が提供しているそれらの trait の実装です。
今回は FromRequest の実装を見ていきます。前々回と同様に、 FromRequest のドキュメントにある実装を順に見ていきます。
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html
impl<S> FromRequest<S> for String
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html#impl-FromRequest<S>-for-String
impl<S> FromRequest<S> for String
where
S: Send + Sync,
{
type Rejection = StringRejection;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let bytes = Bytes::from_request(req, state)
.await
.map_err(|err| match err {
BytesRejection::FailedToBufferBody(inner) => {
StringRejection::FailedToBufferBody(inner)
}
})?;
let string = String::from_utf8(bytes.into()).map_err(InvalidUtf8::from_err)?;
Ok(string)
}
}
Bytes の FromRequest の実装に処理を任せて Bytes を得たあと、それを String::from_utf8 で String にして返すようです。
失敗時にはエラーを変換していますね。
ちなみに StringRejection はここで定義されています。 composite_rejection macro が使われています。こちらも後で見ます。
https://github.com/tokio-rs/axum/blob/axum-v0.8.6/axum-core/src/extract/rejection.rs#L79-L82
impl<S> FromRequest<S> for Bytes
順序が前後していますが、 Bytes のための実装です。
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html#impl-FromRequest<S>-for-Bytes
impl<S> FromRequest<S> for Bytes
where
S: Send + Sync,
{
type Rejection = BytesRejection;
async fn from_request(req: Request, _: &S) -> Result<Self, Self::Rejection> {
let bytes = req
.into_limited_body()
.collect()
.await
.map_err(FailedToBufferBody::from_err)?
.to_bytes();
Ok(bytes)
}
}
http::Request<axum_core::body::Body> の into_limited_body を collect したものを to_bytes したもの。
into_limited_body は RequestExt trait で定義されたものです。
https://github.com/tokio-rs/axum/blob/axum-v0.8.6/axum-core/src/ext_traits/request.rs#L330-L332
fn into_limited_body(self) -> Body {
self.with_limited_body().into_body()
}
with_limited_body は body の上限を設定するためのものです。
https://github.com/tokio-rs/axum/blob/axum-v0.8.6/axum-core/src/ext_traits/request.rs#L316-L328
fn with_limited_body(self) -> Request {
// update docs in `axum-core/src/extract/default_body_limit.rs` and
// `axum/src/docs/extract.md` if this changes
const DEFAULT_LIMIT: usize = 2_097_152; // 2 mb
match self.extensions().get::<DefaultBodyLimitKind>().copied() {
Some(DefaultBodyLimitKind::Disable) => self,
Some(DefaultBodyLimitKind::Limit(limit)) => {
self.map(|b| Body::new(http_body_util::Limited::new(b, limit)))
}
None => self.map(|b| Body::new(http_body_util::Limited::new(b, DEFAULT_LIMIT))),
}
}
ドキュメントにも記載があるとおり DefaultBodyLimit という layer を設定することで変更できます。内部的に DefaultBodyLimitKind を req の extensions に insert するものになっています。
https://github.com/tokio-rs/axum/blob/axum-v0.8.6/axum-core/src/extract/default_body_limit.rs
http_body_util::Limited は limit に達したらエラーにする機能を持った Body です。
https://docs.rs/http-body-util/0.1.3/http_body_util/struct.Limited.html
最初の req.into_limited_body().collect().await に戻ります。 collect は axum::body::Body::collect です。
https://docs.rs/axum/0.8.6/axum/body/struct.Body.html#method.collect
これは http_body_util::combinators::Collect を返します。
https://docs.rs/http-body-util/0.1.3/http_body_util/combinators/struct.Collect.html
さらに、 Collect を .await すると http_body_util::Collected が返されます。
https://docs.rs/http-body-util/0.1.3/http_body_util/struct.Collected.html
map_err は skip して、 to_bytes して終わりです。 to_bytes の結果は Bytes です。
impl<S> FromRequest<S> for BytesMut
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html#impl-FromRequest<S>-for-BytesMut
impl<S> FromRequest<S> for BytesMut
where
S: Send + Sync,
{
type Rejection = BytesRejection;
async fn from_request(req: Request, _: &S) -> Result<Self, Self::Rejection> {
let mut body = req.into_limited_body();
let mut bytes = BytesMut::new();
body_to_bytes_mut(&mut body, &mut bytes).await?;
Ok(bytes)
}
}
async fn body_to_bytes_mut(body: &mut Body, bytes: &mut BytesMut) -> Result<(), BytesRejection> {
while let Some(frame) = body
.frame()
.await
.transpose()
.map_err(FailedToBufferBody::from_err)?
{
let Ok(data) = frame.into_data() else {
return Ok(());
};
bytes.put(data);
}
Ok(())
}
Body を frame で frame 単位に読んで BytesMut に put していき、最後まで読んだら返します。
impl<S> FromRequest<S> for Request<Body>
これも順序が前後していそうですが、もっとも基本的な request をそのまま得る Extractor です。
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html#impl-FromRequest<S>-for-Request<Body>
impl<S> FromRequest<S> for Request
where
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request(req: Request, _: &S) -> Result<Self, Self::Rejection> {
Ok(req)
}
}
FromRequest に渡された Request を返すだけです。
impl<S, T1, ... T16> FromRequest<S> for (T1, ..., T16)
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html#impl-FromRequest<S>-for-(T1,)
all_the_tuples!(impl_from_request);
macro_rules! impl_from_request {
(
[$($ty:ident),*], $last:ident
) => {
// ...
// This impl must not be generic over M, otherwise it would conflict with the blanket
// implementation of `FromRequest<S, Mut>` for `T: FromRequestParts<S>`.
#[allow(non_snake_case, unused_mut, unused_variables)]
impl<S, $($ty,)* $last> FromRequest<S> for ($($ty,)* $last,)
where
$( $ty: FromRequestParts<S> + Send, )*
$last: FromRequest<S> + Send,
S: Send + Sync,
{
type Rejection = Response;
fn from_request(req: Request, state: &S) -> impl Future<Output = Result<Self, Self::Rejection>> {
let (mut parts, body) = req.into_parts();
async move {
$(
let $ty = $ty::from_request_parts(&mut parts, state).await.map_err(|err| err.into_response())?;
)*
let req = Request::from_parts(parts, body);
let $last = $last::from_request(req, state).await.map_err(|err| err.into_response())?;
Ok(($($ty,)* $last,))
}
}
}
};
}
前回と同じ箇所です。ただ今回は impl_from_request 内の FromRequest 部分を見ます。
……とは言え、大きく変わるものではなくて、前回との違いは最後の要素に対して from_request_parts するか from_request するかです。 FromRequestParts の場合は request body を消費してはいけないので from_request_parts するだけ。今回の FromRequest の場合は from_request で request body を消費してしまいます。
impl<S, T> FromRequest<S> for Option<T>
https://docs.rs/axum-core/0.5.5/src/axum_core/extract/option.rs.html#53-63
impl<S, T> FromRequest<S> for Option<T>
where
T: OptionalFromRequest<S>,
S: Send + Sync,
{
type Rejection = T::Rejection;
async fn from_request(req: Request, state: &S) -> Result<Option<T>, Self::Rejection> {
T::from_request(req, state).await
}
}
ここも前回と大きく変わらず、 OptionalFromRequestParts が OptionalFromRequest になったものです。
OptionalFromRequest は次のように定義されています。
https://docs.rs/axum/0.8.6/axum/extract/trait.OptionalFromRequest.html
/// Customize the behavior of `Option<Self>` as a [`FromRequest`] extractor.
pub trait OptionalFromRequest<S, M = private::ViaRequest>: Sized {
/// If the extractor fails, it will use this "rejection" type.
///
/// A rejection is a kind of error that can be converted into a response.
type Rejection: IntoResponse;
/// Perform the extraction.
fn from_request(
req: Request,
state: &S,
) -> impl Future<Output = Result<Option<Self>, Self::Rejection>> + Send;
}
Option で wrap されているくらいの違いです。
impl<S, T> FromRequest<S> for Result<T, <T as FromRequest<S>>::Rejection>
impl<S, T> FromRequest<S> for Result<T, T::Rejection>
where
T: FromRequest<S>,
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
Ok(T::from_request(req, state).await)
}
}
ここも前回と大きく変わらず、 エラーを into_response せずにそのまま Result で返すことでユーザー側でエラーハンドリングできるようにしただけですね。
impl<S> FromRequest<S> for Body
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html#impl-FromRequest<S>-for-Body
impl<S> FromRequest<S> for Body
where
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request(req: Request, _: &S) -> Result<Self, Self::Rejection> {
Ok(req.into_body())
}
}
Request の into_body を呼び出しています。
https://docs.rs/http/1.3.1/http/request/struct.Request.html#method.into_body
pub fn into_body(self) -> T {
self.body
}
impl<S> FromRequest<S> for Multipart
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html#impl-FromRequest<S>-for-Multipart
impl<S> FromRequest<S> for Multipart
where
S: Send + Sync,
{
type Rejection = MultipartRejection;
async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
let boundary = parse_boundary(req.headers()).ok_or(InvalidBoundary)?;
let stream = req.with_limited_body().into_body();
let multipart = multer::Multipart::new(stream.into_data_stream(), boundary);
Ok(Self { inner: multipart })
}
}
fn parse_boundary(headers: &HeaderMap) -> Option<String> {
let content_type = headers.get(CONTENT_TYPE)?.to_str().ok()?;
multer::parse_boundary(content_type).ok()
}
実装としては multer crate で boundary, body を parse しているようです。外部からは axum の Multipart としてしか見えないようになっています。
https://docs.rs/multer/3.1.0/multer/struct.Multipart.html
impl<S> FromRequest<S> for RawForm
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html#impl-FromRequest<S>-for-RawForm
pub struct RawForm(pub Bytes);
impl<S> FromRequest<S> for RawForm
where
S: Send + Sync,
{
type Rejection = RawFormRejection;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
if req.method() == Method::GET {
if let Some(query) = req.uri().query() {
return Ok(Self(Bytes::copy_from_slice(query.as_bytes())));
}
Ok(Self(Bytes::new()))
} else {
if !has_content_type(req.headers(), &mime::APPLICATION_WWW_FORM_URLENCODED) {
return Err(InvalidFormContentType.into());
}
Ok(Self(Bytes::from_request(req, state).await?))
}
}
}
ドキュメントに記載のとおり、 GET の場合は query string を見ているようですね。
https://docs.rs/axum/0.8.6/axum/extract/struct.RawForm.html
impl<S, T> FromRequest<S, ViaParts> for T
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html#impl-FromRequest<S,+ViaParts>-for-T
impl<S, T> FromRequest<S, private::ViaParts> for T
where
S: Send + Sync,
T: FromRequestParts<S>,
{
type Rejection = <Self as FromRequestParts<S>>::Rejection;
fn from_request(
req: Request,
state: &S,
) -> impl Future<Output = Result<Self, Self::Rejection>> {
let (mut parts, _) = req.into_parts();
async move { Self::from_request_parts(&mut parts, state).await }
}
}
T: FromRequestParts<S> なので、 FromRequestParts なら FromRequest として動くということですね。
impl<T, S> FromRequest<S> for Form<T>
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html#impl-FromRequest<S>-for-Form<T>
pub struct Form<T>(pub T);
impl<T, S> FromRequest<S> for Form<T>
where
T: DeserializeOwned,
S: Send + Sync,
{
type Rejection = FormRejection;
async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
let is_get_or_head =
req.method() == http::Method::GET || req.method() == http::Method::HEAD;
match req.extract().await {
Ok(RawForm(bytes)) => {
let deserializer =
serde_urlencoded::Deserializer::new(form_urlencoded::parse(&bytes));
let value = serde_path_to_error::deserialize(deserializer).map_err(
|err| -> FormRejection {
if is_get_or_head {
FailedToDeserializeForm::from_err(err).into()
} else {
FailedToDeserializeFormBody::from_err(err).into()
}
},
)?;
Ok(Form(value))
}
Err(RawFormRejection::BytesRejection(r)) => Err(FormRejection::BytesRejection(r)),
Err(RawFormRejection::InvalidFormContentType(r)) => {
Err(FormRejection::InvalidFormContentType(r))
}
}
}
}
RequestExt::extract で RawForm の FromRequest を動かして bytes を得て、 form_urlencoded で application/x-www-form-urlencoded な body ないし query string を parse して、 serde_urlencoded で serde::Deserialize な型への変換をしています。
serde_path_to_error crate でエラー位置を特定できるようにしています。この crate 知らなかったです。いいですね。
RequestExt::extract は↓です。
https://docs.rs/axum/0.8.6/axum/trait.RequestExt.html#tymethod.extract
impl<T, S> FromRequest<S> for Json<T>
https://docs.rs/axum/0.8.6/axum/extract/trait.FromRequest.html#impl-FromRequest<S>-for-Json<T>
pub struct Json<T>(pub T);
impl<T, S> FromRequest<S> for Json<T>
where
T: DeserializeOwned,
S: Send + Sync,
{
type Rejection = JsonRejection;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
if !json_content_type(req.headers()) {
return Err(MissingJsonContentType.into());
}
let bytes = Bytes::from_request(req, state).await?;
Self::from_bytes(&bytes)
}
}
// ...
impl<T> Json<T>
where
T: DeserializeOwned,
{
/// Construct a `Json<T>` from a byte slice. Most users should prefer to use the `FromRequest` impl
/// but special cases may require first extracting a `Request` into `Bytes` then optionally
/// constructing a `Json<T>`.
pub fn from_bytes(bytes: &[u8]) -> Result<Self, JsonRejection> {
// Extracted into separate fn so it's only compiled once for all T.
fn make_rejection(err: serde_path_to_error::Error<serde_json::Error>) -> JsonRejection {
match err.inner().classify() {
serde_json::error::Category::Data => JsonDataError::from_err(err).into(),
serde_json::error::Category::Syntax | serde_json::error::Category::Eof => {
JsonSyntaxError::from_err(err).into()
}
serde_json::error::Category::Io => {
if cfg!(debug_assertions) {
// we don't use `serde_json::from_reader` and instead always buffer
// bodies first, so we shouldn't encounter any IO errors
unreachable!()
} else {
JsonSyntaxError::from_err(err).into()
}
}
}
}
let deserializer = &mut serde_json::Deserializer::from_slice(bytes);
match serde_path_to_error::deserialize(deserializer) {
Ok(value) => Ok(Json(value)),
Err(err) => Err(make_rejection(err)),
}
}
}
ごちゃごちゃありますが、 Bytes::from_request して serde_json の from_slice して取り出しています。 Form でも出てきた serde_path_to_error crate がここでも使われています。
おわりに
今回は axum crate の FromRequest の実装を見ていきました。
次回は IntoResponse を見ていきます。
Discussion