-
Notifications
You must be signed in to change notification settings - Fork 47
Getting Started with wAsync
The wAsync library can be used with any WebSocket/Asynchronous framework like Atmosphere, Node.js or Vert.x, jsr 356 implementation or any existing WebSocket Server. wAsync is fully asynchronous and non blocking.
The first steps is to decide which Client to use. With version 1.0, three clients are available:
- Default: Works with all frameworks, no specific framework/protocol information.
- Atmosphere: Specific client that support the Atmosphere Protocol.
- Serial: A client that serialize messages so the server receive messages in the order they were send by the client.
The client instance is obtained by doing:
// Default, works with all framework
Client client = ClientFactory.getDefault().newClient();
// Atmosphere
AtmosphereClient client = ClientFactory.getDefault().newClient(AtmosphereClient.class);
// Serial
SerializedClient client = ClientFactory.getDefault().newClient(SerializedClient.class);
If your application doesn't use any framework specific configurations, it is recommended to just do:
// Default, works with all framework
Client client = ClientFactory.getDefault().newClient();
// Atmosphere
Client client = ClientFactory.getDefault().newClient(AtmosphereClient.class);
// Serial
Client client = ClientFactory.getDefault().newClient(SerializedClient.class);
Functions are the central piece of wAsync. Function wil be called by the library when an event or message is received by the library. Function are defined as
public interface Function<T> {
void on(T t);
}
There are two types of Functions: the one associated with an Event, and the one associated with a message received from a remote server.
You can define Functions for the following Event: Supported event are:
/**
* This event is fired when the connection is opened. This event is only fired once.
*/
OPEN,
/**
* This event is fired when the connection gets closed. This event is only fired once.
*/
CLOSE,
/**
* This event is fired every time a new message is received.
*/
MESSAGE,
/**
* This event is fired every time the connection is re-opened
*/
REOPENED,
/**
* This event is fired whenever unexpected error happens. This event is only fired once.
*/
ERROR,
/**
* This event is fired when the response's status is received. This event is only fired once.
*/
STATUS,
/**
* This event is fired when the response's headers is received. This event is only fired once.
*/
HEADERS,
/**
* This event is fired when the response's bytes are received.
*/
MESSAGE_BYTES,
/**
* This event is fired when the connection has been established. The Transport propagated is the one that worked.
* This event is only fired once.
*/
TRANSPORT
For example, you can define a Function that will tell you which transport was used:
new Function<Request.TRANSPORT>() {
@Override
public void on(Request.TRANSPORT t) {
....
}
Or to get notified when the connection is opened
new Function<String>() {
@Override
public void on(String openString) {
...;
}
You will see in the Socket section below how to associate an Event with a Function. Top
Message based Functions are used to handle the server response. If the server is sending String based message, a simple function can be defined as:
new Function<String>() {
@Override
public void on(String message) {
...;
}
With the help of Decoder (see section below), you can also have typed Function. For example, if a Decoder has been defined to decode String into instance of StringBuilder, a Function can be defined as:
new Function<StringBuilder>() {
@Override
public void on(StringBuilder builder) {
...;
}
As you will see below, Function can be associated with an Event, pre-defined String message and event Object by configuring your own FunctionResolver. Top
Next step is to create the Request object. First, you need to obtain a RequestBuilder
RequestBuilder requestBuilder = client.newRequestBuilder()
.method(Request.METHOD.GET)
.uri("http://127.0.0.1:8080")
.transport(Request.TRANSPORT.WEBSOCKET)
.transport(Request.TRANSPORT.STREAMING);
If you are using specific Client implementation, extra methods are available. For example:
RequestBuilder requestBuilder = client.newRequestBuilder()
.method(Request.METHOD.GET)
.uri("http://127.0.0.1:8080")
.trackMessageLength(true)
.transport(Request.TRANSPORT.WEBSOCKET)
.transport(Request.TRANSPORT.STREAMING);
The above will add support for the message's length feature of the Atmosphere Protocol. Top
You may want to encode messages before they are sent to the remote server. An Encoder is defined as:
public interface Encoder<U, T> {
T encode(U s);
}
For example, the following encoder will encode a Message's instance into a Reader
requestBuilder.encoder(
new Encoder<Messsage, Reader>() {
@Override
public Reader encode(Message s) {
return writeValueAsReader(s);
}
}))
Another example is to use the Jackson library for encoding object:
requestBuilder.encoder(
new Encoder<Message, String>() {
@Override
public String encode(Message data) {
try {
return mapper.writeValueAsString(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
))
You can add more that one Encoder for different classes, and you can also chain Encoders, e.g one Encoder result will be used by another encoder:
requestBuilder.encoder(
new Encoder<String, POJO>() {
@Override
public POJO encode(String s) {
return new POJO("<-" + s + "->");
}
})
.encoder(new Encoder<POJO, Reader>() {
@Override
public Reader encode(POJO s) {
return new StringReader(s.message);
}
})
When the library receives a message from a remote server, it is possible to decode the received message into another object. The resulting object will be used by the library to invoke it's associated Function. A Decoder is defined as:
public interface Decoder<U, T> {
T decode(Event e, U s);
}
The library always invokes Decoders with some information about the underlying connection life cycle. The Event enum can be used to decode messages based on the state of the connection. For example, when the connection is established, you can define a Function that will be called as a result of a Decoder:
Client client = ClientFactory.getDefault().newClient();
RequestBuilder request = client.newRequestBuilder()
.method(Request.METHOD.GET)
.uri("http://127.0.0.1:8080")
.decoder(new Decoder<String, ConnectionOpen>() {
@Override
public ConnectionOpen decode(Event e, String s) {
if (e.equals(Event.OPEN)) {
return new ConnectionOpen(s);
}
}
})
.transport(Request.TRANSPORT.WEBSOCKET);
final Socket socket = client.create();
socket.on(Event.OPEN, new Function<ConnectionOpen>() {
@Override
public void on(ConnectionOpen t) {
// The Connection is opened
socket.fire("some message");
}
})
Once Encoders/Decoders has been set, you can create the Request object that will be used by a Socket by doing
Request request = client.newRequestBuilder()
.method(Request.METHOD.GET)
.uri("http://127.0.0.1:8080")
.decoder(new Decoder<String, ConnectionOpen>() {
@Override
public ConnectionOpen decode(Event e, String s) {
if (e.equals(Event.OPEN)) {
return new ConnectionOpen(s);
}
}
})
.transport(Request.TRANSPORT.WEBSOCKET);
A Socket represents a connection to the server. For WebSocket, it's a single connection where for other transport, it's always two: one which is constantly connected (called the suspended connection), the other one used to push data from the client to the server. To create a Socket, just do:
Socket socket = client.create();
You can also set some Options on the Socket, by doing:
OptionsBuilder builder = client.newOptionsBuilder();
builder.requestTimeoutInSeconds(30);
Socket socket = client.create(builder.build());
You can also set specific framework Options. For example, with Serial Support, you can do:
AtmosphereOptionsBuilder builder = client.newOptionsBuilder();
builder.serializedFireStage(new SerializedFireStage() {...});
Socket socket = client.create(builder.build());
Next step is to associate Function with Event of String message. For example, to associate a Function with an Event, you just do:
socket.on(Event.OPEN, new Function<ConnectionOpen>() {
@Override
public void on(ConnectionOpen t) {
// The Connection is opened
socket.fire("some message");
}
});
To associate Function with message
socket.on(Event.MESSAGE, new Function<String>() {
@Override
public void on(String message t) {
....
}
});
If a Decoder has been defined for a Function's Type:
Request request = client.newRequestBuilder()
.method(Request.METHOD.GET)
.uri("http://127.0.0.1:8080")
.decoder(new Decoder<String, Message>() {
@Override
public Message decode(Event e, String s) {
return new Message(s);
}
});
socket.on(Event.MESSAGE, new Function<Message>() {
@Override
public void on(Message message t) {
....
}
});
To open a connection to your server, you just need to pass the previously constructed Request
socket.open(request);
To send message
socket.fire("message");
If you have defined Encoder, you can also send Object:
socket.fire(new Message("Hello"));
You can also send multiple messages by doing:
socket.fire(new Message("Hello").fire(new Messsage("World");
The code below demonstrates how to write a Chat application using the Atmosphere Framework. For the server side component, see the sample
AtmosphereClient client = ClientFactory.getDefault().newClient(AtmosphereClient.class);
RequestBuilder request = client.newRequestBuilder()
.method(Request.METHOD.GET)
.uri("http//127.0.0.1/chat")
.trackMessageLength(true)
.encoder(new Encoder<Message, String>() {
@Override
public String encode(Message data) {
try {
return mapper.writeValueAsString(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
})
.decoder(new Decoder<String, Message>() {
@Override
public Message decode(Event type, String data) {
data = data.trim();
// Padding from Atmosphere, skip
if (data.length() == 0) {
return null;
}
if (type.equals(Event.MESSAGE)) {
try {
return mapper.readValue(data, Message.class);
} catch (IOException e) {
logger.debug("Invalid message {}", data);
return null;
}
} else {
return null;
}
}
})
.transport(Request.TRANSPORT.WEBSOCKET)
.transport(Request.TRANSPORT.SSE)
.transport(Request.TRANSPORT.LONG_POLLING);
Socket socket = client.create();
socket.on("message", new Function<Message>() {
@Override
public void on(Message t) {
Date d = new Date(t.getTime());
logger.info("Author {}: {}", t.getAuthor() + "@ " + d.getHours() + ":" + d.getMinutes(), t.getMessage());
}
}).on(new Function<Throwable>() {
@Override
public void on(Throwable t) {
t.printStackTrace();
}
}).on(Event.CLOSE.name(), new Function<String>() {
@Override
public void on(String t) {
logger.info("Connection closed");
}
}).open(request.build());
logger.info("Choose Name: ");
String name = null;
String a = "";
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (!(a.equals("quit"))) {
a = br.readLine();
if (name == null) {
name = a;
}
socket.fire(new Message(name, a));
}
socket.close();
By default, AHC runtime is not shared between instances of Client. You can turn it on by calling OptionsBuilder.runtimeShared