Jersey JSP View running on Grizzly server #2: Jersey 2

In the first part it was described how to run JSP on Grizzly and Jersey 1.x. In this blog we will take the same sample and make it work on Jersey 2.x.

@Path("/")
@Produces(MediaType.TEXT_HTML)
public class IndexModel {
    @GET
    @Path("index")
    public Viewable index(@Context HttpServletRequest request) {
        request.setAttribute("obj", "IT Works");
        System.out.println("/INDEX called");
        return new Viewable("/index.jsp", null);
    }
}

The Grizzly server initialization code looks like this:

public class Main {
    private static final String JERSEY_SERVLET_CONTEXT_PATH = "";
    private static final String JSP_CLASSPATH_ATTRIBUTE =
            "org.apache.catalina.jsp_classpath";
    
    private static final URI BASE_URI = UriBuilder
            .fromUri("http://127.0.0.1/").port(8080).build();
    
    protected static HttpServer startServer() throws IOException {
        System.out.println("Starting grizzly...");
        
        ResourceConfig rc = new ResourceConfig()
                .register(JspMvcFeature.class)
                .register(IndexModel.class);
        
        // Create HttpServer and register dummy "not found" HttpHandler
        HttpServer httpServer = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, rc);
        
        WebappContext context = new WebappContext("WebappContext", JERSEY_SERVLET_CONTEXT_PATH);
        
        // Initialize and register Jersey Servlet
        FilterRegistration registration = context.addFilter("ServletContainer",
                ServletContainer.class);
        registration.setInitParameter("javax.ws.rs.Application", 
                MyApplication.class.getName());
        registration.setInitParameter(JspProperties.TEMPLATES_BASE_PATH,
                "/WEB-INF/jsp");
        // configure Jersey to bypass non-Jersey requests (static resources and jsps)
        registration.setInitParameter(ServletProperties.FILTER_STATIC_CONTENT_REGEX,
                "(/(image|js|css)/?.*)|(/.*\\.jsp)|(/WEB-INF/.*\\.jsp)|"
                + "(/WEB-INF/.*\\.jspf)|(/.*\\.html)|(/favicon\\.ico)|"
                + "(/robots\\.txt)");
        
        registration.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), "/*");

        // Initialize and register JSP Servlet        
        ServletRegistration jspRegistration = context.addServlet(
                "JSPContainer", JspServlet.class.getName());
        jspRegistration.addMapping("/*");
        
        // Set classpath for Jasper compiler based on the current classpath
        context.setAttribute(JSP_CLASSPATH_ATTRIBUTE,
                System.getProperty("java.class.path"));
        
        context.deploy(httpServer);
        
       return httpServer;
    }

    /**
     * Run standalone server
     */
    public static void main(String[] args) throws IOException {
        HttpServer httpServer = startServer();
        try {
            System.out.println(String.format("Jersey app started with WADL available at "
                    + "%sapplication.wadl\nTry out %stest\nHit enter to stop it...",
                    BASE_URI, BASE_URI));
            System.in.read();
        } finally {
            httpServer.stop();
        }
    }

The actual application looks like this:

public class MyApplication extends ResourceConfig {

    public MyApplication() {
        // Resources.
        register(IndexModel.class);

        // MVC.
        register(JspMvcFeature.class);
    }
}

Maven dependency tree is:

[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ grizzly-jersey2-jsp-sample ---
[INFO] org.grizzly.samples:grizzly-jersey2-jsp-sample:jar:1.0-SNAPSHOT
[INFO] +- org.glassfish.grizzly:grizzly-http-all:jar:2.3.2:compile
[INFO] |  +- org.glassfish.grizzly:grizzly-http-server-core:jar:2.3.2:compile
[INFO] |  |  +- org.glassfish.grizzly:grizzly-core:jar:2.3.2:compile
[INFO] |  |  |  \- org.glassfish.grizzly:grizzly-portunif:jar:2.3.2:compile
[INFO] |  |  +- org.glassfish.grizzly:grizzly-http-ajp:jar:2.3.2:compile
[INFO] |  |  \- org.glassfish.grizzly:grizzly-http-server-multipart:jar:2.3.2:compile
[INFO] |  +- org.glassfish.grizzly:grizzly-http-servlet:jar:2.3.2:compile
[INFO] |  +- org.glassfish.grizzly:grizzly-comet:jar:2.3.2:compile
[INFO] |  |  \- org.glassfish.grizzly:grizzly-framework:jar:2.3.2:compile
[INFO] |  +- org.glassfish.grizzly:grizzly-websockets:jar:2.3.2:compile
[INFO] |  |  \- org.glassfish.grizzly:grizzly-http:jar:2.3.2:compile
[INFO] |  \- javax.servlet:javax.servlet-api:jar:3.1-b05:compile
[INFO] +- org.glassfish.jersey.core:jersey-server:jar:2.3:compile
[INFO] |  +- org.glassfish.jersey.core:jersey-common:jar:2.3:compile
[INFO] |  |  +- javax.annotation:javax.annotation-api:jar:1.2:compile
[INFO] |  |  \- org.glassfish.hk2:osgi-resource-locator:jar:1.0.1:compile
[INFO] |  +- org.glassfish.jersey.core:jersey-client:jar:2.3:compile
[INFO] |  +- javax.ws.rs:javax.ws.rs-api:jar:2.0:compile
[INFO] |  +- com.google.guava:guava:jar:14.0.1:compile
[INFO] |  +- org.glassfish.hk2:hk2-api:jar:2.2.0-b14:compile
[INFO] |  |  \- org.glassfish.hk2:hk2-utils:jar:2.2.0-b14:compile
[INFO] |  +- org.glassfish.hk2.external:javax.inject:jar:2.2.0-b14:compile
[INFO] |  +- org.glassfish.hk2:hk2-locator:jar:2.2.0-b14:compile
[INFO] |  |  +- org.glassfish.hk2.external:asm-all-repackaged:jar:2.2.0-b14:compile
[INFO] |  |  \- org.glassfish.hk2.external:cglib:jar:2.2.0-b14:compile
[INFO] |  \- javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO] +- org.glassfish.jersey.containers:jersey-container-grizzly2-http:jar:2.3:compile
[INFO] |  \- org.glassfish.grizzly:grizzly-http-server:jar:2.3.3:compile
[INFO] |     \- org.glassfish.grizzly:grizzly-rcm:jar:2.3.3:compile
[INFO] +- org.glassfish.jersey.containers:jersey-container-grizzly2-servlet:jar:2.3:compile
[INFO] |  +- javax.servlet:servlet-api:jar:2.4:compile
[INFO] |  \- org.glassfish.jersey.containers:jersey-container-servlet:jar:2.3:compile
[INFO] +- org.glassfish.jersey.ext:jersey-mvc-jsp:jar:2.3:compile
[INFO] |  +- org.glassfish.jersey.containers:jersey-container-servlet-core:jar:2.3:compile
[INFO] |  \- org.glassfish.jersey.ext:jersey-mvc:jar:2.3:compile
[INFO] +- org.glassfish.web:javax.servlet.jsp:jar:2.2.5:compile
[INFO] |  \- javax.servlet.jsp:javax.servlet.jsp-api:jar:2.2.1:compile
[INFO] +- org.glassfish.web:javax.el:jar:2.2.1:compile
[INFO] |  \- javax.el:javax.el-api:jar:2.2.1:compile
[INFO] +- org.glassfish.web:javax.servlet.jsp.jstl:jar:1.2.2:compile
[INFO] |  \- javax.servlet.jsp.jstl:jstl-api:jar:1.2:compile
[INFO] |     \- javax.servlet.jsp:jsp-api:jar:2.1:compile
[INFO] \- junit:junit:jar:3.8.1:test

The complete sample code is available on github
https://github.com/oleksiys/samples/tree/master/grizzly-jersey2-jsp-sample

Posted in Uncategorized | Leave a comment

Grizzly 2.3.4: Client-side Connection Pool

New client-side connection pool API has been introduced in Grizzly 2.3.4. This API is completely different from one provided by Grizzly 1.9.x, it has more features and hopefully is nicer and easier to use🙂
There are 2 main abstractions: SingleEndpointPool and MultiEndpointPool, which represent a connection pool to a single and multiple endpoints* respectively. Each connection pool abstraction has a builder, which helps to construct and initialize a connection pool of a specific configuration.
Let’s talk about these 2 connection pool types separately:

  • SingleEndpointPool
    Represents a connection pool to a single endpoint*. For example if we want to create a connection pool to “grizzly.java.net:443” and set the maximum number of connections in pool equal to 8 – the code will look like:

    TCPNIOTransport transport = TCPNIOTransportBuilder.newInstance()
                    .processor(myFilterChain)
                    .build();
    transport.start();
    
    SingleEndpointPool pool = SingleEndpointPool
                    .builder(SocketAddress.class)
                    .connectorHandler(transport)
                    .endpointAddress(new InetSocketAddress("grizzly.java.net", 443))
                    .maxPoolSize(8)
                    .build();
    
    try {
        // use the connection pool
        ............
    } finally {
        pool.close();
        transport.stop();
    }

    A Connection could be asynchronously obtained from the pool using either Future or CompletionHandler:

    Future<Connection> connectionFuture = pool.take();

    or

    CompletionHandler<Connection> myCompletionHandler = createMyCompletionHandler();
    pool.take(myCompletionHandler);

    Please note, if you try to retrieve a Connection using Future, but suddenly changed your mind and don’t want to wait for a Connection, you have to use the code like:

    if (!connectionFuture.cancel(false)) {
        // means Connection is ready
        pool.release(connectionFuture.get());
    }

    to properly cancel the asynchronous operation and return a Connection (if there is any) back to the pool. In general it is highly important to return a Connection back to a pool once you don’t need it to avoid connection pool starvation, which will make connection pool useless.

    pool.release(connection);

    Other interesting feature of Grizzly connection pool – is an ability to attach/detach connections to/from a pool. For example if you retrieved a connection, but don’t plan to use it as part of the pool or don’t plan to return it back to the pool – you can detach this connection

    Connection connection = pool.take().get(10, TimeUnit.SECONDS);
    pool.detach(connection);

    and the pool will be able to establish a new connection (lazily, if needed) to reimburse it. On other hand if you have a connection, created outside the pool (or detached from the pool) and you want to attach this connection to the pool – you can call:

    pool.attach(foreignConnection);

    When the pool is not needed anymore (usually when you exit application) – it is recommended to close it:

    pool.close();

    During the close() operation execution all the idle connections will be closed. The busy connections, which were not returned back to the pool yet, will be kept open and will be closed once you will try to return them back to the pool.

  • MultiEndpointPool
    Represents a connection pool to multiple endpoints*. We can think of MutliEndpointPool as an Endpoint-to-SingleEndpointPool map, where each endpoint is represented by an EndpointKey. The MultiEndpointPool supports pretty much the same set of operations as SingleEndpointPool, but some of these operations (especially related to the Connection allocation) require EndpointKey parameter. Here is an example of MultiEndpointPool, which is used to allocate connections to 2 different servers:

    // Build a connection pool
    MultiEndpointPool pool = MultiEndpointPool
              .builder(SocketAddress.class)
              .connectorHandler(transport)
              .maxConnectionsPerEndpoint(4)
              .maxConnectionsTotal(16)
              .build();
    
    // define endpoints
    EndpointKey endpointKey1 =
               new EndpointKey("endpoint1",
                               new InetSocketAddress("grizzly.java.net", 80));
    EndpointKey endpointKey2 =
               new EndpointKey("endpoint2",
                               new InetSocketAddress("mytecc.wordpress.com", 80));
    Connection c1 = null;
    Connection c2 = null;
    try {
        c1 = pool.take(endpointKey1).get();
        c2 = pool.take(endpointKey2).get();
    ..........................
    } finally {
        if (c1 != null) {
            pool.release(c1);
        }
        if (c2 != null) {
            pool.release(c2);
        }
    }

The SingleEndpointPool and the MultiEndpointPool have similar configuration properties, which could be tuned: max pool size, connect timeout, keep-alive timeout, reconnect delay etc. Additionally for MultiEndpointPool it is possible to tune max connections per endpoint property, which lets us limit the maximum number of connections to a single endpoint.
You can find the complete connection pool example here.

The Grizzly connection pool implementation is available as a Maven module:

<dependency>
    <groupId>org.glassfish.grizzly</groupId>
    <artifactId>connection-pool</artifactId>
    <version>2.3.4</version>
</dependency>

or could be downloaded here.

Here is the full list of configuration properties, which can be used to customize:

  • SingleEndpointPool
    Property Description Notes
    connectorHandler The ConnectorHandler to be used to establish Connections to the endpoint mandatory
    endpointAddress The remote endpoint address to open Connection to mandatory
    localEndpointAddress The local endpoint address to bind Connection to optional
    corePoolSize The number of Connections, kept in the pool, that are immune to keep-alive mechanism Default value: 0
    maxPoolSize The max number of Connections kept by this pool Default value: 4
    connectTimeout The connect timeout, after which, if a connection is not established, it is considered failed value < 0 disables the timeout. By default disabled
    reconnectDelay The delay to be used before the pool will repeat the attempt to connect to the endpoint after previous connect had failed value < 0 disables reconnect. By default disabled
    maxReconnectAttempts The maximum number of attempts to reconnect that will be made before notification of failure occurs Default value: 5
    keepAliveTimeout The maximum amount of time an idle Connection will be kept in the pool. The idle Connections will be closed till the pool size is greater than corePoolSize value < 0 disables keep-alive mechanism. Default value: 30 seconds
    keepAliveCheckInterval The interval, which specifies how often the pool will perform idle Connections check Default value: 5 seconds
  • MultiEndpointPool
    Property Description Notes
    defaultConnectorHandler The default ConnectorHandler to be used to establish Connections to an endpoint mandatory. It is still possible to set a ConnectorHandler per each endpoint separately
    maxConnectionsPerEndpoint The maximum number of Connections each SingleEndpointPool sub-pool is allowed to have Default value: 2
    maxConnectionsTotal The total maximum number of Connections to be kept by the pool Default value: 16
    connectTimeout The connect timeout, after which, if a connection is not established, it is considered failed value < 0 disables the timeout. By default disabled
    reconnectDelay The delay to be used before the pool will repeat the attempt to connect to the endpoint after previous connect had failed value < 0 disables reconnect. By default disabled
    maxReconnectAttempts The maximum number of attempts to reconnect that will be made before notification of failure occurs Default value: 5
    keepAliveTimeout The maximum amount of time an idle Connection will be kept in the pool value < 0 disables keep-alive mechanism. Default value: 30 seconds
    keepAliveCheckInterval The interval, which specifies how often the pool will perform idle Connections check Default value: 5 seconds


* term endpoint is abstract, it can be either SocketAddress, which represents host:port for TCP and UDP Transports; or any other endpoint type  understood by a custom Grizzly Transport

Posted in Uncategorized | Tagged , , , | 2 Comments

Grizzly 2.3.3: Serving Static HTTP Resources from Jar Files

Besides SPDY updates and SPDY server push support, Grizzly 2.3.3 is coming with another interesting feature – possibility to serve static HTTP resources from jar files, or to be more precise serving static HTTP resources using Java ClassLoader.

As we remember it was always easy to register Grizzly StaticHttpHandler to serve HTTP resources from the specific file system path or even several paths.

For example in order to serve static HTTP resources from “/Users/myhome/mypages” folder we can register StaticHttpHandler like:

StaticHttpHandler staticHttpHandler = new StaticHttpHandler("/Users/myhome/mypages/");
server.getServerConfiguration().addHttpHandler(staticHttpHandler, "/");

But we got many questions from users asking: “What if I have static resources in a jar file?” True, the entire standalone server plus static resources might be bundled to a single jar file, so how to serve these resources?

In Grizzly 2.3.3 we implemented new CLStaticHttpHandler, which is able to serve static HTTP resources using Java ClassLoader.

For example, if our standalone server and static HTTP resources are bundle to a single jar file, the CLStaticHttpHandler might be registered like:

public class StandaloneServer {
    public static void main(String[] args) throws IOException {
        HttpServer httpServer = new HttpServer();
        NetworkListener listener = new NetworkListener("grizzly", "0.0.0.0", 9999);
        httpServer.addListener(listener);

        httpServer.getServerConfiguration().addHttpHandler(
                new CLStaticHttpHandler(StandaloneServer.class.getClassLoader()), "/");

        try {
            httpServer.start();

            System.out.println("Press enter to stop");
            System.in.read();
        } finally {
            httpServer.stop();
        }
    }
}

Or, if we have our static HTTP resources in a separate jar file, that is not on the application class path – the sample above should be changed like:

httpServer.getServerConfiguration().addHttpHandler(
        new CLStaticHttpHandler(new URLClassLoader(new URL[] {new URL("file:///Users/myhome/mypages/static-pages.jar")})), "/");

If you have any question – don’t hesitate to ask on Grizzly mailing lists.

Posted in Uncategorized | Tagged , , | Leave a comment

Grizzly 2.3.3: SPDY Server Push

Starting with 2.3.3, Grizzly offers support for SPDY server push mechanism.

Quote:
SPDY enables a server to send multiple replies to a client for a
single request.  The rationale for this feature is that sometimes a
server knows that it will need to send multiple resources in response
to a single request.  Without server push features, the client must
first download the primary resource, then discover the secondary
resource(s), and request them.  Pushing of resources avoids
the round-trip delay…

For sure, one might ask “what if the client already has pushed resource
cached locally?” It is true, we probably should not push all the associated
resources blindly, for example we probably should not push a huge file
when we are not sure whether or not the client already has it, on other side it might
be a good idea to push small icon, javascript, css resources even if the client
has them already cached because additional round-trips
(especially for mobile networks) might be more time consuming than
receiving extra data.

Grizzly provides *PushResource* builder in order to construct the descriptor for the
resource we want to push. For example:

File imageFile = new File("imgs/1.png");

PushResource pushResource = PushResource.builder()
    .statusCode(HttpStatus.OK_200)
    .contentType("image/png")
    .source(
            Source.factory(spdyStream)
            .createFileSource(imageFile))
    .build();

spdyStream.addPushResource("https://thishost:7070/imgs/1.png", pushResource);

In the sample above we instruct SPDY stream to initiate server push, and send
image file represented by the File object.
Similarly it is possible to build *PushResource* based on Grizzly Buffer, byte[] or String.

It is also possible to customize *PushResource* status code, reason phrase and
headers like:

// Push redirect information, so the client will not need to make additional
// request to get this information from the server

PushResource pushResource = PushResource.builder()
    .statusCode(HttpStatus.MOVED_PERMANENTLY_301)
    .header(Header.Location, "https://anotherhost:7070/imgs/1.png")
    .source(
        Source.factory(spdyStream)
        .createStringSource("The resource has been moved"))
    .build();

spdyStream.addPushResource("https://thishost:7070/imgs/1.png", pushResource);

And finally to give you more idea how the complete code looks like, here is an
*HttpHandler* example:

public class SpdyPushHttpHandler extends HttpHandler {

    @Override
    public void service(Request request, Response response) throws Exception {

        // Get SpdyStream information
        final SpdyStream spdyStream =
                (SpdyStream) request.getAttribute(SpdyStream.SPDY_STREAM_ATTRIBUTE);

        // If spdyStream is null - it is not a SPDY based request
        if (spdyStream != null) {
            // Push the file resource (image)
            spdyStream.addPushResource(
                    "https://serverhost:serverport/imgs/1.png",
                    PushResource.builder()
                    .contentType("image/png")
                    .statusCode(HttpStatus.OK_200)
                    .source(Source.factory(spdyStream)
                        .createFileSource(imgFile))
                    .build());
        }

        // Send the main page here
        response.setContentType("text/html");

        final Writer w = response.getWriter();
        StringBuilder sb = new StringBuilder(128);
        sb.append("<html><head><title>SPDY Push Test</title></head><body>");

        // Here we have the image reference on the main page
        sb.append("<img alt="noimg" src="\"/imgs/1.png\"" />");

        sb.append("</body></html>");

        response.setContentLength(sb.length());
        w.write(sb.toString());
    }
}

The complete SPDY example including Server Push can be found here.

Posted in Uncategorized | Tagged , , | Leave a comment

Jersey JSP View running on Grizzly server

After I read the stackoverflow question on the subj [1], I decided to try it myself, cause theoretically there shouldn’t have been any problem running JSP on Grizzly.

And it was actually true, except configuration part: Jasper classloader, JSP API and implementation versions, JSTL version etcetc.

Finally the Jersey resource (taken from this sample) looks like:

@Path("/")
@Produces(MediaType.TEXT_HTML)
public class IndexModel {
    @GET
    @Path("index")
    public Viewable index(@Context HttpServletRequest request) {
        request.setAttribute("obj", new String("IT Works"));
        System.out.println("/INDEX called");
        return new Viewable("/index.jsp", null);
    }
}

And Grizzly server initialization code looks like this:

public class Main {
    private static final String JERSEY_SERVLET_CONTEXT_PATH = "";
    private static final String JSP_CLASSPATH_ATTRIBUTE =
            "org.apache.catalina.jsp_classpath";

    private static final URI BASE_URI = UriBuilder
            .fromUri("http://127.0.0.1/").port(8080).build();

    protected static HttpServer startServer() throws IOException {
        System.out.println("Starting grizzly...");

        // Create HttpServer and register dummy "not found" HttpHandler
        HttpServer httpServer = GrizzlyServerFactory.createHttpServer(BASE_URI, new HttpHandler() {

            @Override
            public void service(Request rqst, Response rspns) throws Exception {
                rspns.setStatus(404, "Not found");
                rspns.getWriter().write("404: not found");
            }
        });

        WebappContext context = new WebappContext("WebappContext", JERSEY_SERVLET_CONTEXT_PATH);

        // Initialize and register Jersey Servlet
        FilterRegistration registration = context.addFilter("ServletContainer", ServletContainer.class);
        registration.setInitParameter(ServletContainer.RESOURCE_CONFIG_CLASS, 
                ClassNamesResourceConfig.class.getName());
        registration.setInitParameter(ClassNamesResourceConfig.PROPERTY_CLASSNAMES, IndexModel.class.getName());
        registration.setInitParameter("com.sun.jersey.config.property.JSPTemplatesBasePath",
                "/WEB-INF/jsp");
        // configure Jersey to bypass non-Jersey requests (static resources and jsps)
        registration.setInitParameter("com.sun.jersey.config.property.WebPageContentRegex",
                "(/(image|js|css)/?.*)|(/.*\\.jsp)|(/WEB-INF/.*\\.jsp)|"
                + "(/WEB-INF/.*\\.jspf)|(/.*\\.html)|(/favicon\\.ico)|"
                + "(/robots\\.txt)");

        registration.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), "/*");

        // Initialize and register JSP Servlet        
        ServletRegistration jspRegistration = context.addServlet(
                "JSPContainer", JspServlet.class.getName());
        jspRegistration.addMapping("/*");

        // Set classpath for Jasper compiler based on the current classpath
        context.setAttribute(JSP_CLASSPATH_ATTRIBUTE,
                System.getProperty("java.class.path"));

        context.deploy(httpServer);

       return httpServer;
    }

    /**
     * Run standalone server
     */
    public static void main(String[] args) throws IOException {
        HttpServer httpServer = startServer();
        try {
            System.out.println(String.format("Jersey app started with WADL available at "
                    + "%sapplication.wadl\nTry out %stest\nHit enter to stop it...",
                    BASE_URI, BASE_URI));
            System.in.read();
        } finally {
            httpServer.stop();
        }
    }

Maven dependency tree is:

[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ grizzly-jersey-jsp-sample ---
[INFO] org.grizzly.samples:grizzly-jersey-jsp-sample:jar:1.0-SNAPSHOT
[INFO] +- org.glassfish.grizzly:grizzly-http-all:jar:2.3.2:compile
[INFO] |  +- org.glassfish.grizzly:grizzly-http-server-core:jar:2.3.2:compile
[INFO] |  |  +- org.glassfish.grizzly:grizzly-core:jar:2.3.2:compile
[INFO] |  |  |  \- org.glassfish.grizzly:grizzly-portunif:jar:2.3.2:compile
[INFO] |  |  +- org.glassfish.grizzly:grizzly-http-ajp:jar:2.3.2:compile
[INFO] |  |  \- org.glassfish.grizzly:grizzly-http-server-multipart:jar:2.3.2:compile
[INFO] |  +- org.glassfish.grizzly:grizzly-http-servlet:jar:2.3.2:compile
[INFO] |  +- org.glassfish.grizzly:grizzly-comet:jar:2.3.2:compile
[INFO] |  |  \- org.glassfish.grizzly:grizzly-framework:jar:2.3.2:compile
[INFO] |  +- org.glassfish.grizzly:grizzly-websockets:jar:2.3.2:compile
[INFO] |  \- javax.servlet:javax.servlet-api:jar:3.1-b05:compile
[INFO] +- com.sun.jersey:jersey-core:jar:1.17:compile
[INFO] +- com.sun.jersey:jersey-grizzly2:jar:1.17:compile
[INFO] |  +- org.glassfish.grizzly:grizzly-http:jar:2.2.16:compile
[INFO] |  +- org.glassfish.grizzly:grizzly-http-server:jar:2.2.16:compile
[INFO] |  |  \- org.glassfish.grizzly:grizzly-rcm:jar:2.2.16:compile
[INFO] |  \- com.sun.jersey:jersey-server:jar:1.17:compile
[INFO] |     \- asm:asm:jar:3.1:compile
[INFO] +- com.sun.jersey:jersey-servlet:jar:1.17:compile
[INFO] +- org.glassfish.web:javax.servlet.jsp:jar:2.2.5:compile
[INFO] |  \- javax.servlet.jsp:javax.servlet.jsp-api:jar:2.2.1:compile
[INFO] +- org.glassfish.web:javax.el:jar:2.2.1:compile
[INFO] |  \- javax.el:javax.el-api:jar:2.2.1:compile
[INFO] +- org.glassfish.web:javax.servlet.jsp.jstl:jar:1.2.2:compile
[INFO] |  \- javax.servlet.jsp.jstl:jstl-api:jar:1.2:compile
[INFO] |     +- javax.servlet:servlet-api:jar:2.5:compile
[INFO] |     \- javax.servlet.jsp:jsp-api:jar:2.1:compile
[INFO] \- junit:junit:jar:3.8.1:test

The complete sample code is available on github
https://github.com/oleksiys/samples/tree/master/grizzly-jersey-jsp-sample

Posted in Uncategorized | Tagged , , , | 4 Comments

Optimized WebSocket broadcasting with Grizzly 2.3

One of the features introduced in Grizzly 2.3 is Broadcaster API for WebSockets.
There are two reasons for that:

  • Broadcasting is widely used with WebSockets (chat-like usecases) and it’s just good idea to have that API generalized.
  • Significant performance optimization

And here I’d like to stop on the 2nd bullet…
Normally when we use WebSockets API and want to broadcast some message (java.util.String) to a group of clients – we do something like:

String message = "somemessage";
for (WebSocket websocket : group) {
    try {
        websocket.send(message);
    } catch (WebSocketException e) {
}

it means each time we send a message to a particular client we do:

  1. Initialize UTF-8 CharsetEncoder;
  2. Convert java.util.String -> byte[] using UTF-8;
  3. Wrap the payload as WebSocket frame.

Depending on actual implementation steps 1 and 3 might be not that expensive (we can cache and reuse UTF-8 CharsetEncoders etc etc…), but step 2 is always expensive especially for large messages.
So why we should pass all 3 steps for each recipient instead of converting the message to a WebSocket frame just once and send the result to each recipient directly. Schematically it will look like:

String message = "somemessage";
byte[] rawFrame = prepareMessage(message);
for (WebSocket websocket : group) {
    try {
        websocket.sendRaw(rawFrame);
    } catch (WebSocketException e) {
}

This is how Grizzly’s OptimizedBroadcaster works.
Now, coming back to the Broadcaster API, the initial code will look like:

Broadcaster broadcaster = new OptimizedBroadcaster();
broadcaster.broadcast(group, message);

Please note, OptimizedBroadcaster is stateless and thread-safe – so you can use single instance in different threads to send messages to different groups.
Another way to use broadcast is:

websocket.broadcast(group, message);

If the later approach is used, it should be noted that, by default, Grizzly will use an unoptimized version of the Broadcaster (to maintain behavioral compatibility with previous releases). In order to leverage the optimized version, see the following example:

public static class BroadcastApplication
                 extends WebSocketApplication {
    private final Broadcaster broadcaster =
                new OptimizedBroadcaster();

    @Override
    public WebSocket createSocket(
                ProtocolHandler handler,
                HttpRequestPacket requestPacket, 
                WebSocketListener... listeners) {
        final DefaultWebSocket ws =
                (DefaultWebSocket) super.createSocket(
                handler, requestPacket, listeners);

        ws.setBroadcaster(broadcaster);
        return ws;
    }

    @Override
    public void onMessage(WebSocket socket, String data) {
        socket.broadcast(getWebSockets(), data);
    }
}

The complete Broadcast API sample might be found here.

Posted in Uncategorized | Tagged | Leave a comment