Uploaded image for project: 'Undertow'
  1. Undertow
  2. UNDERTOW-1743

Request attributes are lost when a client closes a connection while response is being written

    XMLWordPrintable

Details

    • Hide

      Here's a small example that illustrates the problem:

      package example;
      
      import java.io.IOException;
      import java.io.OutputStream;
      import java.net.InetSocketAddress;
      import java.net.MalformedURLException;
      import java.net.Socket;
      import java.util.Objects;
      
      import javax.servlet.DispatcherType;
      import javax.servlet.Filter;
      import javax.servlet.FilterChain;
      import javax.servlet.ServletException;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      
      import io.undertow.Undertow;
      import io.undertow.server.HttpHandler;
      import io.undertow.servlet.api.DeploymentInfo;
      import io.undertow.servlet.api.DeploymentManager;
      import io.undertow.servlet.api.FilterInfo;
      
      import static io.undertow.servlet.Servlets.defaultContainer;
      import static io.undertow.servlet.Servlets.deployment;
      import static io.undertow.servlet.Servlets.filter;
      import static io.undertow.servlet.Servlets.servlet;
      
      public class UndertowRequestAttributeLost {
      
          public static void main(final String[] args) throws MalformedURLException, IOException {
              try {
                  FilterInfo filter = filter(TestFilter.class);
                  DeploymentInfo servletBuilder = deployment()
                          .setClassLoader(UndertowRequestAttributeLost.class.getClassLoader()).setContextPath("/")
                          .setDeploymentName("test.war").addFilter(filter)
                          .addServlets(servlet("TestServlet", TestServlet.class).addMapping("/*"))
                          .addFilterUrlMapping(filter.getName(), "/*", DispatcherType.REQUEST);
                  DeploymentManager manager = defaultContainer().addDeployment(servletBuilder);
                  manager.deploy();
                  HttpHandler servletHandler = manager.start();
                  Undertow server = Undertow.builder().addHttpListener(8080, "localhost").setHandler(servletHandler).build();
                  server.start();
                  Socket socket = new Socket();
                  socket.connect(new InetSocketAddress("localhost", 8080));
                  OutputStream output = socket.getOutputStream();
                  output.write("GET / HTTP/1.1\nHost: localhost:8080\n\n".getBytes());
                  output.flush();
                  socket.getInputStream().read();
                  socket.close();
                  manager.stop();
                  server.stop();
              }
              catch (ServletException e) {
                  throw new RuntimeException(e);
              }
          }
      
          public static class TestFilter implements Filter {
      
              private static final String ATTRIBUTE_NAME = TestFilter.class.getName();
      
              @Override
              public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                      throws IOException, ServletException {
                  request.setAttribute(ATTRIBUTE_NAME, new Object());
                  try {
                      chain.doFilter(request, response);
                  }
                  finally {
                      Objects.requireNonNull(request.getAttribute(ATTRIBUTE_NAME));
                  }
              }
      
          }
      
          public static class TestServlet extends HttpServlet {
      
              @Override
              protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                  while (true) {
                      resp.getOutputStream().write(1);
                  }
              }
      
          }
      
      }
      

      Using Undertow 2.1.1.Final, the request attribute added by TestFilter is present in its finally block. Using Undertow 2.1.2.Final or later, the attribute is missing and, as a result the Objects.requireNonNull call fails with a NullPointerException:

      java.lang.NullPointerException
      	at java.util.Objects.requireNonNull(Objects.java:203)
      	at example.UndertowRequestAttributeLost$TestFilter.doFilter(UndertowRequestAttributeLost.java:73)
      	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
      	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
      	at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
      	at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
      	at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
      	at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
      	at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
      	at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
      	at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
      	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
      	at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
      	at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
      	at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
      	at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
      	at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
      	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
      	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
      	at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
      	at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
      	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
      	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
      	at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
      	at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
      	at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
      	at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78)
      	at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99)
      	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:370)
      	at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
      	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
      	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019)
      	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558)
      	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1423)
      	at java.lang.Thread.run(Thread.java:748)
      
      Show
      Here's a small example that illustrates the problem: package example; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.Socket; import java.util.Objects; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import io.undertow.Undertow; import io.undertow.server.HttpHandler; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.FilterInfo; import static io.undertow.servlet.Servlets.defaultContainer; import static io.undertow.servlet.Servlets.deployment; import static io.undertow.servlet.Servlets.filter; import static io.undertow.servlet.Servlets.servlet; public class UndertowRequestAttributeLost { public static void main( final String [] args) throws MalformedURLException, IOException { try { FilterInfo filter = filter(TestFilter.class); DeploymentInfo servletBuilder = deployment() .setClassLoader(UndertowRequestAttributeLost. class. getClassLoader()).setContextPath( "/" ) .setDeploymentName( "test.war" ).addFilter(filter) .addServlets(servlet( "TestServlet" , TestServlet.class).addMapping( "/*" )) .addFilterUrlMapping(filter.getName(), "/*" , DispatcherType.REQUEST); DeploymentManager manager = defaultContainer().addDeployment(servletBuilder); manager.deploy(); HttpHandler servletHandler = manager.start(); Undertow server = Undertow.builder().addHttpListener(8080, "localhost" ).setHandler(servletHandler).build(); server.start(); Socket socket = new Socket(); socket.connect( new InetSocketAddress( "localhost" , 8080)); OutputStream output = socket.getOutputStream(); output.write( "GET / HTTP/1.1\nHost: localhost:8080\n\n" .getBytes()); output.flush(); socket.getInputStream().read(); socket.close(); manager.stop(); server.stop(); } catch (ServletException e) { throw new RuntimeException(e); } } public static class TestFilter implements Filter { private static final String ATTRIBUTE_NAME = TestFilter. class. getName(); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setAttribute(ATTRIBUTE_NAME, new Object ()); try { chain.doFilter(request, response); } finally { Objects.requireNonNull(request.getAttribute(ATTRIBUTE_NAME)); } } } public static class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { while ( true ) { resp.getOutputStream().write(1); } } } } Using Undertow 2.1.1.Final, the request attribute added by TestFilter is present in its finally block. Using Undertow 2.1.2.Final or later, the attribute is missing and, as a result the Objects.requireNonNull call fails with a NullPointerException : java.lang.NullPointerException at java.util.Objects.requireNonNull(Objects.java:203) at example.UndertowRequestAttributeLost$TestFilter.doFilter(UndertowRequestAttributeLost.java:73) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68) at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132) at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269) at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78) at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133) at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130) at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249) at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78) at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99) at io.undertow.server.Connectors.executeRootHandler(Connectors.java:370) at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830) at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1423) at java.lang.Thread.run(Thread.java:748)

    Description

      While investigating a Spring Boot issue we have identified a regression in Undertow 2.1.2.Final and later that results in request attributes being cleared too soon. This happens when a client closes a connection while the response is still being written. With thanks to Arnaud Heritier for identifying it, I believe that this commit is the likely cause. The loss of the request attributes prevents Spring Boot from stopping a timer for the request that was stored in the attributes.

      Attachments

        Issue Links

          Activity

            People

              ropalka Richard Opalka
              ankinson Andy Wilkinson
              Votes:
              5 Vote for this issue
              Watchers:
              9 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: