Skip to content

Commit 11273b5

Browse files
author
Mark Pollack
committed
One framework-neutral ACP endpoint, a host contract and a shared TCK
All protocol semantics of the Streamable HTTP and WebSocket transports move into acp-http-core: routing, sessions, the bounded SSE mailboxes and WebSocket send queue, the status and close-code table, the Origin check, keep-alive and drain, behind AcpHttpEndpoint. Hosts only adapt I/O through the contract types (AcpHttpExchange, AcpHttpReply, SseFrame, AcpWsHandshake, AcpWsOutbound, AcpWsHandler), marked @UnstableAcpApi. One neutral WebSocket connection replaces the Jetty-native one. acp-http-servlet is the servlet host, with no Jetty dependency: it upgrades WebSocket requests on its own path through Jakarta WebSocket 2.1 and serializes outbound frames, as Tomcat requires. acp-streamable-http-jetty becomes a launcher that runs the servlet host on Jetty 12.1 with Jetty's Jakarta WebSocket implementation. Spring MVC mounts the servlet host on server.port: HTTP, SSE and WebSocket through the application's filter chain, so Spring Security applies to /acp and the principal reaches the endpoint. The endpoint drains before graceful shutdown (closing comment, then close 1001), and text/event-stream is never compressed. The transport TCK in acp-test (AcpHttpTransportTck) runs on the servlet under Tomcat 11 and Jetty 12.1, on the embedded listener and on Spring MVC. ArchUnit rules keep hosts free of protocol classes. The CI Lincheck matrix follows the mailbox tests to acp-http-core.
1 parent 513f0ac commit 11273b5

72 files changed

Lines changed: 5475 additions & 2256 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106
strategy:
107107
fail-fast: false
108108
matrix:
109-
module: [acp-core, acp-streamable-http-jetty]
109+
module: [acp-core, acp-http-core]
110110

111111
steps:
112112
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

‎CHANGELOG.md‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5050

5151
### Added
5252

53+
- **One framework-neutral ACP endpoint and a host contract: `acp-http-core`.** Every protocol rule
54+
of the Streamable HTTP and WebSocket transports now lives in one place,
55+
`com.agentclientprotocol.sdk.http.server.AcpHttpEndpoint`: routing, sessions, the bounded SSE
56+
mailboxes and WebSocket send queue, the status and close-code table, the `Origin` check,
57+
keep-alive and drain. A server adapter (a host) only adapts I/O through the contract types
58+
`AcpHttpExchange`, `AcpHttpReply` (`Body`, `Empty`, `EventStream`), `SseFrame`, `AcpWsHandshake`,
59+
`AcpWsOutbound` and `AcpWsHandler`, all `@UnstableAcpApi`: applications use the hosts the SDK
60+
ships. The one neutral WebSocket connection replaces the Jetty-native one.
61+
- **WebSocket on the servlet, on the application's own server.** `StreamableHttpAcpServlet` (new
62+
module `acp-http-servlet`, no Jetty dependency) upgrades `Upgrade: websocket` requests on its own
63+
path through Jakarta WebSocket 2.1 (`ServerContainer.upgradeHttpToWebSocket`), on Tomcat, Jetty
64+
and Undertow. A container without a Jakarta WebSocket implementation answers the upgrade 501.
65+
Its outbound frames are serialized, one write at a time, as Tomcat requires.
66+
- **Spring Boot: ACP on `server.port`, under the application's security.** A servlet web
67+
application now serves Streamable HTTP, SSE and WebSocket on its own server, through its filter
68+
chain: Spring Security rules on `/acp` apply to HTTP and to the WebSocket handshake, and the
69+
principal reaches the endpoint (`AcpHttpExchange.principal()`). The endpoint is an
70+
`AcpHttpEndpoint` bean. With `server.compression.enabled`, `text/event-stream` is no longer
71+
compressed.
72+
- **Shutdown drains open streams.** Closing the endpoint (servlet, listener, Spring's
73+
`SmartLifecycle` before graceful shutdown) gives each open SSE stream a closing comment
74+
(`: shutting down`) and completes it, and closes each WebSocket with 1001 (going away), so a
75+
framework's graceful shutdown never waits for them.
76+
- **SSE responses carry `X-Accel-Buffering: no`**, so nginx and similar proxies do not buffer
77+
them, besides `Cache-Control: no-cache`.
78+
- **A shared transport TCK in `acp-test`:** `com.agentclientprotocol.sdk.test.http.AcpHttpTransportTck`,
79+
the suite every host runs (the servlet on Tomcat 11 and Jetty 12.1, the embedded listener, Spring
80+
MVC), with `HttpProbes` for raw HTTP and WebSocket checks. `acp-test` now depends on
81+
`junit-jupiter-api`, `assertj-core` and `acp-http-core`.
82+
5383
- **`AcpTransportThreads` in `acp-integration`: the threads a framework's network transports run
5484
on.** `AcpClientTransports.create(settings, prefix, threads)` and
5585
`AcpListeners.listener(settings, factory, threads)` take one: `executor(Executor)` for the
@@ -472,6 +502,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
472502

473503
### Changed
474504

505+
- **Breaking: the HTTP transport is split into three modules.** `acp-http-core` (the endpoint and
506+
`StreamableHttpAcpAgentTransportOptions`), `acp-http-servlet` (`StreamableHttpAcpServlet`) and
507+
`acp-streamable-http-jetty` (`StreamableHttpAcpAgentTransport`, now embedded Jetty running the
508+
servlet with Jetty's Jakarta WebSocket implementation). Class names and packages of the public
509+
types are unchanged. Migration: an application mounting the servlet in its own container, a
510+
Spring MVC application included, depends on `acp-http-servlet` instead of
511+
`acp-streamable-http-jetty`, and no longer gets Jetty; an application using the SDK's listener
512+
keeps `acp-streamable-http-jetty`. Do not add `acp-streamable-http-jetty` to a Spring Boot
513+
application on Tomcat: its Jakarta WebSocket 2.1 API jar would shadow Tomcat 11's 2.2 one.
514+
- **Breaking: `StreamableHttpAcpServlet` is a host of `AcpHttpEndpoint`.** It gained the
515+
constructor `StreamableHttpAcpServlet(AcpHttpEndpoint)` and `endpoint()`; the package-private
516+
routing, connection and SSE classes moved to `acp-http-core`. A subclass overriding `doGet`,
517+
`doPost` or `doDelete` no longer intercepts anything: `service` hands every request to the
518+
endpoint. Migration: wrap the `AcpHttpEndpoint` instead.
519+
- **Breaking: the shutdown close code of a WebSocket is 1001 (going away)**, not 1000. Clients
520+
that treated 1000 as the server's shutdown treat 1001 the same way.
521+
- `AcpListeners.endpoint(AcpAgentSettings, AcpAgentFactory)` returns the endpoint for a framework
522+
that mounts it itself.
523+
475524
- **Breaking: new record components for the bind address and allowed origins.**
476525
`StreamableHttpAcpAgentTransportOptions` gained `host` and `allowedOrigins`,
477526
`AcpAgentSettings.Http` gained `allowedOrigins`, and `AcpAgentSettings.Listener` gained `host`

‎README.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,8 @@ that authenticates in front. Browser requests whose `Origin` is not a loopback o
460460
| `com.agentclientprotocol.sdk.client` | Client SDK (`AcpClient`, `AcpAsyncClient`, `AcpSyncClient`) |
461461
| `com.agentclientprotocol.sdk.client.transport` | Client transports (`StdioAcpClientTransport`, `WebSocketAcpClientTransport`, `StreamableHttpAcpClientTransport`) |
462462
| `com.agentclientprotocol.sdk.agent` | Agent SDK (`AcpAgent`, `AcpAsyncAgent`, `AcpSyncAgent`, `AcpAgentFactory`) |
463-
| `com.agentclientprotocol.sdk.agent.transport` | Agent transports (`StdioAcpAgentTransport`; `StreamableHttpAcpAgentTransport` and `StreamableHttpAcpServlet` from acp-streamable-http-jetty) |
463+
| `com.agentclientprotocol.sdk.agent.transport` | Agent transports (`StdioAcpAgentTransport`; `StreamableHttpAcpServlet` from acp-http-servlet; `StreamableHttpAcpAgentTransport` from acp-streamable-http-jetty; `StreamableHttpAcpAgentTransportOptions` from acp-http-core) |
464+
| `com.agentclientprotocol.sdk.http.server` | The host contract from acp-http-core (`AcpHttpEndpoint`, `AcpHttpExchange`, `AcpHttpReply`, `AcpWsHandshake`), for writing a server adapter; unstable |
464465
| `com.agentclientprotocol.sdk.agent.support` | Annotation-based agent runtime (`AcpAgentSupport`), with its `resolver`, `handler`, `interceptor` and `invocation` extension points |
465466
| `com.agentclientprotocol.sdk.annotation` | Agent annotations (`@AcpAgent`, `@Prompt`, etc.) |
466467
| `com.agentclientprotocol.sdk.capabilities` | Capability negotiation (`NegotiatedCapabilities`) |
@@ -476,7 +477,9 @@ that authenticates in front. Browser requests whose `Origin` is not a loopback o
476477
| [`acp-core`](https://central.sonatype.com/artifact/com.agentclientprotocol/acp-core) | Client and Agent SDKs, stdio, WebSocket, and Streamable HTTP client transports; needs one JSON module |
477478
| `acp-json-jackson2` | JSON implementation on Jackson 2 (`JacksonAcpJsonMapper`); brought in by the transport and agent-support modules |
478479
| `acp-json-jackson3` | JSON implementation on Jackson 3 (`Jackson3AcpJsonMapper`) |
479-
| `acp-streamable-http-jetty` | Jetty-backed Streamable HTTP agent transport for listener-backed remote agents; also accepts WebSocket upgrades on the same path |
480+
| `acp-http-core` | The framework-neutral ACP endpoint behind every HTTP host: routing, sessions, SSE mailboxes, the status and close-code table, the `Origin` check and shutdown, and the host contract (`com.agentclientprotocol.sdk.http.server`) |
481+
| `acp-http-servlet` | The endpoint as a Servlet 6 servlet, `StreamableHttpAcpServlet`, with WebSocket upgrades on the same path through Jakarta WebSocket 2.1: Spring MVC, Tomcat, Jetty, Undertow |
482+
| `acp-streamable-http-jetty` | The SDK's own listener, `StreamableHttpAcpAgentTransport`: embedded Jetty 12.1 running the servlet, for agents served from a plain Java program |
480483
| [`acp-annotations`](https://central.sonatype.com/artifact/com.agentclientprotocol/acp-annotations) | `@AcpAgent`, `@Prompt`, and other annotations |
481484
| [`acp-agent-support`](https://central.sonatype.com/artifact/com.agentclientprotocol/acp-agent-support) | Annotation-based agent runtime |
482485
| [`acp-test`](https://central.sonatype.com/artifact/com.agentclientprotocol/acp-test) | In-memory transport and mock utilities for testing |
@@ -491,7 +494,7 @@ that authenticates in front. Browser requests whose `Origin` is not a loopback o
491494
| Stdio | `StdioAcpClientTransport` | `StdioAcpAgentTransport` | acp-core |
492495
| WebSocket | `WebSocketAcpClientTransport` | `StreamableHttpAcpAgentTransport` (upgrade on the same path) | acp-core / acp-streamable-http-jetty |
493496
| Streamable HTTP | `StreamableHttpAcpClientTransport` | `StreamableHttpAcpAgentTransport` | acp-core / acp-streamable-http-jetty |
494-
| Streamable HTTP in your own Servlet 6 container | `StreamableHttpAcpClientTransport` | `StreamableHttpAcpServlet` (HTTP/SSE only; the WebSocket upgrade needs `StreamableHttpAcpAgentTransport`) | acp-core / acp-streamable-http-jetty |
497+
| Streamable HTTP and WebSocket in your own Servlet 6 container | `StreamableHttpAcpClientTransport`, `WebSocketAcpClientTransport` | `StreamableHttpAcpServlet` (WebSocket where the container has Jakarta WebSocket 2.1) | acp-core / acp-http-servlet |
495498

496499
---
497500

‎acp-http-core/pom.xml‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.agentclientprotocol</groupId>
9+
<artifactId>acp-java-sdk</artifactId>
10+
<version>0.19.0-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>acp-http-core</artifactId>
14+
<packaging>jar</packaging>
15+
16+
<name>ACP HTTP Core</name>
17+
<description>The framework-neutral ACP Streamable HTTP and WebSocket endpoint: routing, sessions, SSE mailboxes, the status and close-code table, the Origin check and shutdown, behind the host contract every server adapter implements</description>
18+
19+
<properties>
20+
<!-- Coverage floors: measured line 83%, branch 67% (the TCK covers the rest from the host
21+
modules), less 2 points (parent pom, JaCoCo) -->
22+
<jacoco.minimum.line>0.81</jacoco.minimum.line>
23+
<jacoco.minimum.branch>0.65</jacoco.minimum.branch>
24+
<!-- JaCoCo's prepare-agent sets argLine; empty when JaCoCo is skipped. -->
25+
<argLine/>
26+
</properties>
27+
28+
<dependencies>
29+
<dependency>
30+
<groupId>org.jspecify</groupId>
31+
<artifactId>jspecify</artifactId>
32+
</dependency>
33+
<dependency>
34+
<groupId>com.agentclientprotocol</groupId>
35+
<artifactId>acp-core</artifactId>
36+
</dependency>
37+
<!-- The JSON implementation: Jackson 2 by default. Add acp-json-jackson3 to use Jackson 3. -->
38+
<dependency>
39+
<groupId>com.agentclientprotocol</groupId>
40+
<artifactId>acp-json-jackson2</artifactId>
41+
</dependency>
42+
43+
<dependency>
44+
<groupId>org.junit.jupiter</groupId>
45+
<artifactId>junit-jupiter</artifactId>
46+
<scope>test</scope>
47+
</dependency>
48+
<dependency>
49+
<groupId>com.tngtech.archunit</groupId>
50+
<artifactId>archunit-junit5</artifactId>
51+
<scope>test</scope>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.assertj</groupId>
55+
<artifactId>assertj-core</artifactId>
56+
<scope>test</scope>
57+
</dependency>
58+
<dependency>
59+
<groupId>ch.qos.logback</groupId>
60+
<artifactId>logback-classic</artifactId>
61+
<scope>test</scope>
62+
</dependency>
63+
<dependency>
64+
<groupId>org.jetbrains.lincheck</groupId>
65+
<artifactId>lincheck</artifactId>
66+
<scope>test</scope>
67+
</dependency>
68+
<dependency>
69+
<groupId>io.projectreactor</groupId>
70+
<artifactId>reactor-test</artifactId>
71+
<scope>test</scope>
72+
</dependency>
73+
</dependencies>
74+
75+
<build>
76+
<plugins>
77+
<!-- Lincheck (the *LincheckTest classes) attaches its instrumentation agent at
78+
runtime; see acp-core. @{argLine} keeps JaCoCo's agent. -->
79+
<plugin>
80+
<groupId>org.apache.maven.plugins</groupId>
81+
<artifactId>maven-surefire-plugin</artifactId>
82+
<configuration>
83+
<argLine>@{argLine} -XX:+IgnoreUnrecognizedVMOptions -XX:+EnableDynamicAgentLoading</argLine>
84+
</configuration>
85+
</plugin>
86+
</plugins>
87+
</build>
88+
89+
</project>

acp-streamable-http-jetty/src/main/java/com/agentclientprotocol/sdk/agent/transport/OriginPolicy.java renamed to acp-http-core/src/main/java/com/agentclientprotocol/sdk/agent/transport/OriginPolicy.java

File renamed without changes.

acp-streamable-http-jetty/src/main/java/com/agentclientprotocol/sdk/agent/transport/StreamableHttpAcpAgentTransportOptions.java renamed to acp-http-core/src/main/java/com/agentclientprotocol/sdk/agent/transport/StreamableHttpAcpAgentTransportOptions.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414

1515
/**
1616
* The limits and timings of an ACP Streamable HTTP and WebSocket endpoint, for
17-
* {@link StreamableHttpAcpAgentTransport} and {@link StreamableHttpAcpServlet} alike. Every
17+
* every host of the endpoint alike: the SDK's listener ({@code StreamableHttpAcpAgentTransport},
18+
* module {@code acp-streamable-http-jetty}), its servlet ({@code StreamableHttpAcpServlet},
19+
* module {@code acp-http-servlet}) and the framework integrations, all of which serve
20+
* {@link com.agentclientprotocol.sdk.http.server.AcpHttpEndpoint}. Every
1821
* buffer the endpoint keeps for a client is bounded by one of these, so a slow or hostile
1922
* client cannot make the agent's memory grow without bound. Pass one to the listener's or the
2023
* servlet's constructor when a default does not fit; start from {@link #builder()}, or use
@@ -46,9 +49,8 @@
4649
* open at once, each open SSE stream holding one; applied by the listener only, since a
4750
* servlet container configures its own HTTP/2; default 1024
4851
* @param shutdownTimeout how long closing the endpoint
49-
* ({@link StreamableHttpAcpServlet#closeGracefully()},
50-
* {@link StreamableHttpAcpAgentTransport#closeGracefully()} and
51-
* {@link StreamableHttpAcpServlet#destroy()}) waits for its connections'
52+
* ({@code closeGracefully()} of the endpoint, the servlet and the listener, and the servlet's
53+
* {@code destroy()}) waits for its connections'
5254
* agents to close before it closes the rest at once; closing never waits for a client, since
5355
* the SSE responses are completed, not drained; default 5 seconds
5456
* @param executor the application's executor that the listener's Jetty server runs its tasks
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright 2025-2026 the original author or authors.
3+
*/
4+
5+
/**
6+
* The options of the ACP Streamable HTTP and WebSocket endpoint (acp-http-core); this module's
7+
* part of the package shared with acp-core, acp-http-servlet and acp-streamable-http-jetty.
8+
*
9+
* <p>
10+
* The package is null-marked: every type is non-null unless annotated
11+
* {@link org.jspecify.annotations.Nullable @Nullable}.
12+
* </p>
13+
*/
14+
@NullMarked
15+
package com.agentclientprotocol.sdk.agent.transport;
16+
17+
import org.jspecify.annotations.NullMarked;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2025-2026 the original author or authors.
3+
*/
4+
5+
package com.agentclientprotocol.sdk.http.server;
6+
7+
import java.util.function.Consumer;
8+
9+
import com.agentclientprotocol.sdk.agent.AcpAgentFactory;
10+
import com.agentclientprotocol.sdk.agent.transport.StreamableHttpAcpAgentTransportOptions;
11+
import com.agentclientprotocol.sdk.annotation.UnstableAcpApi;
12+
import com.agentclientprotocol.sdk.json.AcpJsonMapper;
13+
import reactor.core.publisher.Mono;
14+
15+
/**
16+
* The ACP Streamable HTTP and WebSocket endpoint, independent of any server: one per mounted
17+
* path, holding every client connection on it and creating one agent per connection with an
18+
* {@link AcpAgentFactory}. A host (a servlet, a framework's router) mounts it and adapts I/O;
19+
* every protocol rule is here.
20+
*
21+
* <p>A host:
22+
* <ul>
23+
* <li>hands every request on the path to {@link #handle}, or a WebSocket upgrade request
24+
* ({@link AcpHttpExchange#isWebSocketUpgrade()}) to {@link #webSocketHandshake}, and writes the
25+
* answer as it is;</li>
26+
* <li>calls {@link #start()} when its server starts serving the path, and
27+
* {@link #closeGracefully()} <em>before</em> its server's own graceful shutdown, since open SSE
28+
* streams and WebSockets never finish by themselves;</li>
29+
* <li>never blocks an I/O thread on the endpoint: {@link #handle} returns a {@link Mono} and
30+
* the agent's work runs on the SDK's schedulers.</li>
31+
* </ul>
32+
*
33+
* <p>The endpoint answers 403 to a request whose {@code Origin} is neither a loopback origin nor
34+
* listed in {@link StreamableHttpAcpAgentTransportOptions#allowedOrigins()}, over HTTP and on the
35+
* handshake. It has no authentication: the host's framework security protects the path, and
36+
* hands the principal over in {@link AcpHttpExchange#principal()}.
37+
*
38+
* @author Mark Pollack
39+
*/
40+
@UnstableAcpApi
41+
public interface AcpHttpEndpoint {
42+
43+
/**
44+
* Creates an endpoint.
45+
* @param jsonMapper the mapper that reads and writes the messages
46+
* @param agentFactory creates the agent for each connection
47+
* @param options the endpoint's limits, timings and allowed origins
48+
* @return the endpoint, not started
49+
* @throws IllegalArgumentException if an argument is null
50+
*/
51+
static AcpHttpEndpoint create(AcpJsonMapper jsonMapper, AcpAgentFactory agentFactory,
52+
StreamableHttpAcpAgentTransportOptions options) {
53+
return new DefaultAcpHttpEndpoint(jsonMapper, agentFactory, options);
54+
}
55+
56+
/**
57+
* Returns the options the endpoint was created with; a host applies the ones that concern
58+
* its server, such as the inbound size limit.
59+
* @return the options
60+
*/
61+
StreamableHttpAcpAgentTransportOptions options();
62+
63+
/**
64+
* Answers one HTTP request: a POST carrying a JSON-RPC message, a GET opening an SSE stream,
65+
* or a DELETE closing a connection. The returned Mono never errors: every refusal, and any
66+
* unexpected failure (500), is a reply.
67+
* @param exchange the request
68+
* @return the reply to write
69+
*/
70+
Mono<AcpHttpReply> handle(AcpHttpExchange exchange);
71+
72+
/**
73+
* Decides a WebSocket upgrade request: refused (with the reply to write) or accepted.
74+
* @param handshake the upgrade request
75+
* @return the decision
76+
*/
77+
AcpWsHandshake webSocketHandshake(AcpHttpExchange handshake);
78+
79+
/**
80+
* Starts the SSE keep-alive. Idempotent; the endpoint serves requests without it too.
81+
*/
82+
void start();
83+
84+
/**
85+
* Closes every connection: new connections are refused (503), an {@code initialize} still in
86+
* flight is answered 503, open SSE streams get a closing comment and complete, WebSockets are
87+
* closed with 1001 (going away), and each connection's agent is closed, cancelling in-flight
88+
* prompts. Nothing waits for a client. A connection whose agent has not closed within the
89+
* {@linkplain StreamableHttpAcpAgentTransportOptions#shutdownTimeout() shutdown timeout} is
90+
* closed at once. Only the first call has an effect.
91+
* @return a Mono that completes when every connection has closed
92+
*/
93+
Mono<Void> closeGracefully();
94+
95+
/**
96+
* Returns the number of open connections, HTTP and WebSocket, not counting those whose
97+
* {@code initialize} is still being answered.
98+
* @return the connection count
99+
*/
100+
int activeConnectionCount();
101+
102+
/**
103+
* Sets the handler for the transport errors of every connection, including those opened
104+
* before the call. The default logs them.
105+
* @param handler receives the connections' transport errors
106+
*/
107+
void setExceptionHandler(Consumer<Throwable> handler);
108+
109+
}

0 commit comments

Comments
 (0)