vertx 3: ClusteredSessionStore always causes java.lang.IllegalStateException when reading body

322 views
Skip to first unread message

Frederico Ferro Schuh

unread,
Aug 24, 2015, 3:47:07 AM8/24/15
to vert.x
Hi,

I'm getting the following Exception when setting my application to use ClusteredSessionStore:

java.lang.IllegalStateException: Request has already been read

The exception only happens if I set a handler to read the request body, and if the client sends the cookie vertx-web.session along with the request (I kept the default cookie name).
I made a small modification to the vertx-web Session sample code to reproduce the issue:

package io.vertx.example.web.sessions;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.example.util.Runner;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.Session;
import io.vertx.ext.web.handler.CookieHandler;
import io.vertx.ext.web.handler.SessionHandler;
import io.vertx.ext.web.sstore.ClusteredSessionStore;
import io.vertx.ext.web.sstore.LocalSessionStore;

/*
 * @author <a href="http://tfox.org">Tim Fox</a>
 */
public class Server extends AbstractVerticle {

  // Convenience method so you can run it in your IDE
  public static void main(String[] args) {
    Runner.runClusteredExample(Server.class);
  }

  @Override
  public void start() throws Exception {

    Router router = Router.router(vertx);

    router.route().handler(CookieHandler.create());
    router.route().handler(SessionHandler.create(ClusteredSessionStore.create(vertx)));

    router.route().handler(routingContext -> {

      Session session = routingContext.session();

      HttpServerRequest req = routingContext.request();
      req.endHandler(aVoid -> {
        Integer cnt = session.get("hitcount");
        cnt = (cnt == null ? 0 : cnt) + 1;

        session.put("hitcount", cnt);

        routingContext.response().putHeader("content-type", "text/html")
                .end("<html><body><h1>Hitcount: " + cnt + "</h1></body></html>");

      });

    });

    vertx.createHttpServer().requestHandler(router::accept).listen(8080);
  }
}



If LocalSessionStore is used, I get no errors at all.
Is this a bug, or something I'm doing wrong?

Thanks very much.
Fred

Tim Fox

unread,
Aug 24, 2015, 5:16:35 AM8/24/15
to ve...@googlegroups.com
You are getting this because the request has already been fully read by the time you try to set an endHandler for it.

What I don't understand here is what you want to achieve with your code. Why do you want to evaluate the hit count in an end handler?

BTW there is a working example in the examples repo that shows a session based hit counter in action:

https://github.com/vert-x3/vertx-examples/blob/master/web-examples/src/main/java/io/vertx/example/web/sessions/Server.java
--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+un...@googlegroups.com.
Visit this group at http://groups.google.com/group/vertx.
To view this discussion on the web, visit https://groups.google.com/d/msgid/vertx/a75eae84-6978-4bbe-8d70-12b3d182c475%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Frederico Ferro Schuh

unread,
Aug 24, 2015, 10:08:57 PM8/24/15
to vert.x
Sorry if I didn't make myself clear, this code is actually based on that very same Session example.
I just modified it in order to reproduce this issue, what it does with that hit counter is not important.

My point is, this sample code doesn't work if you use a ClusteredSessionStore, but it works with a LocalSessionStore.
Does it mean the sample code is broken?

Tim Fox

unread,
Aug 25, 2015, 3:10:36 AM8/25/15
to ve...@googlegroups.com
The sample code works fine.

What I don't understand is why you have added a request end handler. What do you hope to achieve with that?

Frederico Ferro Schuh

unread,
Aug 25, 2015, 5:37:38 AM8/25/15
to vert.x
Ah yes, please let me explain the endHandler.
My application first reads the whole body before it can act on it (I have to serialize the body into a JSON object).
I added that endHandler to the sample app to simulate the same situation.
After the body is read, I'd like to be able to read the session id, but I'm failing to do that when using a ClusteredSessionStore.

Allow me to post a much simplified code to illustrate what I want to achieve:

public class Server extends AbstractVerticle {

 
// Convenience method so you can run it in your IDE
 
public static void main(String[] args) {
   
Runner.runClusteredExample(Server.class);
 
}

 
@Override
 
public void start() throws Exception {

   
Router router = Router.router(vertx);

    router
.route().handler(CookieHandler.create());
    router
.route().handler(SessionHandler.create(ClusteredSessionStore.create(vertx)));

    router
.route().handler(routingContext -> {

     
Session session = routingContext.session();

     
HttpServerRequest req = routingContext.request();

      req
.endHandler(aVoid -> {

       
String id = session.id();
        req
.response().end("your session id is " + id);
     
});
   
});

    vertx
.createHttpServer().requestHandler(router::accept).listen(8080);
 
}
}


That's it, I just want to read the session id, but it must happen after the body has been read.
In this new sample code, my endHandler is not even called, I get the IllegalStateException before it even runs (if using ClusteredSessionStore).
With LocalSessionStore, however, the endHandler is called just fine. That's why I thought it could be a bug.

Any thoughts on it?

Frederico Ferro Schuh

unread,
Aug 25, 2015, 5:58:05 AM8/25/15
to vert.x
I think the following sample code can better isolate the problem:

public class Server extends AbstractVerticle {

 
// Convenience method so you can run it in your IDE
 
public static void main(String[] args) {
   
Runner.runClusteredExample(Server.class);
 
}

 
@Override
 
public void start() throws Exception {

   
Router router = Router.router(vertx);

    router
.route().handler(CookieHandler.create());
    router
.route().handler(SessionHandler.create(ClusteredSessionStore.create(vertx)));

    router
.route().handler(routingContext -> {


     
HttpServerRequest req = routingContext.request();

      req
.endHandler(aVoid -> {
        req
.response().end("finished");
     
});
   
});

    vertx
.createHttpServer().requestHandler(router::accept).listen(8080);
 
}
}


This code does not work with a ClusteredSessionStore, it fails every time.
However it works with LocalSessionStore.
A simple endHandler with no logic in it, just with Session support enabled.

Is this a bad usage of the endHandler, or really a bug?

Tim Fox

unread,
Aug 25, 2015, 6:02:31 AM8/25/15
to ve...@googlegroups.com
If you want to read the whole body, use a BodyHandler, don't use an endHandler on the request.

Frederico Ferro Schuh

unread,
Aug 25, 2015, 6:56:01 AM8/25/15
to vert.x
Ok, I've switched to a bodyHandler now. However, I get the same error:

public class Server extends AbstractVerticle {

 
// Convenience method so you can run it in your IDE
 
public static void main(String[] args) {
   
Runner.runClusteredExample(Server.class);
 
}

 
@Override
 
public void start() throws Exception {

   
Router router = Router.router(vertx);

    router
.route().handler(CookieHandler.create());
    router
.route().handler(SessionHandler.create(ClusteredSessionStore.create(vertx)));

    router
.route().handler(routingContext -> {


     
HttpServerRequest req = routingContext.request();

      req
.bodyHandler(aVoid -> {
        req
.response().end("finished");
     
});
   
});

    vertx
.createHttpServer().requestHandler(router::accept).listen(8080);
 
}
}

The issue seems to happen with any kind of handler I set.

Is my bodyHandler correct in the sample above?

Tim Fox

unread,
Aug 25, 2015, 6:58:59 AM8/25/15
to ve...@googlegroups.com
No, you want BodyHandler.

It's described here in the docs:

http://vertx.io/docs/vertx-web/java/#_request_body_handling

Frederico Ferro Schuh

unread,
Aug 25, 2015, 7:57:05 AM8/25/15
to vert.x
BodyHandler did it, great!

Thanks very much Tim!
...

Chris Jones

unread,
Sep 14, 2015, 3:28:06 AM9/14/15
to vert.x
Hi Fredrico/Tim,

Fredrico, for your use case, I agree 100% with Tim that the BodyHandler is the way to go and I would definitely suggest you keep that as the default solution.

However, I had a use case where the body handler wasn't appropriate and I needed access to the clustered session before I could begin processing the request, and I ran into the exact issue you have described above. The use case was for a proxy between 2 systems where both the request and the response could be arbitrarily large, and we just wanted Vertx to act as an intermediary (however, information in the clustered session was required to identify the user to the system being proxied).

My use case was very close to this example and I used it as a base (and thanks again to Tim on this one!): https://github.com/InfoSec812/simple-vertx-proxy-example

However, the above example will *not* work when used with the ClusteredSessionStore (and will fail because of the error you described). The Pump used to pass the original request to the destination can not start because the original request has been marked as ended before the pump is invoked. I don't understand 100% the process, but the original request is marked as ended by io.vertx.core.http.impl.ServerConnection (processMessage) if the request is not in a paused state after the initial [handleRequest] method returns. The ClusteredSessionStore returns at that point because the call to retrieve the Clustered map and its values are asynchronous.

The solution (if for some reason you need to do this) is to pause the request before invoking the ClusteredSessionStore handler and resuming it when your handlers have been configured. In my example I have multiple routes that just use the standard BodyHandler Tim described, and a single route that pauses the request before the ClusteredSessionStore is invoked and the Handler resumes the request once the Pump/Handlers are configured.

I created a Gist which is basically your second last example with the modifications in place to make it work:

https://gist.github.com/cdjones32/fc3b7ea8ebc009f5a92f

This isn't an issue with ClusteredSessionStore, but a consequence of the ClusteredSessionStore handler performing asynchronous operations before the request has been read. Any handler that performs an Asynchronous operation and then needs to read the original request would have to pause the request before performing the asynchronous call - it's just that that detail is hidden in the case of the ClusteredSessionStore.

Apologies if this went on a bit long. I just wanted to outline what I thought was a valid use case and to help out anyone coming across the same issue by explaining why it was happening.
Reply all
Reply to author
Forward
0 new messages