From 5a5800c1ae4695eaeea52b188c0da9dce7b72ea2 Mon Sep 17 00:00:00 2001 From: Pure White Date: Thu, 25 Nov 2021 16:31:30 +0800 Subject: [PATCH] feat: default to charset=utf-8 for text content type (#554) * feat: default to charset=utf-8 for text content type * added changelog && fix comment * fix workflow --- axum-extra/Cargo.toml | 1 + axum-extra/src/response/erased_json.rs | 11 +++---- axum/CHANGELOG.md | 2 ++ axum/Cargo.toml | 6 ++-- axum/src/docs/response.md | 2 +- axum/src/extract/form.rs | 6 ++-- axum/src/extract/mod.rs | 4 +-- axum/src/json.rs | 14 +++++---- axum/src/lib.rs | 2 +- axum/src/response/mod.rs | 43 ++++++++++++++------------ axum/src/response/sse.rs | 2 +- examples/testing/Cargo.toml | 3 +- examples/testing/src/main.rs | 2 +- 13 files changed, 54 insertions(+), 44 deletions(-) diff --git a/axum-extra/Cargo.toml b/axum-extra/Cargo.toml index f74ca796..13517e66 100644 --- a/axum-extra/Cargo.toml +++ b/axum-extra/Cargo.toml @@ -15,6 +15,7 @@ erased-json = ["serde", "serde_json"] [dependencies] axum = { path = "../axum", version = "0.3" } +mime = "0.3" tower-service = "0.3" # optional dependencies diff --git a/axum-extra/src/response/erased_json.rs b/axum-extra/src/response/erased_json.rs index 6ade7119..411f4f3a 100644 --- a/axum-extra/src/response/erased_json.rs +++ b/axum-extra/src/response/erased_json.rs @@ -45,23 +45,22 @@ impl IntoResponse for ErasedJson { type BodyError = Infallible; fn into_response(self) -> Response { - #[allow(clippy::declare_interior_mutable_const)] - const APPLICATION_JSON: HeaderValue = HeaderValue::from_static("application/json"); - let bytes = match self.0 { Ok(res) => res, Err(err) => { return Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) - .header(header::CONTENT_TYPE, "text/plain") + .header(header::CONTENT_TYPE, mime::TEXT_PLAIN_UTF_8.as_ref()) .body(Full::from(err.to_string())) .unwrap(); } }; let mut res = Response::new(Full::from(bytes)); - res.headers_mut() - .insert(header::CONTENT_TYPE, APPLICATION_JSON); + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()), + ); res } } diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index 4b97aebb..5dce5631 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -33,11 +33,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **breaking:** `Router::nest` will panic if the nested router has a fallback. Previously it would be silently discarded ([#529]) - Update WebSockets to use tokio-tungstenite 0.16 ([#525]) +- **added:** Default to return `charset=utf-8` for text content type. ([#554]) [#525]: https://github.com/tokio-rs/axum/pull/525 [#527]: https://github.com/tokio-rs/axum/pull/527 [#529]: https://github.com/tokio-rs/axum/pull/529 [#534]: https://github.com/tokio-rs/axum/pull/534 +[#554]: https://github.com/tokio-rs/axum/pull/554 # 0.3.3 (13. November, 2021) diff --git a/axum/Cargo.toml b/axum/Cargo.toml index a5070a01..ef401faf 100644 --- a/axum/Cargo.toml +++ b/axum/Cargo.toml @@ -14,8 +14,8 @@ repository = "https://github.com/tokio-rs/axum" default = ["http1", "json", "tower-log"] http1 = ["hyper/http1"] http2 = ["hyper/http2"] -json = ["serde_json", "mime"] -multipart = ["multer", "mime"] +json = ["serde_json"] +multipart = ["multer"] tower-log = ["tower/log"] ws = ["tokio-tungstenite", "sha-1", "base64"] @@ -26,6 +26,7 @@ bytes = "1.0" futures-util = { version = "0.3", default-features = false, features = ["alloc"] } http = "0.2.5" http-body = "0.4.4" +mime = "0.3.16" hyper = { version = "0.14.14", features = ["server", "tcp", "stream"] } matchit = "0.4.4" percent-encoding = "2.1" @@ -43,7 +44,6 @@ tower-service = "0.3" # optional dependencies base64 = { optional = true, version = "0.13" } headers = { optional = true, version = "0.3" } -mime = { optional = true, version = "0.3" } multer = { optional = true, version = "2.0.0" } serde_json = { version = "1.0", optional = true, features = ["raw_value"] } sha-1 = { optional = true, version = "0.9.6" } diff --git a/axum/src/docs/response.md b/axum/src/docs/response.md index e3ef5883..4bb0959f 100644 --- a/axum/src/docs/response.md +++ b/axum/src/docs/response.md @@ -21,7 +21,7 @@ async fn plain_text() -> &'static str { "foo" } -// String works too and will get a `text/plain` content-type +// String works too and will get a `text/plain; charset=utf-8` content-type async fn plain_text_string(uri: Uri) -> String { format!("Hi from {}", uri.path()) } diff --git a/axum/src/extract/form.rs b/axum/src/extract/form.rs index c4d017ca..a2ffeaa7 100644 --- a/axum/src/extract/form.rs +++ b/axum/src/extract/form.rs @@ -60,7 +60,7 @@ where .map_err(FailedToDeserializeQueryString::new::)?; Ok(Form(value)) } else { - if !has_content_type(req, "application/x-www-form-urlencoded")? { + if !has_content_type(req, &mime::APPLICATION_WWW_FORM_URLENCODED)? { return Err(InvalidFormContentType.into()); } @@ -115,7 +115,7 @@ mod tests { .method(Method::POST) .header( http::header::CONTENT_TYPE, - "application/x-www-form-urlencoded", + mime::APPLICATION_WWW_FORM_URLENCODED.as_ref(), ) .body(http_body::Full::::new( serde_urlencoded::to_string(&value).unwrap().into(), @@ -182,7 +182,7 @@ mod tests { Request::builder() .uri("http://example.com/test") .method(Method::POST) - .header(http::header::CONTENT_TYPE, "application/json") + .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) .body(http_body::Full::::new( serde_urlencoded::to_string(&Pagination { size: Some(10), diff --git a/axum/src/extract/mod.rs b/axum/src/extract/mod.rs index 25493adf..9b89570f 100644 --- a/axum/src/extract/mod.rs +++ b/axum/src/extract/mod.rs @@ -332,7 +332,7 @@ where pub(crate) fn has_content_type( req: &RequestParts, - expected_content_type: &str, + expected_content_type: &mime::Mime, ) -> Result { let content_type = if let Some(content_type) = req .headers() @@ -350,7 +350,7 @@ pub(crate) fn has_content_type( return Ok(false); }; - Ok(content_type.starts_with(expected_content_type)) + Ok(content_type.starts_with(expected_content_type.as_ref())) } pub(crate) fn take_body(req: &mut RequestParts) -> Result { diff --git a/axum/src/json.rs b/axum/src/json.rs index e3534301..8c29c57e 100644 --- a/axum/src/json.rs +++ b/axum/src/json.rs @@ -176,23 +176,25 @@ where type BodyError = Infallible; fn into_response(self) -> Response { - #[allow(clippy::declare_interior_mutable_const)] - const APPLICATION_JSON: HeaderValue = HeaderValue::from_static("application/json"); - let bytes = match serde_json::to_vec(&self.0) { Ok(res) => res, Err(err) => { return Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) - .header(header::CONTENT_TYPE, "text/plain") + .header( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()), + ) .body(Full::from(err.to_string())) .unwrap(); } }; let mut res = Response::new(Full::from(bytes)); - res.headers_mut() - .insert(header::CONTENT_TYPE, APPLICATION_JSON); + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()), + ); res } } diff --git a/axum/src/lib.rs b/axum/src/lib.rs index 2f165f20..953310e4 100644 --- a/axum/src/lib.rs +++ b/axum/src/lib.rs @@ -127,7 +127,7 @@ //! }; //! use serde_json::{Value, json}; //! -//! // `&'static str` becomes a `200 OK` with `content-type: text/plain` +//! // `&'static str` becomes a `200 OK` with `content-type: text/plain; charset=utf-8` //! async fn plain_text() -> &'static str { //! "foo" //! } diff --git a/axum/src/response/mod.rs b/axum/src/response/mod.rs index b51a77aa..c206cf84 100644 --- a/axum/src/response/mod.rs +++ b/axum/src/response/mod.rs @@ -331,26 +331,25 @@ impl IntoResponse for std::borrow::Cow<'static, str> { type BodyError = Infallible; fn into_response(self) -> Response { - #[allow(clippy::declare_interior_mutable_const)] - const TEXT_PLAIN: HeaderValue = HeaderValue::from_static("text/plain"); - let mut res = Response::new(Full::from(self)); - res.headers_mut().insert(header::CONTENT_TYPE, TEXT_PLAIN); + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()), + ); res } } -#[allow(clippy::declare_interior_mutable_const)] -const APPLICATION_OCTET_STREAM: HeaderValue = HeaderValue::from_static("application/octet-stream"); - impl IntoResponse for Bytes { type Body = Full; type BodyError = Infallible; fn into_response(self) -> Response { let mut res = Response::new(Full::from(self)); - res.headers_mut() - .insert(header::CONTENT_TYPE, APPLICATION_OCTET_STREAM); + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()), + ); res } } @@ -361,8 +360,10 @@ impl IntoResponse for &'static [u8] { fn into_response(self) -> Response { let mut res = Response::new(Full::from(self)); - res.headers_mut() - .insert(header::CONTENT_TYPE, APPLICATION_OCTET_STREAM); + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()), + ); res } } @@ -373,8 +374,10 @@ impl IntoResponse for Vec { fn into_response(self) -> Response { let mut res = Response::new(Full::from(self)); - res.headers_mut() - .insert(header::CONTENT_TYPE, APPLICATION_OCTET_STREAM); + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()), + ); res } } @@ -385,8 +388,10 @@ impl IntoResponse for std::borrow::Cow<'static, [u8]> { fn into_response(self) -> Response { let mut res = Response::new(Full::from(self)); - res.headers_mut() - .insert(header::CONTENT_TYPE, APPLICATION_OCTET_STREAM); + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()), + ); res } } @@ -468,11 +473,11 @@ where type BodyError = Infallible; fn into_response(self) -> Response { - #[allow(clippy::declare_interior_mutable_const)] - const TEXT_HTML: HeaderValue = HeaderValue::from_static("text/html"); - let mut res = Response::new(self.0.into()); - res.headers_mut().insert(header::CONTENT_TYPE, TEXT_HTML); + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()), + ); res } } diff --git a/axum/src/response/sse.rs b/axum/src/response/sse.rs index b9a19645..18d9a469 100644 --- a/axum/src/response/sse.rs +++ b/axum/src/response/sse.rs @@ -104,7 +104,7 @@ where }; Response::builder() - .header(http::header::CONTENT_TYPE, "text/event-stream") + .header(http::header::CONTENT_TYPE, mime::TEXT_EVENT_STREAM.as_ref()) .header(http::header::CACHE_CONTROL, "no-cache") .body(body) .unwrap() diff --git a/examples/testing/Cargo.toml b/examples/testing/Cargo.toml index 85627bf6..1830ad68 100644 --- a/examples/testing/Cargo.toml +++ b/examples/testing/Cargo.toml @@ -6,9 +6,10 @@ publish = false [dependencies] axum = { path = "../../axum" } +mime = "0.3" tokio = { version = "1.0", features = ["full"] } tracing = "0.1" -tracing-subscriber = { version="0.3", features = ["env-filter"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } tower-http = { version = "0.1", features = ["trace"] } serde_json = "1.0" hyper = { version = "0.14", features = ["full"] } diff --git a/examples/testing/src/main.rs b/examples/testing/src/main.rs index 765c601e..b6677f68 100644 --- a/examples/testing/src/main.rs +++ b/examples/testing/src/main.rs @@ -81,7 +81,7 @@ mod tests { Request::builder() .method(http::Method::POST) .uri("/json") - .header(http::header::CONTENT_TYPE, "application/json") + .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) .body(Body::from( serde_json::to_vec(&json!([1, 2, 3, 4])).unwrap(), ))