Asity 3.0.0 released

wrote this on

It is my pleasure to announce that Asity 3 is available now. This major release includes Play Framework 2 support. Actually, as you may know, Asity supported Play framework 5 years ago though it was dropped because of a lack of features when releasing the Asity 1.0 GA. In this release we rewrote the bridge from scratch using Akka Java API and successfully implemented the missing features.

If you want to jump right in and get started with Asity with Play Framework 2, start with the following examples.

  • Echo server - A simple web fragment to send back data to the client. It uses Play 2.6.
  • Cettia starter kit - A basic chat application using a real-time webapp framework built on top of Asity. It uses Play 2.7.

For the Play bridge’s reference documentation, visit https://asity.cettia.io/#play-2.

There’s no breaking changes with Asity 2.0. If you migrate from the last beta to GA, set the raw body parser for HTTP actions as follows. Of course, it applies to code using Play Framework 2 bridge, asity-bridge-play2, only.

@BodyParser.Of(BodyParser.Raw.class)
public CompletionStage<Result> http(Http.Request request) {
  AsityHttpAction action = new AsityHttpAction();
  action.onhttp(httpAction);

  return action.apply(request);
}

The following bug fixes and improvements are included since the last beta.

  • The minimum required version of Play framework is lowered from 2.7 to 2.6. Play 2.5 and below may work though we haven’t tried it.
  • It’s confirmed that the Play bridge is compatible with Play Framework 2.8.0-M3.
  • Bugs in the Play bridge’s readAsText(String charset) are fixed. It requires to use the raw body parser.
  • A generic wildcard in onbody(Action<?> action) and onchunk(Action<?> action) methods of ServerHttpExchange interface is replaced with a type parameter. Accordingly, you can write code like http.<String>onchunk(http::write); now.

While integrating Play Framework 2 with Asity, we confirmed that most key features work pretty well following the TDD principles, which means that you should have no problems in running a Cettia application on the Play Framework 2 through Asity. However, we also found the following known issues about “Transfer-encoding: chunked” request handling.

  • Play Framework doesn’t support to read request body by chunk asynchronously. Now the bridge waits till the client finishes writing the body and give an action the whole request body when finished. Accordingly the ServerHttpExchange#onchunk methods is called only once with the whole request body.
  • Play Framework doesn’t detect TCP disconnection. For that reason the ServerHttpExchange#onclose method is not called, regardless of the connection state.

If you have any questions, feedback, or hints for known issues, don’t hesitate to contact us, Cettia Groups.


Asity 3.0.0-Beta1 released

wrote this on

I’m glad to announce that the first beta of Asity 3.0.0 is out. Asity 3 includes Play framework 2 support, which is one of web frameworks the community has wanted for a long time. The first beta requires Play framework 2.7 and above but we are considering to lower the required Play framework version during the rest of the beta phase.

If you have ever used Play framework, please help us test, code review, and refactor the play-2 bridge. We found some features are not available in Play framework but not sure it’s because of the framework, e.g. reading request body by chunk asynchronously and detecting disconnection. Here are some resources to help you get started.

Our plan is to release Asity 3.0.0 GA in Q3. If you have any questions or feedback, as always let us, Cettia Groups, know.


Cettia JavaScript Client 1.1.0 released

wrote this on

Cettia JavaScript Client 1.1.0 has been released with many improvements including React Native support.

React Native support

One of the key pieces of the 2019 roadmap is to support mobile application development. As the first milestone React Native support has been added. In React Native, you can load a cettia object as you would in a bundler such as Webpack. (React Native internally uses a bundler called Metro) The rest is the same. Use Cettia as you would when creating a webapp.

import cettia from "cettia-client/cettia-bundler";

To demonstrate how it works on React Native, we added React Native example to the starter kit. Please check it out – https://github.com/cettia/cettia-starter-kit/#react-native

Here’s the full changelog:

As always, feel free to share your feedback or question on the mailing list.


Cettia JavaScript Client 1.0.2 released

wrote this on

Cettia JavaScript Client 1.0.2 has been released. This patch release contains a bug fix for the reconnection #25. It’s highly recommended to update it.

For your information, if you are using jsDelivr as a CDN and import cettia.js without a version, it may take up to 24 hours to fetch the latest package. As always, please let us know if you find any issues.


New Cettia Starter Kit

wrote this on

Based on the feedback so far and the 2019 roadmap, we rewrote the Cettia Starter Kit from scratch, an example project that you can use as a starting point of your own Cettia application, and updated Getting Started guide.

After reviewing support for mobile environment, one of the roadmap items, we concluded that it’s more useful and helpful to provide examples which are consistent across different environments e.g. browser, Android, iOS, and so on than to encourage to use the JavaScript console in the browser on the fly. As a result, we implemented the following user stories in the new example in the starter kit.

  • As a guest I want to sign in to the application by entering a username only so that I don’t have to go through an annoying sign-up process.
  • As a user I want to join the lounge channel where everyone gets together automatically after sign in so that I can talk with everyone.
  • As a user I want to send messages to the lounge channel so that everyone can receive my messages.
  • As a user I want to receive messages when others send them to the lounge channel so that I can keep conversation in real-time.

Here’s a screenshot of the new example.

cettia-starter-kit-1555758896147

Also, in the server side, to demonstrate Cettia’s framework-agnostic nature, we separated the part responsible for real-time event handling with Cettia and the part responsible for integrating Cettia with a web framework, and added integrated examples per each web framework supporting Cettia (Technically speaking, frameworks Asity supports) in addition to the current Java EE example. Accordingly, examples are added, which demonstrate integration with Atmosphere, Grizzly, Java EE, Netty, Spring WebFlux, Spring Web MVC, and Vert.x.

You can take a look at the Cettia Starter Kit at https://github.com/cettia/cettia-starter-kit and the Getting Started guide at https://cettia.io/guides/getting-started. As always, please let us know if you find any issues or have feedback.


Cettia Java Server 1.2.0 released

wrote this on

It is my pleasure to announce that Cettia Java Server 1.2.0 is available now. As announced in August, we dropped support for Java 7 in this release and added useful features based on Java 8 features such as functional interfaces and lambda expressions. To migrate from 1.1 to 1.2, update the version of the dependency. There is no backward incompatible changes other than the minimum Java version.

<dependency>
  <groupId>io.cettia</groupId>
  <artifactId>cettia-server</artifactId>
  <version>1.2.0</version>
</dependency>

When we added Spring MVC and Spring WebFlux supports to Asity 2, we were inspired by the Spring WebFlux’s functional programming model. In this release, we added a ServerSocketPredicates class, a helper for ServerSocketPredicate which consists of static methods that return various useful ServerSocketPredicates, and the default methods, and(ServerSocketPredicate that), or(ServerSocketPredicate that), and negate(), to ServerSocketPredicate. ServerSocketPredicate and ServerSocketPredicates are analogous to RequestPredicate and RequestPredicates of Spring WebFlux.

The following are static methods to create socket predicates defined in the ServerSocketPredicates.

all()
A predicate that always matches.
attr(String key, Object value)
A predicate that tests the socket attributes against the given key-value pair.
id(ServerSocket socket)
A predicate that tests the socket id against the given socket's id.
id(String id)
A predicate that tests the socket id against the given socket id.
tag(String... tags)
A predicate that tests the socket tags against the given tags.

For example, you can find sockets whose username is the same with the given username excluding the given socket as follows. Assume the attr and id are statically imported from the ServerSocketPredicates class.

// import static io.cettia.ServerSocketPredicates.attr;
// import static io.cettia.ServerSocketPredicates.id;
ServerSocketPredicate p = attr("username", username).and(id(socket).negate());

The above predicate can be utilized to limit only one socket per user declaratively like only one session per user. Assume that the username is given through a security framework like Apache Shiro and Spring Security and the signout event handler in the client side closes a connection and stops reconnecting.

server.onsocket(socket -> {
  // Limits only one socket per user
  server.find(attr("username", username).and(id(socket).negate())).send("signout").close();
});

In favor of the ServerSocketPredicates class, the existing finder methods such as Server#all() and Server#byTag(String tags...) are deprecated. Replace them with all() and tag(String... tags) defined in the ServerSocketPredicates with Server#find(ServerSocketPredicate predicate). The finders will be removed in the next major version.

Also, Sentence#find(ServerSocketPredicate predicate) is added which creates and returns a sentence with a given predicate like Server#find(ServerSocketPredicate predicate) but has a composed predicate that represents a short-circuiting logical AND of the original sentence’s predicate and the given predicate. We expect that it will greatly improve the reusability of a sentence. For example, if sockets are tagged with the application type e.g. web, android, desktop when initailized, we can find sockets by the user and then by the application type again.

// import static io.cettia.ServerSocketPredicates.attr;
// import static io.cettia.ServerSocketPredicates.tag;

// Sockets opened by the user
Sentence user = server.find(attr("username", "flowersinthesand"));
user.send(event1);

// Sockets opened through the user's android application
Sentence android = user.find(tag("android"));
android.send(event2);

// Sockets opened through the user's web application
Sentence web = user.find(tag("web"));
web.send(event3);

Cettia starter kit based on Servlet 3 and Java API for WebSocket 1 and ralscha/cettia-demo based on Spring framework 5 are updated with Cettia Java Server 1.2.0. Check out the working examples to see it in action. If you have any feedback, let us know via Cettia Groups.


Cettia Java Server 1.2.0-Beta2 released

wrote this on

I’m happy to announce the availability of the first beta release of Cettia Java Server 1.2.0. As of this release, Java 8 is the minimum requirement. Accordingly, we added various useful features including a collection of useful socket predicates, ServerSocketPredicates class, based on Java 8 features including lambda expressions.

<dependency>
  <groupId>io.cettia</groupId>
  <artifactId>cettia-server</artifactId>
  <version>1.2.0-Beta2</version>
</dependency>

With new features in 1.2, we can rewrite a feature to allow only one socket per username in a more concise and expressive way as follows. The following code snippet finds sockets whose username is the same except the given socket, sends a signout event to the sockets to prevent reconnection, and closes connections.

w/ 1.1

server.find(s -> username.equals(s.get("username")) && !socket.id().equals(s.id())).send("signout").close();

w/ 1.2

server.find(attr("username", username).and(id(socket).negate())).send("signout").close();

Note that the above attr and id are statically imported from ServerSocketPredicates. For the full list of the changes, visit 1.2.0-Beta2 milestone.

In the next GA release, we are thinking of deprecating the current finders like Server#all and Server#byTag in favor of ServerSocketPredicates. If you have any feedback, let us know via Cettia Groups.


Cettia Java Server 1.1.0 released

wrote this on

We are happy to announce that the 1.1.0 release of Cettia Java Server is now generally available. In this release, various useful features have been added which make a real-time web application development much easier. Here are the key highlights of Cettia Java Server 1.1.

  • A predicate to match sockets.
    • Server find(ServerSocketPredicate predicate, SerializableAction<ServerSocket> action) in Server
    • Sentence find(ServerSocketPredicate predicate) in Server
  • A sentence accepts a socket action.
    • Sentence execute(SerializableAction<ServerSocket> action) in Sentence
  • A socket’s attributes.
    • <T> T get(String name) in ServerSocket
    • ServerSocket set(String name, Object value) in ServerSocket
    • ServerSocket remove(String name) in ServerSocket
  • A socket identifier.
    • String id() in ServerSocket

Before walking through each feature in a summary of the starter kit, I would like to thank Ralph who has created and maintained cettia-demo for sharing his ideas that have driven the development of 1.1. As always, Cettia is an open source projects for the community by the community. Feel free to join the community and share your thoughts.


The following is a summary of the Cettia starter kit to help you get started quickly. In the summary, comments starting with ## refer to a title of a related chapter in the tutorial, Building Real-Time Web Applications With Cettia, where you can find a detailed explanation. You may want to highlight the ##.

Maven dependencies.

<!-- ## Setting Up the Project -->
<!-- To write a Cettia application -->
<dependency>
  <groupId>io.cettia</groupId>
  <artifactId>cettia-server</artifactId>
  <version>1.1.0</version>
</dependency>
<!-- To run a Cettia application on Servlet 3 and Java WebSocket API 1 -->
<!-- Besides them, you can also use Spring WebFlux, Spring MVC, Grizzly, Vert.x, Netty, and so on -->
<dependency>
  <groupId>io.cettia.asity</groupId>
  <artifactId>asity-bridge-servlet3</artifactId>
  <version>2.0.0</version>
</dependency>
<dependency>
  <groupId>io.cettia.asity</groupId>
  <artifactId>asity-bridge-jwa1</artifactId>
  <version>2.0.0</version>
</dependency>

A class to play with the Cettia server. Import statements, verbose try-catch blocks, empty methods, etc. are skipped for brevity.

@WebListener
public class CettiaConfigListener implements ServletContextListener {
  public void contextInitialized(ServletContextEvent event) {
    // Cettia part
    // 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);

    // If a client opens a socket, the server creates and passes a ServerSocket to socket handlers
    server.onsocket((ServerSocket 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));

      // ## Finder Methods and Sentence
      if (username != null) {
        // 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"));
        });

        // 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]);
        }
      });
      // 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]);
      }));
    });

    // ## 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(/* ㅇㅅㅇ */ httpAction);
    ServletRegistration.Dynamic reg = context.addServlet(AsityServlet.class.getName(), asityServlet);
    reg.setAsyncSupported(true);
    reg.addMapping("/cettia");

    ServerContainer container = (ServerContainer) context.getAttribute(ServerContainer.class.getName());
    ServerEndpointConfig.Configurator configurator = new ServerEndpointConfig.Configurator() {
      public <T> T getEndpointInstance(Class<T> endpointClass) {
        AsityServerEndpoint asityServerEndpoint = new AsityServerEndpoint();
        asityServerEndpoint.onwebsocket(/* ㅇㅅㅇ */ wsAction);
        return endpointClass.cast(asityServerEndpoint);
      }
    };
    container.addEndpoint(ServerEndpointConfig.Builder.create(AsityServerEndpoint.class, "/cettia")
      .configurator(configurator).build());

    // ## 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()));
  }
}

Here’s an example with the Spring WebFlux 5 to show Cettia’s framework-agnostic nature. Consult Asity’s Run Anywhere section for how to plug a Cettia application into other various frameworks.

@SpringBootApplication
@EnableWebFlux
public class CettiaServer {
  @Bean
  public RouterFunction<ServerResponse> httpMapping(HttpTransportServer httpAction) {
    AsityHandlerFunction asityHandlerFunction = new AsityHandlerFunction();
    asityHandlerFunction.onhttp(/* ㅇㅅㅇ */ httpAction);

    RequestPredicate isNotWebSocket = headers(h -> !"websocket".equalsIgnoreCase(h.asHttpHeaders().getUpgrade()));
    return route(path("/cettia").and(isNotWebSocket), asityHandlerFunction);
  }

  @Bean
  public HandlerMapping wsMapping(WebSocketTransportServer wsAction) {
    AsityWebSocketHandler asityWebSocketHandler = new AsityWebSocketHandler();
    asityWebSocketHandler.onwebsocket(/* ㅇㅅㅇ */ wsAction);
    Map<String, WebSocketHandler> map = new LinkedHashMap<>();
    map.put("/cettia", asityWebSocketHandler);

    SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
    mapping.setUrlMap(map);

    return mapping;
  }
}

You need minimal HTML to load the cettia object. Also, if you have a cettia-client npm module installed, you should be able to load the cettia object with require("cettia-client/cettia-bundler"); and require("cettia-client"); in Webpack and Node, respectively.

 <!DOCTYPE html>
 <title>index</title>
 <script src="https://unpkg.com/cettia-client@1.0.1/cettia-browser.min.js"></script>

Below is the JavaScript code to play with the cettia object. Open the above page and its developer console in several browsers, run the script, and watch results on the fly.

// ## 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));

// ## 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")});
});

The full source code for the starter kit is available at the repository, https://github.com/cettia/cettia-starter-kit. If you want to dig deeper, we strongly recommend to read an introductory tutorial to Cettia, Building Real-Time Web Applications With Cettia. It explains the reason behind key design decisions that the Cettia team have made in the Cettia, as well as various patterns and features required to build real-time oriented applications without compromise with Cettia.


Cettia Java Server 1.1.0-Beta1 released

wrote this on

We are pleased to announce the release of Cettia Java Server 1.1.0-Beta1. This beta release adds a new feature to support “Finding sockets and doing something with them” intuitively, which is one of the basic concepts of Cettia.

server.find(socket -> /* find sockets */).execute(socket -> /* do something with them */);

In addition, various features are added including an attributes for socket, a socket identifier, etc., and accompanying examples and details are supposed to be described in the GA release announcement. By then, check them in the website main. As a summary of a tutorial, a Getting Started section is newly added to the main and covers new features in 1.1.0 as well. For the full list of the changes, visit 1.1.0-Beta1 milestone.

By the way, we are considering dropping support for Java 7 and requiring Java 8 as minimum Java version as of 1.2.0. We think it would be fine since no one seems to use Cettia in Java 7 but if this would be an issue, please let us know.


Asity 2.0.0 released

wrote this on

Finally, we are pleased to announce that the 2.0.0 release of Asity is now generally available. Asity is a lightweight abstraction layer to build universally reusable web fragments on the JVM, and web fragment represents a component that receives HTTP request-response or WebSocket connection like a controller in MVC but is able to be compatible with any web framework in the Java ecosystem.

As a web fragment author, you can write a web fragment once and support almost all popular web frameworks in Java, and as an end-user, you can choose any technology stack as you wish and use web fragments without being frustrated by compatibility issues.

Here’s a comprehensive example for Asity 2. It demonstrates how to build an echo fragment which simply responds to the client with whatever data the client sent, and plug the fragment into Spring WebFlux, a web framework of Spring 5 reactive stack based on Reactive Streams.

Add the following dependencies:

<!-- To write a web fragment -->
<dependency>
  <groupId>io.cettia.asity</groupId>
  <artifactId>asity-http</artifactId>
  <version>2.0.0</version>
</dependency>
<dependency>
  <groupId>io.cettia.asity</groupId>
  <artifactId>asity-websocket</artifactId>
  <version>2.0.0</version>
</dependency>
<!-- To run a web fragment on Spring WebFlux 5 -->
<dependency>
  <groupId>io.cettia.asity</groupId>
  <artifactId>asity-bridge-spring-webflux5</artifactId>
  <version>2.0.0</version>
</dependency>

And the following class:

package io.cettia.asity.example.spring.webflux5;

import io.cettia.asity.action.Action;
import io.cettia.asity.bridge.spring.webflux5.AsityHandlerFunction;
import io.cettia.asity.bridge.spring.webflux5.AsityWebSocketHandler;
import io.cettia.asity.http.HttpStatus;
import io.cettia.asity.http.ServerHttpExchange;
import io.cettia.asity.websocket.ServerWebSocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;

import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.Map;

import static org.springframework.web.reactive.function.server.RequestPredicates.headers;
import static org.springframework.web.reactive.function.server.RequestPredicates.path;

@SpringBootApplication
@EnableWebFlux
public class EchoServer {
  @Bean
  public Action<ServerHttpExchange> httpAction() {
    return new HttpEchoServer();
  }

  @Bean
  public Action<ServerWebSocket> wsAction() {
    return new WebSocketEchoServer();
  }

  @Bean
  public RouterFunction<ServerResponse> httpMapping() {
    AsityHandlerFunction asityHandlerFunction = new AsityHandlerFunction().onhttp(httpAction());

    return RouterFunctions.route(
      path("/echo")
        // Excludes WebSocket handshake requests
        .and(headers(headers -> !"websocket".equalsIgnoreCase(headers.asHttpHeaders().getUpgrade()))), asityHandlerFunction);
  }

  @Bean
  public HandlerMapping wsMapping() {
    AsityWebSocketHandler asityWebSocketHandler = new AsityWebSocketHandler().onwebsocket(wsAction());
    Map<String, WebSocketHandler> map = new LinkedHashMap<>();
    map.put("/echo", asityWebSocketHandler);

    SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
    mapping.setUrlMap(map);

    return mapping;
  }

  @Bean
  public WebSocketHandlerAdapter webSocketHandlerAdapter() {
    return new WebSocketHandlerAdapter();
  }

  public static void main(String[] args) {
    SpringApplication.run(EchoServer.class, args);
  }

  public static class HttpEchoServer implements Action<ServerHttpExchange> {
    @Override
    public void on(ServerHttpExchange http) {
      // Reads request URI, method and headers
      System.out.println(http.method() + " " + http.uri());
      http.headerNames().stream().forEach(name -> System.out.println(name + ": " + String.join(", ", http.headers(name))));

      // Writes response status code and headers
      http.setStatus(HttpStatus.OK).setHeader("content-type", http.header("content-type"));

      // Reads a chunk from request body and writes it to response body
      http.readAsText().onchunk((String chunk) -> http.write(chunk));
      // If request body is binary,
      // http.readAsBinary().onchunk((ByteBuffer binary) -> http.write(binary));

      // Ends response if request ends
      http.onend((Void v) -> http.end());

      // Exception handling
      http.onerror((Throwable t) -> t.printStackTrace()).onclose((Void v) -> System.out.println("disconnected"));
    }
  }

  public static class WebSocketEchoServer implements Action<ServerWebSocket> {
    @Override
    public void on(ServerWebSocket ws) {
      // Reads handshake request URI and headers
      System.out.println(ws.uri());
      ws.headerNames().stream().forEach(name -> System.out.println(name + ": " + String.join(", ", ws.headers(name))));

      // Sends the received text frame and binary frame back
      ws.ontext((String text) -> ws.send(text)).onbinary((ByteBuffer binary) -> ws.send(binary));

      // Exception handling
      ws.onerror((Throwable t) -> t.printStackTrace());
    }
  }
}

As you would intuitively expect, HttpEchoServer and WebSocketEchoServer are a web fragment, and can be reused in other frameworks through other bridges like asity-bridge-spring-webmvc4. Also, note that a bridge implementation is completely transparent to end-users. End-users still have the full-control over web fragments on framework they selected. If they want to filter out requests, they can do that in the way they use the framework, and pass only proper requests to web fragments instead of learning how to filter out requests in Asity.

Now Asity supports Java API for WebSocket 1, Servlet 3, Spring WebFlux 5, Spring MVC 4, Vert.x 3, Netty 4, Grizzly 2, Vert.x 2 and Atmosphere 2. Here’s a list of working examples per supported framework. They include the corresponding client to enable you to test the example as well. The full documentation is available at the website, http://asity.cettia.io.

We sincerely believe that Asity project can make Java web development enjoyable. If you are interested in Asity’s vision and would like to be more involved, feel free to join the mailing list and share your feedback, or just DM me on Twitter ;) (@flowersits).


Asity 2.0.0-RC1 released

wrote this on

It is my pleasure to announce the first release candidate of Asity 2.0.0. Below are notable changes in this release.

  • Delegate thread management to underlying I/O framework #26
  • Grizzly should read request body by chunk asynchronously #25
  • Make sure Servlet 3 bridge is compatible with Servlet 4 - it’s confirmed with Undertow 2 #21

Visit 2.0.0-RC1 milestone for the complete list of the changes. Unless there’s anything else, Asity 2.0.0 GA release will ship in the next two or three weeks with various working examples. If you run into any issues, let us know through Cettia Groups.


Asity 2.0.0-Alpha2 released

wrote this on

I’m pleased to announce the second alpha release of Asity 2.0.0. This is our last alpha release, which means that we are now feature complete and will focus on fixing bugs and enhancing performance. Asity 2 will be a unique framework that allows you to write an asynchronous web app that runs on almost all popular web frameworks in Java without modification whatsoever; Servlet and Java API for WebSocket, Spring WebFlux, Spring Web MVC, Grizzly, Vert.x, Netty, and Atmosphere.

Although a new website for Asity 2 is a work in progress, the contents of the ‘Run anywhere’ part won’t change much. Check out the details at the website: http://asity.cettia.io.

Allow to read WebSocket handshake headers #12

Now you can retrieve WebSocket handshake headers such as Authorization and Sec-WebSocket-Protocol from a ServerWebSocket instance directly with Set<String> headerNames(), String header(String name) and List<String> headers(String name) methods.

Action<ServerWebSocket> wsAction = (ServerWebSocket ws) -> {
  // Logs WebSocket handshake headers
  ws.headerNames().stream().forEach(name -> System.out.println(name + ": " + ws.header(name)));
  // Subprotocols
  List<String> subprotocols = ws.headers("Sec-WebSocket-Protocol");
  // ...
};

Note that to enable this in applications based on Java WebSocket API, you should put a handshake request instance into a map returned by ServerEndpointConfig#getUserProperties with the javax.websocket.server.HandshakeRequest key by overriding a Configurator#modifyHandshake method.

ServerEndpointConfig config = ServerEndpointConfig.Builder.create(AsityServerEndpoint.class, "/cettia")
.configurator(new Configurator() {
  @Override
  public <T> T getEndpointInstance(Class<T> endpointClass) {
    return endpointClass.cast(new AsityServerEndpoint().onwebsocket(wsTransportServer));
  }

  // Required
  @Override
  public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
    config.getUserProperties().put(HandshakeRequest.class.getName(), request);
  }
})
.build();

Spring MVC support #13

io.cettia.asity:asity-bridge-spring-webmvc4:2.0.0-Alpha2 bridge module includes support for Spring Web MVC 4 and later. AsityController and AsityWebSocketHandler take responsibility for handling HTTP request-response exchanges and WebSocket connections, respectively. Here’s an example of building a Cettia application with Spring MVC 4 and Spring Boot 1:

package io.cettia.example.spring.webmvc4;

import io.cettia.asity.bridge.spring.webmvc4.AsityController;
import io.cettia.asity.bridge.spring.webmvc4.AsityWebSocketHandler;

// Skipped for brevity

@SpringBootApplication
@EnableWebMvc
@EnableWebSocket
public class Application implements WebSocketConfigurer {
  @Bean
  public Server server() {
    Server server = new DefaultServer();
    httpTransportServer().ontransport(server);
    wsTransportServer().ontransport(server);
    return server;
  }

  @Bean
  public HttpTransportServer httpTransportServer() {
    return new HttpTransportServer();
  }

  @Bean
  public WebSocketTransportServer wsTransportServer() {
    return new WebSocketTransportServer();
  }

  @Bean
  public HandlerMapping httpMapping() {
    AsityController asityController = new AsityController().onhttp(httpTransportServer());
    AbstractHandlerMapping mapping = new AbstractHandlerMapping() {
      @Override
      protected Object getHandlerInternal(HttpServletRequest request) {
        // Check whether a path equals '/asity'
        return "/cettia".equals(request.getRequestURI()) &&
          // Delegates WebSocket handshake requests to a webSocketHandler bean
          !"websocket".equalsIgnoreCase(request.getHeader("upgrade")) ? asityController : null;
      }
    };
    mapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return mapping;
  }

  @Override
  public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    AsityWebSocketHandler asityWebSocketHandler = new AsityWebSocketHandler().onwebsocket(wsTransportServer());
    registry.addHandler(asityWebSocketHandler, "/cettia").setAllowedOrigins("*");
  }

  @Bean
  public CorsFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.applyPermitDefaultValues();
    config.setAllowCredentials(true);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/cettia", config);

    return new CorsFilter(source);
  }

  public static void main(String[] args) {
    SpringApplication.run(Application.class);
  }
}

Vert.x 3 support #9

io.cettia.asity:asity-bridge-vertx3:2.0.0-Alpha2 bridge module includes support for Vert.x 3. AsityRequestHandler and AsityWebSocketHandler take responsibility for handling HTTP request-response exchanges and WebSocket connections, respectively. Here’s an example of building a Cettia application with Vert.x 3.

package io.cettia.example.vertx3;

import io.cettia.asity.bridge.vertx3.AsityRequestHandler;
import io.cettia.asity.bridge.vertx3.AsityWebSocketHandler;

// Skipped for brevity

public class Application extends AbstractVerticle {
  @Override
  public void start() throws Exception {
    Server server = new DefaultServer();
    HttpTransportServer httpTransportServer = new HttpTransportServer().ontransport(server);
    WebSocketTransportServer webSocketTransportServer = new WebSocketTransportServer().ontransport(server);

    HttpServer httpServer = vertx.createHttpServer();
    AsityRequestHandler requestHandler = new AsityRequestHandler().onhttp(httpTransportServer);
    httpServer.requestHandler(request -> {
      if (request.path().equals("/cettia")) {
        requestHandler.handle(request);
      }
    });
    AsityWebSocketHandler websocketHandler = new AsityWebSocketHandler().onwebsocket(webSocketTransportServer);
    httpServer.websocketHandler(socket -> {
      if (socket.path().equals("/cettia")) {
        websocketHandler.handle(socket);
      }
    });
    httpServer.listen(8080);
  }
};

Here’s the full changelog. If you have any idea or proposal, don’t hesitate to let us know on Cettia Groups.


Asity 2.0.0-Alpha1 released

wrote this on

I’m happy to announce the availability of the first alpha release of Asity 2.0.0. First of all, we rephrased the project definition to make clear the identity of the project:

Asity is an abstraction layer for various web frameworks on the Java Virtual Machine.

This articulates the reason why we created the Asity project; Cettia works with any web framework on the JVM, and allows the end user to plug in the desired web framework. Here’s a roadmap for Astiy 2:

For the details of the progress and information, visit https://github.com/cettia/asity. Asity 2 will be available early July. If you have any idea or proposal, don’t hesitate to let us know on Cettia Groups!

As for the first alpha release, it includes support for Spring WebFlux, a web framework of Spring’s reactive stack based on Reactive Streams. With io.cettia.asity:asity-bridge-spring-webflux5:2.0.0-Alpha1 bridge module, you can build a Cettia application with Spring WebFlux, and run it through Spring Boot just like a plain Spring application. Here’s an example:

package io.cettia.example.spring;

import io.cettia.asity.bridge.spring.webflux5.AsityHandlerFunction;
import io.cettia.asity.bridge.spring.webflux5.AsityWebSocketHandler;

// Skipped for brevity

@SpringBootApplication
@EnableWebFlux
public class Application {

  @Bean
  public Server server(HttpTransportServer httpTransportServer, WebSocketTransportServer wsTransportServer) {
    Server server = new DefaultServer();
    httpTransportServer.ontransport(server);
    wsTransportServer.ontransport(server);
    return server;
  }

  @Bean
  public HttpTransportServer httpTransportServer() {
    return new HttpTransportServer();
  }

  @Bean
  public WebSocketTransportServer wsTransportServer() {
    return new WebSocketTransportServer();
  }

  @Bean
  public RouterFunction<ServerResponse> httpMapping(HttpTransportServer httpTransportServer) {
    AsityHandlerFunction asityHandlerFunction = new AsityHandlerFunction().onhttp(httpTransportServer);

    return RouterFunctions.route(
      path("/cettia")
        // Excludes WebSocket handshake requests
        .and(headers(headers -> !"websocket".equalsIgnoreCase(headers.asHttpHeaders().getUpgrade()))), asityHandlerFunction);
  }

  @Bean
  public HandlerMapping wsMapping(WebSocketTransportServer wsTransportServer) {
    AsityWebSocketHandler asityWebSocketHandler = new AsityWebSocketHandler().onwebsocket(wsTransportServer);
    Map<String, WebSocketHandler> map = new LinkedHashMap<>();
    map.put("/cettia", asityWebSocketHandler);

    SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
    mapping.setUrlMap(map);

    return mapping;
  }

  @Bean
  public WebSocketHandlerAdapter webSocketHandlerAdapter() {
    return new WebSocketHandlerAdapter();
  }

  @Bean
  public CorsWebFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.applyPermitDefaultValues();
    config.setAllowCredentials(true);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/cettia", config);

    return new CorsWebFilter(source);
  }

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

}

Cettia JavaScript Client 1.0.1 released

wrote this on

Cettia JavaScript Client 1.0.1 has been released. This release contains a bug-fix for the reconnection #23.

By the way, we’ve been busy writing an introductory tutorial to Cettia, Building real-time web applications with Cettia, in the meantime. It walks you through the reason behind key design decisions that Cettia team have made in the Cettia as well as various patterns and features required to build real-time oriented applications without compromise with Cettia.

Please check it out if you are interested!


Cettia 1.0.0 released

wrote this on

Finally, several years of long journey up to 1.0.0 has ended and I’m very happy to announce the release of Cettia 1.0.0 (Cetita Protocol 1.0.0, Cettia Java Server 1.0.0, and Cettia JavaScript Client 1.0.0).

Cettia is a real-time web application framework that allows you to focus on event handling itself. It provides reliable full duplex connection which frees you from the pitfalls of traditional real-time programming, and elegant patterns which eliminate the boilerplate code involved with full duplex connection. Besides, it’s designed to work well with any transport technologies, any I/O frameworks, and any scaling methods, which is critical for modern enterprise applications.

Comparing to 7 years ago when I started Cettia’s predecessor’s predecessor (A jQuery plugin for HTTP streaming; I used to use it to demonstrate Servlet 3.0’s Async Servlet in IE 6), it has become much easier to create a real-time web application in both server-side and client-side but is still difficult now to meet the following conditions when writing a real-time web app:

  • I/O framework agnostic
    • Reactive everywhere. When adopting an I/O framework to serve your application, you should be able to select it according to performance, productivity, familiarity and so on with no regard to this kind of framework.
    • That’s why Cettia is designed to run on any I/O framework on Java Virtual Machine (JVM) seamlessly without degrading the underlying framework’s performance. Now Atmosphere, Grizzly, Java Servlet, Java WebSocket API, Netty and Vert.x are supported.
    • Even if your favorite I/O framework is not supported, don’t worry. With more or less 200 code lines, you can write a bridge to your framework and run Cettia via that bridge.
  • Reliable full duplex connection
    • If given proxy, firewall, anti-virus software or arbitrary Platform as a Service (PaaS), it’s very difficult to be absolutely-sure that WebSocket will just work. You still need “Comet” in 2018, a set of giant hacks for HTTP server-push defined more than 10 years ago.
    • HTTP-based transports Cettia provides i.e. streaming and long polling can carry, not only text but also binary payload based on message framing bidirectionally in real-time just like WebSocket. They just work in the wild.
    • Of course, you don’t need to (and should not) know under the hood details. Just open a socket and register event handlers. Cettia will do the rest and call your event handlers.
  • Elegant suite of patterns
    • WebSocket’s message event is not flexible enough to branch your logic especially in handling text and binary data together. Cettia provides the event system which allows to define your own events regardless of the type of data. No more boilerplate code.
    • One of common pitfalls in working on real world user stories is regarding a user as a single socket. Instead, you should send event to all browsers and devices the user signed in. To help this, Cettia provides a concise API that allows to regard a set of sockets as a single entity.
    • Whatever the reason is, it’s inevitable that the connection is disconnected. To help you handle disconnection declaratively, Cettia provides separate events for temporary disconnection and permanent disconnection along with events failed to send during disconnection.
  • Scalable horizontally
    • Because the location of the socket is transparent and Cettia servers don’t share any data, you can scale your application horizontally with publish-subscribe model like Java Message Service (JMS). Of course, you don’t need to modify existing event handlers.
    • Within the microservice architecture or the serverless architecture, it’s possible to scale out only the Cettia part of your application using publish-subscribe solution, which is much more cost-effective comparing to the monolithic architecture.
    • With the cloud computing service supporting autoscaling like Amazon Web Service, Microsoft Azure and Google Cloud Platform, it’s quite easy to set up scalable cluster of Cettia.

Are you convinced? I bet you are ;) Visit sub-projects for quick starts and working examples:

Here’s a brief roadmap for the next major version.

  • Set Java 8 as a minimum JDK version.
  • Reference application.
  • Reactive Streams support.
  • More I/O frameworks support i.e. Play 2, Vert.x 3, Servlet 4 and so on.
  • Support batch processing for exchanging messages in HTTP transports.
  • Interchangeable binary event format e.g. from MessagePack to BSON.
  • Introduce new transport based on HTTP2 server push.

I would like to thank everyone who has given us feedback on Cettia and its predecessors over the few years. These feedbacks have been and will be very useful in developing Cettia! Feel free to get in touch with us and share your questions and experience or just say hi on Cettia Groups :)


Cettia 1.0.0-RC1 released

wrote this on

I’m happy to announce the release of the Cettia 1.0.0-RC1 (Cetita Protocol 1.0.0-RC1, Cettia Java Server 1.0.0-RC1 and Cettia JavaScript Client 1.0.0-RC1). Based on the feedback, we added a small helper to resolve HttpSession from Servlet applications.

HttpSessionResolver httpSessionResolver = new HttpSessionResolver();
HttpSession httpSession = httpSessionResolver.resolve(socket);

Please test this release so that we can handle any issues before releasing GA. And, here’s one more thing.

It’s time to write the reference application and tutorial that demonstrate how to use the Cettia to develop a real-time web application. I guess it will be yet yet another Slack or Gitter ;) Of course, the source code will be open sourced under the Apache 2.0 license.

We are going to define the application details based on the features of the Cettia, choose the tech stack based on the popularity in the Java ecosystem, and so on. If you are interested, feel free to share your thought on this.

You can share your thought at this post.

Here’s the full changelog:

As always, please let us know if you have any question or feedback.


Cettia JavaScript Client 1.0.0-Beta2 released

wrote this on

Cettia JavaScript Client 1.0.0-Beta2 has been released. This release brings support for bundlers such as webpack, Browserify or Rollup.

Kudos to @DDKnoll for the great work. For the details of how to make Cettia work with bundlers, see this pull request.

To require the module for a browser bundle:

import cettia from "cettia-client/cettia-bundler"; // ES6 way
var cettia = require('cettia-client/cettia-bundler'); // CommonJS way

As of this release, for the sake of easy management, we have dropped support for Asynchronous Module Definition (AMD) and Bower and made a decision to use unpkg that serves files from npm packages as a CDN. Accordingly, cettia.min.js has been renamed to cettia-browser.min.js and is available at https://unpkg.com/cettia-client@1.0.0-Beta2/cettia-browser.min.js.

As always, please let us, Cettia Groups, know if you have any question or feedback.


Cettia 1.0.0-Beta1 released

wrote this on

Finally, all functionalities of Cettia 1.0 are implemented. Accordingly, Cetita Protocol 1.0.0-Beta1, Cettia JavaScript Client 1.0.0-Beta1 and Cettia Java Server 1.0.0-Beta1 have been released. This release focuses on binary event which allows to exchange binary data without using binary-to-text encoding and is one of features that many users have requested.

So far, you have had to serialize binary to text and send that text data to send binary, and receive text data and deserialize that text to binary to read binary, using an binary-to-text encoding scheme like Base64. It has inevitably brought about performance degradation as well as payload size increase. Moreover, you have had to figure out which events handle binary in advance and do serialization and deserialization manually.

Thanks to MessagePack which is a schemaless binary interchange format, now it is possible to deal with binary as a first-class citizen. If a given data or its one of properties is evaluated as binary, it will be internally serialized and deserialized according to MessagePack instead of JSON. Besides, it’s designed to work with any binary interchange format so you can let client determine an interchange format it will use e.g. BSON for Rust client and MessagePack for Go client per connection. Just let us know your needs. Anyway, here the most important thing is you don’t need to know about this at all.

Let’s take a look at the new feature. In the following example, both client-side and server-side sockets send text, binary and composite event to their counterpart on open event.

JavaScript Client

// In Node.js, replace 'cettia' with 'require("cettia-client")'
var socket = cettia.open(uri);
socket.on("open", function() {
  // String object is text
  socket.send("discard", "test");

  // According to W3C Encoding standard https://encoding.spec.whatwg.org/
  // encoder.encode takes text and returns binary in the form of ArrayBuffer
  var encoder = new TextEncoder();
  // In Node.js, replace 'encoder.encode("test")' with 'new Buffer("test")'
  socket.send("discard", encoder.encode("test"));

  // Even composite data including both text and binary can be exchanged
  socket.send("discard", {text: "test", binary: encoder.encode("test")});
});
// Prints all received data
socket.on("discard", function(data) {
  console.log(data);
});

Java Server

Server server = new DefaultServer();
server.onsocket((ServerSocket socket) -> {
  socket.onopen((Void v) -> {
    // String instance is text
    socket.send("discard", "test");

    // Byte array is binary
    socket.send("discard", "test".getBytes());

    // ByteBuffer is regarded as binary as well
    socket.send("discard", ByteBuffer.wrap("test".getBytes());

    // Even POJO as well as plain map including both text and binary can be exchanged
    socket.send("discard", new LinkedHashMap<String, Object>() {{
      put("text", "test");
      put("binary", "test".getBytes());
    }});
  });
  // Prints all received data
  socket.on("discard", (Object data) -> System.out.println(data));
});

Also, cettia.js’s size is notably reduced from 5.18KB to 4.6KB minified and gzipped by dropping support for Internet Explorer 6-8 and now works pretty well in Node 4/5.

Here’s the full changelog:

As always, please let us know if you have any question or feedback.


Cettia Java Platform is now Asity

wrote this on

Cettia Java Platform is now called Asity, which is created to run Java web applications on any platform on top of the Java Virtual Machine seamlessly. Today, Asity 1.0.0-Beta1 and accordingly Cettia Java Server 1.0.0-Alpha3 are released.

Asity is a lightweight abstraction layer for web frameworks which is designed to build applications that can run on any full-stack framework, any micro framework or any raw server on the JVM.

With Asity, you can build web framework-agnostic applications on the JVM with ease. Now an application based on Asity can run on Atmosphere, Grizzly, Java WebSocket API, Netty, Servlet and Vert.x transparently.

Visit the asity.cettia.io for full documentation.

How to migrate

Here’s how to migrate from Cettia Java Platform 1.0.0-Alpha1 to Asity 1.0.0-Beta1. Just rename the followings:

  • GAV from io.cettia.platform:cettia-platform-xxx:1.0.0-Alpha1 to to io.cettia.asity:asity-xxx:1.0.0-Beta1.
  • Package from io.cettia.platform to io.cettia.asity.
  • Class from CettiaXXX to AsityXXX.

Please let us, Cettia Groups, know if you have any question or feedback.


Cettia 1.0.0-Alpha2 released

wrote this on

I’m pleased to announce that Cetita Protocol 1.0.0-Alpha2, Cettia JavaScript Client 1.0.0-Alpha2 and Cettia Java Server 1.0.0-Alpha2 have been released. The theme of this release is offline application, which provides a flexible way to deal with disconnection making it highly annoying to build real-time application.

In the real world, losing connection is not uncommon so that it is pretty important to make applications relying on full-duplex connection functional while offline in some way. For example, users lose connection every time they navigate from one page to another page and may lose network while on the move. Even if the time between disconnection and reconnection is very short, it’s true that any message can’t be sent and received after every disconnection and authentication which is a process to verify a user is who they say they are should be done and messages which couldn’t be sent and received meanwhile should be synchronized after every reconnection. Also if reconnection doesn’t occur for a long time, these messages might have to be sent through each user’s email.

To solve such problems, it has been required to design and implement application’s own protocol on top of the full duplex connection. With this feature, you can handle sockets regardless of their online/offline state as well as deal with such issues with ease just by handling some reserved socket events. Moreover, a reference to socket is not affected by disconnection and reconnection at all and doing authentication once is enough for socket. Of course, it applies to every transport not just HTTP based ones.

Let’s take a look at the new feature through code snippet.

JavaScript Client

var socket = cettia.open(uri, {
    // 'name' option allows for the socket of the next page to inherit the lifecycle of the socket of the current page
    // This means the server can cache events which couldn't be sent to the socket of the previous page 
    // due to temporary disconnection during page navigation and send them to the socket of the next page on reconnection
    // With this option, you don't need to follow single page application model to workaround such issues
    name: "main"
});

// A queue containing events the client couldn't send to the server while disconnection
var cache = [];

// Fired when the server issues a new id for this socket as the beginning of the new lifecycle and the end of the old lifecycle
// The 'open' event always follows this event but not vice versa
socket.on("new", function() {
    // You can reset resources having been used for the old lifecycle for the new lifecycle here
    cache.length = 0;
});

// Fired when the underlying transport establishes a connection
socket.on("open", function() {
    // Now that communication is possible, you can flush the cache
    while(socket.state() === "opened" && cache.length) {
        // Removes the first event from the cache and sends it to the server one by one
        var args = cache.shift();
        socket.send.apply(socket, args);
    }
});

// Fired if some event is sent via this socket while there is no connection
socket.on("cache", function(args) {
    // You can determine whether or not to cache this arguments used to call the 'send' method
    // For example, in some cases, you may want to avoid caching to deliver live data in time
    cache.push(args);
});

// You don't need to pay attention to socket's online/offline state at all
// Internally, it will be delegated to the underlying transport while online and the 'cache' event while offline
socket.send("event", data);

Java Server

// Fired when a socket has been created as the beginning of the lifecycle
// However the handshake is not performed yet and it is not allowed to exchange events
server.onsocket(new Action<ServerSocket>() {
    @Override
    public void on(final ServerSocket socket) {
        // You can authenticate a given socket here e.g. using token-based approach or cookie-based approach
        final Map<String, String> authentication = new TokenVerifier().verify(new Uri(socket.uri()).parameter("token"));

        // Once is enough because this reference is unaffected by disconnection and reconnection
        socket.tag(authentication.get("username"));
        // Then, it's possible to send events to a specific user who may have used multiple devices by username
        // The given socket represents a specific device like desktop, phone, tablet and so on
        // e.g. server.byTag("flowersinthesand", socket -> socket.send("directmessage", "Hello there!"));
        
        // A queue containing events the server couldn't send to the client while disconnection
        final Queue<Object[]> cache = new ConcurrentLinkedQueue<>();
        
        // Fired when the handshake is performed successfully
        socket.onopen(new VoidAction() {
            @Override
            public void on() {
                // Now that communication is possible, you can flush the cache
                while (socket.state() == ServerSocket.State.OPENED && !cache.isEmpty()) {
                    // Removes the first event from the cache and sends it to the client one by one
                    Object[] args = cache.poll();
                    socket.send((String) args[0], args[1], (Action<?>) args[2], (Action<?>) args[3]);
                }
            }
        });

        // Fired if some event is sent via this socket while there is no connection
        socket.oncache(new Action<Object[]>() {
            @Override
            public void on(Object[] args) {
                // You can determine whether or not to cache this arguments used to call the 'send' method
                // For example, in some cases, you may want to avoid caching to deliver live data in time
                cache.offer(args);
            }
        });

        // Fired if the socket has been closed for a long time i.e. 1 minute and deleted from the server as the end of the lifecycle
        // A deleted socket can't be and shouldn't be used
        socket.ondelete(new VoidAction() {
            @Override
            public void on() {
                // If the cache is not empty, that is to say, there are still some messages user should receive
                // you can send an email to notify user or store them to database for user to check on next logging in
                if (!cache.isEmpty()) {
                    Account account = Account.findByUsername(authentication.get("username"));
                    // Assumes this method checks if user have not really received the unread messages
                    // or some of them through other devices or other sockets and sends an email 
                    // to notify user of the final unread messages if they exist
                    account.notifyUnreadMessages(cache);
                }
            }
        });        
    }
});
    
// You can pass a socket action to server at any time not paying attention to given socket's online/offline state
server.all(new Action<ServerSocket>() {
    @Override
    public void on(final ServerSocket socket) {
        // It will be delegated to the underlying transport while online or the cache event while offline
        socket.send("event", data);
    }
});

For your information, TokenVerifier, Uri and Account are imaginary classes to help describe a scenario matching with this feature well. As you can see that the above code snippets are just boilerplate, we have agonized over introducing helper classes to remove these boilerplate but have decided to wait for the community’s feedback. So don’t hesitate to give us your thought about this.

Here’s the full changelog:

As always, please let us, Cettia Groups, know if you have any question or feedback.


Cettia 1.0.0-Alpha1 released

wrote this on

After a year and a half of research and experiment, it is my pleasure to announce that the first alpha of Cettia, a real-time web application framework, is now available.

Cettia is a new web framework to write real-time web application and service based on its own polyglot protocol built over transport such as HTTP and WebSocket. Cettia focuses on providing reliable full duplex connection and making the best use of it for modern enterprise applications so that teams can focus on event handling itself without unnecessary ties to low-level details and integrate application with any high-level technologies or patterns for enterprise application with ease.

The Cettia project consists of protocol and implementations and as implementation Java Server and JavaScript Client are provided. Please note that each project has many more useful features besides its name suggests.

Here’s a very simple example demonstrating essential functionalities. For working example, please refer to the quick start guide of Java Server and JavaScript Client or visit repository for a lot of examples.

Java Server

final Server server = new DefaultServer();
server.onsocket(new Action<ServerSocket>() {
    @Override
    public void on(final ServerSocket socket) {
        socket.on("echo", new Action<String>() {
            @Override
            public void on(String data) {
                socket.send("echo", data);
            }
        });
        socket.on("chat", new Action<String>() {
            @Override
            public void on(String data) {
                server.all().send("chat", data);
            }
        });
    }
});
// And bridge the above server to your favorite platform like 
// Atmosphere, Grizzly, Netty, Play, Servlet, Vert.x and so on

JavaScript Client

var socket = cettia.open("http://localhost:8080/cettia");
socket.on("open", function() {
    socket.send("echo", "An echo message");
    socket.send("chat", "A chat message");
});
socket.on("echo", function(data) {
    console.log("on echo event:", data);
});
socket.on("chat", function(data) {
    console.log("on chat event:", data);
});

Here is a summary of key features in Cettia overall:

  • Based on protocol - Real-time web will be everywhere soon. You will need more features to write a just simple real-time webapp. The separated protocol is the baseline to build such features.
  • Polyglot - It’s not just for Java and JavaScript but for any language. With the reference implementation and test suite, you can easily not only implement the protocol in any language but also verify your client and server.
  • Standards - As component of the protocol, RFC and W3C standards are mainly adopted. Just use existing implementation as desired. You don’t need to reinvent the wheel.
  • Transport layer - Any transport technology besides WebSocket and HTTP can be used to bridge client and server as long as it meets requirements of Cettia transport. Along that way, you can control sockets backed by hetero transports through one server.
  • Event not message - The unit of data to be sent and received from the semantic point of view is an event object associated with a customizable type, which is easy to compose a controller from MVC.
  • Remote Procedure Call - It also allows to attach callbacks in sending event and to call those callbacks with the result of the event processing in receiving event. It’s useful where request-response model is more suited than notification model.
  • Server and Socket - All the interfaces you need to know to handle server are Server producing and managing socket and Socket representing the remote client. Select some sockets from server and do something with them like manipulating DOM using jQuery.
  • Entity as well as connection - Tag gives you a way to handle a specific entity in the real world as an identifier of a group of sockets. For example, you can use it to model a user logged in using multiple devices or subscribers to a specific topic.
  • Dependency injection friendly - A use case with DI framework is definite. Define a server as a singleton and inject it wherever you want to handle socket just like when using EntityManager from JPA.
  • Scalability - A publish-subscribe messaging system is enough to scale your application. Because servers don’t share any data, you can scale application horizontally with ease as well as don’t need to prepare for data grid system or NoSQL solution.
  • Run on any platform - Because server is built on a simple abstraction layer for various application platforms running on JVM, you can run your application on any supported platform seamlessly. Now Atmosphere, Grizzly, Java WebSocket API, Netty, Play, Servlet and Vert.x are supported.
  • Lightweight - JavaScript client takes 5.1KB minified and gzipped. Compare it to others: Socket.IO 1.3.4 - 19.96KB, Sockjs 0.3.4 - 10.09KB and Autobahn latest - 37.17KB.
  • Wide browser support - Wherever jQuery 1 is available, you can write a real-time webapp. A multitude of browsers are supported according to jQuery 1’s browser support policy embracing IE 6.
  • Proved flexibility - It is flexible enough to integrate with any technologies, patterns or frameworks for enterprise application with ease, which has been proved by a lot of examples.
  • 100% open source - All projects are distributed under the Apache Software License 2.0 which is one of the most flexible open source licenses.

And here is the roadmap of Cettia 1.0:

  • Offline application - It is necessary to handle sockets whose connection is disconnected for a little while. This feature will provide events you can utilize to deal with such case properly by making socket to be backed by multiple transports not just one. cettia-protocol#1
  • Binary support - Cettia transport can carry binary data but how to make use of it for event object is not yet determined. With this feature, you will be able to use an object containing binary data as event’s data and exchange it without binary-to-text conversion.
  • Complete Play support - The current implementation written in Play’s Java API misses some functionalities. To fix that issue, new implementation will be written in Play’s Scala API and also come with helpers making it easy to bridge application and Play. cettia-java-platform#3
  • More examples - A reference application to illustrate how Cettia can be used to build modern web application and more archetype applications to demonstrate how Cettia can be integrated with other technologies and frameworks will be provided.

For full documentation and information on Cettia, please visit the website.

Thanks for all the feedback from early adopters. The feedback has been and will be very important to us, so please feel free to get in touch with us, Cettia Groups, if you have any question or feedback.