This repository has been archived by the owner on Feb 12, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Execution): better subscription model with example
- Loading branch information
Marek Magdziak
committed
Jun 18, 2017
1 parent
04dd8ab
commit 6c103c0
Showing
29 changed files
with
2,355 additions
and
70 deletions.
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
examples/GraphQLCore.GraphiQLExample/Middlewares/GraphQLWs/GraphQLInitHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using GraphQLCore.Type; | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.WebSockets; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace GraphQLCore.GraphiQLExample.Middlewares.GraphQLWs | ||
{ | ||
public class GraphQLInitHandler : IGraphQLWsHandler | ||
{ | ||
public async Task Handle(WebSocket socket, string clientId, IGraphQLSchema schema, WsInputObject input) | ||
{ | ||
var dataString = JsonConvert.SerializeObject( new | ||
{ | ||
type = "init_success" | ||
}); | ||
|
||
var resultBuffer = System.Text.Encoding.UTF8.GetBytes(dataString); | ||
|
||
await socket.SendAsync( | ||
new ArraySegment<byte>(resultBuffer), WebSocketMessageType.Text, true, CancellationToken.None); | ||
} | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
examples/GraphQLCore.GraphiQLExample/Middlewares/GraphQLWs/GraphQLSubscriptionEndHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using GraphQLCore.Type; | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.WebSockets; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace GraphQLCore.GraphiQLExample.Middlewares.GraphQLWs | ||
{ | ||
public class GraphQLSubscriptionEndHandler : IGraphQLWsHandler | ||
{ | ||
public async Task Handle(WebSocket socket, string clientId, IGraphQLSchema schema, WsInputObject input) | ||
{ | ||
await Task.Yield(); | ||
|
||
if (input.Id.HasValue) | ||
{ | ||
schema.Unsubscribe(clientId, input.Id.Value); | ||
} | ||
} | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
...ples/GraphQLCore.GraphiQLExample/Middlewares/GraphQLWs/GraphQLSubscriptionStartHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
using GraphQLCore.Exceptions; | ||
using GraphQLCore.Type; | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.WebSockets; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace GraphQLCore.GraphiQLExample.Middlewares.GraphQLWs | ||
{ | ||
public class GraphQLSubscriptionStartHandler : IGraphQLWsHandler | ||
{ | ||
public async Task Handle(WebSocket socket, string clientId, IGraphQLSchema schema, WsInputObject input) | ||
{ | ||
try | ||
{ | ||
await Subscribe(socket, clientId, schema, input); | ||
} | ||
catch (GraphQLValidationException ex) | ||
{ | ||
await SendResponseToGraphQLValidationException(socket, input.Id.Value, ex); | ||
} | ||
catch (GraphQLException ex) | ||
{ | ||
await SendResponseToGraphQLException(socket, input.Id.Value, ex); | ||
} | ||
catch (Exception ex) | ||
{ | ||
await SendReponseToException(socket, input.Id.Value, ex); | ||
} | ||
} | ||
|
||
private static async Task Subscribe(WebSocket socket, string clientId, IGraphQLSchema schema, WsInputObject input) | ||
{ | ||
var data = schema.Execute(input.Query, null, null, clientId, input.Id.Value); | ||
|
||
var dataString = JsonConvert.SerializeObject(new { id = input.Id, type = "subscription_success" }); | ||
var resultBuffer = System.Text.Encoding.UTF8.GetBytes(dataString); | ||
|
||
await socket.SendAsync( | ||
new ArraySegment<byte>(resultBuffer), WebSocketMessageType.Text, true, CancellationToken.None); | ||
} | ||
|
||
private static async Task SendResponseToGraphQLValidationException(WebSocket socket, int id, GraphQLValidationException ex) | ||
{ | ||
var dataString = JsonConvert.SerializeObject(new | ||
{ | ||
id, | ||
type = "subscription_fail", | ||
payload = new | ||
{ | ||
errors = ex.Errors | ||
} | ||
}); | ||
|
||
await SendResponse(socket, dataString); | ||
} | ||
|
||
private static async Task SendResponseToGraphQLException(WebSocket socket, int id, GraphQLException ex) | ||
{ | ||
var dataString = JsonConvert.SerializeObject(new | ||
{ | ||
id, | ||
type = "subscription_fail", | ||
payload = new | ||
{ | ||
errors = new dynamic[] { new { message = ex.Message + "\n" + ex.StackTrace } } | ||
} | ||
}); | ||
|
||
await SendResponse(socket, dataString); | ||
} | ||
|
||
private static async Task SendReponseToException(WebSocket socket, int id, Exception ex) | ||
{ | ||
var dataString = JsonConvert.SerializeObject( new | ||
{ | ||
id, | ||
type = "subscription_fail", | ||
payload = new | ||
{ | ||
errors = new dynamic[] { new { message = ex.Message + "\n" + ex.StackTrace } } | ||
} | ||
}); | ||
|
||
await SendResponse(socket, dataString); | ||
} | ||
|
||
private static async Task SendResponse(WebSocket socket, string dataString) | ||
{ | ||
var resultBuffer = System.Text.Encoding.UTF8.GetBytes(dataString); | ||
|
||
await socket.SendAsync( | ||
new ArraySegment<byte>(resultBuffer), WebSocketMessageType.Text, true, CancellationToken.None); | ||
} | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
examples/GraphQLCore.GraphiQLExample/Middlewares/GraphQLWs/IGraphQLWsHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using GraphQLCore.Type; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.WebSockets; | ||
using System.Threading.Tasks; | ||
|
||
namespace GraphQLCore.GraphiQLExample.Middlewares.GraphQLWs | ||
{ | ||
public interface IGraphQLWsHandler | ||
{ | ||
Task Handle(WebSocket socket, string clientId, IGraphQLSchema schema, WsInputObject input); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
examples/GraphQLCore.GraphiQLExample/Middlewares/GraphQLWs/WsInputObject.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace GraphQLCore.GraphiQLExample.Middlewares.GraphQLWs | ||
{ | ||
public class WsInputObject | ||
{ | ||
[JsonProperty(PropertyName = "type")] | ||
public string Type { get; set; } | ||
|
||
[JsonProperty(PropertyName = "query")] | ||
public string Query { get; set; } | ||
|
||
[JsonProperty(PropertyName = "id")] | ||
public int? Id { get; set; } | ||
} | ||
} |
156 changes: 156 additions & 0 deletions
156
examples/GraphQLCore.GraphiQLExample/Middlewares/GraphQLWsMiddleware.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
using GraphQLCore.Exceptions; | ||
using GraphQLCore.GraphiQLExample.Middlewares.GraphQLWs; | ||
using GraphQLCore.Type; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Http; | ||
using Newtonsoft.Json; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.WebSockets; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace GraphQLCore.GraphiQLExample.Middlewares | ||
{ | ||
public static class GraphQLWsMiddleware | ||
{ | ||
private static Dictionary<string, IGraphQLWsHandler> handlers = new Dictionary<string, IGraphQLWsHandler>() | ||
{ | ||
{ "init", new GraphQLInitHandler() }, | ||
{ "subscription_start", new GraphQLSubscriptionStartHandler() }, | ||
{ "subscription_end", new GraphQLSubscriptionEndHandler() } | ||
}; | ||
|
||
public static void AddGraphQLWs(this IApplicationBuilder app) | ||
{ | ||
app.Use(Middleware); | ||
} | ||
|
||
private static async Task Middleware(HttpContext context, Func<Task> next) | ||
{ | ||
if (context.Request.Path == "/graphql") | ||
{ | ||
if (context.WebSockets.IsWebSocketRequest) | ||
{ | ||
var webSocket = await context.WebSockets.AcceptWebSocketAsync(); | ||
|
||
await StartCommunication(context, webSocket); | ||
} | ||
else | ||
{ | ||
context.Response.StatusCode = 400; | ||
} | ||
} | ||
else | ||
{ | ||
await next(); | ||
} | ||
} | ||
|
||
private static async Task StartCommunication(HttpContext context, WebSocket webSocket) | ||
{ | ||
var clientId = GenerateClientId(); | ||
|
||
var onDataReceived = GetCallback(webSocket, clientId); | ||
|
||
var schema = GetSchema(context, onDataReceived); | ||
|
||
var result = await MainLoop(webSocket, clientId, schema); | ||
|
||
schema.Unsubscribe(clientId); | ||
schema.OnSubscriptionMessageReceived -= onDataReceived; | ||
|
||
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); | ||
} | ||
|
||
private static string GenerateClientId() | ||
{ | ||
return Guid.NewGuid().ToString(); | ||
} | ||
|
||
private static IGraphQLSchema GetSchema(HttpContext context, SubscriptionMessageReceived received) | ||
{ | ||
var schema = context.RequestServices.GetService(typeof(IGraphQLSchema)) as IGraphQLSchema; | ||
schema.OnSubscriptionMessageReceived += received; | ||
return schema; | ||
} | ||
|
||
private static async Task<WebSocketReceiveResult> MainLoop(WebSocket webSocket, string clientId, IGraphQLSchema schema) | ||
{ | ||
var buffer = new byte[1024 * 4]; | ||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); | ||
|
||
GetKeepAliveTask(webSocket, result); | ||
|
||
while (!result.CloseStatus.HasValue) | ||
{ | ||
var text = System.Text.Encoding.UTF8.GetString(buffer); | ||
var input = JsonConvert.DeserializeObject<WsInputObject>(text); | ||
|
||
if (handlers.ContainsKey(input.Type)) | ||
{ | ||
await handlers[input.Type].Handle(webSocket, clientId, schema, input); | ||
} | ||
|
||
buffer = new byte[1024 * 4]; | ||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static void GetKeepAliveTask(WebSocket webSocket, WebSocketReceiveResult result) | ||
{ | ||
var keepAliveTask = Task.Run(async () => | ||
{ | ||
await Task.Yield(); | ||
while (!result.CloseStatus.HasValue) | ||
{ | ||
await Task.Delay(1000); | ||
var dataString = JsonConvert.SerializeObject(new | ||
{ | ||
type = "keepalive" | ||
}); | ||
var resultBuffer = System.Text.Encoding.UTF8.GetBytes(dataString); | ||
await webSocket.SendAsync( | ||
new ArraySegment<byte>(resultBuffer), WebSocketMessageType.Text, true, CancellationToken.None); | ||
} | ||
}); | ||
} | ||
|
||
private static SubscriptionMessageReceived GetCallback(WebSocket webSocket, string clientId) | ||
{ | ||
return async (string msgClientId, int subscriptionId, dynamic subscriptionData) => | ||
{ | ||
try | ||
{ | ||
if (clientId == msgClientId) | ||
{ | ||
var ds = JsonConvert.SerializeObject(new | ||
{ | ||
id = subscriptionId, | ||
type = "subscription_data", | ||
payload = new | ||
{ | ||
data = subscriptionData | ||
} | ||
}); | ||
var db = System.Text.Encoding.UTF8.GetBytes(ds); | ||
await webSocket.SendAsync( | ||
new ArraySegment<byte>(db), WebSocketMessageType.Text, true, CancellationToken.None); | ||
} | ||
} | ||
catch (Exception ex) | ||
{ | ||
} | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.