Documented WebSocketClient.
Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
3b88b4713c
commit
8b10f3ebd5
|
@ -222,5 +222,10 @@
|
|||
<artifactId>jetty-nosql</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-jetty-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -16,12 +16,4 @@
|
|||
|
||||
These pages are works in progress that have not been moved to their respective sections yet.
|
||||
|
||||
include::websocket-jetty-api.adoc[]
|
||||
include::websocket-jetty-api-events.adoc[]
|
||||
include::websocket-jetty-api-session.adoc[]
|
||||
include::websocket-jetty-api-send-message.adoc[]
|
||||
include::websocket-jetty-api-annotations.adoc[]
|
||||
include::websocket-jetty-api-listener.adoc[]
|
||||
include::websocket-jetty-api-adapter.adoc[]
|
||||
include::websocket-jetty-server-api.adoc[]
|
||||
include::websocket-jetty-client-api.adoc[]
|
||||
include::jetty-websocket-server-api.adoc[]
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
[[websocket-jetty-api-adapter]]
|
||||
=== Using the WebSocketAdapter
|
||||
|
||||
A basic adapter for managing the Session object on the WebSocketListener.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
include::{SRCDIR}/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AdapterEchoSocket.java[]
|
||||
----
|
||||
|
||||
This is a convenience class to make using the WebSocketListener easier, and provides some useful methods to check the state of the Session.
|
|
@ -1,104 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
[[websocket-jetty-api-annotations]]
|
||||
=== Using WebSocket Annotations
|
||||
|
||||
The most basic form of WebSocket is a marked up POJO with annotations
|
||||
provided by the Jetty WebSocket API.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
include::{SRCDIR}/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AnnotatedEchoSocket.java[]
|
||||
----
|
||||
|
||||
The above example is a simple WebSocket echo endpoint that will echo back any TEXT messages it receives.
|
||||
|
||||
This implementation is using a stateless approach to a Echo socket, as the Session is being passed into the Message event as the event occurs.
|
||||
This would allow you to reuse the single instance of the AnnotatedEchoSocket for working with multiple endpoints.
|
||||
|
||||
The annotations you have available:
|
||||
|
||||
link:{JDURL}/org/eclipse/jetty/websocket/api/annotations/WebSocket.html[@WebSocket]::
|
||||
A required class level annotation.
|
||||
+
|
||||
Flags this POJO as being a WebSocket.
|
||||
+
|
||||
The class must be not abstract and public.
|
||||
link:{JDURL}/org/eclipse/jetty/websocket/api/annotations/OnWebSocketConnect.html[@OnWebSocketConnect]::
|
||||
An optional method level annotation.
|
||||
+
|
||||
Flags one method in the class as receiving the On Connect event.
|
||||
+
|
||||
Method must be public, not abstract, return void, and have a single link:{JDURL}/org/eclipse/jetty/websocket/api/Session.html[Session] parameter.
|
||||
|
||||
link:{JDURL}/org/eclipse/jetty/websocket/api/annotations/OnWebSocketClose.html[@OnWebSocketClose]::
|
||||
An optional method level annotation.
|
||||
+
|
||||
Flags one method in the class as receiving the On Close event.
|
||||
+
|
||||
Method signature must be public, not abstract, and return void.
|
||||
+
|
||||
The method parameters:
|
||||
+
|
||||
. link:{JDURL}/org/eclipse/jetty/websocket/api/Session.html[`Session`] (optional)
|
||||
. `int closeCode` (required)
|
||||
. `String closeReason` (required)
|
||||
|
||||
link:{JDURL}/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.html[@OnWebSocketMessage]::
|
||||
An optional method level annotation.
|
||||
+
|
||||
Flags up to 2 methods in the class as receiving On Message events.
|
||||
+
|
||||
You can have 1 method for TEXT messages, and 1 method for BINARY messages.
|
||||
+
|
||||
Method signature must be public, not abstract, and return void.
|
||||
+
|
||||
The method parameters for Text messages:
|
||||
+
|
||||
* link:{JDURL}/org/eclipse/jetty/websocket/api/Session.html[`Session`] (optional)
|
||||
* `String text` (required)
|
||||
+
|
||||
The method parameters for Binary messages:
|
||||
+
|
||||
* link:{JDURL}/org/eclipse/jetty/websocket/api/Session.html[`Session`] (optional)
|
||||
* `byte buf[]` (required)
|
||||
* `int offset` (required)
|
||||
* `int length` (required)
|
||||
|
||||
link:{JDURL}/org/eclipse/jetty/websocket/api/annotations/OnWebSocketError.html[@OnWebSocketError]::
|
||||
An optional method level annotation.
|
||||
+
|
||||
Flags one method in the class as receiving Error events from the WebSocket implementation.
|
||||
+
|
||||
Method signatures must be public, not abstract, and return void.
|
||||
+
|
||||
The method parameters:
|
||||
+
|
||||
1. link:{JDURL}/org/eclipse/jetty/websocket/api/Session.html[`Session`] (optional)
|
||||
2. `Throwable cause` (required)
|
||||
|
||||
link:{JDURL}/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.html[@OnWebSocketFrame]::
|
||||
An optional method level annotation.
|
||||
+
|
||||
Flags one method in the class as receiving Frame events from the WebSocket implementation after they have been processed by any extensions declared during the Upgrade handshake.
|
||||
+
|
||||
Method signatures must be public, not abstract, and return void.
|
||||
+
|
||||
The method parameters:
|
||||
+
|
||||
1. link:{JDURL}/org/eclipse/jetty/websocket/api/Session.html[`Session`] (optional)
|
||||
2. link:{JDURL}/org/eclipse/jetty/websocket/api/extensions/Frame.html[`Frame`] (required)
|
||||
+
|
||||
The Frame received will be notified on this method, then be processed by Jetty, possibly resulting in another event, such as On Close, or On Message.
|
||||
Changes to the Frame will not be seen by Jetty.
|
|
@ -1,44 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
[[websocket-jetty-api-events]]
|
||||
=== WebSocket Events
|
||||
|
||||
Every WebSocket can receive various events:
|
||||
|
||||
On Connect Event::
|
||||
An indication to the WebSocket that the Upgrade has succeeded and the WebSocket is now open.
|
||||
+
|
||||
You will receive a link:{JDURL}/org/eclipse/jetty/websocket/api/Session.html[`org.eclipse.jetty.websocket.api.Session`] object that references the specific session for this Open Event.
|
||||
+
|
||||
For normal WebSockets, it is important to hold onto this Session and use it for communicating with the Remote Endpoint.
|
||||
+
|
||||
For Stateless WebSockets, the Session will be passed into each event as it occurs, allowing you to only have 1 instance of a WebSocket serving many Remote Endpoints.
|
||||
|
||||
On Close Event::
|
||||
An indication that the WebSocket is now closed.
|
||||
+
|
||||
Every Close Event will have a link:{JDURL}/org/eclipse/jetty/websocket/api/StatusCode.html[Status Code] (and an optional Closure Reason Message)
|
||||
+
|
||||
A normal WebSocket closure will go through a Close Handshake where both the Local Endpoint and the Remote Endpoint both send a Close frame to indicate that the connection is closed.
|
||||
+
|
||||
It is possible for the Local WebSocket to indicate its desire to Close by issuing a Close frame to the Remote Endpoint, but the Remote Endpoint can continue to send messages until it sends a Close Frame.
|
||||
This is known as a Half-Open connection, and it is important to note that once the Local Endpoint has send the Close Frame it cannot write anymore WebSocket traffic.
|
||||
+
|
||||
On an abnormal closure, such as a connection disconnect or a connection timeout, the low level connection will be terminated without going through a Close Handshake, this will still result in an On Close Event (and likely a corresponding On Error Event).
|
||||
On Error Event::
|
||||
If an error occurred, during the implementation, the WebSocket will be notified via this event handler.
|
||||
On Message Event::
|
||||
An indication that a complete message has been received and is ready for handling by your WebSocket.
|
||||
+
|
||||
This can be a (UTF8) TEXT message or a raw BINARY message.
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
[[websocket-jetty-api-listener]]
|
||||
=== Using WebSocketListener
|
||||
|
||||
The basic form of a WebSocket using the link:{JDURL}/org/eclipse/jetty/websocket/api/WebSocketListener.html[`org.eclipse.jetty.websocket.api.WebSocketListener`] for incoming events.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
include::{SRCDIR}/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/ListenerEchoSocket.java[]
|
||||
----
|
||||
|
||||
This is by far the most basic and best performing (speed and memory wise) WebSocket implementation you can create.
|
||||
If the listener is too much work for you, you can instead opt for the WebSocketAdapter
|
|
@ -1,219 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
[[websocket-jetty-api-send-message]]
|
||||
=== Send Messages to Remote Endpoint
|
||||
|
||||
The most important feature of the Session is access to the link:{JDURL}/org/eclipse/jetty/websocket/api/RemoteEndpoint.html[`org.eclipse.jetty.websocket.api.RemoteEndpoint`] needed to send messages.
|
||||
|
||||
With RemoteEndpoint you can choose to send TEXT or BINARY WebSocket messages, or the WebSocket PING and PONG control frames.
|
||||
|
||||
[[blocking]]
|
||||
==== Blocking Send Message
|
||||
|
||||
Most calls are blocking in nature, and will not return until the send has completed (or has thrown an exception).
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
// Blocking Send of a BINARY message to remote endpoint
|
||||
ByteBuffer buf = ByteBuffer.wrap(new byte[] { 0x11, 0x22, 0x33, 0x44 });
|
||||
try
|
||||
{
|
||||
remote.sendBytes(buf);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
----
|
||||
|
||||
How to send a simple Binary message using the RemoteEndpoint.
|
||||
This will block until the message is sent, possibly throwing an IOException if unable to send the message.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
// Blocking Send of a TEXT message to remote endpoint
|
||||
try
|
||||
{
|
||||
remote.sendString("Hello World");
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
----
|
||||
|
||||
How to send a simple Text message using the RemoteEndpoint.
|
||||
This will block until the message is sent, possibly throwing an IOException if unable to send the message.
|
||||
|
||||
[[partial]]
|
||||
==== Send Partial Message
|
||||
|
||||
If you have a large message to send, and want to send it in pieces and parts, you can utilize the partial message sending methods of RemoteEndpoint.
|
||||
Just be sure you finish sending your message (`isLast == true`).
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
// Blocking Send of a BINARY message to remote endpoint
|
||||
// Part 1
|
||||
ByteBuffer buf1 = ByteBuffer.wrap(new byte[] { 0x11, 0x22 });
|
||||
// Part 2 (last part)
|
||||
ByteBuffer buf2 = ByteBuffer.wrap(new byte[] { 0x33, 0x44 });
|
||||
try
|
||||
{
|
||||
remote.sendPartialBytes(buf1,false);
|
||||
remote.sendPartialBytes(buf2,true); // isLast is true
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
----
|
||||
|
||||
How to send a Binary message in 2 parts, using the partial message support in RemoteEndpoint.
|
||||
This will block until each part of the message is sent, possibly throwing an IOException if unable to send the partial message.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
// Blocking Send of a TEXT message to remote endpoint
|
||||
String part1 = "Hello";
|
||||
String part2 = " World";
|
||||
try
|
||||
{
|
||||
remote.sendPartialString(part1,false);
|
||||
remote.sendPartialString(part2,true); // last part
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
----
|
||||
|
||||
How to send a Text message in 2 parts, using the partial message support in RemoteEndpoint.
|
||||
This will block until each part of the message is sent, possibly throwing an IOException if unable to send the partial message.
|
||||
|
||||
[[websocket-async-send]]
|
||||
==== Async Send Message
|
||||
|
||||
There are also four (4) async send message methods available:
|
||||
|
||||
* link:{JDURL}/org/eclipse/jetty/websocket/api/RemoteEndpoint.html#sendBytes(java.nio.ByteBuffer,org.eclipse.jetty.websocket.api.WriteCallback)[`RemoteEndpoint.sendBytes(ByteBuffer message, WriteCallback callback)`]
|
||||
* link:{JDURL}/org/eclipse/jetty/websocket/api/RemoteEndpoint.html#sendPartialBytes(java.nio.ByteBuffer,boolean,org.eclipse.jetty.websocket.api.WriteCallback)[`RemoteEndpoint.sendPartialBytes(ByteBuffer message, boolean isLast, WriteCallback callback)`]
|
||||
* link:{JDURL}/org/eclipse/jetty/websocket/api/RemoteEndpoint.html#sendString(java.lang.String,org.eclipse.jetty.websocket.api.WriteCallback)[`RemoteEndpoint.sendString(String message, WriteCallback callback)`]
|
||||
* link:{JDURL}/org/eclipse/jetty/websocket/api/RemoteEndpoint.html#sendPartialString(java.lang.String,boolean,org.eclipse.jetty.websocket.api.WriteCallback)[`RemoteEndpoint.sendPartialString(String message, boolean isLast, WriteCallback callback)`]
|
||||
|
||||
All these async send methods use `WriteCallback`, which allows you to be notified when the write either succeeds or fails.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
WriteCallback callback = new WriteCallback()
|
||||
{
|
||||
@Override
|
||||
public void writeSuccess()
|
||||
{
|
||||
// Notification that the write has succeeded.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFailed(Throwable x)
|
||||
{
|
||||
// Notification that the write has failed.
|
||||
t.printStackTrace();
|
||||
}
|
||||
};
|
||||
----
|
||||
|
||||
The async send methods can be used in a similar way to the blocking send methods, however the method will return before the message is transmitted, and you are notified of the final result of the message transmission through the `WriteCallback`.
|
||||
The static `WriteCallback.NOOP` can be used to do nothing on success / failure of the callback.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
// Async Send of a BINARY message to remote endpoint
|
||||
ByteBuffer message = ByteBuffer.wrap(new byte[] { 0x11, 0x22, 0x33, 0x44 });
|
||||
remote.sendBytes(message, callback);
|
||||
----
|
||||
|
||||
|
||||
[[pingpong]]
|
||||
==== Send Ping / Pong Control Frame
|
||||
|
||||
You can also send Ping and Pong control frames using the `RemoteEndpoint`.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
// Blocking Send of a PING to remote endpoint
|
||||
String data = "You There?";
|
||||
ByteBuffer payload = ByteBuffer.wrap(data.getBytes());
|
||||
try
|
||||
{
|
||||
remote.sendPing(payload);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
----
|
||||
|
||||
How to send a Ping control frame, with a payload of `"You There?"` (arriving at Remote Endpoint as a byte array payload).
|
||||
This will block until the message is sent, possibly throwing an `IOException` if unable to send the ping frame.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
// Blocking Send of a PONG to remote endpoint
|
||||
String data = "Yup, I'm here";
|
||||
ByteBuffer payload = ByteBuffer.wrap(data.getBytes());
|
||||
try
|
||||
{
|
||||
remote.sendPong(payload);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
----
|
||||
|
||||
How to send a Pong control frame, with a payload of `"Yup I'm here"` (arriving at Remote Endpoint as a byte array payload).
|
||||
This will block until the message is sent, possibly throwing an `IOException` if unable to send the pong frame.
|
||||
|
||||
To be correct in your usage of Pong frames, you should return the same byte array data that you received in the Ping frame.
|
||||
|
||||
You can also asynchronously send Ping and Pong frames using the `WriteCallback`, this will return before the Ping/Pong is
|
||||
transmitted and notify you of the result in `WriteCallback` `writeSuccess()` or `writeFailed()`.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
String pingData = "You There?";
|
||||
ByteBuffer pingPayload = ByteBuffer.wrap(data.getBytes());
|
||||
|
||||
String pongData = "Yup, I'm here";
|
||||
ByteBuffer pongPayload = ByteBuffer.wrap(data.getBytes());
|
||||
|
||||
remote.sendPing(pingPayload, WriteCallback.NOOP);
|
||||
remote.sendPong(pongPayload, WriteCallback.NOOP);
|
||||
----
|
|
@ -1,60 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
[[websocket-jetty-api-session]]
|
||||
=== WebSocket Session
|
||||
|
||||
The link:{JDURL}/org/eclipse/jetty/websocket/api/Session.html[Session] object can be used to:
|
||||
|
||||
The Connection State (is it open or not).
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
if(session.isOpen()) {
|
||||
// send message
|
||||
}
|
||||
----
|
||||
|
||||
Is the Connection Secure.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
if(session.isSecure()) {
|
||||
// connection is using 'wss://'
|
||||
}
|
||||
----
|
||||
|
||||
What was in the Upgrade Request and Response.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
UpgradeRequest req = session.getUpgradeRequest();
|
||||
String channelName = req.getParameterMap().get("channelName");
|
||||
|
||||
UpgradeResponse resp = session.getUpgradeResponse();
|
||||
String subprotocol = resp.getAcceptedSubProtocol();
|
||||
----
|
||||
|
||||
What is the Local and Remote Address.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
SocketAddress remoteAddr = session.getRemoteAddress();
|
||||
----
|
||||
|
||||
Get and Set the Idle Timeout
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
session.setIdleTimeout(Duration.ofMillis(2000));
|
||||
----
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
[[websocket-jetty-api]]
|
||||
=== Jetty WebSocket API Usage
|
||||
|
||||
Jetty provides its own more powerful WebSocket API, with a common core API for both server and client use of WebSockets.
|
||||
|
||||
It is an event driven API based on WebSocket Messages.
|
|
@ -1,48 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
[[websocket-jetty-client-api]]
|
||||
=== Jetty WebSocket Client API
|
||||
|
||||
Jetty also provides a Jetty WebSocket Client Library to write make talking to WebSocket servers easier.
|
||||
|
||||
To use the Jetty WebSocket Client on your own Java project you will need the following maven artifacts.
|
||||
|
||||
[source, xml, subs="{sub-order}"]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
==== The WebSocketClient
|
||||
|
||||
To use the WebSocketClient you will need to hook up a WebSocket object instance to a specific destination WebSocket URI.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
include::{SRCDIR}/jetty-websocket/websocket-jetty-client/src/test/java/examples/SimpleEchoClient.java[]
|
||||
----
|
||||
|
||||
The above example connects to a remote WebSocket server and hands off a SimpleEchoSocket to perform the logic on the websocket once connected, waiting for the socket to register that it has closed.
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
----
|
||||
include::{SRCDIR}/jetty-websocket/websocket-jetty-client/src/test/java/examples/SimpleEchoSocket.java[]
|
||||
----
|
||||
|
||||
When the SimpleEchoSocket connects, it sends 2 Text messages and then closes the socket.
|
||||
|
||||
The onMessage(String msg) receives the responses from the remote server WebSocket and outputs them to the console.
|
|
@ -14,4 +14,188 @@
|
|||
[[pg-client-websocket]]
|
||||
=== WebSocket Client
|
||||
|
||||
TODO
|
||||
Jetty's `WebSocketClient` is a more powerful alternative to the WebSocket client provided by the standard JSR 356 `javax.websocket` APIs.
|
||||
|
||||
Similarly to Jetty's xref:pg-client-http[`HttpClient`], the `WebSocketClient` is non-blocking and asynchronous, making it very efficient in resource utilization.
|
||||
A synchronous, blocking, API is also offered for simpler cases.
|
||||
|
||||
Since the first step of establishing a WebSocket communication is an HTTP request, `WebSocketClient` makes use of `HttpClient` and therefore depends on it.
|
||||
|
||||
The Maven artifact coordinates are the following:
|
||||
|
||||
[source,xml,subs=normal]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-jetty-client</artifactId>
|
||||
<version>{version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
[[pg-client-websocket-start]]
|
||||
==== Starting WebSocketClient
|
||||
|
||||
The main class is `org.eclipse.jetty.websocket.client.WebSocketClient`; you instantiate it, configure it, and then start it like may other Jetty components.
|
||||
This is a minimal example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java[tags=start]
|
||||
----
|
||||
|
||||
However, it is recommended that you explicitly pass an `HttpClient` instance to `WebSocketClient` so that you can have control over the HTTP configuration as well:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java[tags=startWithHttpClient]
|
||||
----
|
||||
|
||||
You may create multiple instances of `WebSocketClient`, but typically one instance is enough for most applications.
|
||||
Creating multiple instances may be necessary for example when you need to specify different configuration parameters for different instances.
|
||||
|
||||
[[pg-client-websocket-stop]]
|
||||
==== Stopping WebSocketClient
|
||||
|
||||
It is recommended that when your application stops, you also stop the `WebSocketClient` instance (or instances) that you are using.
|
||||
|
||||
Similarly to xref:pg-client-http-stop[stopping `HttpClient`], you want to stop `WebSocketClient` from a thread that is not owned by `WebSocketClient` itself, for example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java[tags=stop]
|
||||
----
|
||||
|
||||
[[pg-client-websocket-connect]]
|
||||
==== Connecting to a Remote Host
|
||||
|
||||
A WebSocket client may initiate the communication with the server either xref:pg-client-websocket-connect-http11[using HTTP/1.1] or xref:pg-client-websocket-connect-http2[using HTTP/2].
|
||||
The two mechanism are quite different and detailed in the following sections.
|
||||
|
||||
[[pg-client-websocket-connect-http11]]
|
||||
===== Using HTTP/1.1
|
||||
|
||||
Initiating a WebSocket communication with a server using HTTP/1.1 is detailed in link:https://tools.ietf.org/html/rfc6455#section-1.8[RFC 6455].
|
||||
|
||||
A WebSocket client first establishes a TCP connection to the server, then sends an HTTP/1.1 _upgrade_ request.
|
||||
|
||||
If the server supports upgrading to WebSocket, it responds with HTTP status code `101`, and then switches the communication over that connection, either incoming or outgoing, to happen using the WebSocket protocol.
|
||||
|
||||
When the client receives the HTTP status code `101`, it switches the communication over that connection, either incoming or outgoing, to happen using the WebSocket protocol.
|
||||
|
||||
[plantuml]
|
||||
----
|
||||
skinparam backgroundColor transparent
|
||||
skinparam monochrome true
|
||||
skinparam shadowing false
|
||||
|
||||
participant ClientEndPoint
|
||||
participant WebSocketClient
|
||||
participant HttpClient
|
||||
participant Server
|
||||
participant ServerEndPoint
|
||||
|
||||
WebSocketClient -> HttpClient : connect()
|
||||
HttpClient -> Server : TCP/TLS connect
|
||||
HttpClient -> Server : GET / HTTP/1.1\nUpgrade: websocket
|
||||
Server -> ServerEndPoint ** : create
|
||||
Server -> HttpClient : HTTP/1.1 101\nUpgrade: websocket
|
||||
HttpClient -> WebSocketClient
|
||||
WebSocketClient -> ClientEndPoint ** : create
|
||||
ClientEndPoint -> WebSocketClient : WebSocket Frame A
|
||||
WebSocketClient -> Server : WebSocket Frame A
|
||||
Server -> ServerEndPoint : WebSocket Frame A
|
||||
ServerEndPoint -> Server : WebSocket Frame B
|
||||
Server -> WebSocketClient : WebSocket Frame B
|
||||
WebSocketClient -> ClientEndPoint : WebSocket Frame B
|
||||
----
|
||||
|
||||
In code:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java[tags=connectHTTP11]
|
||||
----
|
||||
|
||||
`WebSocketClient.connect()` links the client-side WebSocket _endpoint_ to a specific server URI, and returns a `CompletableFuture` of an `org.eclipse.jetty.websocket.api.Session`.
|
||||
|
||||
The endpoint offers APIs to _receive_ WebSocket data (or errors) from the server, while the session offers APIs to _send_ WebSocket data to the server.
|
||||
|
||||
[[pg-client-websocket-connect-http2]]
|
||||
===== Using HTTP/2
|
||||
|
||||
Initiating a WebSocket communication with a server using HTTP/1.1 is detailed in link:https://tools.ietf.org/html/rfc8441[RFC 8441].
|
||||
|
||||
A WebSocket client establishes a TCP connection to the server or reuses an existing one currently used for HTTP/2, then sends an HTTP/2 _connect_ request over an HTTP/2 stream.
|
||||
|
||||
If the server supports upgrading to WebSocket, it responds with HTTP status code `200`, then switches the communication over that stream, either incoming or outgoing, to happen using HTTP/2 `DATA` frames wrapping WebSocket frames.
|
||||
|
||||
When the client receives the HTTP status code `200`, it switches the communication over that stream, either incoming or outgoing, to happen using HTTP/2 `DATA` frames wrapping WebSocket frames.
|
||||
|
||||
From an external point of view, it will look like client is sending chunks of an infinite HTTP/2 request upload, and the server is sending chunks of an infinite HTTP/2 response download, as they will exchange HTTP/2 `DATA` frames; but the HTTP/2 `DATA` frames will contain each one or more WebSocket frames that both client and server know how to deliver to the respective WebSocket endpoints.
|
||||
|
||||
When either WebSocket endpoint decides to terminate the communication, the HTTP/2 stream will be closed as well.
|
||||
|
||||
[plantuml]
|
||||
----
|
||||
skinparam backgroundColor transparent
|
||||
skinparam monochrome true
|
||||
skinparam shadowing false
|
||||
|
||||
participant ClientEndPoint
|
||||
participant WebSocketClient
|
||||
participant HttpClient
|
||||
participant Server
|
||||
participant ServerEndPoint
|
||||
|
||||
WebSocketClient -> HttpClient : connect()
|
||||
HttpClient --> Server : TCP/TLS connect
|
||||
HttpClient -> Server : HEADERS\n:method: CONNECT\n:protocol: websocket
|
||||
Server -> ServerEndPoint ** : create
|
||||
Server -> HttpClient : HEADERS\n:status: 200\n: websocket
|
||||
HttpClient -> WebSocketClient
|
||||
WebSocketClient -> ClientEndPoint ** : create
|
||||
ClientEndPoint -> HttpClient : WebSocket Frame A
|
||||
HttpClient -> Server : DATA\nWebSocket Frame A
|
||||
Server -> ServerEndPoint : WebSocket Frame A
|
||||
ServerEndPoint -> Server : WebSocket Frame B
|
||||
Server -> HttpClient : DATA\nWebSocket Frame B
|
||||
HttpClient -> ClientEndPoint : WebSocket Frame B
|
||||
----
|
||||
|
||||
In code:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java[tags=connectHTTP2]
|
||||
----
|
||||
|
||||
Alternatively, you can use the xref:pg-client-http-transport-dynamic[dynamic `HttpClient` transport]:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java[tags=connectHTTP2Dynamic]
|
||||
----
|
||||
|
||||
[[pg-client-websocket-connect-custom-http-request]]
|
||||
===== Customizing the Initial HTTP Request
|
||||
|
||||
Sometimes you need to add custom cookies, or other HTTP headers, or specify a WebSocket sub-protocol to the HTTP request that initiates the WebSocket communication.
|
||||
|
||||
You can do this by using overloaded versions of the `WebSocketClient.connect(...)` method:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java[tags=customHTTPRequest]
|
||||
----
|
||||
|
||||
[[pg-client-websocket-connect-inspect-http-response]]
|
||||
===== Inspecting the Initial HTTP Response
|
||||
|
||||
If you want to inspect the HTTP response returned by the server as a reply to the HTTP request that initiates the WebSocket communication, you may provide a `JettyUpgradeListener`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java[tags=inspectHTTPResponse]
|
||||
----
|
||||
|
||||
include::../../websocket.adoc[]
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
// Snippets of WebSocket documentation that are common between client and server.
|
||||
|
||||
[[pg-websocket-architecture]]
|
||||
==== WebSocket Architecture
|
||||
|
||||
The Jetty WebSocket architecture is organized around the concept of a logical _connection_ between the client and the server.
|
||||
|
||||
The connection may be physical, when connecting to the server using HTTP/1.1, as the WebSocket bytes are carried directly by the TCP connection.
|
||||
|
||||
The connection may be virtual, when connecting to the server using HTTP/2, as the WebSocket bytes are wrapped into HTTP/2 `DATA` frames of an HTTP/2 stream.
|
||||
In this case, a single TCP connection may carry several WebSocket virtual connections, each wrapped in its own HTTP/2 stream.
|
||||
|
||||
Each side of a WebSocket connection, either client or server, is made of two entities:
|
||||
|
||||
* A xref:pg-websocket-endpoints[WebSocket _endpoint_], the entity that _receives_ WebSocket events.
|
||||
* A xref:pg-websocket-session[WebSocket _session_], the entity that offers an API to _send_ WebSocket data (and to close the WebSocket connection), as well as to configure WebSocket connection parameters.
|
||||
|
||||
[[pg-websocket-endpoints]]
|
||||
==== WebSocket Endpoints
|
||||
|
||||
A WebSocket endpoint is the entity that receives WebSocket events.
|
||||
|
||||
The WebSocket events are the following:
|
||||
|
||||
* The _connect_ event.
|
||||
This event is emitted when the WebSocket communication has been successfully established.
|
||||
Applications interested in the connect event receive the WebSocket _session_ so that they can use it to send data to the remote peer.
|
||||
* The _close_ event.
|
||||
This event is emitted when the WebSocket communication has been closed.
|
||||
// TODO: is this event emitted when the application calls Session.close()?
|
||||
Applications interested in the close event receive a WebSocket status code and an optional close reason message.
|
||||
* The _error_ event.
|
||||
This event is emitted when the WebSocket communication encounters a fatal error, such as an I/O error (for example, the network connection has been broken), or a protocol error (for example, the remote peer sends an invalid WebSocket frame).
|
||||
Applications interested in the error event receive a `Throwable` that represent the error.
|
||||
* The _message_ event.
|
||||
The message event is emitted when a WebSocket message is received.
|
||||
The message event can be of two types:
|
||||
** Textual message event.
|
||||
Applications interested in this type of messages receive a `String` representing the UTF-8 bytes received.
|
||||
** Binary message event.
|
||||
Applications interested in this type of messages receive a `byte[]` representing the raw bytes received.
|
||||
|
||||
[[pg-websocket-endpoints-listener]]
|
||||
===== Listener Endpoints
|
||||
|
||||
A WebSocket endpoint may implement the `org.eclipse.jetty.websocket.api.WebSocketListener` interface to receive WebSocket events:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=listenerEndpoint]
|
||||
----
|
||||
<1> Your listener class implements `WebSocketListener`.
|
||||
|
||||
====== Message Streaming Reads
|
||||
|
||||
If you need to deal with large WebSocket messages, you may reduce the memory usage by streaming the message content.
|
||||
For large WebSocket messages, the memory usage may be large due to the fact that the text or the bytes must be accumulated until the message is complete before delivering the message event.
|
||||
|
||||
To stream textual or binary messages, you must implement interface `org.eclipse.jetty.websocket.api.WebSocketPartialListener` instead of `WebSocketListener`.
|
||||
|
||||
Interface `WebSocketPartialListener` exposes one method for textual messages, and one method to binary messages that receive _chunks_ of, respectively, text and bytes that form the whole WebSocket message.
|
||||
|
||||
You may accumulate the chunks yourself, or process each chunk as it arrives, or stream the chunks elsewhere, for example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=streamingListenerEndpoint]
|
||||
----
|
||||
|
||||
[[pg-websocket-endpoints-annotated]]
|
||||
===== Annotated Endpoints
|
||||
|
||||
A WebSocket endpoint may annotate methods with `org.eclipse.jetty.websocket.api.annotations.*` annotations to receive WebSocket events:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=annotatedEndpoint]
|
||||
----
|
||||
<1> You must annotate the class with the `@WebSocket` annotation to make it an endpoint.
|
||||
<2> For each event you are interested in, use the correspondent annotation.
|
||||
<3> Note how methods annotated with `@OnWebSocketMessage` may optionally declare `Session` as first parameter.
|
||||
|
||||
====== Message Streaming Reads
|
||||
|
||||
If you need to deal with large WebSocket messages, you may reduce the memory usage by streaming the message content.
|
||||
|
||||
To stream textual or binary messages, you still use the `@OnWebSocketMessage` annotation, but you change the signature of the method to take, respectively a `Reader` and an `InputStream`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=streamingAnnotatedEndpoint]
|
||||
----
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
`Reader` or `InputStream` only offer blocking APIs, so if the remote peers are slow in sending the large WebSocket messages, reading threads may be blocked in `Reader.read(char[])` or `InputStream.read(byte[])`, possibly exhausting the thread pool.
|
||||
====
|
||||
|
||||
[[pg-websocket-session]]
|
||||
==== WebSocket Session
|
||||
|
||||
A WebSocket session is the entity that offers an API to send data to the remote peer, to close the WebSocket connection, and to configure WebSocket connection parameters.
|
||||
|
||||
[[pg-websocket-session-configure]]
|
||||
===== Configuring the Session
|
||||
|
||||
You may configure the WebSocket session behavior using the `org.eclipse.jetty.websocket.api.Session` APIs.
|
||||
You want to do this as soon as you have access to the `Session` object, typically from the xref:pg-websocket-endpoints[_connect_ event] handler:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=sessionConfigure]
|
||||
----
|
||||
|
||||
Please refer to the `Session` link:{JDURL}/org/eclipse/jetty/websocket/api/Session.html[javadocs] for the complete list of configuration APIs.
|
||||
|
||||
[[pg-websocket-session-send]]
|
||||
===== Sending Data
|
||||
|
||||
To send data to the remote peer, you need to obtain the `RemoteEndpoint` object from the `Session`, and then use its API to send data.
|
||||
|
||||
`RemoteEndpoint` offers two styles of APIs to send data:
|
||||
|
||||
* Blocking APIs, where the call returns when the data has been sent, or throws an `IOException` if the data cannot be sent.
|
||||
* Non-blocking APIs, where a callback object is notified when the data has been sent, or when there was a failure sending the data.
|
||||
|
||||
[[pg-websocket-session-send-blocking]]
|
||||
====== Blocking APIs
|
||||
|
||||
`RemoteEndpoint` blocking APIs throw `IOException`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=sendBlocking]
|
||||
----
|
||||
|
||||
Blocking APIs are simpler to use since they can be invoked one after the other sequentially.
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
Sending large messages to the remote peer may cause the sending thread to block, possibly exhausting the thread pool.
|
||||
Consider using non-blocking APIs for large messages.
|
||||
====
|
||||
|
||||
[[pg-websocket-session-send-non-blocking]]
|
||||
====== Non-Blocking APIs
|
||||
|
||||
`RemoteEndpoint` non-blocking APIs have an additional callback parameter:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=sendNonBlocking]
|
||||
----
|
||||
<1> Non-blocking APIs require a `WriteCallback` parameter.
|
||||
<2> Note how the second send must be performed from inside the callback.
|
||||
<3> Sequential sends may throw `WritePendingException`.
|
||||
|
||||
Non-blocking APIs are more difficult to use since you are required to meet the following condition:
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
You cannot initiate another send of any kind until the previous send is completed.
|
||||
|
||||
For example, if you have initiated a text send, you cannot initiate a binary send, until the previous send has completed.
|
||||
|
||||
Furthermore, if you have initiated a non-blocking send, you cannot initiate a blocking send, until the previous send has completed.
|
||||
====
|
||||
|
||||
This requirement is necessary to avoid unbounded buffering that could lead to ``OutOfMemoryError``s.
|
||||
|
||||
While non-blocking APIs are more difficult to use, they don't block the sender thread and therefore use less resources, which in turn typically allows for greater scalability under load: with respect to blocking APIs, non-blocking APIs need less resources to cope with the same load.
|
||||
|
||||
[[pg-websocket-session-send-stream]]
|
||||
====== Streaming Send APIs
|
||||
|
||||
If you need to send large WebSocket messages, you may reduce the memory usage by streaming the message content.
|
||||
|
||||
Both blocking and non-blocking APIs offer `sendPartial*(...)` methods that allow you to send a chunk of the whole message at a time, therefore reducing the memory usage since it is not necessary to have the whole message `String` or `byte[]` in memory to send it.
|
||||
|
||||
Streaming sends using blocking APIs is quite simple:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=streamSendBlocking]
|
||||
----
|
||||
|
||||
Streaming sends using non-blocking APIs is more complicated, as you should wait (without blocking!) for the callbacks to complete.
|
||||
|
||||
Fortunately, Jetty provides you with the `IteratingCallback` utility class (described in more details xref:pg-arch-io-echo[in this section]) which greatly simplify the use of non-blocking APIs:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=streamSendNonBlocking]
|
||||
----
|
||||
<1> Implementing `WriteCallback` allows to pass `this` to `sendPartialBytes(...)`.
|
||||
<2> The `process()` method is called iteratively when each `sendPartialBytes(...)` is completed.
|
||||
<3> Send the message chunks.
|
||||
|
||||
[[pg-websocket-session-ping]]
|
||||
===== Sending Ping/Pong
|
||||
|
||||
The WebSocket protocol defines two special frame, named `Ping` and `Pong` that may be interesting to applications for these use cases:
|
||||
|
||||
* Calculate the round-trip time with the remote peer.
|
||||
* Keep the connection from being closed due to idle timeout -- a heartbeat-like mechanism.
|
||||
|
||||
To handle `Ping`/`Pong` events, you may implement interface `org.eclipse.jetty.websocket.api.WebSocketPingPongListener`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`Ping`/`Pong` events are not supported when using annotations.
|
||||
====
|
||||
|
||||
`Ping` frames may contain opaque application bytes, and the WebSocket implementation replies to them with a `Pong` frame containing the same bytes:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=pingPongListener]
|
||||
----
|
||||
<1> The WebSocket endpoint class must implement `WebSocketPingPongListener`
|
||||
|
||||
[[pg-websocket-session-close]]
|
||||
===== Closing the Session
|
||||
|
||||
When you want to terminate the communication with the remote peer, you close the `Session`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=sessionClose]
|
||||
----
|
||||
|
||||
Closing a WebSocket `Session` carries a status code and a reason message that the remote peer can inspect in the _close_ event handler (see xref:pg-websocket-endpoints[this section]).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The reason message is optional, and may be truncated to fit into the WebSocket frame sent to the client.
|
||||
It is best to use short tokens such as `"shutdown"`, or `"idle_timeout"`, etc. or even application specific codes such as `"0001"` or `"00AF"` that can be converted by the application into more meaningful messages.
|
||||
====
|
|
@ -0,0 +1,491 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.docs.programming;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.eclipse.jetty.util.IteratingCallback;
|
||||
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketListener;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketPartialListener;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketPingPongListener;
|
||||
import org.eclipse.jetty.websocket.api.WriteCallback;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class WebSocketDocs
|
||||
{
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::listenerEndpoint[]
|
||||
public class ListenerEndPoint implements WebSocketListener // <1>
|
||||
{
|
||||
private Session session;
|
||||
|
||||
@Override
|
||||
public void onWebSocketConnect(Session session)
|
||||
{
|
||||
// The WebSocket connection is established.
|
||||
|
||||
// Store the session to be able to send data to the remote peer.
|
||||
this.session = session;
|
||||
|
||||
// You may configure the session.
|
||||
session.setMaxTextMessageSize(16 * 1024);
|
||||
|
||||
// You may immediately send a message to the remote peer.
|
||||
session.getRemote().sendString("connected", WriteCallback.NOOP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketClose(int statusCode, String reason)
|
||||
{
|
||||
// The WebSocket connection is closed.
|
||||
|
||||
// You may dispose resources.
|
||||
disposeResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketError(Throwable cause)
|
||||
{
|
||||
// The WebSocket connection failed.
|
||||
|
||||
// You may log the error.
|
||||
cause.printStackTrace();
|
||||
|
||||
// You may dispose resources.
|
||||
disposeResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message)
|
||||
{
|
||||
// A WebSocket textual message is received.
|
||||
|
||||
// You may echo it back if it matches certain criteria.
|
||||
if (message.startsWith("echo:"))
|
||||
session.getRemote().sendString(message.substring("echo:".length()), WriteCallback.NOOP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketBinary(byte[] payload, int offset, int len)
|
||||
{
|
||||
// A WebSocket binary message is received.
|
||||
|
||||
// Save only PNG images.
|
||||
byte[] pngBytes = new byte[]{(byte)0x89, 'P', 'N', 'G'};
|
||||
for (int i = 0; i < pngBytes.length; ++i)
|
||||
{
|
||||
if (pngBytes[i] != payload[offset + i])
|
||||
return;
|
||||
}
|
||||
savePNGImage(payload, offset, len);
|
||||
}
|
||||
}
|
||||
// end::listenerEndpoint[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::streamingListenerEndpoint[]
|
||||
public class StreamingListenerEndpoint implements WebSocketPartialListener
|
||||
{
|
||||
private Path textPath;
|
||||
|
||||
@Override
|
||||
public void onWebSocketPartialText(String payload, boolean fin)
|
||||
{
|
||||
// Forward chunks to external REST service.
|
||||
forwardToREST(payload, fin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin)
|
||||
{
|
||||
// Save chunks to file.
|
||||
appendToFile(payload, fin);
|
||||
}
|
||||
}
|
||||
// end::streamingListenerEndpoint[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::annotatedEndpoint[]
|
||||
@WebSocket // <1>
|
||||
public class AnnotatedEndPoint
|
||||
{
|
||||
private Session session;
|
||||
|
||||
@OnWebSocketConnect // <2>
|
||||
public void onConnect(Session session)
|
||||
{
|
||||
// The WebSocket connection is established.
|
||||
|
||||
// Store the session to be able to send data to the remote peer.
|
||||
this.session = session;
|
||||
|
||||
// You may configure the session.
|
||||
session.setMaxTextMessageSize(16 * 1024);
|
||||
|
||||
// You may immediately send a message to the remote peer.
|
||||
session.getRemote().sendString("connected", WriteCallback.NOOP);
|
||||
}
|
||||
|
||||
@OnWebSocketClose // <2>
|
||||
public void onClose(int statusCode, String reason)
|
||||
{
|
||||
// The WebSocket connection is closed.
|
||||
|
||||
// You may dispose resources.
|
||||
disposeResources();
|
||||
}
|
||||
|
||||
@OnWebSocketError // <2>
|
||||
public void onError(Throwable cause)
|
||||
{
|
||||
// The WebSocket connection failed.
|
||||
|
||||
// You may log the error.
|
||||
cause.printStackTrace();
|
||||
|
||||
// You may dispose resources.
|
||||
disposeResources();
|
||||
}
|
||||
|
||||
@OnWebSocketMessage // <2>
|
||||
public void onTextMessage(Session session, String message) // <3>
|
||||
{
|
||||
// A WebSocket textual message is received.
|
||||
|
||||
// You may echo it back if it matches certain criteria.
|
||||
if (message.startsWith("echo:"))
|
||||
session.getRemote().sendString(message.substring("echo:".length()), WriteCallback.NOOP);
|
||||
}
|
||||
|
||||
@OnWebSocketMessage // <2>
|
||||
public void onBinaryMessage(byte[] payload, int offset, int len)
|
||||
{
|
||||
// A WebSocket binary message is received.
|
||||
|
||||
// Save only PNG images.
|
||||
byte[] pngBytes = new byte[]{(byte)0x89, 'P', 'N', 'G'};
|
||||
for (int i = 0; i < pngBytes.length; ++i)
|
||||
{
|
||||
if (pngBytes[i] != payload[offset + i])
|
||||
return;
|
||||
}
|
||||
savePNGImage(payload, offset, len);
|
||||
}
|
||||
}
|
||||
// end::annotatedEndpoint[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::streamingAnnotatedEndpoint[]
|
||||
@WebSocket
|
||||
public class StreamingAnnotatedEndpoint
|
||||
{
|
||||
@OnWebSocketMessage
|
||||
public void onTextMessage(Reader reader)
|
||||
{
|
||||
// Read chunks and forward.
|
||||
forwardToREST(reader);
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onBinaryMessage(InputStream stream)
|
||||
{
|
||||
// Save chunks to file.
|
||||
appendToFile(stream);
|
||||
}
|
||||
}
|
||||
// end::streamingAnnotatedEndpoint[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::sessionConfigure[]
|
||||
public class ConfigureEndpoint implements WebSocketListener
|
||||
{
|
||||
@Override
|
||||
public void onWebSocketConnect(Session session)
|
||||
{
|
||||
// Configure the max length of incoming messages.
|
||||
session.setMaxTextMessageSize(16 * 1024);
|
||||
|
||||
// Configure the idle timeout.
|
||||
session.setIdleTimeout(Duration.ofSeconds(30));
|
||||
}
|
||||
}
|
||||
// end::sessionConfigure[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::sendBlocking[]
|
||||
@WebSocket
|
||||
public class BlockingSendEndpoint
|
||||
{
|
||||
@OnWebSocketMessage
|
||||
public void onText(Session session, String text)
|
||||
{
|
||||
// Obtain the RemoteEndpoint APIs.
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
try
|
||||
{
|
||||
// Send textual data to the remote peer.
|
||||
remote.sendString("data");
|
||||
|
||||
// Send binary data to the remote peer.
|
||||
ByteBuffer bytes = readImageFromFile();
|
||||
remote.sendBytes(bytes);
|
||||
|
||||
// Send a PING frame to the remote peer.
|
||||
remote.sendPing(ByteBuffer.allocate(8).putLong(System.nanoTime()).flip());
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
// No need to rethrow or close the session.
|
||||
System.getLogger("websocket").log(System.Logger.Level.WARNING, "could not send data", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
// end::sendBlocking[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::sendNonBlocking[]
|
||||
@WebSocket
|
||||
public class NonBlockingSendEndpoint
|
||||
{
|
||||
@OnWebSocketMessage
|
||||
public void onText(Session session, String text)
|
||||
{
|
||||
// Obtain the RemoteEndpoint APIs.
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
// Send textual data to the remote peer.
|
||||
remote.sendString("data", new WriteCallback() // <1>
|
||||
{
|
||||
@Override
|
||||
public void writeSuccess()
|
||||
{
|
||||
// Send binary data to the remote peer.
|
||||
ByteBuffer bytes = readImageFromFile();
|
||||
remote.sendBytes(bytes, new WriteCallback() // <2>
|
||||
{
|
||||
@Override
|
||||
public void writeSuccess()
|
||||
{
|
||||
// Both sends succeeded.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFailed(Throwable x)
|
||||
{
|
||||
System.getLogger("websocket").log(System.Logger.Level.WARNING, "could not send binary data", x);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFailed(Throwable x)
|
||||
{
|
||||
// No need to rethrow or close the session.
|
||||
System.getLogger("websocket").log(System.Logger.Level.WARNING, "could not send textual data", x);
|
||||
}
|
||||
});
|
||||
|
||||
// remote.sendString("wrong", WriteCallback.NOOP); // May throw WritePendingException! <3>
|
||||
}
|
||||
}
|
||||
// end::sendNonBlocking[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::streamSendBlocking[]
|
||||
@WebSocket
|
||||
public class StreamSendBlockingEndpoint
|
||||
{
|
||||
@OnWebSocketMessage
|
||||
public void onText(Session session, String text)
|
||||
{
|
||||
try
|
||||
{
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
while (true)
|
||||
{
|
||||
ByteBuffer chunk = readChunkToSend();
|
||||
if (chunk == null)
|
||||
{
|
||||
// No more bytes, finish the WebSocket message.
|
||||
remote.sendPartialBytes(ByteBuffer.allocate(0), true);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send the chunk.
|
||||
remote.sendPartialBytes(chunk, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
x.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
// end::streamSendBlocking[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::streamSendNonBlocking[]
|
||||
@WebSocket
|
||||
public class StreamSendNonBlockingEndpoint
|
||||
{
|
||||
@OnWebSocketMessage
|
||||
public void onText(Session session, String text)
|
||||
{
|
||||
RemoteEndpoint remote = session.getRemote();
|
||||
new Sender(remote).iterate();
|
||||
}
|
||||
|
||||
private class Sender extends IteratingCallback implements WriteCallback // <1>
|
||||
{
|
||||
private final RemoteEndpoint remote;
|
||||
private boolean finished;
|
||||
|
||||
private Sender(RemoteEndpoint remote)
|
||||
{
|
||||
this.remote = remote;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Action process() throws Throwable // <2>
|
||||
{
|
||||
if (finished)
|
||||
return Action.SUCCEEDED;
|
||||
|
||||
ByteBuffer chunk = readChunkToSend();
|
||||
if (chunk == null)
|
||||
{
|
||||
// No more bytes, finish the WebSocket message.
|
||||
remote.sendPartialBytes(ByteBuffer.allocate(0), true, this); // <3>
|
||||
finished = true;
|
||||
return Action.SCHEDULED;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send the chunk.
|
||||
remote.sendPartialBytes(ByteBuffer.allocate(0), false, this); // <3>
|
||||
return Action.SCHEDULED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSuccess()
|
||||
{
|
||||
// When the send succeeds, succeed this IteratingCallback.
|
||||
succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFailed(Throwable x)
|
||||
{
|
||||
// When the send fails, fail this IteratingCallback.
|
||||
failed(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteFailure(Throwable x)
|
||||
{
|
||||
x.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
// end::streamSendNonBlocking[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::pingPongListener[]
|
||||
public class RoundTripListenerEndpoint implements WebSocketPingPongListener // <1>
|
||||
{
|
||||
@Override
|
||||
public void onWebSocketConnect(Session session)
|
||||
{
|
||||
// Send to the remote peer the local nanoTime.
|
||||
ByteBuffer buffer = ByteBuffer.allocate(8).putLong(System.nanoTime()).flip();
|
||||
session.getRemote().sendPing(buffer, WriteCallback.NOOP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketPong(ByteBuffer payload)
|
||||
{
|
||||
// The remote peer echoed back the local nanoTime.
|
||||
long start = payload.getLong();
|
||||
|
||||
// Calculate the round-trip time.
|
||||
long roundTrip = System.nanoTime() - start;
|
||||
}
|
||||
}
|
||||
// end::pingPongListener[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::sessionClose[]
|
||||
@WebSocket
|
||||
public class CloseEndpoint
|
||||
{
|
||||
@OnWebSocketMessage
|
||||
public void onText(Session session, String text)
|
||||
{
|
||||
if ("close".equalsIgnoreCase(text))
|
||||
session.close(StatusCode.NORMAL, "bye");
|
||||
}
|
||||
}
|
||||
// end::sessionClose[]
|
||||
|
||||
private static void forwardToREST(String payload, boolean fin)
|
||||
{
|
||||
}
|
||||
|
||||
private static void forwardToREST(Reader reader)
|
||||
{
|
||||
}
|
||||
|
||||
private static void appendToFile(ByteBuffer payload, boolean fin)
|
||||
{
|
||||
}
|
||||
|
||||
private static void appendToFile(InputStream stream)
|
||||
{
|
||||
}
|
||||
|
||||
private static void disposeResources()
|
||||
{
|
||||
}
|
||||
|
||||
private static void savePNGImage(byte[] payload, int offset, int len)
|
||||
{
|
||||
}
|
||||
|
||||
private static ByteBuffer readImageFromFile()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ByteBuffer readChunkToSend()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.docs.programming.client.websocket;
|
||||
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpProxy;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.HttpResponse;
|
||||
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||
import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2;
|
||||
import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.JettyUpgradeListener;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class WebSocketClientDocs
|
||||
{
|
||||
public void start() throws Exception
|
||||
{
|
||||
// tag::start[]
|
||||
// Instantiate WebSocketClient.
|
||||
WebSocketClient webSocketClient = new WebSocketClient();
|
||||
|
||||
// Configure WebSocketClient, for example:
|
||||
webSocketClient.setMaxTextMessageSize(8 * 1024);
|
||||
|
||||
// Start WebSocketClient.
|
||||
webSocketClient.start();
|
||||
// end::start[]
|
||||
}
|
||||
|
||||
public void startWithHttpClient() throws Exception
|
||||
{
|
||||
// tag::startWithHttpClient[]
|
||||
// Instantiate and configure HttpClient.
|
||||
HttpClient httpClient = new HttpClient();
|
||||
// For example, configure a proxy.
|
||||
httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", 8888));
|
||||
|
||||
// Instantiate WebSocketClient, passing HttpClient to the constructor.
|
||||
WebSocketClient webSocketClient = new WebSocketClient(httpClient);
|
||||
// Configure WebSocketClient, for example:
|
||||
webSocketClient.setMaxTextMessageSize(8 * 1024);
|
||||
|
||||
// Start WebSocketClient; this implicitly starts also HttpClient.
|
||||
webSocketClient.start();
|
||||
// end::startWithHttpClient[]
|
||||
}
|
||||
|
||||
public void stop() throws Exception
|
||||
{
|
||||
WebSocketClient webSocketClient = new WebSocketClient();
|
||||
webSocketClient.start();
|
||||
// tag::stop[]
|
||||
// Stop WebSocketClient.
|
||||
// Use LifeCycle.stop(...) to rethrow checked exceptions as unchecked.
|
||||
new Thread(() -> LifeCycle.stop(webSocketClient)).start();
|
||||
// end::stop[]
|
||||
}
|
||||
|
||||
public void connectHTTP11() throws Exception
|
||||
{
|
||||
// tag::connectHTTP11[]
|
||||
// Use a standard, HTTP/1.1, HttpClient.
|
||||
HttpClient httpClient = new HttpClient();
|
||||
|
||||
// Create and start WebSocketClient.
|
||||
WebSocketClient webSocketClient = new WebSocketClient(httpClient);
|
||||
webSocketClient.start();
|
||||
|
||||
// The client-side WebSocket EndPoint that
|
||||
// receives WebSocket messages from the server.
|
||||
ClientEndPoint clientEndPoint = new ClientEndPoint();
|
||||
// The server URI to connect to.
|
||||
URI serverURI = URI.create("ws://domain.com/path");
|
||||
|
||||
// Connect the client EndPoint to the server.
|
||||
CompletableFuture<Session> clientSessionPromise = webSocketClient.connect(clientEndPoint, serverURI);
|
||||
// end::connectHTTP11[]
|
||||
}
|
||||
|
||||
public void connectHTTP2() throws Exception
|
||||
{
|
||||
// tag::connectHTTP2[]
|
||||
// Use the HTTP/2 transport for HttpClient.
|
||||
HTTP2Client http2Client = new HTTP2Client();
|
||||
HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP2(http2Client));
|
||||
|
||||
// Create and start WebSocketClient.
|
||||
WebSocketClient webSocketClient = new WebSocketClient(httpClient);
|
||||
webSocketClient.start();
|
||||
|
||||
// The client-side WebSocket EndPoint that
|
||||
// receives WebSocket messages from the server.
|
||||
ClientEndPoint clientEndPoint = new ClientEndPoint();
|
||||
// The server URI to connect to.
|
||||
URI serverURI = URI.create("ws://domain.com/path");
|
||||
|
||||
// Connect the client EndPoint to the server.
|
||||
CompletableFuture<Session> clientSessionPromise = webSocketClient.connect(clientEndPoint, serverURI);
|
||||
// end::connectHTTP2[]
|
||||
}
|
||||
|
||||
public void connectHTTP2Dynamic() throws Exception
|
||||
{
|
||||
// tag::connectHTTP2Dynamic[]
|
||||
// Use the dynamic HTTP/2 transport for HttpClient.
|
||||
HTTP2Client http2Client = new HTTP2Client();
|
||||
HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client)));
|
||||
|
||||
// Create and start WebSocketClient.
|
||||
WebSocketClient webSocketClient = new WebSocketClient(httpClient);
|
||||
webSocketClient.start();
|
||||
|
||||
ClientEndPoint clientEndPoint = new ClientEndPoint();
|
||||
URI serverURI = URI.create("ws://domain.com/path");
|
||||
|
||||
// Connect the client EndPoint to the server.
|
||||
CompletableFuture<Session> clientSessionPromise = webSocketClient.connect(clientEndPoint, serverURI);
|
||||
// end::connectHTTP2Dynamic[]
|
||||
}
|
||||
|
||||
public void customHTTPRequest() throws Exception
|
||||
{
|
||||
WebSocketClient webSocketClient = new WebSocketClient(new HttpClient());
|
||||
webSocketClient.start();
|
||||
|
||||
// tag::customHTTPRequest[]
|
||||
ClientEndPoint clientEndPoint = new ClientEndPoint();
|
||||
URI serverURI = URI.create("ws://domain.com/path");
|
||||
|
||||
// Create a custom HTTP request.
|
||||
ClientUpgradeRequest customRequest = new ClientUpgradeRequest();
|
||||
// Specify a cookie.
|
||||
customRequest.getCookies().add(new HttpCookie("name", "value"));
|
||||
// Specify a custom header.
|
||||
customRequest.setHeader("X-Token", "0123456789ABCDEF");
|
||||
// Specify a custom sub-protocol.
|
||||
customRequest.setSubProtocols("chat");
|
||||
|
||||
// Connect the client EndPoint to the server with a custom HTTP request.
|
||||
CompletableFuture<Session> clientSessionPromise = webSocketClient.connect(clientEndPoint, serverURI, customRequest);
|
||||
// end::customHTTPRequest[]
|
||||
}
|
||||
|
||||
public void inspectHTTPResponse() throws Exception
|
||||
{
|
||||
WebSocketClient webSocketClient = new WebSocketClient(new HttpClient());
|
||||
webSocketClient.start();
|
||||
|
||||
// tag::inspectHTTPResponse[]
|
||||
ClientEndPoint clientEndPoint = new ClientEndPoint();
|
||||
URI serverURI = URI.create("ws://domain.com/path");
|
||||
|
||||
// The listener to inspect the HTTP response.
|
||||
JettyUpgradeListener listener = new JettyUpgradeListener()
|
||||
{
|
||||
@Override
|
||||
public void onHandshakeResponse(HttpRequest request, HttpResponse response)
|
||||
{
|
||||
// Inspect the HTTP response here.
|
||||
}
|
||||
};
|
||||
|
||||
// Connect the client EndPoint to the server with a custom HTTP request.
|
||||
CompletableFuture<Session> clientSessionPromise = webSocketClient.connect(clientEndPoint, serverURI, null, listener);
|
||||
// end::inspectHTTPResponse[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ClientEndPoint
|
||||
{
|
||||
}
|
||||
}
|
|
@ -29,16 +29,20 @@ public interface WriteCallback
|
|||
*
|
||||
* @param x the reason for the write failure
|
||||
*/
|
||||
void writeFailed(Throwable x);
|
||||
default void writeFailed(Throwable x)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Callback invoked when the write completes.
|
||||
* Callback invoked when the write succeeds.
|
||||
* </p>
|
||||
*
|
||||
* @see #writeFailed(Throwable)
|
||||
*/
|
||||
void writeSuccess();
|
||||
default void writeSuccess()
|
||||
{
|
||||
}
|
||||
|
||||
class Adaptor implements WriteCallback
|
||||
{
|
||||
|
|
|
@ -153,7 +153,7 @@ public final class ClientUpgradeRequest implements UpgradeRequest
|
|||
@Override
|
||||
public String getQueryString()
|
||||
{
|
||||
return requestURI.getQuery();
|
||||
return requestURI == null ? null : requestURI.getQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue