Large Session Issue with WebServlet sessionInit()

XRebel shows a default session size of 25.3 MB using the first set of code below. The session drops below 50kb if I use the second set instead.

I have this “sw.js” code as part of the [Progressive Web App (PWA)]
(https://vaadin.com/pwa/build). XRebel seems to isolate it to MavenSession inside AtmosphereFramework’s ShutdownHook. I use Jetty for production.

  • Why does removing that code reduce the session size so drastically?
  • Does this large size only exist in production? I’m trying to optimize for scale.

This code causes session size: 25.3 MB

@WebServlet(urlPatterns = "/*", name = "LoginUIServlet", asyncSupported = true)
   @VaadinServletConfiguration(closeIdleSessions = true, ui = LoginUI.class, productionMode = false)
   public static class LoginUIServlet extends VaadinServlet {

       @Override
       protected void servletInitialized() throws ServletException {
           super.servletInitialized();

           HeaderTagHandler.init(getService());

           getService().addSessionInitListener(new SessionInitListener() {

               @Override
               public void sessionInit(SessionInitEvent event) {
                   event.getSession().addRequestHandler(new RequestHandler() {

                       @Override
                       public boolean handleRequest(VaadinSession session, VaadinRequest request,
                                                    VaadinResponse response) throws IOException {

                           String pathInfo = request.getPathInfo();
                           InputStream in = null;

                           if (pathInfo.endsWith("sw.js")) {
                               response.setContentType("application/javascript");
                               in = getClass().getResourceAsStream("/sw.js");
                           }

                           if (in != null) {
                               OutputStream out = response.getOutputStream();
                               IOUtils.copy(in, out);
                               in.close();
                               out.close();
                               return true;
                           } else {
                               return false;
                           }
                       }
                   });
               }
           });
       }
   }

This code causes only: 50kb

@WebServlet(urlPatterns = "/*", name = "LoginUIServlet", asyncSupported = true)
    @VaadinServletConfiguration(closeIdleSessions = true, ui = LoginUI.class, productionMode = false)
    public static class LoginUIServlet extends VaadinServlet {

        @Override
        protected void servletInitialized() throws ServletException {
            super.servletInitialized();

            HeaderTagHandler.init(getService());

            getService().addSessionInitListener(new SessionInitListener() {

                @Override
                public void sessionInit(SessionInitEvent event) {
             
                }
            });
        }
    }

The memory usage in your case is attributed by the size of “sw.js” and implementation of actual InputStream and OutputStream used. In this case InputStream and OutputStream are inherited from standard Servlet implementation, you could check discussion here, which is somewhat related

https://stackoverflow.com/questions/685271/using-servletoutputstream-to-write-very-large-files-in-a-java-servlet-without-me

Thanks Tatu, following your link I tried:

  • Flushing the OutputStream prior to closing
  • Setting the content length beforehand

But XRebel still shows 25MB default session size.

			 @Override
             public boolean handleRequest(VaadinSession session, VaadinRequest request,
                                          VaadinResponse response) throws IOException {

                            String pathInfo = request.getPathInfo();
                            InputStream in = null;

                            if (pathInfo.endsWith("sw.js")) {
                                System.out.println("length: " + new File("/sw.js").length());
                                System.out.println("length: " + new File("sw.js").length());
                                System.out.println("length: " + new File("./sw.js").length());

                                response.setHeader("Content-Length", String.valueOf(new File("/sw.js").length()));
                                response.setContentType("application/javascript");
                                in = getClass().getResourceAsStream("/sw.js");
                            }
                            
                            if (in != null) {
                                OutputStream out = null;
                                try {
                                    out = response.getOutputStream();
                                    IOUtils.copy(in, out);
                                    in.close();
                                    return true;
                                } finally {
                                    if (out != null) {
                                        out.flush();
                                        out.close();
                                    }
                                }
                            } else {
                                return false;
                            }
                        }

The 3 System.out.println all return 0. The sw.js file is in src/main/resources

Any thoughts?

Hmm, you use IOUtils.copy(in, out); , did you check this:

https://commons.apache.org/proper/commons-io/javadocs/api-2.5/org/apache/commons/io/IOUtils.html

"For example, copy(InputStream, OutputStream) calls copyLarge(InputStream, OutputStream) which calls copy(InputStream, OutputStream, int) which creates the buffer and calls copyLarge(InputStream, OutputStream, byte).

Applications can re-use buffers by using the underlying methods directly. This may improve performance for applications that need to do a lot of copying.

Wherever possible, the methods in this class do not flush or close the stream. This is to avoid making non-portable assumptions about the streams’ origin and further use. Thus the caller is still responsible for closing streams after use."

What are you suggesting I do differently than what I’m trying? My code shows I’m already closing both the InputStream and OutputStream, and flushing the OutputStream beforehand. Still a 25 MB session.

2 Theories:

  • I noticed that this code:
 String pathInfo = request.getPathInfo();
 InputStream in = null;
 System.out.println("path info: "+pathInfo);
 if (pathInfo.endsWith("sw.js")) {
    response.setHeader("Content-Length", String.valueOf(new File("/sw.js").length()));
    response.setContentType("application/javascript");
    in = getClass().getResourceAsStream("/sw.js");
    System.out.println("got here");
 }

returns this in console:

path info: /
path info: /sw.js
got here
path info: /

Why is handleRequest() being called 3 times? Perhaps this is relevant.

  • This screenshot shows that “Atmosphere” is responsible for retaining 25MB in session (on refresh too).

![screenshot]
(https://i.imgur.com/3ai6jaf.png)

I notice when I run my Jetty Server in Debug mode, I see this in console:

...
[INFO]
 jetty-9.3.9.v20160517
[WARNING]
 org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher defines neither @WebServlet.value nor @WebServlet.urlPatterns
[INFO]
 Initializing AtmosphereFramework
Jan 07, 2019 10:49:22 AM org.atmosphere.cpr.AtmosphereFramework addAtmosphereHandler
INFO: Installed AtmosphereHandler com.vaadin.server.communication.PushAtmosphereHandler mapped to context-path: /*
...

Not sure if this 25MB is only in production due to Jetty, or will it be retained. Any help is great!