Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from fanout/http-server-api
Http server api
- Loading branch information
Showing
9 changed files
with
456 additions
and
9 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/** | ||
* API Demo of adding WebSocketOverHttp support to any http.RequestListener function (e.g. (req, res) => void). | ||
* Almost all node.js web libraries support creating one of these from the underlying Application object. | ||
* In this example, we use zeit/micro, but you can do something similar with koa, express, raw node http, etc. | ||
*/ | ||
|
||
import { buildSchemaFromTypeDefinitions, PubSub } from "apollo-server"; | ||
import { ApolloServer } from "apollo-server-micro"; | ||
import * as http from "http"; | ||
import { run as microRun } from "micro"; | ||
import FanoutGraphqlApolloConfig, { | ||
FanoutGraphqlTypeDefs, | ||
} from "../FanoutGraphqlApolloConfig"; | ||
import EpcpPubSubMixin from "../graphql-epcp-pubsub/EpcpPubSubMixin"; | ||
import { MapSimpleTable } from "../SimpleTable"; | ||
import GraphqlWsOverWebSocketOverHttpRequestListener from "../subscriptions-transport-ws-over-http/GraphqlWsOverWebSocketOverHttpRequestListener"; | ||
|
||
// This is what you need to support EPCP Publishes (make sure it gets to your resolvers who call pubsub.publish) | ||
const pubsub = EpcpPubSubMixin({ | ||
grip: { | ||
url: process.env.GRIP_URL || "http://localhost:5561", | ||
}, | ||
// Build a schema from typedefs here but without resolvers (since they will need the resulting pubsub to publish to) | ||
schema: buildSchemaFromTypeDefinitions(FanoutGraphqlTypeDefs(true)), | ||
})(new PubSub()); | ||
|
||
const apolloServer = new ApolloServer( | ||
FanoutGraphqlApolloConfig({ | ||
pubsub, | ||
subscriptions: true, | ||
tables: { | ||
notes: MapSimpleTable(), | ||
}, | ||
}), | ||
); | ||
|
||
// This won't throw, but it also won't result in working WebSocket Subscriptions (when you create the subscription via gql api, a response comes back mentioning: | ||
// { "error": { "name": "TypeError", "message": "Cannot read property 'addListener' of undefined" } | ||
// But there is nothing useful on stderr of the server. | ||
// apolloServer.installSubscriptionHandlers(httpServer) | ||
|
||
// In micro 9.3.5, the default export of micro(handler) will return an http.RequestListener (after https://github.com/zeit/micro/pull/399). | ||
// As of this authoring, only 9.3.4 is out, which returns an http.Server. So we manually build the RequestListner here. | ||
// After 9.3.5, the following will work: | ||
// import micro from "micro" | ||
// const microRequestListener = micro(apolloServer.createHandler()) | ||
const microRequestListener: http.RequestListener = (req, res) => | ||
microRun(req, res, apolloServer.createHandler()); | ||
|
||
const httpServer = http.createServer( | ||
GraphqlWsOverWebSocketOverHttpRequestListener(microRequestListener), | ||
); | ||
|
||
const port = process.env.PORT || 57410; | ||
httpServer.listen(port, () => { | ||
console.log(`Server is now running on http://localhost:${port}`); | ||
}); |
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,50 @@ | ||
/** | ||
* API Demo of adding WebSocketOverHttp support patching any http.Server | ||
* Almost all node.js web libraries support creating one of these from the underlying Application object. | ||
* In this example, we use zeit/micro, but you can do something similar with koa, express, raw node http, etc. | ||
*/ | ||
|
||
import { buildSchemaFromTypeDefinitions, PubSub } from "apollo-server"; | ||
import { ApolloServer } from "apollo-server-micro"; | ||
import * as http from "http"; | ||
import micro from "micro"; | ||
import FanoutGraphqlApolloConfig, { | ||
FanoutGraphqlTypeDefs, | ||
} from "../FanoutGraphqlApolloConfig"; | ||
import EpcpPubSubMixin from "../graphql-epcp-pubsub/EpcpPubSubMixin"; | ||
import { MapSimpleTable } from "../SimpleTable"; | ||
import GraphqlWsOverWebSocketOverHttpSubscriptionHandlerInstaller from "../subscriptions-transport-ws-over-http/GraphqlWsOverWebSocketOverHttpSubscriptionHandlerInstaller"; | ||
|
||
// This is what you need to support EPCP Publishes (make sure it gets to your resolvers who call pubsub.publish) | ||
const pubsub = EpcpPubSubMixin({ | ||
grip: { | ||
url: process.env.GRIP_URL || "http://localhost:5561", | ||
}, | ||
// Build a schema from typedefs here but without resolvers (since they will need the resulting pubsub to publish to) | ||
schema: buildSchemaFromTypeDefinitions(FanoutGraphqlTypeDefs(true)), | ||
})(new PubSub()); | ||
|
||
const apolloServer = new ApolloServer( | ||
FanoutGraphqlApolloConfig({ | ||
pubsub, | ||
subscriptions: true, | ||
tables: { | ||
notes: MapSimpleTable(), | ||
}, | ||
}), | ||
); | ||
|
||
// Note: In micro 9.3.5 this will return an http.RequestListener instead (after https://github.com/zeit/micro/pull/399) | ||
// Provide it to http.createServer to create an http.Server | ||
const httpServer: http.Server = micro(apolloServer.createHandler()); | ||
|
||
// This won't throw, but it also won't result in working WebSocket Subscriptions (when you create the subscription via gql api, a response comes back mentioning: | ||
// { "error": { "name": "TypeError", "message": "Cannot read property 'addListener' of undefined" } | ||
// But there is nothing useful on stderr of the server. | ||
// apolloServer.installSubscriptionHandlers(httpServer) | ||
GraphqlWsOverWebSocketOverHttpSubscriptionHandlerInstaller()(httpServer); | ||
|
||
const port = process.env.PORT || 57410; | ||
httpServer.listen(port, () => { | ||
console.log(`Server is now running on http://localhost:${port}`); | ||
}); |
2 changes: 1 addition & 1 deletion
2
src/subscriptions-transport-ws-over-http/GraphqlWebSocketOverHttpConnectionListener.ts
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
2 changes: 1 addition & 1 deletion
2
src/subscriptions-transport-ws-over-http/GraphqlWsOverWebSocketOverHttpExpressMiddleware.ts
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
20 changes: 20 additions & 0 deletions
20
src/subscriptions-transport-ws-over-http/GraphqlWsOverWebSocketOverHttpRequestListener.ts
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 @@ | ||
import * as express from "express"; | ||
import * as http from "http"; | ||
import GraphqlWsOverWebSocketOverHttpExpressMiddleware from "./GraphqlWsOverWebSocketOverHttpExpressMiddleware"; | ||
|
||
/** | ||
* GraphqlWsOverWebSocketOverHttpRequestListener. | ||
* Given an http RequestListener, return a new one that will respond to incoming WebSocket-Over-Http requests that are graphql-ws | ||
* Subscriptions and accept the subscriptions. | ||
*/ | ||
export default ( | ||
originalRequestListener: http.RequestListener, | ||
): http.RequestListener => (req, res) => { | ||
const handleWebSocketOverHttpRequestHandler: http.RequestListener = express() | ||
.use(GraphqlWsOverWebSocketOverHttpExpressMiddleware()) | ||
.use((expressRequest, expressResponse) => { | ||
// It wasn't handled by GraphqlWsOverWebSocketOverHttpExpressMiddleware | ||
originalRequestListener(req, res); | ||
}); | ||
handleWebSocketOverHttpRequestHandler(req, res); | ||
}; |
61 changes: 61 additions & 0 deletions
61
...ions-transport-ws-over-http/GraphqlWsOverWebSocketOverHttpSubscriptionHandlerInstaller.ts
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,61 @@ | ||
import { EventEmitter } from "events"; | ||
import * as express from "express"; | ||
import * as http from "http"; | ||
import GraphqlWsOverWebSocketOverHttpExpressMiddleware from "./GraphqlWsOverWebSocketOverHttpExpressMiddleware"; | ||
|
||
/** | ||
* Create a function that will patch an http.Server instance such that it responds to incoming graphql-ws over WebSocket-Over-Http requests in a way that will allow all GraphQL Subscriptions to initiate. | ||
* If the incoming request is not of this specific kind, it will be handled however the http.Server normally would. | ||
*/ | ||
export default () => (httpServer: http.Server) => { | ||
interceptRequests(httpServer, (request, response, next) => { | ||
const handleWebSocketOverHttpRequestHandler: http.RequestListener = express() | ||
.use(GraphqlWsOverWebSocketOverHttpExpressMiddleware()) | ||
.use((expressRequest, expressResponse) => { | ||
// It wasn't handled by GraphqlWsOverWebSocketOverHttpExpressMiddleware | ||
next(); | ||
}); | ||
handleWebSocketOverHttpRequestHandler(request, response); | ||
}); | ||
}; | ||
|
||
type AnyFunction = (...args: any[]) => any; | ||
|
||
/** NodeJS.EventEmitter properties that do exist but are not documented and aren't on the TypeScript types */ | ||
interface IEventEmitterPrivates { | ||
/** Internal state holding refs to all listeners */ | ||
_events: Record<string, AnyFunction | AnyFunction[] | undefined>; | ||
} | ||
/** Use declaration merigng to add IEventEmitterPrivates to NodeJs.EventEmitters like http.Server used below */ | ||
declare module "events" { | ||
// EventEmitter | ||
// tslint:disable-next-line:interface-name no-empty-interface | ||
interface EventEmitter extends IEventEmitterPrivates {} | ||
} | ||
|
||
type RequestInterceptor = ( | ||
request: http.IncomingMessage, | ||
response: http.ServerResponse, | ||
next: () => void, | ||
) => void; | ||
|
||
/** Patch an httpServer to pass all incoming requests through an interceptor before doing what it would normally do */ | ||
function interceptRequests( | ||
httpServer: http.Server, | ||
intercept: RequestInterceptor, | ||
) { | ||
const originalRequestListeners = httpServer._events.request; | ||
httpServer._events.request = ( | ||
request: http.IncomingMessage, | ||
response: http.ServerResponse, | ||
) => { | ||
intercept(request, response, () => { | ||
const listeners = originalRequestListeners | ||
? Array.isArray(originalRequestListeners) | ||
? originalRequestListeners | ||
: [originalRequestListeners] | ||
: []; | ||
listeners.forEach(listener => listener(request, response)); | ||
}); | ||
}; | ||
} |
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 |
---|---|---|
|
@@ -22,7 +22,7 @@ | |
"rulesDirectory": [], | ||
"linterOptions": { | ||
"exclude": [ | ||
"node_modules/**", | ||
"node_modules/**" | ||
] | ||
} | ||
} |