ciscorn / starlette-graphene3

An ASGI app for using Graphene v3 with Starlette / FastAPI
MIT License
101 stars 14 forks source link

Websockets #6

Closed karlzwinnen closed 3 years ago

karlzwinnen commented 3 years ago

Great project! I tried to use the websocket functionality, which works when i run the tests, but when i try to do so from a simple html page, the websocket endpoint this error: WebSocket connection to 'ws://127.0.0.1:3000/graphql-ws' failed: Error during WebSocket handshake: Response must not include 'Sec-WebSocket-Protocol' header if not present in request: graphql-ws.

import graphene
from graphene import String

#from starlette.graphql import GraphQLApp
from starlette_graphene3 import GraphQLApp

from app.main import app

html = '''
<!DOCTYPE html>
<html>
    <head>
        <title>Websocket test</title>
    </head>
    <body>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var GQL_CONNECTION_ACK = "connection_ack";
            var GQL_CONNECTION_ERROR = "connection_error";
            var GQL_CONNECTION_INIT = "connection_init";
            var GQL_CONNECTION_TERMINATE = "connection_terminate";
            var GQL_COMPLETE = "complete";
            var GQL_DATA = "data";
            var GQL_ERROR = "error";
            var GQL_START = "start";
            var GQL_STOP = "stop";

            var ws = new WebSocket("ws://127.0.0.1:3000/graphql-ws");

            ws.onconnect = function(event) {
                ws.send('{"type": ' + GQL_CONNECTION_INIT+ '}')
            }
            ws.onmessage = function(event) {
                console.log("RECEIVED MESSAGE: ", event.data);
            };
        </script>
    </body>
</html>
'''

@app.get("/")
def get() -> Any:
    return HTMLResponse(html)

class GraphQ(graphene.ObjectType):
    echo = graphene.String(msg=graphene.String())

    def resolve_echo(self, info, msg):
        return f'Message was: {msg}'

gapp = GraphQLApp(schema=graphene.Schema(query=GraphQLQueries))
app.add_route("/graphql", gapp)
app.add_websocket_route("/graphql-ws", gapp)

I can see starlette.test_client.py adds some headers:

    def websocket_connect(
        self, url: str, subprotocols: typing.Sequence[str] = None, **kwargs: typing.Any
    ) -> typing.Any:
        url = urljoin("ws://testserver", url)
        headers = kwargs.get("headers", {})
        headers.setdefault("connection", "upgrade")
        headers.setdefault("sec-websocket-key", "testserver==")
        headers.setdefault("sec-websocket-version", "13")
        if subprotocols is not None:
            headers.setdefault("sec-websocket-protocol", ", ".join(subprotocols))
        kwargs["headers"] = headers
        try:
            super().request("GET", url, **kwargs)
        except _Upgrade as exc:
            session = exc.session
        else:
            raise RuntimeError("Expected WebSocket upgrade")  # pragma: no cover

        return session

But i dont think i can/should add these when adding the websocket route?

karlzwinnen commented 3 years ago

Whoops, figured it out, had to provide the protocol which matches the endpoints name:

var ws = new WebSocket("ws://127.0.0.1:3000/graphql-ws", "graphql-ws");

Now it works!