WebsocketsTransport

The websockets transport supports both:

It will propose both subprotocols to the backend and detect the supported protocol from the response http headers returned by the backend.

Note

For some backends (graphql-ws before version 5.6.1 without backwards compatibility), it may be necessary to specify only one subprotocol to the backend. It can be done by using subprotocols=[WebsocketsTransport.GRAPHQLWS_SUBPROTOCOL] or subprotocols=[WebsocketsTransport.APOLLO_SUBPROTOCOL] in the transport arguments.

This transport allows to do multiple queries, mutations and subscriptions on the same websocket connection.

Reference: gql.transport.websockets.WebsocketsTransport

import asyncio
import logging

from gql import Client, gql
from gql.transport.websockets import WebsocketsTransport

logging.basicConfig(level=logging.INFO)


async def main():

    transport = WebsocketsTransport(url="wss://countries.trevorblades.com/graphql")

    # Using `async with` on the client will start a connection on the transport
    # and provide a `session` variable to execute queries on this connection
    async with Client(
        transport=transport,
        fetch_schema_from_transport=True,
    ) as session:

        # Execute single query
        query = gql(
            """
            query getContinents {
              continents {
                code
                name
              }
            }
        """
        )
        result = await session.execute(query)
        print(result)

        # Request subscription
        subscription = gql(
            """
            subscription {
                somethingChanged {
                    id
                }
            }
        """
        )
        async for result in session.subscribe(subscription):
            print(result)


asyncio.run(main())

Websockets SSL

If you need to connect to an ssl encrypted endpoint:

  • use wss instead of ws in the url of the transport

transport = WebsocketsTransport(
    url='wss://SERVER_URL:SERVER_PORT/graphql',
    headers={'Authorization': 'token'}
)

If you have a self-signed ssl certificate, you need to provide an ssl_context with the server public certificate:

import pathlib
import ssl

ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
localhost_pem = pathlib.Path(__file__).with_name("YOUR_SERVER_PUBLIC_CERTIFICATE.pem")
ssl_context.load_verify_locations(localhost_pem)

transport = WebsocketsTransport(
    url='wss://SERVER_URL:SERVER_PORT/graphql',
    ssl=ssl_context
)

If you have also need to have a client ssl certificate, add:

ssl_context.load_cert_chain(certfile='YOUR_CLIENT_CERTIFICATE.pem', keyfile='YOUR_CLIENT_CERTIFICATE_KEY.key')

Websockets authentication

There are two ways to send authentication tokens with websockets depending on the server configuration.

  1. Using HTTP Headers

transport = WebsocketsTransport(
    url='wss://SERVER_URL:SERVER_PORT/graphql',
    headers={'Authorization': 'token'}
)
  1. With a payload in the connection_init websocket message

transport = WebsocketsTransport(
    url='wss://SERVER_URL:SERVER_PORT/graphql',
    init_payload={'Authorization': 'token'}
)

Keep-Alives

Apollo protocol

With the Apollo protocol, the backend can optionally send unidirectional keep-alive (“ka”) messages (only from the server to the client).

It is possible to configure the transport to close if we don’t receive a “ka” message within a specified time using the keep_alive_timeout parameter.

Here is an example with 60 seconds:

transport = WebsocketsTransport(
    url='wss://SERVER_URL:SERVER_PORT/graphql',
    keep_alive_timeout=60,
)

One disadvantage of the Apollo protocol is that because the keep-alives are only sent from the server to the client, it can be difficult to detect the loss of a connection quickly from the server side.

GraphQL-ws protocol

With the GraphQL-ws protocol, it is possible to send bidirectional ping/pong messages. Pings can be sent either from the client or the server and the other party should answer with a pong.

As with the Apollo protocol, it is possible to configure the transport to close if we don’t receive any message from the backend within the specified time using the keep_alive_timeout parameter.

But there is also the possibility for the client to send pings at a regular interval and verify that the backend sends a pong within a specified delay. This can be done using the ping_interval and pong_timeout parameters.

Here is an example with a ping sent every 60 seconds, expecting a pong within 10 seconds:

transport = WebsocketsTransport(
    url='wss://SERVER_URL:SERVER_PORT/graphql',
    ping_interval=60,
    pong_timeout=10,
)

Underlying websockets protocol

In addition to the keep-alives described above for the apollo and graphql-ws protocols, there are also ping frames sent by the underlying websocket connection itself for both of them.

These pings are enabled by default (every 20 seconds) and could be modified or disabled by passing extra arguments to the connect call of the websockets client using the connect_args argument of the transport.

# Disabling websocket protocol level pings
transport = WebsocketsTransport(
    url='wss://SERVER_URL:SERVER_PORT/graphql',
    connect_args={"ping_interval": None},
)

See the websockets keepalive documentation for details.