Update http-headers to use http::HeaderMap

This commit is contained in:
Naaman 2021-01-10 10:57:28 +00:00
parent b8279d1e44
commit f92dda0271
7 changed files with 128 additions and 14 deletions

View File

@ -55,6 +55,7 @@ serde_json = "1.0.48"
spin = "0.7.0"
thiserror = "1.0.21"
static_assertions = "1.1.0"
http = "0.2.3"
# Feature optional dependencies
bson = { version = "1.0.0", optional = true }

View File

@ -169,17 +169,17 @@ impl<'r> Responder<'r, 'static> for Response {
let body = serde_json::to_string(&self.0).unwrap();
let mut response = rocket::Response::new();
response.set_header(ContentType::new("application", "json"));
if self.0.is_ok() {
if let Some(cache_control) = self.0.cache_control().value() {
response.set_header(Header::new("cache-control", cache_control));
}
for (name, value) in self.0.http_headers() {
response.set_header(Header::new(name.to_string(), value.to_string()));
response.adjoin_header(Header::new(name.to_string(), value.to_string()));
}
}
response.set_header(ContentType::new("application", "json"));
response.set_sized_body(body.len(), Cursor::new(body));
Ok(response)

View File

@ -164,7 +164,7 @@ pub fn respond(resp: impl Into<async_graphql::BatchResponse>) -> tide::Result {
response.insert_header(headers::CACHE_CONTROL, cache_control);
}
for (name, value) in resp.http_headers() {
response.insert_header(name, value);
response.append_header(name, value);
}
}
response.set_body(Body::from_json(&resp)?);

View File

@ -86,7 +86,7 @@ impl Reply for BatchResponse {
if let (Ok(name), Ok(value)) =
(TryInto::<HeaderName>::try_into(name), value.try_into())
{
resp.headers_mut().insert(name, value);
resp.headers_mut().append(name, value);
}
}
}

View File

@ -10,6 +10,7 @@ use std::sync::Arc;
use async_graphql_value::Value as InputValue;
use fnv::FnvHashMap;
use http::header::{AsHeaderName, HeaderMap, IntoHeaderName};
use serde::de::{Deserialize, Deserializer};
use serde::ser::{SerializeSeq, Serializer};
use serde::Serialize;
@ -320,7 +321,7 @@ pub struct QueryEnvInner {
pub fragments: HashMap<Name, Positioned<FragmentDefinition>>,
pub uploads: Vec<UploadValue>,
pub ctx_data: Arc<Data>,
pub http_headers: spin::Mutex<HashMap<String, String>>,
pub http_headers: spin::Mutex<HeaderMap<String>>,
}
#[doc(hidden)]
@ -443,12 +444,124 @@ impl<'a, T> ContextBase<'a, T> {
.and_then(|d| d.downcast_ref::<D>())
}
/// Sets an HTTP header to response.
pub fn set_http_header(&self, name: impl Into<String>, value: impl Into<String>) {
/// Returns whether the HTTP header `key` is currently set on the response
///
/// # Examples
///
/// ```no_run
/// use async_graphql::*;
/// use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN;
///
/// struct Query;
///
/// #[Object]
/// impl Query {
/// async fn greet(&self, ctx: &Context<'_>) -> String {
///
/// let header_exists = ctx.http_header_contains("Access-Control-Allow-Origin");
/// assert!(!header_exists);
///
/// ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
///
/// let header_exists = ctx.http_header_contains("Access-Control-Allow-Origin");
/// assert!(header_exists);
///
/// String::from("Hello world")
/// }
/// }
/// ```
pub fn http_header_contains(&self, key: impl AsHeaderName) -> bool {
self.query_env.http_headers.lock().contains_key(key)
}
/// Sets a HTTP header to response.
///
/// If the header was not currently set on the response, then `None` is returned.
///
/// If the response already contained this header then the new value is associated with this key
/// and __all the previous values are removed__, however only a the first previous
/// value is returned.
///
/// See [`http::HeaderMap`] for more details on the underlying implementation
///
/// # Examples
///
/// ```no_run
/// use async_graphql::*;
/// use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN;
///
/// struct Query;
///
/// #[Object]
/// impl Query {
/// async fn greet(&self, ctx: &Context<'_>) -> String {
///
/// // Headers can be inserted using the `http` constants
/// let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
/// assert_eq!(was_in_headers, None);
///
/// // They can also be inserted using &str
/// let was_in_headers = ctx.insert_http_header("Custom-Header", "1234");
/// assert_eq!(was_in_headers, None);
///
/// // If multiple headers with the same key are `inserted` then the most recent
/// // one overwrites the previous. If you want multiple headers for the same key, use
/// // `append_http_header` for subsequent headers
/// let was_in_headers = ctx.insert_http_header("Custom-Header", "Hello World");
/// assert_eq!(was_in_headers, Some("1234".to_string()));
///
/// String::from("Hello world")
/// }
/// }
/// ```
pub fn insert_http_header(
&self,
name: impl IntoHeaderName,
value: impl Into<String>,
) -> Option<String> {
self.query_env
.http_headers
.lock()
.insert(name.into(), value.into());
.insert(name, value.into())
}
/// Sets a HTTP header to response.
///
/// If the header was not currently set on the response, then `false` is returned.
///
/// If the response did have this header then the new value is appended to the end of the
/// list of values currently associated with the key, however the key is not updated
/// _(which is important for types that can be `==` without being identical)_.
///
/// See [`http::HeaderMap`] for more details on the underlying implementation
///
/// # Examples
///
/// ```no_run
/// use async_graphql::*;
/// use ::http::header::SET_COOKIE;
///
/// struct Query;
///
/// #[Object]
/// impl Query {
/// async fn greet(&self, ctx: &Context<'_>) -> String {
/// // Insert the first instance of the header
/// ctx.insert_http_header(SET_COOKIE, "Chocolate Chip");
///
/// // Subsequent values should be appended
/// let header_already_exists = ctx.append_http_header("Set-Cookie", "Macadamia");
/// assert!(header_already_exists);
///
/// String::from("Hello world")
/// }
/// }
/// ```
pub fn append_http_header(&self, name: impl IntoHeaderName, value: impl Into<String>) -> bool {
self.query_env
.http_headers
.lock()
.append(name, value.into())
}
fn var_value(&self, name: &str, pos: Pos) -> ServerResult<Value> {

View File

@ -70,7 +70,7 @@ impl Extension for LoggerExtension {
struct DisplayError<'a> {
log: &'a LoggerExtension,
e: &'a ServerError,
};
}
impl<'a> Display for DisplayError<'a> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "[Error] ")?;

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use http::header::HeaderMap;
use serde::{Deserialize, Serialize};
@ -25,7 +25,7 @@ pub struct Response {
/// HTTP headers
#[serde(skip)]
pub http_headers: HashMap<String, String>,
pub http_headers: HeaderMap<String>,
}
impl Response {
@ -116,20 +116,20 @@ impl BatchResponse {
}
}
/// Gets HTTP headers
/// Provides an iterator over all of the HTTP headers set on the response
pub fn http_headers(&self) -> impl Iterator<Item = (&str, &str)> {
let it: Box<dyn Iterator<Item = (&str, &str)>> = match self {
BatchResponse::Single(resp) => Box::new(
resp.http_headers
.iter()
.map(|item| (item.0.as_str(), item.1.as_str())),
.map(|(key, value)| (key.as_str(), value.as_str())),
),
BatchResponse::Batch(resp) => Box::new(
resp.iter()
.map(|r| {
r.http_headers
.iter()
.map(|item| (item.0.as_str(), item.1.as_str()))
.map(|(key, value)| (key.as_str(), value.as_str()))
})
.flatten(),
),