DEV Community

Kamil
Kamil

Posted on • Originally published at banach.net.pl on

2 2

How to customize Jetty in Spring - Custom Error Handler

Last week we found out that in some cases our application is showing redundant data on error - for example stacktrace. It happens when there was an error which was handled by the application container which was Jetty. For example, sending a header like X-FORWARDED-PORT: some-not-numeric-value causesNumberFormatException and shows a full stacktrace.

We’ve looked over the documentation and see that we could hide stacktrace in default error handler. Instead of that, we decided to replace it with a custom one. That allows us to put an additional logging logic and customize the output. And this is a short description of how to do it. ;-)

To implement the custom error handler we can extend org.eclipse.jetty.server.handler.AbstractHandler and override handle method. On the code snippet below there is an example handler. For sake of clarity it returns always simple text (content-type text/plain) with code, error message and URL used.

We can extend it by adding additional logic like different responses based on headers from the client, additional logging and so on. :-)

package pl.net.banach.customizeJetty;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.ByteBufferOutputStream;
import org.eclipse.jetty.server.Dispatcher;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.StringUtil;

public class CustomJettyErrorHandler extends AbstractHandler {

    @Override
    public void handle(String target, Request baseRequest, 
                       HttpServletRequest request,
                       HttpServletResponse response) 
                       throws IOException, ServletException {


        try {
            // Get error message, sanitize it, just in case.
            String message = StringUtil.sanitizeXmlString(
                (String) request.getAttribute(Dispatcher.ERROR_MESSAGE)
            );

            // Get error code that will returned
            int code = response.getStatus();

            var charset = StandardCharsets.UTF_8;

            // Get writer used
            var buffer = baseRequest.getResponse().getHttpOutput().getBuffer();
            var out = new ByteBufferOutputStream(buffer);
            var writer = new PrintWriter(new OutputStreamWriter(out, charset));

            // Set content type, encoding and write response
            response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString());
            response.setCharacterEncoding(charset.name());
            writer.print("HTTP ERROR ");
            writer.print(code);
            writer.print("\nMessage: ");
            writer.print(message);
            writer.print("\nURI: ");
            writer.print(request.getRequestURI());


            writer.flush();
        } catch (BufferOverflowException e) {
            baseRequest.getResponse().resetContent();
        }

        baseRequest.getHttpChannel().sendResponseAndComplete();
    }
}
Enter fullscreen mode Exit fullscreen mode

When we have the error handler ready then it registering it is easy. To do this we will create a custom configuration which implements WebServerFactoryCustomizer with @Configuration annotation. In a customize method we will create a customizer and add a newly created error handler.

Seems simple, but be sure to put this configuration class in a package that is scanned by Spring @ComponentScan annotation!

package pl.net.banach.customizeJetty;

import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JettyConfiguration
            implements WebServerFactoryCustomizer<JettyServletWebServerFactory> {

    @Override
    public void customize(JettyServletWebServerFactory factory) {
        JettyServerCustomizer customizer = server -> {
            server.setErrorHandler(new CustomJettyErrorHandler());
        };
        factory.addServerCustomizers(customizer);
    }
}
Enter fullscreen mode Exit fullscreen mode

And that’s all - from now all errors that go to Jetty (application server) will be handled by our custom handler. :-)

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay