Skip to content

Commit

Permalink
docs: Update subscriptions for multipart over HTTP (#2926)
Browse files Browse the repository at this point in the history
Co-authored-by: Stephen Barlow <[email protected]>
  • Loading branch information
calvincestari and Stephen Barlow authored Jun 21, 2023
1 parent 4cc0751 commit 0531744
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 130 deletions.
129 changes: 0 additions & 129 deletions docs/source/fetching/subscriptions.md

This file was deleted.

143 changes: 143 additions & 0 deletions docs/source/fetching/subscriptions.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
title: Subscriptions
---

Subscriptions are long-lived GraphQL read operations that can update their response over time, enabling clients to receive new data as it becomes available.

Apollo iOS supports subscriptions over the following protocols:

- [WebSocket](#websocket), including multiple [subprotocols](#websocket-subprotocols)
- [HTTP](#http), using [chunked multipart responses](https://github.com/graphql/graphql-over-http/blob/main/rfcs/IncrementalDelivery.md) (version 1.1.0 and later)

**You must use whichever protocol is supported by your GraphQL endpoint.**

## Enabling support

To support GraphQL subscriptions, you need to initialize your `ApolloClient` instance with a [`NetworkTransport`](https://www.apollographql.com/docs/ios/docc/documentation/apollo/networktransport) that supports subscriptions. See below for applicable classes for each subscription protocol.

### WebSocket

To use Apollo iOS with WebSocket-based subscriptions, you need to install the optional `ApolloWebSocket` library. This library includes two classes that conform to the [`NetworkTransport` protocol](../docc/documentation/apollo/networktransport):

- **[`WebSocketTransport`](../docc/documentation/apollowebsocket/websockettransport)** sends _all_ operations (including queries and mutations) over WebSocket.
- **[`SplitNetworkTransport`](../docc/documentation/apollowebsocket/splitnetworktransport)** maintains a [`WebSocketTransport`](../docc/documentation/apollowebsocket/websockettransport) instance, _along with_ an [`UploadingNetworkTransport`](../docc/documentation/apollo/uploadingnetworktransport) instance (usually [`RequestChainNetworkTransport`](https://www.apollographql.com/docs/ios/docc/documentation/apollo/requestchainnetworktransport)). This provides a single network transport that uses HTTP for queries and mutations and WebSocket for subscriptions.

`SplitNetworkTransport` is recommended for most use cases, because it enables you to retain a single `NetworkTransport` setup that avoids issues caused by using multiple client objects.

Here's an example of setting up an `ApolloClient` that uses a `SplitNetworkTransport` to support all operation types:

<ExpansionPanel title="Click to expand">

```swift
/// A common store to use for `httpTransport` and `webSocketTransport`.
let store = ApolloStore()

/// A web socket transport to use for subscriptions
let webSocketTransport: WebSocketTransport = {
let url = URL(string: "ws://localhost:8080/websocket")!
let webSocketClient = WebSocket(url: url, protocol: .graphql_transport_ws)
return WebSocketTransport(websocket: webSocketClient)
}()

/// An HTTP transport to use for queries and mutations
let httpTransport: RequestChainNetworkTransport = {
let url = URL(string: "http://localhost:8080/graphql")!
return RequestChainNetworkTransport(interceptorProvider: DefaultInterceptorProvider(store: store), endpointURL: url)
}()

/// A split network transport to allow the use of both of the above
/// transports through a single `NetworkTransport` instance.
let splitNetworkTransport = SplitNetworkTransport(
uploadingNetworkTransport: httpTransport,
webSocketNetworkTransport: webSocketTransport
)

/// Create a client using the `SplitNetworkTransport`.
let client = ApolloClient(networkTransport: splitNetworkTransport, store: store)
```

</ExpansionPanel>

#### WebSocket subprotocols

Apollo iOS supports the following WebSocket subprotocols for subscriptions:

- [`graphql-ws`](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md), which is implemented in the [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws) and [AWS AppSync](https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#handshake-details-to-establish-the-websocket-connection) libraries. (⚠️ This protocol is not actively maintained!)
- [`graphql-transport-ws`](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md), which is implemented in the [graphql-ws](https://github.com/enisdenjo/graphql-ws) library.

> **Note:** These protocols are **not** cross-compatible. You need to use whichever protocol is supported by your GraphQL endpoint. All `WebSocket` initializers enable you to specify which protocol to use (expand the code block above for an example).
#### Providing authorization tokens

In a standard HTTP operation, if authentication is necessary an `Authorization` header is often sent with requests. However, with WebSocket, this can't be sent with every payload because a persistent connection is required.

For WebSocket, the `connectingPayload` provides the parameters you would typically specify in your request headers.

Note that this must be set **when the `WebSocketTransport` is created**. If you need to update the `connectingPayload`, you need to recreate the client using a new `webSocketTransport`.

```swift
let webSocketTransport: WebSocketTransport = {
let url = URL(string: "ws://localhost:8080/websocket")!
let webSocketClient = WebSocket(url: url, protocol: .graphql_transport_ws)
let authPayload = ["authToken": myAuthToken]
return WebSocketTransport(websocket: webSocketClient, connectingPayload: authPayload)
}()
```

### HTTP

The default `NetworkTransport` for Apollo iOS is the [`RequestChainNetworkTransport`](../docc/documentation/apollo/requestchainnetworktransport). In Apollo iOS `1.1.0` and later, this transport supports subscriptions over HTTP, with no additional configuration required.

> See the instructions for [creating a client](../networking/client-creation).
## Generating and executing

Apollo iOS supports subscriptions via [code generation](../code-generation/introduction/). Similar to queries, subscriptions are represented by instances of generated classes, conforming to the `GraphQLSubscription` protocol.

```graphql title="ReviewAddedSubscription.graphql"
subscription ReviewAdded {
reviewAdded {
id
stars
}
}
```

After you generate these classes, you can execute subscriptions using `ApolloClient.subscribe(subscription:)` with a [subscription-supporting `NetworkTransport`](#enabling-support). If you do, your client continues to receive updated data until the subscription is [canceled](#canceling-a-subscription).

```swift
let subscription = client.subscribe(subscription: ReviewAddedSubscription()) { result in
guard let data = try? result.get().data else { return }
print(data.reviews.map { $0.stars })
}
```

> **Note:** GraphQL subscriptions are distinct from [watching queries](./queries#watching-queries). A query watcher is only updated when new data is written to the local cache (usually by another network operation). A GraphQL subscription is a long-lived request that might receive updated data from the server continually.
## Canceling a subscription

It's important to cancel a subscription connection whenever you're done with it. As long as a subscription is active, it maintains a connection to the server, and its `resultHandler` completion block is retained. This can create memory leaks and reduce your application's performance.

When you call `ApolloClient.subscribe(subscription:)` an opaque `Cancellable` is returned. You can cancel the subscription by calling `cancel()` on the returned `Cancellable`. This terminates the connection to the server and releases the `resultHandler` completion block.

**A subscription's cancellation object does not cancel itself when it's deallocated!** Make sure to `cancel()` it yourself. A `class` can ensure any subscriptions it manages are canceled when it's released by using its deinitializer:

```swift
class ReviewViewController {

let client: ApolloClient!
private var subscription: Cancellable?

func subscribeToReviews() {
// Keep a reference to the subscription's cancellation object.
self.subscription = client.subscribe(subscription: ReviewAddedSubscription()) { [weak self] result in
// Handle each update from the subscription.
}
}

deinit {
// Make sure the subscription is cancelled, if it exists, when this object is deallocated.
self.subscription?.cancel()
}
}
```
18 changes: 17 additions & 1 deletion docs/source/networking/request-pipeline.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ flowchart TB
max(MaxRetryInterceptor) --> cacheRead(CacheReadInterceptor)
cacheRead --> network(NetworkFetchInterceptor)
network --> response(ResponseCodeInterceptor)
response --> json(JSONResponseParsingInterceptor)
response --> multipart(MultipartResponseParsingInterceptor)
multipart --> json(JSONResponseParsingInterceptor)
json --> apq(AutomaticPersistedQueryInterceptor)
apq --> cacheWrite(CacheWriteInterceptor)
```
Expand Down Expand Up @@ -203,6 +204,21 @@ Checks a GraphQL server's response _after_ execution to see whether the provided
<tr>
<td>

##### [`MultipartResponseParsingInterceptor`](https://www.apollographql.com/docs/ios/docc/documentation/apollo/multipartresponseparsinginterceptor)

</td>
<td>

Parses a multipart response using the Incremental Delivery over HTTP spec, and passes it on to the next interceptor.

It is important that the next interceptor must be the JSONResponseParsingInterceptor in order to have the individual messages parsed into a `GraphQLResult`.

</td>
</tr>

<tr>
<td>

##### [`JSONResponseParsingInterceptor`](https://www.apollographql.com/docs/ios/docc/documentation/apollo/jsonresponseparsinginterceptor)

</td>
Expand Down

0 comments on commit 0531744

Please sign in to comment.