Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ Changes log
streaming and reading Server-Sent Events over the HTTP connectors, including optional
comment heartbeats and reconnects honoring `retry:` and `Last-Event-ID`. The
`server.maxEventStreams` parameter limits concurrent event streams.
- Added the `MultipartStreamRepresentation` and `MultipartStreamReader` APIs for streaming
`multipart/*` content whose parts are produced over time, such as `multipart/mixed`
responses of incremental protocols. Each part is flushed with its trailing delimiter so
that readers process it at once; content containing the delimiter is rejected. The
reader returns parts as they arrive, with a maximum part size. Added the
`MediaType.MULTIPART_MIXED` constant. Issue #1587.
- Added the experimental `org.restlet.ext.websocket` extension, on HTTP/1.1 upgrades.
`WebSocketUpgrader` (or `WebSocketUpgrade.accept`) accepts upgrades that went through
the normal routing, filters and authenticators, and runs a `WebSocketHandler` on its own
Expand Down Expand Up @@ -156,7 +162,10 @@ Changes log
the server adapter also accepts `graphql-transport-ws` WebSocket upgrades on its own path
(HTTP/1.1 only), with several operations per connection, a `connection_init` timeout
and the protocol's close codes; `org.restlet.ext.websocket` is an optional dependency,
loaded only then. Issue #1581.
loaded only then. Incremental delivery of `@defer` results over `multipart/mixed` is
available on both sides when enabled (experimental, off by default): the client
returns graphql-java's `IncrementalExecutionResult`, whose item publisher reads the
delayed results. Issue #1581.
- Added the `NettyConnectionUpgrade` engine hook: a Restlet returning a "101 Switching
Protocols" response with this response attribute takes over the HTTP/1.1 connection
(`NettyUpgradeContext`), which the WebSocket extension relies on.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ public class GraphqlClientAdapter {

private static final int EXCERPT_BYTES = 1024;

private static final MediaType INCREMENTAL =
new MediaType(MediaType.MULTIPART_MIXED, "deferSpec", "20220824");

private enum State {
NEW,
STARTED,
Expand All @@ -99,6 +102,8 @@ private enum State {

private volatile State state = State.NEW;

private volatile boolean incrementalDeliveryEnabled;

private final Set<GraphqlSsePublisher.EventStreamSubscription> subscriptions =
ConcurrentHashMap.newKeySet();

Expand Down Expand Up @@ -250,6 +255,33 @@ public void setRequestCustomizer(Consumer<Request> requestCustomizer) {
this.requestCustomizer = requestCustomizer;
}

/**
* Indicates if incremental delivery is accepted.
*
* @return True if incremental delivery is accepted.
*/
public boolean isIncrementalDeliveryEnabled() {
return incrementalDeliveryEnabled;
}

/**
* Indicates if incremental delivery ({@code @defer}) is accepted. Experimental and off by
* default: the GraphQL incremental delivery RFC is not finalized.
*
* <p>When enabled, {@code execute} accepts {@code multipart/mixed} responses in graphql-java's
* format ({@code deferSpec=20220824}). Such a response is returned as an {@link
* graphql.incremental.IncrementalExecutionResult} with the initial result. When it has more
* results, its item publisher reads them from the response, which stays open until the
* publisher completes, fails or is cancelled: subscribe to it, or cancel its subscription. Each
* part is bounded by {@link #getMaxResponseBytes()}.
*
* @param incrementalDeliveryEnabled True to accept incremental delivery.
*/
public void setIncrementalDeliveryEnabled(boolean incrementalDeliveryEnabled) {
checkNew();
this.incrementalDeliveryEnabled = incrementalDeliveryEnabled;
}

/**
* Validates and freezes the configuration.
*
Expand Down Expand Up @@ -531,8 +563,20 @@ private ExecutionResult send(
request.getClientInfo()
.getAcceptedMediaTypes()
.add(new Preference<>(MediaType.APPLICATION_JSON, 0.9F));
if (incrementalDeliveryEnabled) {
request.getClientInfo().getAcceptedMediaTypes().add(new Preference<>(INCREMENTAL, 1F));
}

Response response = client.handle(request);
Representation multipart = response.getEntity();
if (incrementalDeliveryEnabled
&& response.getStatus().isSuccess()
&& multipart != null
&& multipart.getMediaType() != null
&& MediaType.MULTIPART_MIXED.equals(multipart.getMediaType(), true)) {
// Releases the response itself, once the last result is read.
return GraphqlIncrementalPublisher.read(response, maxResponseBytes);
}
try {
return toResult(response);
} finally {
Expand Down
Loading
Loading