# Sending Messages

This chapter will dive into the details of how to send messages to WebSocket endpoints.

> The API for sending messages is the same for annotated as well as programmatic endpoints which in _contrast_ to receiving messages (next chapter) which are handled differently for different endpoints

As already stated, the Java WebSocket API supports binary, text, custom Java objects and ping-pong message payloads. These message types can be sent using various styles/modes

* Asynchronous
* Synchronous
* Partial, and
* Streaming

As you might guess, this leads to a _lot of possible permutations and combinations_ for sending messages (and can be quite confusing at times). Hopefully, things should be clear by the end of this chapter

## Send modes: a quick primer

Before we dive into the nitty gritty, it'll be good to have an overview of the different message transmission modes

| Mode         | Description                                                                                                                    |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| Synchronous  | the client sending the message is blocked until the process is completed (or an error occurs)                                  |
| Asynchronous | client thread is released immediately and it can track the process using a Future object or a callback implementation          |
| Partial      | the message is delivered in parts, the client needs to keep track of them and tell the API when its done sending all the parts |
| Streaming    | makes use of Java character/output stream to send messages                                                                     |

## Sending text messages

### Synchronous

This is the most easy-to-understand method. Just make used of the `public void sendText(String msg)` in `RemoteEndpoint.Basic` interface

```
//synchronous delivery

....
@OnMessage
public void onReceipt(String msg, Session session){
  session.getBasicRemote().sendText("got your message ");
}
....
```

### Asynchronous

This mode is handled by the `RemoteEndpoint.Async` interface which exposes two methods

* `public Future<Void> sendText(String msg)`: returns a `java.util.concurrent.Future` object
* `public void sendText(String msg, SendHandler handler)`: allows the user to provide a **callback handler** implementation

```
//asynchronous text message delivery

....
@OnMessage
public void onReceipt(String msg, Session session){
  Future<Void> deliveryTracker = session.getAsyncRemote().sendText("got your message ");
  deliveryTracker.isDone(); //blocks
}
....
```

```
//asynchronous text message delivery using a callback

....
@OnMessage
public void onMsg(String msg, Session session){
  session.getAsyncRemote().sendText("got your message ", new SendHandler() {
  @Override
  public void onResult(SendResult result) {
    pushToDB(session.getID(), msg, result.isOK());
   }
 });
}
....
```

```
//Java 8 lambda style

....
session.getAsyncRemote()
.sendText("got your message ",
(SendResult result) -> {pushToDB(session.getId(),msg, result.isOK());}
);
....
```

### Partial

Sending messages in part can be done by using an overloaded version of the `sendText` method in the `RemoteEndpoint.Basic` interface. The process is _synchronous_ in nature

```
//partial message delivery

....
String partialData = fetch(request);
try {
  session.getBasicRemote().sendText(partialData, false);
} catch (IOException ex) {
  throw new RuntimeException(ex);
}
...
```

### Streaming

One can stream textual (character) data to a `java.io.Writer` provided by the `public void getSendWriter()` in `RemoteEndpoint.Basic`. Any of the **overloaded** `write` methods in [`Writer`](https://docs.oracle.com/javase/8/docs/api/java/io/Writer.html) can be used

```
//streaming strings

....
private Session session;

public void broadcast(String msg){
  session.getBasicRemote().getSendWriter().write(msg);
}
....
```

### Summary

Here is a table summarizing possible message sending combinations for text data

| Sending style for text messages | Method signature                                                                              |
| ------------------------------- | --------------------------------------------------------------------------------------------- |
| Synchronous                     | `public void sendText(String msg)`                                                            |
| Asynchronous                    | `public Future<Void> sendText(String msg)`, `void sendText(String msg, SendHandler callback)` |
| Partial                         | `public void sendText(String part, boolean isLast)`                                           |
| Streaming                       | `public void getSendWriter().write(String msg)`                                               |

## Sending binary data

Handling (sending) Binary data is similar to String as far as the API is concerned. The only (obvious) difference being the data type - [`ByteBuffer`](https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html) in case of binary data as opposed to `String` for textual data. The supported modes are also the same (as for text data)

### Synchronous

```
//synchronous delivery of an image

....
public void syncImage(byte[] image, Session session){
  ByteBuffer img = ByteBuffer.wrap(image);
  session.getBasicRemote().sendBinary(img);
}
....
```

### Asynchronous

```
//asynchronous delivery of an image

....
public void syncLargeImage(byte[] image, Session session){
  ByteBuffer img = ByteBuffer.wrap(image);
  Future<Void> deliveryProgress = session.getAsyncRemote().sendBinary(img);
  boolean delivered = deliveryProgress.isDone(); //blocks until completion or failure
}
....
```

### Partial

```
//partial delivery of binary data

....
ByteBuffer partialData = fetch(request);
try {
  session.getBasicRemote().sendBinary(partialData, false);
} catch (IOException ex) {
  throw new RuntimeException(ex);
}
...
```

### Streaming

Use the `getSendStream()` method on `RemoteEndpoint.Basic` to get an [`OutputStream`](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html) and use any of the overloaded write methods to transmit binary data

```
//binary data - streaming style

....
ByteBuffer data = fetch(request);
try {
session.getBasicRemote().getSendStream().write(data.array());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
...
```

### Summary

Here is the gist

| Sending style for binary messages | Method signature                                                                                                   |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Synchronous                       | `public void sendBinary(ByteBuffer data)`                                                                          |
| Asynchronous                      | `public Future<Void> sendBinary(ByteBuffer data)` ,`public void sendBinary(ByteBuffer data, SendHandler callback)` |
| Partial                           | `public void sendBinary(ByteBuffer part, boolean isLast)`                                                          |
| Streaming                         | `public void getSendStream().write(byte[] data)`                                                                   |

## Sending Java objects

More often than not, your business logic will internally deal with objects rather than their raw binary or textual representations. As mentioned earlier, WebSocket supports text and binary data as it on-wire format. Thus, Java objects within your code would need to be transformed into their text or binary forms for them to be sent over a WebSocket connection.

Similar to what we saw in case of native (binary and text) messages, the `RemoteEndpoint.Basic` and `RemoteEndpoint.Async` interfaces contain appropriate methods which support Java object transmission - _both synchronously and asynchronously_.

Here is a quick peek

| Sending style for (Java) objects | Method signature                                                                                         |
| -------------------------------- | -------------------------------------------------------------------------------------------------------- |
| Synchronous                      | `public void sendObject(Object obj)`                                                                     |
| Asynchronous                     | `public Future<Void> sendObject(Object obj)`, `public void sendObject(Object obj, SendHandler callback)` |

The question is -

> how does a `java.lang.Object` representation get converted into text or binary format ?

This is done with the help of an **Encoder**. A `javax.websocket.Encoder` encapsulates the logic to convert a message from a Java object into an on-wire format supported by the WebSocket protocol (i.e. text or binary). Before diving into Encoders, let's look at a code sample for sending stock prices (represents as a `StockQuote` java object) asynchronously

> The synchronous counterpart is very simple - you just need to use the `sendObject` method on `RemoteEndpoint.Basic`

```
....
private Session client;

public void broadcast(String msg) {
  Set<String> subscriptions = (Set<String>) client.getUserProperties().get("TICKER_SUBSCRIPTIONS");
  StockQuote quote = null; //the Java object
  for (String subscription : subscriptions) {
   try {
    quote = fetchQuote(subscription);
    //sending stock quotes with a Java 8 lambda style callback
    peer.getAsyncRemote().sendObject(quote,
    (SendResult result) -> {audit(session.getId(),quote, result.isOK());}
    );
      }
   catch (Exception e) {
    //log and continue...
    }
  }
}
....
```

### WebSocket Encoders: the details

Take a look at the diagram below to visualize how _Encoders_ work at runtime. It should be relatively easy to grasp what's going on

![Encoders in action](https://2698670403-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3y20se5T5tgBXBcWyM%2F-M3y23Fq1q0Wn8xweQA9%2F-M3y24AYEczUXPR7XjUq%2Fencoders.jpg?generation=1585883938442458\&alt=media)

A summary of Encoders and their types

| Encoder Type        | Description                                                                                         |
| ------------------- | --------------------------------------------------------------------------------------------------- |
| `Encoder`           | The top level interface for different types of Encoders                                             |
| `Encoder.Text<T>`   | Transforms a custom Java object (of type T) to a textual (`java.lang.String`) format                |
| `Encoder.Binary<T>` | Transforms to transform a custom Java object (of type T) to a binary (`java.nio.ByteBuffer`) format |

A sample to demonstrate how an `Encoder` implementation would look like for a Java object which represents stock prices

```
//encoding a 'StockQuote' Java object to a JSON string

public class StockQuoteJSONEncoder implements Encoder.Text<StockQuote> {
  @Override
  public void init(EndpointConfig config) {
    //for custom initialization logic (details omitted)
  }
  @Override
  public String encode(StockQuote stockQuoteObject) throws EncodeException {
    //using the JSON processing API (JSR 353)
    return Json.createObjectBuilder()
    .add("quote", stockQuoteObject.getQuote())
    .add("ticker", stockQuoteObject.getTicker())
    .toString();
  }
  @Override
  public void destroy() {
    //close resources (details omitted)
  }
}
```

### Streaming your Java objects

Native text and binary messages can be sent as **streams** - as explained in the previous sections. Java objects can also be transmitted in a streaming style. The respective methods are not directly exposed via `RemoteEndpoint.Basic` or `RemoteEndpoint.Async`. It's actually handled via the `Encoder` implementation

| Encoder Type              | Description                                                                                                              |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `Encoder.TextStream<T>`   | Converts a custom Java object (of type T) and transmit it as a character stream (using `java.io.Writer`)                 |
| `Encoder.BinaryStream<T>` | Converts to transform a custom Java object (of type T) and transmit it as a binary stream (using `java.io.OutputStream`) |

This example should help

```
//sending Java objects as character stream

public class StockQuoteJSONEncoder implements Encoder.TextStream<StockQuote> {
  @Override
  public void init(EndpointConfig config) {
    //for custom initialization logic (details omitted)
  }
  @Override
  public void encode(StockQuote stockQuoteObject, Writer writer) throws EncodeException {
    //using the JSON processing API (JSR 353)
    String jsonStockQuote = Json.createObjectBuilder()
    .add("quote", stockQuoteObject.getQuote())
    .add("ticker", stockQuoteObject.getTicker())
    .toString();
    writer.write(jsonStockQuote);
  }
  @Override
  public void destroy() {
    //close resources (details omitted)
  }
}
```

Thus, if you need to send your Java objects as a binary or character stream, you would need implement and register an appropriate Encoder corresponding to your Java type and the rest will be handled by the WebSocket runtime (your Encoder will be automatically invoked)

> #### What about Java primitives ?
>
> A WebSocket implementation provides default encoders for Java primitive (int-Integer, long-Long, double-Double etc.) data types. It is possible to write a custom encoder for any of these in order to override the default ones

## Exchanging health status (ping-pong) messages

Exchaning ping-pong messages is a way to check up on the health status of the connection b/w a pair of WebSocket peers

| Ping/Pong | Description                                                                                                                                                                       |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ping      | A health check request message. The API does not provide an object corresponding to this message (its a byte buffer)                                                              |
| Pong      | Response to a health check status, represented by `javax.websocket.PongMessage`. It can also be used as a _one-way heartbeat_ message (without the _ping_ message being involved) |

### Common semantics for Ping-Pong messages

* They are nothing but _binary_ data and take the form of a [`ByteBuffer`](https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html) (as explained earlier) as far the API is concerned
* They cannot be larger than **125 bytes** (as is the case with [WebSocket Control Frames](https://tools.ietf.org/html/rfc6455#section-5.5) in general) - these are just health-check messages and not meant for core/application level data exchange b/w WebSocket endpoints

Capability to send ping and pong messages are defined by the top level `javax.websocket.RemoteEndpoint` interface which means that they are inherited by its `RemoteEndpoint.Basic` and `RemoteEndpoint.Async`

> This means that both these messages can be sent in a sync and async manner

| Sending style for Ping and Pong messages | Method signature (ping)          | Method signature (pong)          |
| ---------------------------------------- | -------------------------------- | -------------------------------- |
| Synchronous & Asynchronous               | `void sendPing(ByteBuffer ping)` | `void sendPong(ByteBuffer pong)` |

```
//sending a ping (health check request)
.....
private Session s;

public void healthCheck(){
  s.getBasicRemote().sendPing(ByteBuffer.wrap("health-check".getBytes()));
}
.....
```

### Additional notes

* A Ping message is only meant to be sent (not recieved) as opposed to Pong, which can be sent and recieved
* One does **not** need to write logic to explicitly return a pong message in response to a ping - the Java WebSocket API implementation will do that for you **automatically**
* A Pong message can also be used as a self inititated heart beat message (not just in response to ping)

```
//sending a pong (as a one-way heart beat)

s.getBasicRemote().sendPong(ByteBuffer.wrap("health-check".getBytes()));
```

## Asynchronous timeouts

Throughout this lesson, we have seen strategies of being able to send messages in an asynchronous manner which avoid blocking the sending thread. This is a great where your solution needs to scale in order to support a large number of clients.

> But, is there limit on how long can we wait for the asynchronous process to complete ?

The answer is _yes_

### Timeout support in the API

* first and foremost, there is a notion of a _timeout_ and this can be configured using the `setSendTimeout` method in the `RemoteEndpoint.Async` interface
* secondly, the failure result manifests itself using the `Future` object or `SendResult`

### How do timeouts manifest ?

In case you are using the `SendHandler` i.e. the callback handler route, the timeout exception details will be available via `SendResult.getException()`

```
//bail out if the message is not sent in 1 second

....
public void broadcast(Session s, String msg){
  RemoteEndpoint asyncHandle = s.getRemoteAsync();
  asyncHandle.setSendTimeout(1000); //1 second
  asyncHandle.sendText(msg,
  new SendHandler(){
    @Override
    public void onResult(SendResult result) {
      if(!result.isOK()){
      System.out.println("Async send failure: "+ result.getException());
      }
    }
  }); //will timeout after 2 seconds
}
....
```

If you chose to use `Future` to track the completion, calling it's `get` method will result in a `java.util.concurrent.ExecutionException`

```
//bail out if the message is not sent in 2 seconds

....
public void broadcast(Session s, String msg){
  RemoteEndpoint asyncHandle = s.getRemoteAsync();
  asyncHandle.setSendTimeout(2000); //2000 ms
  Future<Void> tracker = asyncHandle.sendText(msg); //will timeout after 2 seconds
  tracker.get(); //will throw java.util.ExecutionException if the process had timed out
}
....
```

## Before we proceed

.. a quick refresher. The below table provides a quick preview of which mode is supported for which message type

| Sending style | Text | Binary | Java object | Pong | Ping |
| ------------- | ---- | ------ | ----------- | ---- | ---- |
| Synchronous   | y    | y      | y           | y    | y    |
| Asynchronous  | y    | y      | y           | y    | y    |
| Partial       | y    | y      | n           | n    | n    |
| Streaming     | y    | y      | y           | n    | n    |

## The next lesson ...

... will dive into the other half of the message exchange process i.e. **Receiving Messages**
