From 1775dea2c9a9dbee7a47a11ae53d926696dcc80d Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Fri, 23 Feb 2018 14:05:34 +0530 Subject: [PATCH 01/10] add juniper example --- Cargo.toml | 1 + examples/juniper/Cargo.toml | 17 ++++++++++ examples/juniper/README.md | 15 ++++++++ examples/juniper/src/main.rs | 62 ++++++++++++++++++++++++++++++++++ examples/juniper/src/schema.rs | 58 +++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 examples/juniper/Cargo.toml create mode 100644 examples/juniper/README.md create mode 100644 examples/juniper/src/main.rs create mode 100644 examples/juniper/src/schema.rs diff --git a/Cargo.toml b/Cargo.toml index b7999b743..47cc2301b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ members = [ "./", "examples/basics", "examples/diesel", + "examples/juniper", "examples/r2d2", "examples/json", "examples/hello-world", diff --git a/examples/juniper/Cargo.toml b/examples/juniper/Cargo.toml new file mode 100644 index 000000000..d66edc0c4 --- /dev/null +++ b/examples/juniper/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "juniper-example" +version = "0.1.0" +authors = ["Nikolay Kim "] +workspace = "../.." + +[dependencies] +env_logger = "0.5" +actix = "0.5" +actix-web = { path = "../../" } + +futures = "0.1" +serde = "1.0" +serde_json = "1.0" +serde_derive = "1.0" + +juniper = "0.9.2" diff --git a/examples/juniper/README.md b/examples/juniper/README.md new file mode 100644 index 000000000..2ac0eac4e --- /dev/null +++ b/examples/juniper/README.md @@ -0,0 +1,15 @@ +# juniper + +Juniper integration for Actix web + +### server + +```bash +cd actix-web/examples/juniper +cargo run (or ``cargo watch -x run``) +# Started http server: 127.0.0.1:8080 +``` + +### web client + +[http://127.0.0.1:8080/graphiql](http://127.0.0.1:8080/graphiql) diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs new file mode 100644 index 000000000..b5c620f1b --- /dev/null +++ b/examples/juniper/src/main.rs @@ -0,0 +1,62 @@ +//! Actix web juniper example +//! +//! Juniper is a graphql framework implemetation for rust +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate juniper; +extern crate futures; +extern crate actix; +extern crate actix_web; +extern crate env_logger; + +use actix_web::*; +use juniper::http::graphiql::graphiql_source; +use juniper::http::GraphQLRequest; + +use futures::future::Future; + +mod schema; + +use schema::Schema; +use schema::create_schema; + +struct State { + schema: Schema, +} + +fn graphiql(_req: HttpRequest) -> Result { + let html = graphiql_source("http://localhost:8080/graphql"); + Ok(HttpResponse::build(StatusCode::OK) + .content_type("text/html; charset=utf-8") + .body(html).unwrap()) +} + +fn graphql(req: HttpRequest) -> Box> { + req.json() + .from_err() + .and_then(move |val: GraphQLRequest| { + let response = val.execute(&req.state().schema, &()); + Ok(httpcodes::HTTPOk.build().json(response)?) + }) + .responder() +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("juniper-example"); + + // Start http server + let _addr = HttpServer::new(move || { + Application::with_state(State{schema: create_schema() }) + // enable logger + .middleware(middleware::Logger::default()) + .resource("/graphql", |r| r.method(Method::POST).a(graphql)) + .resource("/graphiql", |r| r.method(Method::GET).f(graphiql))}) + .bind("127.0.0.1:8080").unwrap() + .start(); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} diff --git a/examples/juniper/src/schema.rs b/examples/juniper/src/schema.rs new file mode 100644 index 000000000..2b4cf3042 --- /dev/null +++ b/examples/juniper/src/schema.rs @@ -0,0 +1,58 @@ +use juniper::FieldResult; +use juniper::RootNode; + +#[derive(GraphQLEnum)] +enum Episode { + NewHope, + Empire, + Jedi, +} + +#[derive(GraphQLObject)] +#[graphql(description = "A humanoid creature in the Star Wars universe")] +struct Human { + id: String, + name: String, + appears_in: Vec, + home_planet: String, +} + +#[derive(GraphQLInputObject)] +#[graphql(description = "A humanoid creature in the Star Wars universe")] +struct NewHuman { + name: String, + appears_in: Vec, + home_planet: String, +} + +pub struct QueryRoot; + +graphql_object!(QueryRoot: () |&self| { + field human(&executor, id: String) -> FieldResult { + Ok(Human{ + id: "1234".to_owned(), + name: "Luke".to_owned(), + appears_in: vec![Episode::NewHope], + home_planet: "Mars".to_owned(), + }) + } +}); + +pub struct MutationRoot; + +graphql_object!(MutationRoot: () |&self| { + field createHuman(&executor, new_human: NewHuman) -> FieldResult { + Ok(Human{ + id: "1234".to_owned(), + name: new_human.name, + appears_in: new_human.appears_in, + home_planet: new_human.home_planet, + }) + } +}); + +pub type Schema = RootNode<'static, QueryRoot, MutationRoot>; + +pub fn create_schema() -> Schema { + Schema::new(QueryRoot {}, MutationRoot {}) +} From 3243d807ddb2705cf08eee34f803fc8083d46038 Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Fri, 23 Feb 2018 20:21:43 +0530 Subject: [PATCH 02/10] add juniper-websocket example --- examples/juniper-websocket/Cargo.toml | 23 ++++ examples/juniper-websocket/README.md | 25 ++++ examples/juniper-websocket/src/client.rs | 113 ++++++++++++++++++ examples/juniper-websocket/src/main.rs | 145 +++++++++++++++++++++++ examples/juniper-websocket/src/schema.rs | 58 +++++++++ 5 files changed, 364 insertions(+) create mode 100644 examples/juniper-websocket/Cargo.toml create mode 100644 examples/juniper-websocket/README.md create mode 100644 examples/juniper-websocket/src/client.rs create mode 100644 examples/juniper-websocket/src/main.rs create mode 100644 examples/juniper-websocket/src/schema.rs diff --git a/examples/juniper-websocket/Cargo.toml b/examples/juniper-websocket/Cargo.toml new file mode 100644 index 000000000..e767111ed --- /dev/null +++ b/examples/juniper-websocket/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "juniper-websocket" +version = "0.1.0" +authors = ["Nikolay Kim "] +workspace = "../.." + +[[bin]] +name = "server" +path = "src/main.rs" + +[[bin]] +name = "client" +path = "src/client.rs" + +[dependencies] +env_logger = "*" +futures = "0.1" +tokio-core = "0.1" +actix = "0.5" +actix-web = { path="../../" } +juniper = "0.9.2" +lazy_static = "1.0" +serde_json = "1.0" diff --git a/examples/juniper-websocket/README.md b/examples/juniper-websocket/README.md new file mode 100644 index 000000000..3c0951782 --- /dev/null +++ b/examples/juniper-websocket/README.md @@ -0,0 +1,25 @@ +# juniper-websockect + +Simple echo websocket server which handles graphql requests. + +## Usage + +### server + +```bash +cd actix-web/examples/juniper-websocket +cargo run --bin server +# Started http server: 127.0.0.1:8080 +``` + +### web client + +- [http://localhost:8080/graphiql](http://localhost:8080/graphiql) + +### rust client + +```bash +cd actix-web/examples/juniper-websocket +cargo run --bin client +# Started http server: 127.0.0.1:8080 +``` \ No newline at end of file diff --git a/examples/juniper-websocket/src/client.rs b/examples/juniper-websocket/src/client.rs new file mode 100644 index 000000000..81ad11d23 --- /dev/null +++ b/examples/juniper-websocket/src/client.rs @@ -0,0 +1,113 @@ +//! Simple websocket client. + +#![allow(unused_variables)] +extern crate actix; +extern crate actix_web; +extern crate env_logger; +extern crate futures; +extern crate tokio_core; + +use std::{io, thread}; +use std::time::Duration; + +use actix::*; +use futures::Future; +use actix_web::ws::{Message, WsClientError, WsClient, WsClientWriter}; + + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("ws-example"); + + Arbiter::handle().spawn( + WsClient::new("http://127.0.0.1:8080/ws/") + .connect().unwrap() + .map_err(|e| { + println!("Error: {}", e); + () + }) + .map(|(reader, writer)| { + let addr: Addr = ChatClient::create(|ctx| { + ChatClient::add_stream(reader, ctx); + ChatClient(writer) + }); + + // start console loop + thread::spawn(move|| { + loop { + let mut cmd = String::new(); + if io::stdin().read_line(&mut cmd).is_err() { + println!("error"); + return + } + addr.do_send(ClientCommand(cmd)); + } + }); + + () + }) + ); + + let _ = sys.run(); +} + + +struct ChatClient(WsClientWriter); + +#[derive(Message)] +struct ClientCommand(String); + +impl Actor for ChatClient { + type Context = Context; + + fn started(&mut self, ctx: &mut Context) { + // start heartbeats otherwise server will disconnect after 10 seconds + self.hb(ctx) + } + + fn stopped(&mut self, _: &mut Context) { + println!("Disconnected"); + + // Stop application on disconnect + Arbiter::system().do_send(actix::msgs::SystemExit(0)); + } +} + +impl ChatClient { + fn hb(&self, ctx: &mut Context) { + ctx.run_later(Duration::new(1, 0), |act, ctx| { + act.0.text(r#"{"operationName":"K","query":"query K { human(id: \"213\") { id name homePlanet } }","variables":"{}"}"#); + act.hb(ctx); + }); + } +} + +/// Handle stdin commands +impl Handler for ChatClient { + type Result = (); + + fn handle(&mut self, msg: ClientCommand, ctx: &mut Context) { + self.0.text(msg.0.as_str()) + } +} + +/// Handle server websocket messages +impl StreamHandler for ChatClient { + + fn handle(&mut self, msg: Message, ctx: &mut Context) { + match msg { + Message::Text(txt) => println!("Server: {:?}", txt), + _ => () + } + } + + fn started(&mut self, ctx: &mut Context) { + println!("Connected"); + } + + fn finished(&mut self, ctx: &mut Context) { + println!("Server disconnected"); + ctx.stop() + } +} diff --git a/examples/juniper-websocket/src/main.rs b/examples/juniper-websocket/src/main.rs new file mode 100644 index 000000000..760470897 --- /dev/null +++ b/examples/juniper-websocket/src/main.rs @@ -0,0 +1,145 @@ +//! Simple echo websocket server. +//! Open `http://localhost:8080/ws/index.html` in browser +//! or [python console client](https://github.com/actix/actix-web/blob/master/examples/websocket-client.py) +//! could be used for testing. + +#![allow(unused_variables)] +extern crate actix; +extern crate actix_web; +extern crate env_logger; +#[macro_use] +extern crate juniper; +#[macro_use] +extern crate lazy_static; +extern crate serde_json; + +use actix::*; +use actix_web::*; +use juniper::http::GraphQLRequest; + +mod schema; + +use schema::Schema; +use schema::create_schema; + +lazy_static! { + static ref SCHEMA: Schema = create_schema(); +} + +fn graphiql(_req: HttpRequest) -> Result { + let html = r#" + + + + GraphQL + + + + +
+ + + + + + + + "#; + Ok(HttpResponse::build(StatusCode::OK) + .content_type("text/html; charset=utf-8") + .body(html) + .unwrap()) +} + +/// do websocket handshake and start `MyWebSocket` actor +fn ws_index(r: HttpRequest) -> Result { + ws::start(r, MyWebSocket) +} + +/// websocket connection is long running connection, it easier +/// to handle with an actor +struct MyWebSocket; + +impl Actor for MyWebSocket { + type Context = ws::WebsocketContext; +} + +/// Handler for `ws::Message` +impl Handler for MyWebSocket { + type Result = (); + + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { + match msg { + ws::Message::Ping(msg) => ctx.pong(&msg), + ws::Message::Text(text) => { + println!("server req: {}", text); + let req: GraphQLRequest = serde_json::from_str(&text).unwrap(); + let res = req.execute(&SCHEMA, &()); + let res_text = serde_json::to_string(&res).unwrap(); + ctx.text(res_text) + } + ws::Message::Binary(bin) => ctx.binary(bin), + ws::Message::Closed | ws::Message::Error => { + ctx.stop(); + } + _ => (), + } + } +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("ws-example"); + let schema = create_schema(); + + let _addr = HttpServer::new( + || Application::new() + // enable logger + .middleware(middleware::Logger::default()) + // websocket route + .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) + .resource("/graphiql", |r| r.method(Method::GET).f(graphiql))) + // start http server on 127.0.0.1:8080 + .bind("127.0.0.1:8080").unwrap() + .start(); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} diff --git a/examples/juniper-websocket/src/schema.rs b/examples/juniper-websocket/src/schema.rs new file mode 100644 index 000000000..2b4cf3042 --- /dev/null +++ b/examples/juniper-websocket/src/schema.rs @@ -0,0 +1,58 @@ +use juniper::FieldResult; +use juniper::RootNode; + +#[derive(GraphQLEnum)] +enum Episode { + NewHope, + Empire, + Jedi, +} + +#[derive(GraphQLObject)] +#[graphql(description = "A humanoid creature in the Star Wars universe")] +struct Human { + id: String, + name: String, + appears_in: Vec, + home_planet: String, +} + +#[derive(GraphQLInputObject)] +#[graphql(description = "A humanoid creature in the Star Wars universe")] +struct NewHuman { + name: String, + appears_in: Vec, + home_planet: String, +} + +pub struct QueryRoot; + +graphql_object!(QueryRoot: () |&self| { + field human(&executor, id: String) -> FieldResult { + Ok(Human{ + id: "1234".to_owned(), + name: "Luke".to_owned(), + appears_in: vec![Episode::NewHope], + home_planet: "Mars".to_owned(), + }) + } +}); + +pub struct MutationRoot; + +graphql_object!(MutationRoot: () |&self| { + field createHuman(&executor, new_human: NewHuman) -> FieldResult { + Ok(Human{ + id: "1234".to_owned(), + name: new_human.name, + appears_in: new_human.appears_in, + home_planet: new_human.home_planet, + }) + } +}); + +pub type Schema = RootNode<'static, QueryRoot, MutationRoot>; + +pub fn create_schema() -> Schema { + Schema::new(QueryRoot {}, MutationRoot {}) +} From e2acb0030a0d05fa7ed09634bf60422546f9fca3 Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Mon, 26 Feb 2018 21:30:23 +0530 Subject: [PATCH 03/10] add juniper-websocket --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 47cc2301b..4587e11f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,7 @@ members = [ "examples/basics", "examples/diesel", "examples/juniper", + "examples/juniper-websocket", "examples/r2d2", "examples/json", "examples/hello-world", From 46a9c715217bf9ff64f111ad7c0a387dfa30ebcf Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Tue, 27 Feb 2018 22:02:56 +0530 Subject: [PATCH 04/10] improve juniper example --- examples/juniper/Cargo.toml | 1 + examples/juniper/src/main.rs | 55 ++++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/examples/juniper/Cargo.toml b/examples/juniper/Cargo.toml index d66edc0c4..ffcd6616e 100644 --- a/examples/juniper/Cargo.toml +++ b/examples/juniper/Cargo.toml @@ -15,3 +15,4 @@ serde_json = "1.0" serde_derive = "1.0" juniper = "0.9.2" +lazy_static = "1.0" diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs index b5c620f1b..ba9c6bf00 100644 --- a/examples/juniper/src/main.rs +++ b/examples/juniper/src/main.rs @@ -1,15 +1,20 @@ //! Actix web juniper example //! -//! Juniper is a graphql framework implemetation for rust +//! A simple example integrating juniper in actix-web extern crate serde; extern crate serde_json; #[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate lazy_static; +#[macro_use] extern crate juniper; extern crate futures; extern crate actix; extern crate actix_web; extern crate env_logger; +use actix::*; use actix_web::*; use juniper::http::graphiql::graphiql_source; use juniper::http::GraphQLRequest; @@ -21,8 +26,35 @@ mod schema; use schema::Schema; use schema::create_schema; +lazy_static! { + static ref SCHEMA: Schema = create_schema(); +} + struct State { - schema: Schema, + executor: Addr, +} + +#[derive(Serialize, Deserialize)] +pub struct GraphQLData(GraphQLRequest); + +impl Message for GraphQLData { + type Result = Result; +} + +pub struct GraphQLExecutor; + +impl Actor for GraphQLExecutor { + type Context = SyncContext; +} + +impl Handler for GraphQLExecutor { + type Result = Result; + + fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result { + let res = msg.0.execute(&SCHEMA, &()); + let res_text = serde_json::to_string(&res)?; + Ok(res_text) + } } fn graphiql(_req: HttpRequest) -> Result { @@ -33,11 +65,18 @@ fn graphiql(_req: HttpRequest) -> Result { } fn graphql(req: HttpRequest) -> Box> { + req.json() .from_err() - .and_then(move |val: GraphQLRequest| { - let response = val.execute(&req.state().schema, &()); - Ok(httpcodes::HTTPOk.build().json(response)?) + .and_then(move |val: GraphQLData| { + req.state().executor.send(val) + .from_err() + .and_then(|res| { + match res { + Ok(user) => Ok(httpcodes::HTTPOk.build().body(user)?), + Err(_) => Ok(httpcodes::HTTPInternalServerError.into()) + } + }) }) .responder() } @@ -47,9 +86,13 @@ fn main() { let _ = env_logger::init(); let sys = actix::System::new("juniper-example"); + let addr = SyncArbiter::start(3, || { + GraphQLExecutor{} + }); + // Start http server let _addr = HttpServer::new(move || { - Application::with_state(State{schema: create_schema() }) + Application::with_state(State{executor: addr.clone()}) // enable logger .middleware(middleware::Logger::default()) .resource("/graphql", |r| r.method(Method::POST).a(graphql)) From bd9536cf662ba06ebeb8072a4f2bd6f3bb4d1be1 Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Wed, 28 Feb 2018 10:16:39 +0530 Subject: [PATCH 05/10] make schema belong to actor --- examples/juniper/Cargo.toml | 3 +-- examples/juniper/src/main.rs | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/juniper/Cargo.toml b/examples/juniper/Cargo.toml index ffcd6616e..9e52b0a83 100644 --- a/examples/juniper/Cargo.toml +++ b/examples/juniper/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "juniper-example" version = "0.1.0" -authors = ["Nikolay Kim "] +authors = ["pyros2097 "] workspace = "../.." [dependencies] @@ -15,4 +15,3 @@ serde_json = "1.0" serde_derive = "1.0" juniper = "0.9.2" -lazy_static = "1.0" diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs index ba9c6bf00..91441f4ba 100644 --- a/examples/juniper/src/main.rs +++ b/examples/juniper/src/main.rs @@ -6,8 +6,6 @@ extern crate serde_json; #[macro_use] extern crate serde_derive; #[macro_use] -extern crate lazy_static; -#[macro_use] extern crate juniper; extern crate futures; extern crate actix; @@ -26,10 +24,6 @@ mod schema; use schema::Schema; use schema::create_schema; -lazy_static! { - static ref SCHEMA: Schema = create_schema(); -} - struct State { executor: Addr, } @@ -41,7 +35,17 @@ impl Message for GraphQLData { type Result = Result; } -pub struct GraphQLExecutor; +pub struct GraphQLExecutor { + schema: std::sync::Arc +} + +impl GraphQLExecutor { + fn new(schema: std::sync::Arc) -> GraphQLExecutor { + GraphQLExecutor { + schema: schema, + } + } +} impl Actor for GraphQLExecutor { type Context = SyncContext; @@ -51,7 +55,7 @@ impl Handler for GraphQLExecutor { type Result = Result; fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result { - let res = msg.0.execute(&SCHEMA, &()); + let res = msg.0.execute(&self.schema, &()); let res_text = serde_json::to_string(&res)?; Ok(res_text) } @@ -86,8 +90,9 @@ fn main() { let _ = env_logger::init(); let sys = actix::System::new("juniper-example"); - let addr = SyncArbiter::start(3, || { - GraphQLExecutor{} + let schema = std::sync::Arc::new(create_schema()); + let addr = SyncArbiter::start(3, move || { + GraphQLExecutor::new(schema.clone()) }); // Start http server From 435bafc336751a02424825e494761a91ea970252 Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Fri, 23 Feb 2018 14:05:34 +0530 Subject: [PATCH 06/10] add juniper example --- Cargo.toml | 1 + examples/juniper/Cargo.toml | 17 ++++++++++ examples/juniper/README.md | 15 ++++++++ examples/juniper/src/main.rs | 62 ++++++++++++++++++++++++++++++++++ examples/juniper/src/schema.rs | 58 +++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 examples/juniper/Cargo.toml create mode 100644 examples/juniper/README.md create mode 100644 examples/juniper/src/main.rs create mode 100644 examples/juniper/src/schema.rs diff --git a/Cargo.toml b/Cargo.toml index e66eb42d6..630170c68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,7 @@ members = [ "./", "examples/basics", "examples/diesel", + "examples/juniper", "examples/r2d2", "examples/json", "examples/hello-world", diff --git a/examples/juniper/Cargo.toml b/examples/juniper/Cargo.toml new file mode 100644 index 000000000..d66edc0c4 --- /dev/null +++ b/examples/juniper/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "juniper-example" +version = "0.1.0" +authors = ["Nikolay Kim "] +workspace = "../.." + +[dependencies] +env_logger = "0.5" +actix = "0.5" +actix-web = { path = "../../" } + +futures = "0.1" +serde = "1.0" +serde_json = "1.0" +serde_derive = "1.0" + +juniper = "0.9.2" diff --git a/examples/juniper/README.md b/examples/juniper/README.md new file mode 100644 index 000000000..2ac0eac4e --- /dev/null +++ b/examples/juniper/README.md @@ -0,0 +1,15 @@ +# juniper + +Juniper integration for Actix web + +### server + +```bash +cd actix-web/examples/juniper +cargo run (or ``cargo watch -x run``) +# Started http server: 127.0.0.1:8080 +``` + +### web client + +[http://127.0.0.1:8080/graphiql](http://127.0.0.1:8080/graphiql) diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs new file mode 100644 index 000000000..b5c620f1b --- /dev/null +++ b/examples/juniper/src/main.rs @@ -0,0 +1,62 @@ +//! Actix web juniper example +//! +//! Juniper is a graphql framework implemetation for rust +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate juniper; +extern crate futures; +extern crate actix; +extern crate actix_web; +extern crate env_logger; + +use actix_web::*; +use juniper::http::graphiql::graphiql_source; +use juniper::http::GraphQLRequest; + +use futures::future::Future; + +mod schema; + +use schema::Schema; +use schema::create_schema; + +struct State { + schema: Schema, +} + +fn graphiql(_req: HttpRequest) -> Result { + let html = graphiql_source("http://localhost:8080/graphql"); + Ok(HttpResponse::build(StatusCode::OK) + .content_type("text/html; charset=utf-8") + .body(html).unwrap()) +} + +fn graphql(req: HttpRequest) -> Box> { + req.json() + .from_err() + .and_then(move |val: GraphQLRequest| { + let response = val.execute(&req.state().schema, &()); + Ok(httpcodes::HTTPOk.build().json(response)?) + }) + .responder() +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("juniper-example"); + + // Start http server + let _addr = HttpServer::new(move || { + Application::with_state(State{schema: create_schema() }) + // enable logger + .middleware(middleware::Logger::default()) + .resource("/graphql", |r| r.method(Method::POST).a(graphql)) + .resource("/graphiql", |r| r.method(Method::GET).f(graphiql))}) + .bind("127.0.0.1:8080").unwrap() + .start(); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} diff --git a/examples/juniper/src/schema.rs b/examples/juniper/src/schema.rs new file mode 100644 index 000000000..2b4cf3042 --- /dev/null +++ b/examples/juniper/src/schema.rs @@ -0,0 +1,58 @@ +use juniper::FieldResult; +use juniper::RootNode; + +#[derive(GraphQLEnum)] +enum Episode { + NewHope, + Empire, + Jedi, +} + +#[derive(GraphQLObject)] +#[graphql(description = "A humanoid creature in the Star Wars universe")] +struct Human { + id: String, + name: String, + appears_in: Vec, + home_planet: String, +} + +#[derive(GraphQLInputObject)] +#[graphql(description = "A humanoid creature in the Star Wars universe")] +struct NewHuman { + name: String, + appears_in: Vec, + home_planet: String, +} + +pub struct QueryRoot; + +graphql_object!(QueryRoot: () |&self| { + field human(&executor, id: String) -> FieldResult { + Ok(Human{ + id: "1234".to_owned(), + name: "Luke".to_owned(), + appears_in: vec![Episode::NewHope], + home_planet: "Mars".to_owned(), + }) + } +}); + +pub struct MutationRoot; + +graphql_object!(MutationRoot: () |&self| { + field createHuman(&executor, new_human: NewHuman) -> FieldResult { + Ok(Human{ + id: "1234".to_owned(), + name: new_human.name, + appears_in: new_human.appears_in, + home_planet: new_human.home_planet, + }) + } +}); + +pub type Schema = RootNode<'static, QueryRoot, MutationRoot>; + +pub fn create_schema() -> Schema { + Schema::new(QueryRoot {}, MutationRoot {}) +} From ae53ae7979e0c5a3f02ca92c87bad8505a4565f7 Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Fri, 23 Feb 2018 20:21:43 +0530 Subject: [PATCH 07/10] add juniper-websocket example --- examples/juniper-websocket/Cargo.toml | 23 ++++ examples/juniper-websocket/README.md | 25 ++++ examples/juniper-websocket/src/client.rs | 113 ++++++++++++++++++ examples/juniper-websocket/src/main.rs | 145 +++++++++++++++++++++++ examples/juniper-websocket/src/schema.rs | 58 +++++++++ 5 files changed, 364 insertions(+) create mode 100644 examples/juniper-websocket/Cargo.toml create mode 100644 examples/juniper-websocket/README.md create mode 100644 examples/juniper-websocket/src/client.rs create mode 100644 examples/juniper-websocket/src/main.rs create mode 100644 examples/juniper-websocket/src/schema.rs diff --git a/examples/juniper-websocket/Cargo.toml b/examples/juniper-websocket/Cargo.toml new file mode 100644 index 000000000..e767111ed --- /dev/null +++ b/examples/juniper-websocket/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "juniper-websocket" +version = "0.1.0" +authors = ["Nikolay Kim "] +workspace = "../.." + +[[bin]] +name = "server" +path = "src/main.rs" + +[[bin]] +name = "client" +path = "src/client.rs" + +[dependencies] +env_logger = "*" +futures = "0.1" +tokio-core = "0.1" +actix = "0.5" +actix-web = { path="../../" } +juniper = "0.9.2" +lazy_static = "1.0" +serde_json = "1.0" diff --git a/examples/juniper-websocket/README.md b/examples/juniper-websocket/README.md new file mode 100644 index 000000000..3c0951782 --- /dev/null +++ b/examples/juniper-websocket/README.md @@ -0,0 +1,25 @@ +# juniper-websockect + +Simple echo websocket server which handles graphql requests. + +## Usage + +### server + +```bash +cd actix-web/examples/juniper-websocket +cargo run --bin server +# Started http server: 127.0.0.1:8080 +``` + +### web client + +- [http://localhost:8080/graphiql](http://localhost:8080/graphiql) + +### rust client + +```bash +cd actix-web/examples/juniper-websocket +cargo run --bin client +# Started http server: 127.0.0.1:8080 +``` \ No newline at end of file diff --git a/examples/juniper-websocket/src/client.rs b/examples/juniper-websocket/src/client.rs new file mode 100644 index 000000000..81ad11d23 --- /dev/null +++ b/examples/juniper-websocket/src/client.rs @@ -0,0 +1,113 @@ +//! Simple websocket client. + +#![allow(unused_variables)] +extern crate actix; +extern crate actix_web; +extern crate env_logger; +extern crate futures; +extern crate tokio_core; + +use std::{io, thread}; +use std::time::Duration; + +use actix::*; +use futures::Future; +use actix_web::ws::{Message, WsClientError, WsClient, WsClientWriter}; + + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("ws-example"); + + Arbiter::handle().spawn( + WsClient::new("http://127.0.0.1:8080/ws/") + .connect().unwrap() + .map_err(|e| { + println!("Error: {}", e); + () + }) + .map(|(reader, writer)| { + let addr: Addr = ChatClient::create(|ctx| { + ChatClient::add_stream(reader, ctx); + ChatClient(writer) + }); + + // start console loop + thread::spawn(move|| { + loop { + let mut cmd = String::new(); + if io::stdin().read_line(&mut cmd).is_err() { + println!("error"); + return + } + addr.do_send(ClientCommand(cmd)); + } + }); + + () + }) + ); + + let _ = sys.run(); +} + + +struct ChatClient(WsClientWriter); + +#[derive(Message)] +struct ClientCommand(String); + +impl Actor for ChatClient { + type Context = Context; + + fn started(&mut self, ctx: &mut Context) { + // start heartbeats otherwise server will disconnect after 10 seconds + self.hb(ctx) + } + + fn stopped(&mut self, _: &mut Context) { + println!("Disconnected"); + + // Stop application on disconnect + Arbiter::system().do_send(actix::msgs::SystemExit(0)); + } +} + +impl ChatClient { + fn hb(&self, ctx: &mut Context) { + ctx.run_later(Duration::new(1, 0), |act, ctx| { + act.0.text(r#"{"operationName":"K","query":"query K { human(id: \"213\") { id name homePlanet } }","variables":"{}"}"#); + act.hb(ctx); + }); + } +} + +/// Handle stdin commands +impl Handler for ChatClient { + type Result = (); + + fn handle(&mut self, msg: ClientCommand, ctx: &mut Context) { + self.0.text(msg.0.as_str()) + } +} + +/// Handle server websocket messages +impl StreamHandler for ChatClient { + + fn handle(&mut self, msg: Message, ctx: &mut Context) { + match msg { + Message::Text(txt) => println!("Server: {:?}", txt), + _ => () + } + } + + fn started(&mut self, ctx: &mut Context) { + println!("Connected"); + } + + fn finished(&mut self, ctx: &mut Context) { + println!("Server disconnected"); + ctx.stop() + } +} diff --git a/examples/juniper-websocket/src/main.rs b/examples/juniper-websocket/src/main.rs new file mode 100644 index 000000000..760470897 --- /dev/null +++ b/examples/juniper-websocket/src/main.rs @@ -0,0 +1,145 @@ +//! Simple echo websocket server. +//! Open `http://localhost:8080/ws/index.html` in browser +//! or [python console client](https://github.com/actix/actix-web/blob/master/examples/websocket-client.py) +//! could be used for testing. + +#![allow(unused_variables)] +extern crate actix; +extern crate actix_web; +extern crate env_logger; +#[macro_use] +extern crate juniper; +#[macro_use] +extern crate lazy_static; +extern crate serde_json; + +use actix::*; +use actix_web::*; +use juniper::http::GraphQLRequest; + +mod schema; + +use schema::Schema; +use schema::create_schema; + +lazy_static! { + static ref SCHEMA: Schema = create_schema(); +} + +fn graphiql(_req: HttpRequest) -> Result { + let html = r#" + + + + GraphQL + + + + +
+ + + + + + + + "#; + Ok(HttpResponse::build(StatusCode::OK) + .content_type("text/html; charset=utf-8") + .body(html) + .unwrap()) +} + +/// do websocket handshake and start `MyWebSocket` actor +fn ws_index(r: HttpRequest) -> Result { + ws::start(r, MyWebSocket) +} + +/// websocket connection is long running connection, it easier +/// to handle with an actor +struct MyWebSocket; + +impl Actor for MyWebSocket { + type Context = ws::WebsocketContext; +} + +/// Handler for `ws::Message` +impl Handler for MyWebSocket { + type Result = (); + + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { + match msg { + ws::Message::Ping(msg) => ctx.pong(&msg), + ws::Message::Text(text) => { + println!("server req: {}", text); + let req: GraphQLRequest = serde_json::from_str(&text).unwrap(); + let res = req.execute(&SCHEMA, &()); + let res_text = serde_json::to_string(&res).unwrap(); + ctx.text(res_text) + } + ws::Message::Binary(bin) => ctx.binary(bin), + ws::Message::Closed | ws::Message::Error => { + ctx.stop(); + } + _ => (), + } + } +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("ws-example"); + let schema = create_schema(); + + let _addr = HttpServer::new( + || Application::new() + // enable logger + .middleware(middleware::Logger::default()) + // websocket route + .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) + .resource("/graphiql", |r| r.method(Method::GET).f(graphiql))) + // start http server on 127.0.0.1:8080 + .bind("127.0.0.1:8080").unwrap() + .start(); + + println!("Started http server: 127.0.0.1:8080"); + let _ = sys.run(); +} diff --git a/examples/juniper-websocket/src/schema.rs b/examples/juniper-websocket/src/schema.rs new file mode 100644 index 000000000..2b4cf3042 --- /dev/null +++ b/examples/juniper-websocket/src/schema.rs @@ -0,0 +1,58 @@ +use juniper::FieldResult; +use juniper::RootNode; + +#[derive(GraphQLEnum)] +enum Episode { + NewHope, + Empire, + Jedi, +} + +#[derive(GraphQLObject)] +#[graphql(description = "A humanoid creature in the Star Wars universe")] +struct Human { + id: String, + name: String, + appears_in: Vec, + home_planet: String, +} + +#[derive(GraphQLInputObject)] +#[graphql(description = "A humanoid creature in the Star Wars universe")] +struct NewHuman { + name: String, + appears_in: Vec, + home_planet: String, +} + +pub struct QueryRoot; + +graphql_object!(QueryRoot: () |&self| { + field human(&executor, id: String) -> FieldResult { + Ok(Human{ + id: "1234".to_owned(), + name: "Luke".to_owned(), + appears_in: vec![Episode::NewHope], + home_planet: "Mars".to_owned(), + }) + } +}); + +pub struct MutationRoot; + +graphql_object!(MutationRoot: () |&self| { + field createHuman(&executor, new_human: NewHuman) -> FieldResult { + Ok(Human{ + id: "1234".to_owned(), + name: new_human.name, + appears_in: new_human.appears_in, + home_planet: new_human.home_planet, + }) + } +}); + +pub type Schema = RootNode<'static, QueryRoot, MutationRoot>; + +pub fn create_schema() -> Schema { + Schema::new(QueryRoot {}, MutationRoot {}) +} From 7c585fe533a925d5c30e350c922420157acd8c10 Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Mon, 26 Feb 2018 21:30:23 +0530 Subject: [PATCH 08/10] add juniper-websocket --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 630170c68..db4bfce65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,7 @@ members = [ "examples/basics", "examples/diesel", "examples/juniper", + "examples/juniper-websocket", "examples/r2d2", "examples/json", "examples/hello-world", From 7d5e3e98a57269a02e27e6aa1e840e65f5733a8e Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Tue, 27 Feb 2018 22:02:56 +0530 Subject: [PATCH 09/10] improve juniper example --- examples/juniper/Cargo.toml | 1 + examples/juniper/src/main.rs | 55 ++++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/examples/juniper/Cargo.toml b/examples/juniper/Cargo.toml index d66edc0c4..ffcd6616e 100644 --- a/examples/juniper/Cargo.toml +++ b/examples/juniper/Cargo.toml @@ -15,3 +15,4 @@ serde_json = "1.0" serde_derive = "1.0" juniper = "0.9.2" +lazy_static = "1.0" diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs index b5c620f1b..ba9c6bf00 100644 --- a/examples/juniper/src/main.rs +++ b/examples/juniper/src/main.rs @@ -1,15 +1,20 @@ //! Actix web juniper example //! -//! Juniper is a graphql framework implemetation for rust +//! A simple example integrating juniper in actix-web extern crate serde; extern crate serde_json; #[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate lazy_static; +#[macro_use] extern crate juniper; extern crate futures; extern crate actix; extern crate actix_web; extern crate env_logger; +use actix::*; use actix_web::*; use juniper::http::graphiql::graphiql_source; use juniper::http::GraphQLRequest; @@ -21,8 +26,35 @@ mod schema; use schema::Schema; use schema::create_schema; +lazy_static! { + static ref SCHEMA: Schema = create_schema(); +} + struct State { - schema: Schema, + executor: Addr, +} + +#[derive(Serialize, Deserialize)] +pub struct GraphQLData(GraphQLRequest); + +impl Message for GraphQLData { + type Result = Result; +} + +pub struct GraphQLExecutor; + +impl Actor for GraphQLExecutor { + type Context = SyncContext; +} + +impl Handler for GraphQLExecutor { + type Result = Result; + + fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result { + let res = msg.0.execute(&SCHEMA, &()); + let res_text = serde_json::to_string(&res)?; + Ok(res_text) + } } fn graphiql(_req: HttpRequest) -> Result { @@ -33,11 +65,18 @@ fn graphiql(_req: HttpRequest) -> Result { } fn graphql(req: HttpRequest) -> Box> { + req.json() .from_err() - .and_then(move |val: GraphQLRequest| { - let response = val.execute(&req.state().schema, &()); - Ok(httpcodes::HTTPOk.build().json(response)?) + .and_then(move |val: GraphQLData| { + req.state().executor.send(val) + .from_err() + .and_then(|res| { + match res { + Ok(user) => Ok(httpcodes::HTTPOk.build().body(user)?), + Err(_) => Ok(httpcodes::HTTPInternalServerError.into()) + } + }) }) .responder() } @@ -47,9 +86,13 @@ fn main() { let _ = env_logger::init(); let sys = actix::System::new("juniper-example"); + let addr = SyncArbiter::start(3, || { + GraphQLExecutor{} + }); + // Start http server let _addr = HttpServer::new(move || { - Application::with_state(State{schema: create_schema() }) + Application::with_state(State{executor: addr.clone()}) // enable logger .middleware(middleware::Logger::default()) .resource("/graphql", |r| r.method(Method::POST).a(graphql)) From c82539e86b103c7e50573d99d8118f10effb8011 Mon Sep 17 00:00:00 2001 From: pyros2097 Date: Wed, 28 Feb 2018 10:16:39 +0530 Subject: [PATCH 10/10] make schema belong to actor --- examples/juniper/Cargo.toml | 3 +-- examples/juniper/src/main.rs | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/juniper/Cargo.toml b/examples/juniper/Cargo.toml index ffcd6616e..9e52b0a83 100644 --- a/examples/juniper/Cargo.toml +++ b/examples/juniper/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "juniper-example" version = "0.1.0" -authors = ["Nikolay Kim "] +authors = ["pyros2097 "] workspace = "../.." [dependencies] @@ -15,4 +15,3 @@ serde_json = "1.0" serde_derive = "1.0" juniper = "0.9.2" -lazy_static = "1.0" diff --git a/examples/juniper/src/main.rs b/examples/juniper/src/main.rs index ba9c6bf00..91441f4ba 100644 --- a/examples/juniper/src/main.rs +++ b/examples/juniper/src/main.rs @@ -6,8 +6,6 @@ extern crate serde_json; #[macro_use] extern crate serde_derive; #[macro_use] -extern crate lazy_static; -#[macro_use] extern crate juniper; extern crate futures; extern crate actix; @@ -26,10 +24,6 @@ mod schema; use schema::Schema; use schema::create_schema; -lazy_static! { - static ref SCHEMA: Schema = create_schema(); -} - struct State { executor: Addr, } @@ -41,7 +35,17 @@ impl Message for GraphQLData { type Result = Result; } -pub struct GraphQLExecutor; +pub struct GraphQLExecutor { + schema: std::sync::Arc +} + +impl GraphQLExecutor { + fn new(schema: std::sync::Arc) -> GraphQLExecutor { + GraphQLExecutor { + schema: schema, + } + } +} impl Actor for GraphQLExecutor { type Context = SyncContext; @@ -51,7 +55,7 @@ impl Handler for GraphQLExecutor { type Result = Result; fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result { - let res = msg.0.execute(&SCHEMA, &()); + let res = msg.0.execute(&self.schema, &()); let res_text = serde_json::to_string(&res)?; Ok(res_text) } @@ -86,8 +90,9 @@ fn main() { let _ = env_logger::init(); let sys = actix::System::new("juniper-example"); - let addr = SyncArbiter::start(3, || { - GraphQLExecutor{} + let schema = std::sync::Arc::new(create_schema()); + let addr = SyncArbiter::start(3, move || { + GraphQLExecutor::new(schema.clone()) }); // Start http server