forked from shurcooL/graphql
-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add more WebSocket options for subscription client (#126)
* add more WebSocket options for subscription client * add a more graphql-ws example
- Loading branch information
Showing
23 changed files
with
845 additions
and
88 deletions.
There are no files selected for viewing
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 |
---|---|---|
@@ -1,7 +1,6 @@ | ||
name: Unit tests | ||
|
||
on: | ||
pull_request: | ||
push: | ||
paths: | ||
- "**.go" | ||
|
@@ -16,13 +15,17 @@ jobs: | |
runs-on: ubuntu-20.04 | ||
permissions: | ||
pull-requests: write | ||
# Required: allow read access to the content for analysis. | ||
contents: read | ||
# Optional: Allow write access to checks to allow the action to annotate code in the PR. | ||
checks: write | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v3 | ||
- uses: actions/setup-go@v4 | ||
uses: actions/checkout@v4 | ||
- uses: actions/setup-go@v5 | ||
with: | ||
go-version: "1.20" | ||
- uses: actions/cache@v3 | ||
- uses: actions/cache@v4 | ||
with: | ||
path: | | ||
~/go/pkg/mod | ||
|
@@ -42,14 +45,22 @@ jobs: | |
run: | | ||
cd ./example/hasura | ||
docker-compose up -d | ||
- name: Lint | ||
uses: golangci/golangci-lint-action@v3 | ||
with: | ||
version: latest | ||
only-new-issues: true | ||
skip-cache: false | ||
- name: Run Go unit tests | ||
run: go test -v -race -timeout 3m -coverprofile=coverage.out ./... | ||
- name: Go coverage format | ||
if: ${{ github.event_name == 'pull_request' }} | ||
run: | | ||
go get github.com/boumenot/gocover-cobertura | ||
go install github.com/boumenot/gocover-cobertura | ||
gocover-cobertura < coverage.out > coverage.xml | ||
- name: Code Coverage Summary Report | ||
if: ${{ github.event_name == 'pull_request' }} | ||
uses: irongut/[email protected] | ||
with: | ||
filename: coverage.xml | ||
|
@@ -63,7 +74,7 @@ jobs: | |
thresholds: "60 80" | ||
- name: Add Coverage PR Comment | ||
uses: marocchino/sticky-pull-request-comment@v2 | ||
if: ${{ github.event_name == 'pull_request_target' }} | ||
if: ${{ github.event_name == 'pull_request' }} | ||
with: | ||
path: code-coverage-results.md | ||
- name: Dump docker logs on failure | ||
|
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,33 @@ | ||
# Subscription example with graphql-ws backwards compatibility | ||
|
||
The example demonstrates the subscription client with the native graphql-ws Node.js server, using [ws server usage with subscriptions-transport-ws backwards compatibility](https://the-guild.dev/graphql/ws/recipes#ws-server-usage-with-subscriptions-transport-ws-backwards-compatibility) and [custom auth handling](https://the-guild.dev/graphql/ws/recipes#server-usage-with-ws-and-custom-auth-handling) recipes. The client authenticates with the server via HTTP header. | ||
|
||
```go | ||
client := graphql.NewSubscriptionClient(serverEndpoint). | ||
WithWebSocketOptions(graphql.WebsocketOptions{ | ||
HTTPHeader: http.Header{ | ||
"Authorization": []string{"Bearer random-secret"}, | ||
}, | ||
}) | ||
``` | ||
|
||
## Get started | ||
|
||
### Server | ||
|
||
Requires Node.js and npm | ||
|
||
```bash | ||
cd server | ||
npm install | ||
npm start | ||
``` | ||
|
||
The server will be hosted on `localhost:4000`. | ||
|
||
### Client | ||
|
||
```bash | ||
go run ./client | ||
``` | ||
|
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,85 @@ | ||
// subscription is a test program currently being used for developing graphql package. | ||
// It performs queries against a local test GraphQL server instance. | ||
// | ||
// It's not meant to be a clean or readable example. But it's functional. | ||
// Better, actual examples will be created in the future. | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"log" | ||
"net/http" | ||
|
||
graphql "github.com/hasura/go-graphql-client" | ||
) | ||
|
||
func main() { | ||
protocol := graphql.GraphQLWS | ||
protocolArg := flag.String("protocol", "graphql-ws", "The protocol is used for the subscription") | ||
flag.Parse() | ||
|
||
if protocolArg != nil { | ||
switch *protocolArg { | ||
case "graphql-ws": | ||
case "": | ||
case "ws": | ||
protocol = graphql.SubscriptionsTransportWS | ||
default: | ||
panic("invalid protocol. Accept [ws, graphql-ws]") | ||
} | ||
} | ||
|
||
if err := startSubscription(protocol); err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
const serverEndpoint = "http://localhost:4000" | ||
|
||
func startSubscription(protocol graphql.SubscriptionProtocolType) error { | ||
log.Printf("start subscription with protocol: %s", protocol) | ||
client := graphql.NewSubscriptionClient(serverEndpoint). | ||
WithWebSocketOptions(graphql.WebsocketOptions{ | ||
HTTPHeader: http.Header{ | ||
"Authorization": []string{"Bearer random-secret"}, | ||
}, | ||
}). | ||
WithLog(log.Println). | ||
WithProtocol(protocol). | ||
WithoutLogTypes(graphql.GQLData, graphql.GQLConnectionKeepAlive). | ||
OnError(func(sc *graphql.SubscriptionClient, err error) error { | ||
log.Print("err", err) | ||
return err | ||
}) | ||
|
||
defer client.Close() | ||
|
||
/* | ||
subscription { | ||
greetings | ||
} | ||
*/ | ||
var sub struct { | ||
Greetings string `graphql:"greetings"` | ||
} | ||
|
||
_, err := client.Subscribe(sub, nil, func(data []byte, err error) error { | ||
|
||
if err != nil { | ||
log.Println(err) | ||
return nil | ||
} | ||
|
||
if data == nil { | ||
return nil | ||
} | ||
log.Printf("hello: %+v", string(data)) | ||
return nil | ||
}) | ||
|
||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
return client.Run() | ||
} |
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 @@ | ||
node_modules |
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,86 @@ | ||
// The example is copied from ws server usage with subscriptions-transport-ws backwards compatibility example | ||
// https://the-guild.dev/graphql/ws/recipes#ws-server-usage-with-subscriptions-transport-ws-backwards-compatibility | ||
|
||
import http from "http"; | ||
import { WebSocketServer } from "ws"; // yarn add ws | ||
// import ws from 'ws'; yarn add ws@7 | ||
// const WebSocketServer = ws.Server; | ||
import { execute, subscribe } from "graphql"; | ||
import { GRAPHQL_TRANSPORT_WS_PROTOCOL } from "graphql-ws"; | ||
import { useServer } from "graphql-ws/lib/use/ws"; | ||
import { SubscriptionServer, GRAPHQL_WS } from "subscriptions-transport-ws"; | ||
import { schema } from "./schema"; | ||
|
||
// extra in the context | ||
interface Extra { | ||
readonly request: http.IncomingMessage; | ||
} | ||
|
||
// your custom auth | ||
class Forbidden extends Error {} | ||
function handleAuth(request: http.IncomingMessage) { | ||
// do your auth on every subscription connect | ||
const token = request.headers["authorization"]; | ||
|
||
// or const { iDontApprove } = session(request.cookies); | ||
if (token !== "Bearer random-secret") { | ||
// throw a custom error to be handled | ||
throw new Forbidden(":("); | ||
} | ||
} | ||
|
||
// graphql-ws | ||
const graphqlWs = new WebSocketServer({ noServer: true }); | ||
useServer( | ||
{ | ||
schema, | ||
onConnect: async (ctx) => { | ||
// do your auth on every connect (recommended) | ||
await handleAuth(ctx.extra.request); | ||
}, | ||
}, | ||
graphqlWs | ||
); | ||
|
||
// subscriptions-transport-ws | ||
const subTransWs = new WebSocketServer({ noServer: true }); | ||
SubscriptionServer.create( | ||
{ | ||
schema, | ||
execute, | ||
subscribe, | ||
}, | ||
subTransWs | ||
); | ||
|
||
// create http server | ||
const server = http.createServer(function weServeSocketsOnly(_, res) { | ||
res.writeHead(404); | ||
res.end(); | ||
}); | ||
|
||
// listen for upgrades and delegate requests according to the WS subprotocol | ||
server.on("upgrade", (req, socket, head) => { | ||
// extract websocket subprotocol from header | ||
const protocol = req.headers["sec-websocket-protocol"]; | ||
const protocols = Array.isArray(protocol) | ||
? protocol | ||
: protocol?.split(",").map((p) => p.trim()); | ||
|
||
// decide which websocket server to use | ||
const wss = | ||
protocols?.includes(GRAPHQL_WS) && // subscriptions-transport-ws subprotocol | ||
!protocols.includes(GRAPHQL_TRANSPORT_WS_PROTOCOL) // graphql-ws subprotocol | ||
? subTransWs | ||
: // graphql-ws will welcome its own subprotocol and | ||
// gracefully reject invalid ones. if the client supports | ||
// both transports, graphql-ws will prevail | ||
graphqlWs; | ||
wss.handleUpgrade(req, socket, head, (ws) => { | ||
wss.emit("connection", ws, req); | ||
}); | ||
}); | ||
|
||
const port = 4000; | ||
console.log(`listen server on localhost:${port}`); | ||
server.listen(port); |
Oops, something went wrong.