3

tl;dr:

How do I get the ServletResponse during ServletRequestListener.requestDestroyed?

Short Version

In JavaEE, I want to know when:

  • when a request starts
  • and when a request ends

and be able to inspect the request and response objects.

Long Version

In the ASP.NET world, if you want to know when a request starts and ends, you write an IHttpModule:

public class ExampleModuleForThisQuestion : IHttpModule
{
}

And then register your "module" in the web XML configuration file:

web.config:

    <system.webServer>
        <modules>
            <add name="DoesntMatter" type="ExampleModuleForThisQuestion "/>
        </modules>
    </system.webServer>

Inside your module, you can register callback handlers for:

  • BeginRequest event
  • EndRequest event

The web server infrastructure then calls you Init method. That is your opportunity to register that you want to receive notifications when a request starts, and when a request ends:

public class ExampleModuleForThisQuestion : IHttpModule
{
   public void Init(HttpApplication application)
   {
      application.BeginRequest += new EventHandler(beginRequest); //register the "BeginRequet" event
      application.EndRequest += new EventHandler(endRequest); //register the "EndRequest" event
   }
}

And now we have our callbacks when a request starts:

private void beginRequest(object sender, EventArgs e)
{
   HttpApplication application = (HttpApplication)sender;

   //Record the time the request started
   application.Context.Items["RequestStartTime"] = DateTime.Now;

   //We can even access the Request and Response objects
   application.ContenxtLog(application.Context.Request.Headers["User-Agent"]);
}

And we have our callback when a request ends:

private void endRequest(object sender, EventArgs e)
{
   HttpApplication application = (HttpApplication)sender;

   //We can even access the Request and Response objects

   //Get the response status code (e.g. 418 I'm a teapot)
   int statusCode = application.Context.Response.StatusCode;

   //Get the request method (e.g. GET, POST, BREW)
   String method = application.context.Request.RequestType;

   //Get the path from the request (e.g. /ViewCustomer)
   String path = application.context.Request.AppRelativeCurrentExecutionFilePath'

   //Get when the request started - that we recorded during "Begin Request"
   DateTime requestStartTime = (DateTime)application.Context.Items["RequestStartTime"];

   //And we can modify the response
   if ((DateTime.Now - requestStartTime).TotalSeconds = 17)
       application.Context.Response.StatusCode = 451;
}

The Java Almost-Equivalent is ServletRequestListener

In Java, apparently the corresponding technique is to create and object that implements the ServletRequestListener interface:

@WebListener
public class ExampleListenerForThisQuestion
      implements javax.servlet.ServletRequestListener {

}

and register our listener with the application server by including it in our web XML configuration file:

web.xml

<listener>
    <listener-class>ExampleListenerForThisQuestion</listener-class>
</listener>

Now we can implement the requestInitialized and requestDestroyed methods to get when a request starts and ends:

public class ExampleListenerForThisQuestion
      implements javax.servlet.ServletRequestListener {

   @Override
   public void requestInitialized(ServletRequestEvent sre) {
      ServletRequest sr = sre.getServletRequest();
      sr.setAttribute("requestStartTicks", getCurrentTickCount());

      HttpServletRequest request = (HttpServletRequest) sr;

      // e.g. "PUT /Customers/1234"
      System.out.printf("%s %s\r\n", request.getMethod());
   }

   @Override
   public void requestDestroyed(ServletRequestEvent sre) {
      ServletRequest sr = sre.getServletRequest();
      long requestStartTicks = (long)sr.getAttribute("requestStartTicks");

      HttpServletResponse response = (HttpServletRequest)...nothing, because i don't know how...

      // e.g. "226 IM Used"
      System.out.printf("%d %s\r\n", response.getStatus(), response.getStatusDescription());
   }
}

But how do we get the response?

Now that I'm notified when the response ends, I need the result of that request:

  • I need the HTTP status code (e.g., 424)
  • I need the HTTP status description (e.g., Failed Dependency)
  • I need to inspect response headers
  • I need to modify response headers

You notice the line in my code above:

HttpServletResponse response = (HttpServletRequest)...nothing, because i don't know how...

How can I get hold of the response?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • @PeterMortensen You are correct about how most people should generally use i when referring to themselves; but i do it for specific reasons (which aren't important here). See: my profile. – Ian Boyd Sep 23 '22 at 18:34

1 Answers1

3

You can create a Filter instead of a listener. Filters allow you to create wrappers around request processing. See the documentation on that topic.

For HTTP, you can use HTTPFilter. This could look like the following:

@WebFilter("/*")//or via deployment descriptor
public class YourFilter extends HttpFilter{ //or just Filter for general (non-HTTP) processing
    @Override
    public void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) {//for generic filters, use ServletRequest/ServletResponse instead
        //before request processing 
        chain.doFilter(req, res);//calls other filters and processes request
        //after request processing
        //you can use res here
    }
}

If you do not call chain.doFilter, other filters and the servlet will not be executed.

If you prefer declaring the filter in your deployment descriptor (web.xml), you can do that as well:

<filter>
  <filter-name>yourFilter</filter-name>
  <filter-class>your.FilterClass</filter-class>
</filter>
<filter-mapping>
  <filter-name>yourFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
dan1st
  • 12,568
  • 8
  • 34
  • 67
  • How does my filter know when the request starts, and how does my filter know when the request ends? – Ian Boyd Aug 02 '22 at 20:59
  • The request is processed in the `chain.doFilter` call so everything before that runs before the request is processed and everything after that runs after request processing. – dan1st Aug 02 '22 at 21:01
  • Oh I see it now. I was hung up on the filter registration order (and what still runs before me, and what runs after me) – Ian Boyd Aug 02 '22 at 21:05
  • How do you register an `HttpFilter`? It's not being called; presumably because `@WebFilter("")` just isn't enough. Is it similar to a ServletRequestListener, and you register it someplace in `web.xm`? – Ian Boyd Aug 02 '22 at 22:00
  • You can either do that using the `@WebFilter` annotation or using a deployment descriptor (`web.xml`) using a ``. – dan1st Aug 02 '22 at 22:03
  • The `@WebFilter` annotation is present; the filter just isn't being called. What's the syntax for `web.xml`? – Ian Boyd Aug 02 '22 at 22:04
  • I have edited my answer and added that. Aside from that, try using `/*` instead of the empty string in the argument to `@WebFilter`. – dan1st Aug 02 '22 at 22:16
  • Registering it in the `web.xml` works. I assume `chain.doFilter(req, res, chain);` is a typo, as `FilterChain` interface's `doFilter` doesn't take 3 arguments. But calling it with only the 2 arguments (request and response), and the processing hangs - and the request never completes. Does it make sense that calling `filter.doFilter` would cause the web-server to lockup? – Ian Boyd Aug 02 '22 at 22:28
  • Yes, it was a typo. I edited my answer. Normally, filters should not make the application hang except you have direct or indirect loops (like redirects or forwards), blocking operations or something else in your processing code. – dan1st Aug 02 '22 at 22:41
  • I got the answer over [here](https://stackoverflow.com/questions/73214360/how-to-register-an-httpfilter). Putting `/*` in the `@WebFilter("/*")` did it. I thought `/*` was the opening of a comment block; so i was quite confused. But they explained that it's a path wildcard: `/*`. – Ian Boyd Aug 02 '22 at 23:09