async-graphql-actix-web 2.0

This commit is contained in:
Sunli 2020-09-11 17:52:06 +08:00
parent a4fdf6a38a
commit 4da65fc8e3
2 changed files with 43 additions and 49 deletions

View File

@ -8,10 +8,8 @@ mod subscription;
use actix_web::dev::{HttpResponseBuilder, Payload, PayloadStream};
use actix_web::http::StatusCode;
use actix_web::{http, web, Error, FromRequest, HttpRequest, HttpResponse, Responder};
use async_graphql::http::StreamBody;
use async_graphql::{
IntoQueryBuilder, ParseRequestError, QueryBuilder, ReceiveMultipartOptions, Response,
};
use async_graphql::http::{receive_body, MultipartOptions, StreamBody};
use async_graphql::{ParseRequestError, Request, Response};
use futures::channel::mpsc;
use futures::future::Ready;
use futures::{Future, SinkExt, StreamExt, TryFutureExt};
@ -21,13 +19,13 @@ pub use subscription::WSSubscription;
/// Extractor for GraphQL request
///
/// It's a wrapper of `QueryBuilder`, you can use `GQLRequest::into_inner` unwrap it to `QueryBuilder`.
/// `async_graphql::IntoQueryBuilderOpts` allows to configure extraction process.
pub struct GQLRequest(QueryBuilder);
/// It's a wrapper of `async_graphql::Request`, you can use `GQLRequest::into_inner` unwrap it to `async_graphql::Request`.
/// `async_graphql::http::MultipartOptions` allows to configure extraction process.
pub struct GQLRequest(Request);
impl GQLRequest {
/// Unwrap it to `QueryBuilder`.
pub fn into_inner(self) -> QueryBuilder {
/// Unwraps the value to `async_graphql::Request`.
pub fn into_inner(self) -> Request {
self.0
}
}
@ -35,7 +33,7 @@ impl GQLRequest {
impl FromRequest for GQLRequest {
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<GQLRequest, Error>>>>;
type Config = ReceiveMultipartOptions;
type Config = MultipartOptions;
fn from_request(req: &HttpRequest, payload: &mut Payload<PayloadStream>) -> Self::Future {
let config = req.app_data::<Self::Config>().cloned().unwrap_or_default();
@ -44,12 +42,7 @@ impl FromRequest for GQLRequest {
let res = web::Query::<async_graphql::http::GQLRequest>::from_query(req.query_string());
Box::pin(async move {
let gql_request = res?;
gql_request
.into_inner()
.into_query_builder_opts(&config)
.map_ok(GQLRequest)
.map_err(actix_web::error::ErrorBadRequest)
.await
Ok(GQLRequest(gql_request.into_inner().into()))
})
} else {
let content_type = req
@ -71,26 +64,26 @@ impl FromRequest for GQLRequest {
});
Box::pin(async move {
(content_type, StreamBody::new(rx))
.into_query_builder_opts(&config)
.map_ok(GQLRequest)
.map_err(|err| match err {
ParseRequestError::PayloadTooLarge => {
actix_web::error::ErrorPayloadTooLarge(err)
}
_ => actix_web::error::ErrorBadRequest(err),
})
.await
Ok(GQLRequest(
receive_body(content_type, StreamBody::new(rx), config)
.map_err(|err| match err {
ParseRequestError::PayloadTooLarge => {
actix_web::error::ErrorPayloadTooLarge(err)
}
_ => actix_web::error::ErrorBadRequest(err),
})
.await?,
))
})
}
}
}
/// Responder for GraphQL response
pub struct GQLResponse(async_graphql::Result<Response>);
pub struct GQLResponse(Response);
impl From<async_graphql::Result<Response>> for GQLResponse {
fn from(resp: async_graphql::Result<Response>) -> Self {
impl From<Response> for GQLResponse {
fn from(resp: Response) -> Self {
GQLResponse(resp)
}
}
@ -103,15 +96,14 @@ impl Responder for GQLResponse {
let mut res = HttpResponse::build(StatusCode::OK);
res.content_type("application/json");
add_cache_control(&mut res, &self.0);
let res =
res.body(serde_json::to_string(&async_graphql::http::GQLResponse(self.0)).unwrap());
let res = res.body(serde_json::to_string(&self.0).unwrap());
futures::future::ok(res)
}
}
fn add_cache_control(builder: &mut HttpResponseBuilder, resp: &async_graphql::Result<Response>) {
if let Ok(Response { cache_control, .. }) = resp {
if let Some(cache_control) = cache_control.value() {
fn add_cache_control(builder: &mut HttpResponseBuilder, resp: &Response) {
if resp.is_ok() {
if let Some(cache_control) = resp.cache_control.value() {
builder.header("cache-control", cache_control);
}
}

View File

@ -2,7 +2,7 @@ use actix::{
Actor, ActorContext, ActorFuture, AsyncContext, ContextFutureSpawner, StreamHandler, WrapFuture,
};
use actix_web_actors::ws::{Message, ProtocolError, WebsocketContext};
use async_graphql::{Data, FieldResult, ObjectType, Schema, SubscriptionType, WebSocketTransport};
use async_graphql::{Data, FieldResult, ObjectType, Schema, SubscriptionType};
use futures::channel::mpsc;
use futures::SinkExt;
use std::time::{Duration, Instant};
@ -15,7 +15,7 @@ pub struct WSSubscription<Query, Mutation, Subscription> {
schema: Schema<Query, Mutation, Subscription>,
hb: Instant,
sink: Option<mpsc::UnboundedSender<Vec<u8>>>,
init_context_data: Option<Box<dyn Fn(serde_json::Value) -> FieldResult<Data> + Send + Sync>>,
initializer: Option<Box<dyn Fn(serde_json::Value) -> FieldResult<Data> + Send + Sync>>,
}
impl<Query, Mutation, Subscription> WSSubscription<Query, Mutation, Subscription>
@ -30,17 +30,17 @@ where
schema: schema.clone(),
hb: Instant::now(),
sink: None,
init_context_data: None,
initializer: None,
}
}
/// Set a context data initialization function.
pub fn init_context_data<F>(self, f: F) -> Self
pub fn initializer<F>(self, f: F) -> Self
where
F: Fn(serde_json::Value) -> FieldResult<Data> + Send + Sync + 'static,
{
Self {
init_context_data: Some(Box::new(f)),
initializer: Some(Box::new(f)),
..self
}
}
@ -65,16 +65,18 @@ where
fn started(&mut self, ctx: &mut Self::Context) {
self.hb(ctx);
let schema = self.schema.clone();
let (sink, stream) = schema.subscription_connection(
if let Some(init_with_payload) = self.init_context_data.take() {
WebSocketTransport::new(init_with_payload)
} else {
WebSocketTransport::default()
},
);
ctx.add_stream(stream);
self.sink = Some(sink);
if let Some(initializer) = self.initializer.take() {
let (sink, stream) = async_graphql::transports::websocket::create_with_initializer(
&self.schema,
initializer,
);
ctx.add_stream(stream);
self.sink = Some(sink);
} else {
let (sink, stream) = async_graphql::transports::websocket::create(&self.schema);
ctx.add_stream(stream);
self.sink = Some(sink);
};
}
}