Young Emil
Young Emil

Reputation: 2266

How to make jetty ErrorHandler display error in json format in java

I have the jetty server started this way by the sample code below where I have written my own errorHandler class. Through some few research, I got some information here. But it seems not enough to get me what I want. The name of the class that I set to be called by the server is HandleClient.

So if error 404 occurs, it display {"message":"HTTP error 404"} . the program works fine anyway But the response is in text/plain format.

My Problem is: How can I configure the class to format and display the error in MIME MediaType: application/json.

I have had sleepless nights on this. Will be very grateful to all helps.

    public class MVCPattern{

    public MVCPattern(){
    }

    public static void main(String args[]) throws JSONException, IOException{
        MVCPattern mvcPattern = new MVCPattern();


        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");

        Server jettyServer = new Server(9000);
        jettyServer.setHandler(context);
        context.setErrorHandler(new ErrorHandler());
        // default error handler for resources out of "context" scope
        jettyServer.addBean(new ErrorHandler());

        ServletHolder jerseyServlet = context.addServlet(org.glassfish.jersey.servlet.ServletContainer.class, "/*");
        jerseyServlet.setInitOrder(0);

        // Tells the Jersey Servlet which REST service/class to load.
        jerseyServlet.setInitParameter("jersey.config.server.provider.classnames",
                HandleClient.class.getCanonicalName() );

        try {
            jettyServer.start();            
            jettyServer.join();

        } catch (Exception ex) {
            Logger.getLogger(HandleClient.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            jettyServer.destroy();
        }
    }
    /**
     * Dummy error handler that disables any error pages or jetty related messages and returns our
     * ERROR status JSON with plain HTTP status instead. All original error messages (from our code) are preserved
     * as they are not handled by this code.
     */
    static class ErrorHandler extends ErrorPageErrorHandler {
        @Override
        public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
            response.getWriter()
            .append("{\"message\":\"HTTP error ")
            .append(String.valueOf(response.getStatus()))
            .append("\"}");
        }
    }

}

Upvotes: 3

Views: 9588

Answers (2)

xrom888
xrom888

Reputation: 39

This is not a big deal.

By default Jetty uses ErrorPageErrorHandler, you can check Jetty's WebAppContext () constructors, the default one looks like this (jetty 9.4.19.v20190610):

public WebAppContext()
    {
        this(null,null,null,null,null,new ErrorPageErrorHandler(),SESSIONS|SECURITY);
    }

https://github.com/eclipse/jetty.project/blob/jetty-9.4.x/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java

You can extend ErrorPageErrorHandler and write to response errors in JSON.

Example:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.igorkhromov.dto.Errors;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;


public class ErrorHandler extends ErrorPageErrorHandler {

    /*
        Messages to error made based on:
        Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
        https://tools.ietf.org/html/rfc7231
    */

    private static final String ERROR_404_MESSAGE = "Target resource not found";

    private static final String ERROR_501_MESSAGE = "Server functionality to process request is not implemented";

    private static final String ERROR_502_MESSAGE = "Server cannot proxy request";

    private static final String ERROR_503_MESSAGE = "Server is currently unable to handle the request";

    private static final String ERROR_504_MESSAGE = "Server did not receive a timely response from an upstream server";

    private static final String ERROR_UNEXPECTED_MESSAGE = "Unexpected error occurs";

    private static final ObjectMapper MAPPER = new ObjectMapper();

    @Override
    protected void generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message, String mimeType)
            throws IOException
    {

            baseRequest.setHandled(true);
            Writer writer = getAcceptableWriter(baseRequest, request, response);
            if (null != writer) {
                response.setContentType(MimeTypes.Type.APPLICATION_JSON.asString());
                response.setStatus(code);
                handleErrorPage(request, writer, code, message);
            }

    }

    @Override
    protected Writer getAcceptableWriter(Request baseRequest, HttpServletRequest request, HttpServletResponse response)
            throws IOException
    {

            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            return response.getWriter();
    }

    @Override
    protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
            throws IOException
    {

            try {
                writer.write(MAPPER.writeValueAsString(new Errors(getMessage(code))));
            }
            catch (Exception e) {
                // Log if needed
            }
    }

    private boolean isRestRequest(HttpServletRequest request) {
        return request.getServletPath().startsWith("/api/");
    }

    private String getMessage(int code) {
        switch (code) {
            case 404 : return ERROR_404_MESSAGE;
            case 501 : return ERROR_501_MESSAGE;
            case 502 : return ERROR_502_MESSAGE;
            case 503 : return ERROR_503_MESSAGE;
            case 504 : return ERROR_504_MESSAGE;
            default  : return ERROR_UNEXPECTED_MESSAGE;
        }
    }
}

Code in my GitHub repo:https://github.com/xrom888/blog_jetty-display-errors-in-json

Custom ErrorHandler: https://github.com/xrom888/blog_jetty-display-errors-in-json/blob/master/src/main/java/com/igorkhromov/ErrorHandler.java

All code under the MIT license, enjoy )).

Upvotes: -1

Joakim Erdfelt
Joakim Erdfelt

Reputation: 49462

You should check the Accept header in the HttpServletRequest.getHeader("Accept") to first see if the client can accept that type.

Then use HttpServletResponse.setContentType("text/json") to set the content type for the response.

Or, if you are using Jetty 9.3.11.v20160721 (or newer) you can override the ErrorHandler.generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message, String mimeType) method and handle it accordingly.

See: https://github.com/eclipse/jetty.project/blob/jetty-9.3.11.v20160721/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java#L196-L226 for example on its use.

Upvotes: 3

Related Questions