Skip to content

Commit c2ec83b

Browse files
magicmarkclaude
andcommitted
Add HTTP multipart transport for GraphQL subscriptions
This commit adds support for HTTP multipart transport, which allows GraphQL subscriptions to work over HTTP using the multipart response format. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent df1214e commit c2ec83b

File tree

8 files changed

+1216
-0
lines changed

8 files changed

+1216
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import asyncio
2+
import logging
3+
4+
from gql import Client, gql
5+
from gql.transport.http_multipart_transport import HTTPMultipartTransport
6+
7+
logging.basicConfig(level=logging.INFO)
8+
9+
10+
async def main():
11+
12+
transport = HTTPMultipartTransport(url="https://gql-book-server.fly.dev/graphql")
13+
14+
# Using `async with` on the client will start a connection on the transport
15+
# and provide a `session` variable to execute queries on this connection
16+
async with Client(
17+
transport=transport,
18+
) as session:
19+
20+
# Request subscription
21+
subscription = gql(
22+
"""
23+
subscription {
24+
book {
25+
title
26+
author
27+
}
28+
}
29+
"""
30+
)
31+
32+
# Subscribe and receive streaming updates
33+
async for result in session.subscribe(subscription):
34+
print(f"Received: {result}")
35+
36+
37+
asyncio.run(main())

docs/modules/gql.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Sub-Packages
2929
transport_common_adapters_aiohttp
3030
transport_common_adapters_websockets
3131
transport_exceptions
32+
transport_http_multipart
3233
transport_phoenix_channel_websockets
3334
transport_requests
3435
transport_httpx
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
gql.transport.http\_multipart\_transport module
2+
===============================================
3+
4+
.. automodule:: gql.transport.http_multipart_transport
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

docs/transports/async_transports.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Async transports are transports which are using an underlying async library. The
1111

1212
aiohttp
1313
httpx_async
14+
http_multipart
1415
websockets
1516
aiohttp_websockets
1617
phoenix

docs/transports/http_multipart.rst

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
.. _http_multipart_transport:
2+
3+
HTTPMultipartTransport
4+
======================
5+
6+
This transport implements GraphQL subscriptions over HTTP using the `multipart subscription protocol`_
7+
as implemented by Apollo GraphOS Router and other compatible servers.
8+
9+
This provides an HTTP-based alternative to WebSocket transports for receiving streaming
10+
subscription updates. It's particularly useful when:
11+
12+
- WebSocket connections are not available or blocked by infrastructure
13+
- You want to use standard HTTP with existing load balancers and proxies
14+
- The backend implements the multipart subscription protocol
15+
16+
Reference: :class:`gql.transport.http_multipart_transport.HTTPMultipartTransport`
17+
18+
.. note::
19+
20+
This transport is specifically designed for GraphQL subscriptions. While it can handle
21+
queries and mutations via the ``execute()`` method, standard HTTP transports like
22+
:ref:`AIOHTTPTransport <aiohttp_transport>` are more efficient for those operations.
23+
24+
.. literalinclude:: ../code_examples/http_multipart_async.py
25+
26+
How It Works
27+
------------
28+
29+
The transport sends a standard HTTP POST request with an ``Accept`` header indicating
30+
support for multipart responses:
31+
32+
.. code-block:: text
33+
34+
Accept: multipart/mixed;subscriptionSpec="1.0", application/json
35+
36+
The server responds with a ``multipart/mixed`` content type and streams subscription
37+
updates as separate parts in the response body. Each part contains a JSON payload
38+
with GraphQL execution results.
39+
40+
Protocol Details
41+
----------------
42+
43+
**Message Format**
44+
45+
Each message part follows this structure:
46+
47+
.. code-block:: text
48+
49+
--graphql
50+
Content-Type: application/json
51+
52+
{"payload": {"data": {...}, "errors": [...]}}
53+
54+
**Heartbeats**
55+
56+
Servers may send empty JSON objects (``{}``) as heartbeat messages to keep the
57+
connection alive. These are automatically filtered out by the transport.
58+
59+
**Error Handling**
60+
61+
The protocol distinguishes between two types of errors:
62+
63+
- **GraphQL errors**: Returned within the ``payload`` property alongside data
64+
- **Transport errors**: Returned with a top-level ``errors`` field and ``null`` payload
65+
66+
**End of Stream**
67+
68+
The subscription ends when the server sends the final boundary marker:
69+
70+
.. code-block:: text
71+
72+
--graphql--
73+
74+
Authentication
75+
--------------
76+
77+
Authentication works the same as with :ref:`AIOHTTPTransport <aiohttp_transport>`.
78+
79+
Using HTTP Headers
80+
^^^^^^^^^^^^^^^^^^
81+
82+
.. code-block:: python
83+
84+
transport = HTTPMultipartTransport(
85+
url='https://SERVER_URL:SERVER_PORT/graphql',
86+
headers={'Authorization': 'Bearer YOUR_TOKEN'}
87+
)
88+
89+
Using HTTP Cookies
90+
^^^^^^^^^^^^^^^^^^
91+
92+
.. code-block:: python
93+
94+
transport = HTTPMultipartTransport(
95+
url=url,
96+
cookies={"session_id": "your_session_cookie"}
97+
)
98+
99+
Or use a cookie jar to save and reuse cookies:
100+
101+
.. code-block:: python
102+
103+
import aiohttp
104+
105+
jar = aiohttp.CookieJar()
106+
transport = HTTPMultipartTransport(
107+
url=url,
108+
client_session_args={'cookie_jar': jar}
109+
)
110+
111+
Configuration
112+
-------------
113+
114+
Timeout Settings
115+
^^^^^^^^^^^^^^^^
116+
117+
Set a timeout for the HTTP request:
118+
119+
.. code-block:: python
120+
121+
transport = HTTPMultipartTransport(
122+
url='https://SERVER_URL/graphql',
123+
timeout=30 # 30 second timeout
124+
)
125+
126+
SSL Configuration
127+
^^^^^^^^^^^^^^^^^
128+
129+
Control SSL certificate verification:
130+
131+
.. code-block:: python
132+
133+
transport = HTTPMultipartTransport(
134+
url='https://SERVER_URL/graphql',
135+
ssl=False # Disable SSL verification (not recommended for production)
136+
)
137+
138+
Or provide a custom SSL context:
139+
140+
.. code-block:: python
141+
142+
import ssl
143+
144+
ssl_context = ssl.create_default_context()
145+
ssl_context.load_cert_chain('client.crt', 'client.key')
146+
147+
transport = HTTPMultipartTransport(
148+
url='https://SERVER_URL/graphql',
149+
ssl=ssl_context
150+
)
151+
152+
Limitations
153+
-----------
154+
155+
- This transport requires the server to implement the multipart subscription protocol
156+
- Long-lived connections may be terminated by intermediate proxies or load balancers
157+
- Some server configurations may not support HTTP/1.1 chunked transfer encoding required for streaming
158+
159+
.. _multipart subscription protocol: https://www.apollographql.com/docs/graphos/routing/operations/subscriptions/multipart-protocol

0 commit comments

Comments
 (0)