Skip to content

Commit

Permalink
Update the starter kit for Cettia 1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
flowersinthesand committed Aug 26, 2018
1 parent e3a944b commit 0320c3e
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 53 deletions.
4 changes: 2 additions & 2 deletions README.md
@@ -1,6 +1,6 @@
# Cettia starter kit

This starter kit is created to help you make a quick start with [Cettia](http://cettia.io). It is based on Servlet 3 and Java WebSocket API 1, which are the most-used I/O frameworks in writing Cettia applications. For the details, see [Building real-time web applications with Cettia](http://cettia.io/guides/cettia-tutorial/) dealing with .
This starter kit is created to help you make a quick start with [Cettia](http://cettia.io). It is based on Servlet 3 and Java WebSocket API 1, which are the most-used I/O frameworks in writing Cettia applications. For the details, see [Building real-time web applications with Cettia](http://cettia.io/guides/cettia-tutorial/).

To get started, make sure you have Java 8+ and Maven 3+ installed.

Expand All @@ -13,4 +13,4 @@ To get started, make sure you have Java 8+ and Maven 3+ installed.
mvn jetty:run
```

Now the application is running now, open your browser and navigate to [http://127.0.0.1:8080/](http://127.0.0.1:8080/).
Now the application is running now, open your browser and navigate to [http://127.0.0.1:8080/](http://127.0.0.1:8080/). If you want to form a cluster, start up one more server on port 8090 by running `mvn jetty:run -Djetty.port=8090`.
22 changes: 11 additions & 11 deletions pom.xml
Expand Up @@ -17,7 +17,7 @@
<dependency>
<groupId>io.cettia</groupId>
<artifactId>cettia-server</artifactId>
<version>1.0.0</version>
<version>1.1.0-Beta1</version>
</dependency>
<dependency>
<groupId>io.cettia.asity</groupId>
Expand All @@ -41,16 +41,16 @@
<version>1.0</version>
<scope>provided</scope>
</dependency>
<!--<dependency>-->
<!--<groupId>com.hazelcast</groupId>-->
<!--<artifactId>hazelcast</artifactId>-->
<!--<version>3.9.3</version>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>com.hazelcast</groupId>-->
<!--<artifactId>hazelcast-client</artifactId>-->
<!--<version>3.9.3</version>-->
<!--</dependency>-->
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>3.9.3</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-client</artifactId>
<version>3.9.3</version>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
118 changes: 86 additions & 32 deletions src/main/java/io/cettia/starter/CettiaConfigListener.java
@@ -1,12 +1,10 @@
package io.cettia.starter;

//import com.hazelcast.config.Config;
//import com.hazelcast.core.HazelcastInstance;
//import com.hazelcast.core.ITopic;
//import com.hazelcast.instance.HazelcastInstanceFactory;
//import io.cettia.ClusteredServer;
import io.cettia.DefaultServer;
import io.cettia.Server;
import com.hazelcast.config.Config;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.ITopic;
import com.hazelcast.instance.HazelcastInstanceFactory;
import io.cettia.ClusteredServer;
import io.cettia.ServerSocket;
import io.cettia.asity.action.Action;
import io.cettia.asity.bridge.jwa1.AsityServerEndpoint;
Expand All @@ -22,59 +20,110 @@
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
//import java.util.Map;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import static java.util.concurrent.TimeUnit.SECONDS;

@WebListener
public class CettiaConfigListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
// Cettia part
// ClusteredServer server = new ClusteredServer();
Server server = new DefaultServer();
HttpTransportServer httpTransportServer = new HttpTransportServer().ontransport(server);
WebSocketTransportServer wsTransportServer = new WebSocketTransportServer().ontransport(server);
// If you don't want to form a cluster,
// replace the following line with `Server server = new DefaultServer();`
ClusteredServer server = new ClusteredServer();
HttpTransportServer httpAction = new HttpTransportServer().ontransport(server);
WebSocketTransportServer wsAction = new WebSocketTransportServer().ontransport(server);

// The socket handler
// If a client opens a socket, the server creates and passes a ServerSocket to socket handlers
server.onsocket((ServerSocket socket) -> {
System.out.println(socket);

// ## Socket Lifecycle
Action<Void> logState = v -> System.out.println(socket + " " + socket.state());
socket.onopen(logState).onclose(logState).ondelete(logState);

// ## Sending and Receiving Events
// An `echo` event handler where any received echo event is sent back
socket.on("echo", data -> socket.send("echo", data));

// ## Attributes and Tags
// Attributes and tags are contexts to store the socket state in the form of Map and Set
String username = findParam(socket.uri(), "username");
if (username == null) {
// Attaches a tag to the socket.
socket.tag("nonmember");
} else {
// Associates an attribute with the the socket
socket.set("username", username);
}

// ## Working with Sockets
// A `chat` event handler to send a given chat event to every socket in every server in the cluster
socket.on("chat", data -> server.all().send("chat", data));

String username = findUsername(socket.uri());
// ## Finder Methods and Sentence
if (username != null) {
socket.tag(username).on("myself", data -> server.byTag(username).send("myself", data));
// A myself event handler to send a given myself event to sockets whose username is the same
socket.on("myself", data -> {
// Find sockets by the attribute and deal with them directly
server.find(s -> username.equals(s.get("username"))).execute(s -> s.send("myself", data));
});

// How to allow only one socket per username
boolean onlyOneSocket = Boolean.parseBoolean(findParam(socket.uri(), "onlyOneSocket"));
if (onlyOneSocket) {
// Finds sockets whose username is the same except this socket
String me = socket.id();
server.find(s -> username.equals(s.get("username")) && !me.equals(s.id()))
// Sends a `signout` event to prevent reconnection and closes a connection
.send("signout").close();
// As of 1.2, it can be written more concisely
// `server.byAttr("username", username).exclude(socket).send("signout").close();`
}
}

// ## Recovering Missed Events
Queue<Object[]> queue = new ConcurrentLinkedQueue<>();
// Caches events that fail to send due to disconnection
socket.oncache(args -> queue.offer(args));
// Sends cached events on the next connection
socket.onopen(v -> {
while (socket.state() == ServerSocket.State.OPENED && !queue.isEmpty()) {
Object[] args = queue.poll();
socket.send((String) args[0], args[1], (Action<?>) args[2], (Action<?>) args[3]);
}
});
socket.ondelete(v -> queue.forEach(args -> System.out.println(socket + " missed event - name: " + args[0] + ", data: " + args[1])));
// If the client fails to connect within 1 minute after disconnection,
// You may want to consider notifying the user of finally missed events, like push notifications
socket.ondelete(v -> queue.forEach(args -> {
System.out.println(socket + " missed event - name: " + args[0] + ", data: " + args[1]);
}));
});

// Servlet part
// ## Working with Sockets
// To deal with sockets, inject the server wherever you want
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
// Sends a welcome event to sockets representing user not signed in every 5 seconds
executor.scheduleAtFixedRate(() -> server.byTag("nonmember").send("welcome"), 0, 5, SECONDS);

// ## Plugging Into the Web Framework
// Cettia is designed to run on any web framework seamlessly on the JVM
// Note how `httpAction` and `wsAction` are plugged into Servlet and Java API for Websocket
ServletContext context = event.getServletContext();
AsityServlet asityServlet = new AsityServlet().onhttp(httpTransportServer);
AsityServlet asityServlet = new AsityServlet().onhttp(/* ㅇㅅㅇ */ httpAction);
ServletRegistration.Dynamic reg = context.addServlet(AsityServlet.class.getName(), asityServlet);
reg.setAsyncSupported(true);
reg.addMapping("/cettia");

// Java WebSocket API part
ServerContainer container = (ServerContainer) context.getAttribute(ServerContainer.class.getName());
ServerEndpointConfig.Configurator configurator = new ServerEndpointConfig.Configurator() {
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) {
AsityServerEndpoint asityServerEndpoint = new AsityServerEndpoint().onwebsocket(wsTransportServer);
AsityServerEndpoint asityServerEndpoint = new AsityServerEndpoint();
asityServerEndpoint.onwebsocket(/* ㅇㅅㅇ */ wsAction);
return endpointClass.cast(asityServerEndpoint);
}
};
Expand All @@ -84,24 +133,29 @@ public <T> T getEndpointInstance(Class<T> endpointClass) {
throw new RuntimeException(e);
}

// Hazelcast part
// HazelcastInstance hazelcast = HazelcastInstanceFactory.newHazelcastInstance(new Config());
// ITopic<Map<String, Object>> topic = hazelcast.getTopic("cettia");
// server.onpublish(message -> topic.publish(message));
// topic.addMessageListener(message -> server.messageAction().on(message.getMessageObject()));
// ## Scaling a Cettia Application
// Any publish-subscribe messaging system can be used to scale a Cettia application horizontally,
// and it doesn’t require any modification in the existing application.
HazelcastInstance hazelcast = HazelcastInstanceFactory.newHazelcastInstance(new Config());
ITopic<Map<String, Object>> topic = hazelcast.getTopic("cettia");
// It publishes messages given by the server
server.onpublish(message -> topic.publish(message));
// It relays published messages to the server
topic.addMessageListener(message -> server.messageAction().on(message.getMessageObject()));
}

private String findUsername(String uri) {
String username = null;
String regex = "(?:^|.*&)username=([^&]+).*";
private String findParam(String uri, String key) {
String value = null;
String regex = "(?:^|.*&)" + key + "=([^&]+).*";
String query = java.net.URI.create(uri).getQuery();
if (query.matches(regex)) {
username = query.replaceAll(regex, "$1");
value = query.replaceAll(regex, "$1");
}

return username;
return value;
}

@Override
public void contextDestroyed(ServletContextEvent sce) {}

}
35 changes: 27 additions & 8 deletions src/main/webapp/index.html
Expand Up @@ -19,24 +19,43 @@
<body>
<p>This page loads <code>cettia</code> object for you to play with the <a href="http://cettia.io" target="_blank">Cettia</a> client interactively. Open the JavaScript console of the developer tools and run code snippets from the tutorial, <a href="http://cettia.io/guides/cettia-tutorial/" target="_blank">Building real-time web applications with Cettia</a>.</p>
<p>Otherwise, start with the following full-blown example:</p>
<pre><code class="language-javascript line-numbers">var socket = cettia.open("http://127.0.0.1:8080/cettia?username=flowersinthesand");
socket.on("connecting", () => console.log(socket.state()));
socket.on("open", () => console.log(socket.state()));
socket.on("close", () => console.log(socket.state()));
<pre><code class="language-javascript line-numbers">// ## Opening a Socket
// Manipulates the below params object to play with the server implementation
var params = {
username: "flowersinthesand",
onlyOneSocket: true
};
// Let's assume that each key and value are already encoding safe
var query = Object.keys(params).filter(k => params[k]).map(k => `${k}=${params[k]}`).join("&");
var socket = cettia.open("/cettia?" + query);

// ## Socket Lifecycle
var logState = () => console.log(socket.state());
socket.on("connecting", logState).on("open", logState).on("close", logState);
socket.on("waiting", (delay, attempts) => console.log(socket.state(), delay, attempts));

socket.on("echo", data => console.log("echo", data));
socket.on("chat", data => console.log("chat", data));
socket.on("myself", data => console.log("myself", data));
// ## Sending and Receiving Events
["echo", "chat", "myself", "welcome"].forEach(event => {
socket.on(event, data => console.log(event, data));
});
socket.on("signout", () => {
console.log("signout", "You've been signed out since you signed in on another device");
// It prevents reconnection
socket.close();
});

// This open event handler registered through `once` is called at maximum once
socket.once("open", () => {
// Sends an echo event to be returned
socket.send("echo", "Hello world");
// Sends a chat event to be broadcast to every sockets in the server
socket.send("chat", {text: "I'm a text"});
// Sends an event to sockets whose username is the same
// with composite data consisting of text data and binary data
socket.send("myself", {text: "I'm a text", binary: new TextEncoder().encode("I'm a binary")});
});
</code></pre>
<p>If you have any question or feedback, please feel free to share them on <a href="http://groups.google.com/group/cettia" target="_blank">Cettia Groups</a>.</p>

<script src="https://cdn.jsdelivr.net/combine/npm/prismjs@1.14.0,npm/prismjs@1.14.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
<script src="https://unpkg.com/cettia-client@1.0.1/cettia-browser.js"></script>
</body>
Expand Down

0 comments on commit 0320c3e

Please sign in to comment.