Migrating from Jetty 11.0.x to Jetty 12.0.x
Maven Artifacts Changes
Jetty 11.0.x | Jetty 12.0.x |
---|---|
org.eclipse.jetty.fcgi:fcgi-client |
org.eclipse.jetty.fcgi:jetty-fcgi-client |
org.eclipse.jetty.fcgi:fcgi-server |
org.eclipse.jetty.fcgi:jetty-fcgi-server |
org.eclipse.jetty.http2:http2-client |
org.eclipse.jetty.http2:jetty-http2-client |
org.eclipse.jetty.http2:http2-common |
org.eclipse.jetty.http2:jetty-http2-common |
org.eclipse.jetty.http2:http2-hpack |
org.eclipse.jetty.http2:jetty-http2-hpack |
org.eclipse.jetty.http2:http2-http-client-transport |
org.eclipse.jetty.http2:jetty-http2-client-transport |
org.eclipse.jetty.http2:http2-server |
org.eclipse.jetty.http2:jetty-http2-server |
org.eclipse.jetty.http3:http3-client |
org.eclipse.jetty.http3:jetty-http3-client |
org.eclipse.jetty.http3:http3-common |
org.eclipse.jetty.http3:jetty-http3-common |
org.eclipse.jetty.http3:http3-http-client-transport |
org.eclipse.jetty.http3:jetty-http3-client-transport |
org.eclipse.jetty.http3:http3-qpack |
org.eclipse.jetty.http3:jetty-http3-qpack |
org.eclipse.jetty.http3:http3-server |
org.eclipse.jetty.http3:jetty-http3-server |
org.eclipse.jetty:jetty-osgi.* |
|
org.eclipse.jetty:jetty-proxy |
|
org.eclipse.jetty.quic:quic-client |
org.eclipse.jetty.quic:jetty-quic-client |
org.eclipse.jetty.quic:quic-common |
org.eclipse.jetty.quic:jetty-quic-common |
org.eclipse.jetty.quic:quic-quiche |
org.eclipse.jetty.quic:jetty-quic-quiche |
org.eclipse.jetty.quic:quic-server |
org.eclipse.jetty.quic:jetty-quic-server |
org.eclipse.jetty:jetty-unixsocket.* |
Removed — Use org.eclipse.jetty:jetty-unixdomain-server |
org.eclipse.jetty.websocket:websocket-core-client |
org.eclipse.jetty.websocket:jetty-websocket-core-client |
org.eclipse.jetty.websocket:websocket-core-common |
org.eclipse.jetty.websocket:jetty-websocket-core-common |
org.eclipse.jetty.websocket:websocket-core-server |
org.eclipse.jetty.websocket:jetty-websocket-core-server |
org.eclipse.jetty.websocket:websocket-jetty-api |
org.eclipse.jetty.websocket:jetty-websocket-jetty-api |
org.eclipse.jetty.websocket:websocket-jetty-client |
|
org.eclipse.jetty.websocket:websocket-jetty-common |
|
org.eclipse.jetty.websocket:websocket-jetty-server |
|
org.eclipse.jetty.websocket:websocket-jakarta-client |
org.eclipse.jetty.ee{8,9,10}.websocket:jetty-ee{8,9,10}-websocket-jakarta-client |
org.eclipse.jetty.websocket:websocket-jakarta-common |
org.eclipse.jetty.ee{8,9,10}.websocket:jetty-ee{8,9,10}-websocket-jakarta-common |
org.eclipse.jetty.websocket:websocket-jakarta-server |
org.eclipse.jetty.ee{8,9,10}.websocket:jetty-ee{8,9,10}-websocket-jakarta-server |
org.eclipse.jetty.websocket:websocket-servlet |
org.eclipse.jetty.ee{8,9,10}.websocket:jetty-ee{8,9,10}-websocket-servlet |
org.eclipse.jetty:apache-jsp |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-apache-jsp |
org.eclipse.jetty:jetty-annotations |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-annotations |
org.eclipse.jetty:jetty-ant |
Removed — No Replacement |
org.eclipse.jetty:jetty-cdi |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-cdi |
org.eclipse.jetty:glassfish-jstl |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-glassfish-jstl |
org.eclipse.jetty:jetty-jaspi |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-jaspi |
org.eclipse.jetty:jetty-jndi |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-jndi |
org.eclipse.jetty:jetty-jspc-maven-plugin |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-jspc-maven-plugin |
org.eclipse.jetty:jetty-maven-plugin |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-maven-plugin |
org.eclipse.jetty:jetty-plus |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-plus |
org.eclipse.jetty:jetty-quickstart |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-quickstart |
org.eclipse.jetty:jetty-runner |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-runner |
org.eclipse.jetty:jetty-servlet |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-servlet |
org.eclipse.jetty:jetty-servlets |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-servlets |
org.eclipse.jetty:jetty-webapp |
org.eclipse.jetty.ee{8,9,10}:jetty-ee{8,9,10}-webapp |
Class Packages/Names Changes
Jetty 11.0.x | Jetty 12.0.x |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Server-Side Web Application APIs Changes
Jetty 12 introduced redesigned server-side APIs for web applications. In Jetty 11, these APIs were based on a mix of Jakarta Servlet APIs and Jetty Handler APIs, while in Jetty 12 they are solely based on Jetty Handler APIs.
In Jetty 12 you can now write web applications independently of the Servlet APIs, so you can migrate Jakarta Servlets to Jetty Handlers as explained in this section.
If you were already using the Jetty 11 Handler APIs, you can migrate them to the Jetty 12 Handler APIs as explained in this section.
Migrate Servlets to Jetty Handlers
Web applications written using the Servlet APIs may be re-written using the Jetty Handler
APIs.
The sections below outline the Jetty Handler
APIs that correspond to the Servlet APIs.
For more information about why using the Jetty Handler
APIs instead of the Servlet APIs, refer to this section.
For more information about replacing HttpServlet
s or Servlet Filter
s with Jetty Handler
s, refer to this section.
Handler Request APIs
public class RequestAPIs extends Handler.Abstract
{
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
// Gets the request method.
// Replaces:
// - servletRequest.getMethod();
String method = request.getMethod();
// Gets the request protocol name and version.
// Replaces:
// - servletRequest.getProtocol();
String protocol = request.getConnectionMetaData().getProtocol();
// Gets the request URL.
// Replaces:
// - servletRequest.getRequestURL();
HttpURI httpURI = HttpURI.build(request.getHttpURI()).query(null);
StringBuffer requestURL = new StringBuffer(httpURI.asString());
// Gets the request context.
// Replaces:
// - servletRequest.getServletContext()
Context context = request.getContext();
// Gets the context path.
// Replaces:
// - servletRequest.getContextPath()
String contextPath = context.getContextPath();
// Gets the request path.
// Replaces:
// - servletRequest.getRequestURI();
String requestPath = request.getHttpURI().getPath();
// Gets the request path after the context path.
// Replaces:
// - servletRequest.getServletPath() + servletRequest.getPathInfo()
String pathInContext = Request.getPathInContext(request);
// Gets the request query.
// Replaces:
// - servletRequest.getQueryString()
String queryString = request.getHttpURI().getQuery();
// Gets request parameters.
// Replaces:
// - servletRequest.getParameterNames();
// - servletRequest.getParameter(name);
// - servletRequest.getParameterValues(name);
// - servletRequest.getParameterMap();
Fields queryParameters = Request.extractQueryParameters(request, UTF_8);
Fields allParameters = Request.getParameters(request);
// Gets cookies.
// Replaces:
// - servletRequest.getCookies();
List<HttpCookie> cookies = Request.getCookies(request);
// Gets request HTTP headers.
// Replaces:
// - servletRequest.getHeaderNames()
// - servletRequest.getHeader(name)
// - servletRequest.getHeaders(name)
// - servletRequest.getDateHeader(name)
// - servletRequest.getIntHeader(name)
HttpFields requestHeaders = request.getHeaders();
// Gets the request Content-Type.
// Replaces:
// - servletRequest.getContentType()
String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE);
// Gets the request Content-Length.
// Replaces:
// - servletRequest.getContentLength()
// - servletRequest.getContentLengthLong()
long contentLength = request.getLength();
// Gets the request locales.
// Replaces:
// - servletRequest.getLocale()
// - servletRequest.getLocales()
List<Locale> locales = Request.getLocales(request);
// Gets the request scheme.
// Replaces:
// - servletRequest.getScheme()
String scheme = request.getHttpURI().getScheme();
// Gets the server name.
// Replaces:
// - servletRequest.getServerName()
String serverName = Request.getServerName(request);
// Gets the server port.
// Replaces:
// - servletRequest.getServerPort()
int serverPort = Request.getServerPort(request);
// Gets the remote host/address.
// Replaces:
// - servletRequest.getRemoteAddr()
// - servletRequest.getRemoteHost()
String remoteAddress = Request.getRemoteAddr(request);
// Gets the remote port.
// Replaces:
// - servletRequest.getRemotePort()
int remotePort = Request.getRemotePort(request);
// Gets the local host/address.
// Replaces:
// - servletRequest.getLocalAddr()
// - servletRequest.getLocalHost()
String localAddress = Request.getLocalAddr(request);
// Gets the local port.
// Replaces:
// - servletRequest.getLocalPort()
int localPort = Request.getLocalPort(request);
// Gets the request attributes.
// Replaces:
// - servletRequest.getAttributeNames()
// - servletRequest.getAttribute(name)
// - servletRequest.setAttribute(name, value)
// - servletRequest.removeAttribute(name)
String name = "name";
Object value = "value";
Set<String> names = request.getAttributeNameSet();
Object attribute = request.getAttribute(name);
Object oldValue = request.setAttribute(name, value);
Object removedValue = request.removeAttribute(name);
request.clearAttributes();
Map<String, Object> map = request.asAttributeMap();
// Gets the request trailers.
// Replaces:
// - servletRequest.getTrailerFields()
HttpFields trailers = request.getTrailers();
// Gets the HTTP session.
// Replaces:
// - servletRequest.getSession()
// - servletRequest.getSession(create)
boolean create = true;
Session session = request.getSession(create);
callback.succeeded();
return false;
}
}
Handler Request Content APIs
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
// Non-blocking read the request content as a String.
// Use with caution as the request content may be large.
CompletableFuture<String> completable = Content.Source.asStringAsync(request, UTF_8);
completable.whenComplete((requestContent, failure) ->
{
if (failure == null)
{
// Process the request content here.
// Implicitly respond with status code 200 and no content.
callback.succeeded();
}
else
{
// Implicitly respond with status code 500.
callback.failed(failure);
}
});
return true;
}
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
// Non-blocking read the request content as a ByteBuffer.
// Use with caution as the request content may be large.
CompletableFuture<ByteBuffer> completable = Content.Source.asByteBufferAsync(request);
completable.whenComplete((requestContent, failure) ->
{
if (failure == null)
{
// Process the request content here.
// Implicitly respond with status code 200 and no content.
callback.succeeded();
}
else
{
// Implicitly respond with status code 500.
callback.failed(failure);
}
});
return true;
}
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
// Read the request content as an InputStream.
// Note that InputStream.read() may block.
try (InputStream inputStream = Content.Source.asInputStream(request))
{
while (true)
{
int read = inputStream.read();
// EOF was reached, stop reading.
if (read < 0)
break;
// Process the read byte here.
}
}
// Implicitly respond with status code 200 and no content.
callback.succeeded();
return true;
}
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
CompletableTask<Void> reader = new CompletableTask<>()
{
@Override
public void run()
{
// Read in a loop.
while (true)
{
// Read a chunk of content.
Content.Chunk chunk = request.read();
// If there is no content, demand to be
// called back when more content is available.
if (chunk == null)
{
request.demand(this);
return;
}
// If a failure is read, complete with a failure.
if (Content.Chunk.isFailure(chunk))
{
Throwable failure = chunk.getFailure();
completeExceptionally(failure);
return;
}
if (chunk instanceof Trailers trailers)
{
// Possibly process the request trailers here.
// Trailers have an empty ByteBuffer and are a last chunk.
}
// Process the request content chunk here.
// After the processing, the chunk MUST be released.
chunk.release();
// If the last chunk is read, complete normally.
if (chunk.isLast())
{
complete(null);
return;
}
// Not the last chunk of content, loop around to read more.
}
}
};
// Initiate the read of the request content.
reader.start();
// When the read is complete, complete the Handler callback.
callback.completeWith(reader);
return true;
}
Refer also to the Content.Source
APIs detailed in this section.
Handler Response APIs
public class ResponseAPIs extends Handler.Abstract
{
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
// Sets/Gets the response HTTP status.
// Replaces:
// - servletResponse.setStatus(code);
// - servletResponse.getStatus();
response.setStatus(HttpStatus.OK_200);
int status = response.getStatus();
// Gets the response HTTP headers.
// Replaces:
// - servletResponse.setHeader(name, value);
// - servletResponse.addHeader(name, value);
// - servletResponse.setDateHeader(name, date);
// - servletResponse.addDateHeader(name, date);
// - servletResponse.setIntHeader(name, value);
// - servletResponse.addIntHeader(name, value);
// - servletResponse.getHeaderNames()
// - servletResponse.getHeader(name)
// - servletResponse.getHeaders(name)
// - servletResponse.containsHeader(name)
HttpFields.Mutable responseHeaders = response.getHeaders();
// Sets an HTTP cookie.
// Replaces:
// - Cookie cookie = new Cookie("name", "value");
// - cookie.setDomain("example.org");
// - cookie.setPath("/path");
// - cookie.setMaxAge(24 * 3600);
// - cookie.setAttribute("SameSite", "Lax");
// - servletResponse.addCookie(cookie);
HttpCookie cookie = HttpCookie.build("name", "value")
.domain("example.org")
.path("/path")
.maxAge(Duration.ofDays(1).toSeconds())
.sameSite(HttpCookie.SameSite.LAX)
.build();
Response.addCookie(response, cookie);
// Sets the response Content-Type.
// Replaces:
// - servletResponse.setContentType(type)
responseHeaders.put(HttpHeader.CONTENT_TYPE, "text/plain; charset=UTF-8");
// Sets the response Content-Length.
// Replaces:
// - servletResponse.setContentLength(length)
// - servletResponse.setContentLengthLong(length)
responseHeaders.put(HttpHeader.CONTENT_LENGTH, 1024L);
// Sets/Gets the response trailers.
// Replaces:
// - servletResponse.setTrailerFields(() -> trailers)
// - servletResponse.getTrailerFields()
HttpFields trailers = HttpFields.build().put("checksum", 0xCAFE);
response.setTrailersSupplier(trailers);
Supplier<HttpFields> trailersSupplier = response.getTrailersSupplier();
// Gets whether the response is committed.
// Replaces:
// - servletResponse.isCommitted()
boolean committed = response.isCommitted();
// Resets the response.
// Replaces:
// - servletResponse.reset();
response.reset();
// Sends a redirect response.
// Replaces:
// - servletResponse.encodeRedirectURL(location)
// - servletResponse.sendRedirect(location)
String location = Request.toRedirectURI(request, "/redirect");
Response.sendRedirect(request, response, callback, location);
// Sends an error response.
// Replaces:
// - servletResponse.sendError(code);
// - servletResponse.sendError(code, message);
Response.writeError(request, response, callback, HttpStatus.SERVICE_UNAVAILABLE_503, "Request Cannot be Processed");
callback.succeeded();
return true;
}
}
Handler Response Content APIs
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
// Produces an implicit response with status code 200
// with no content when returning from this method.
// The Handler callback must be completed when returning true.
callback.succeeded();
return true;
}
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
// Produces an implicit response with status 204
// with no content when returning from this method.
response.setStatus(HttpStatus.NO_CONTENT_204);
// The Handler callback must be completed when returning true.
callback.succeeded();
return true;
}
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
// Produces an explicit response with status 204 with no content.
response.setStatus(HttpStatus.NO_CONTENT_204);
// This explicit first write() writes the response status code and headers.
// It is also the last write (as specified by the first parameter)
// and writes an empty content (the second parameter, a null ByteBuffer).
// When this write completes, the Handler callback is completed.
response.write(true, null, callback);
return true;
}
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
response.setStatus(HttpStatus.OK_200);
ByteBuffer content = UTF_8.encode("Hello World");
// Explicit first write that writes the response status code, headers and content.
// When this write completes, the Handler callback is completed.
response.write(true, content, callback);
return true;
}
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
response.setStatus(HttpStatus.OK_200);
ByteBuffer content = UTF_8.encode("Hello World");
response.getHeaders().put(HttpHeader.CONTENT_LENGTH, content.remaining());
// Flush the response status code and the headers (no content).
// This is the fist but non-last write.
Callback.Completable completable = new Callback.Completable();
response.write(false, null, completable);
// When the first write completes, perform the second (and last) write.
completable.whenComplete((ignored, failure) ->
{
if (failure == null)
{
// Now explicitly write the content as the last write.
// When this write completes, the Handler callback is completed.
response.write(true, content, callback);
}
else
{
// Implicitly respond with status code 500.
callback.failed(failure);
}
});
return true;
}
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
response.setStatus(HttpStatus.OK_200);
// Utility method to write UTF-8 string content.
// When this write completes, the Handler callback is completed.
Content.Sink.write(response, true, "Hello World", callback);
return true;
}
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
response.setStatus(HttpStatus.OK_200);
// Utility method to echo the content from the request to the response.
// When the echo completes, the Handler callback is completed.
Content.copy(request, response, callback);
return true;
}
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
response.setStatus(HttpStatus.OK_200);
// The trailers must be set on the response before the first write.
HttpFields.Mutable trailers = HttpFields.build();
response.setTrailersSupplier(trailers);
// Explicit first write that writes the response status code, headers and content.
// The trailers have not been written yet; they will be written with the last write.
ByteBuffer content = UTF_8.encode("Hello World");
Callback.Completable completable = new Callback.Completable();
response.write(false, content, completable);
completable.whenComplete((ignored, failure) ->
{
if (failure == null)
{
// Update the trailers
trailers.put("Content-Checksum", 0xCAFE);
// Explicit last write to write the trailers
// and complete the Handler callback.
response.write(true, null, callback);
}
else
{
// Implicitly respond with status code 500.
callback.failed(failure);
}
});
return true;
}
Refer also to the Content.Sink
APIs detailed in this section.
APIs Changes
Handler
The server-side Handler
class, and the APIs to use for request/response processing, have been redesigned in Jetty 12.
The Jetty 11 Handler
method:
Handler.handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
has been changed in Jetty 12 to:
Handler.handle(Request request, Response response, Callback callback)
The Jetty 11 target
parameter has been removed, and in Jetty 12 it has been replaced by the information present in Request.getHttpURI()
.
In Jetty 11, Handler
s would mark the fact that they handled the request, and therefore are producing a response, by calling Request.setHandled(true)
.
In Jetty 12, this is performed by returning true
from the Handler.handle(...)
method, which also requires that the Callback
parameter must be completed, either by succeeding it or failing it.
In Jetty 11, the Handler.handle(...)
method has a blocking semantic, while in Jetty 12 the Handler.handle(...)
method has an asynchronous semantic, thanks to the Callback
parameter.
This means that you can return from the Handler.handle(...)
method before the response has been sent, similarly to what you can do with the Servlet APIs when you call HttpServletRequest.startAsync()
.
Similarly, in Jetty 11 after a call to startAsync()
you must call AsyncContext.complete()
, while in Jetty 12 you must complete the Callback
parameter, either by succeeding it or failing it.
In Jetty 11, AbstractHandler
provides a utility class to implement Handler
.
In Jetty 12, use Handler.Abstract
.
In Jetty 11, the APIs to deal with request or response HTTP headers are based on either Jetty’s HttpFields
, or the Servlet APIs.
In Jetty 12, the HTTP headers API are only based on HttpFields
.
Please refer to the HttpFields
javadocs for details.
In Jetty 11, the request content is accessed via Request.getInputStream()
or HttpServletRequest.getInputStream()
.
In Jetty 12, the Request
object itself is-a Content.Source
that can be read as explained in this section.
In Jetty 12, you can use Content.Source.asInputStream(request)
to obtain an InputStream
and minimize the changes to your code, but remember that InputStream
only provides blocking APIs, while Content.Source
provides non-blocking APIs.
In Jetty 11, the response content is accessed via Response.getOutputStream()
or HttpServletResponse.getOutputStream()
.
In Jetty 12, the Response
object itself is-a Content.Sink
that can be written as explained in this section.
In Jetty 12, you can use Content.Sink.asOutputStream(response)
to obtain an OutputStream
and minimize the changes to your code, but remember that OutputStream
only provides blocking APIs, while Content.Sink
provides non-blocking APIs.
HttpClient
The Jetty 11 Request.onResponseContentDemanded(Response.DemandedContentListener)
API has been replaced by Request.onResponseContentSource(Response.ContentSourceListener)
in Jetty 12.
However, also look at Request.onResponseContentAsync(Response.AsyncContentListener)
and Request.onResponseContent(Response.ContentListener)
for simpler usages.
The Jetty 11 model was a "demand+push" model: the application was demanding content; when the content was available, the implementation was pushing content to the application by calling DemandedContentListener.onContent(Response, LongConsumer, ByteBuffer, Callback)
for every content chunk.
The Jetty 12 model is a "demand+pull" model: when the content is available, the implementation calls once Response.ContentSourceListener.onContentSource(Content.Source)
; the application can then pull the content chunks from the Content.Source
.
For more information about the new model, see this section.
Jetty 12 introduced the concept of low-level transport for high-level protocols, described in this section.
WebSocket
The Jetty WebSocket APIs have been vastly simplified, and brought in line with the style of other APIs.
The Jetty 12 WebSocket APIs are now fully asynchronous, so the Jetty 11 SuspendToken
class has been removed in favor of an explicit (or automatic) demand mechanism in Jetty 12 (for more information, refer to this section).
The various Jetty 11 WebSocket*Listener
interfaces have been replaced by a single interface in Jetty 12, Session.Listener.AutoDemanding
(for more information, refer to this section).
The Jetty 11 RemoteEndpoint
APIs have been merged into the Session
APIs in Jetty 12.
The Jetty 11 WriteCallback
class has been renamed to just Callback
in Jetty 12, because it is now also used when receiving binary data.
Note that this Callback
interface is a different interface from the org.eclipse.jetty.util.Callback
interface, which cannot be used in the Jetty WebSocket APIs due to class loader visibility issues.
On the server-side, the Jetty WebSocket APIs have been made independent of the Servlet APIs.
Jetty 11 JettyWebSocketServerContainer
has been replaced by ServerWebSocketContainer
in Jetty 12, with similar APIs (for more information, refer to this section).
On the client-side the WebSocketClient
APIs are practically unchanged, as most of the changes come from the HttpClient
changes described above.