javarestspring-bootapache-camelrestlet

How to set HTTP Status code reason with Apache Camel REST DSL (Servlet/Restlet)


I have a web application built using Spring Boot with Apache Camel and I'm implementing a REST interface. Currently, using either Camel default Servlet or Restlet component, I'm not getting the HTTP Status code reason in the response.

Here is an example response I'm getting while setting the HTTP Status code to 403:

< HTTP/1.1 403 
< Date: Mon, 19 Feb 2018 10:01:21 GMT
< Server: Restlet-Framework/2.4.0
< Content-Type: application/json
< Content-Length: 75

How it should be:

< HTTP/1.1 403 Forbidden
< Date: Mon, 19 Feb 2018 10:01:21 GMT
< Server: Restlet-Framework/2.4.0
< Content-Type: application/json
< Content-Length: 75

How can I configure Camel/Restlet/Servlet to include the reason on the HTTP Status code?

My current configuration:

Application.java

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    private static final Logger appLogger = LoggerFactory.getLogger(Application.class);
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        appLogger.info("--Application Started--");
    }

    @Bean
    public ServletRegistrationBean servletRegistrationBean() {

        SpringServerServlet serverServlet = new SpringServerServlet();
        ServletRegistrationBean regBean = new ServletRegistrationBean(serverServlet, "/*");

        Map<String,String> params = new HashMap<>();
        params.put("org.restlet.component", "restletComponent");

        regBean.setInitParameters(params);

        return regBean;
    }


    @Bean
    public Component restletComponent() {
        return new Component();
    }

    @Bean
    public RestletComponent restletComponentService() {
        return new RestletComponent(restletComponent());
    }

}

Route Configuration:

@Component
public class RestRouteBuilder extends RouteBuilder {
    private static final Logger appLogger = LoggerFactory.getLogger(RestRouteBuilder.class);
    private Predicate isAuthorizedRequest = header(HttpHeaders.AUTHORIZATION).isNotNull();

    @Override
    public void configure() throws Exception {
        restConfiguration().component("restlet")
                           .contextPath("/overlay")
                           .bindingMode(RestBindingMode.json)
                           .skipBindingOnErrorCode(false)
                           .dataFormatProperty("prettyPrint", "true");

        rest("/")
                .get()
                .route()
                .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(403))
                .setBody(constant("Forbidden"))
                .endRest();
    }
}

I also tried adding .setHeader(Exchange.HTTP_RESPONSE_TEXT, constant("Forbidden")) but the result was the same.


Solution

  • How can I configure Camel/Restlet/Servlet to include the reason on the HTTP Status code?

    Without a custom core, I believe you can't:

    The response is being sent at org.restlet.engine.adapter.ServerCall.sendResponse(), where the response head and body are written to the OutputStream:

    writeResponseHead(response); // <--
    if (responseEntity != null) {
        responseEntityStream = getResponseEntityStream();
        writeResponseBody(responseEntity, responseEntityStream);
    }
    

    ... and writeResponseHead(response) does nothing by default, check it:

    protected void writeResponseHead(Response response) throws IOException {
        // Do nothing by default
    }
    

    Update: ... the HttpStatus(value, reasonPhrase) has the reasonPhrase, but isn't used to stringify:

    HttpStatus(int value, String reasonPhrase) {
        this.value = value;
        this.reasonPhrase = reasonPhrase;
    }
    ...
    @Override
    public String toString() {
        return Integer.toString(this.value);
    }
    

    Update 2: ... the DefaultRestletBinding.populateRestletResponseFromExchange does the following:

    // get response code
    Integer responseCode = out.getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
    if (responseCode != null) {
        response.setStatus(Status.valueOf(responseCode));
    }
    

    ... it only uses the Status.valueOf.

    Although there is a Status.reasonPhrase, it isn't accessible.


    Answer:

    Without custom core, (I believe) you can't!


    ... what isn't inappropriate, given that:

    6.1.1 Status Code and Reason Phrase

    (...) The client is not required to examine or display the Reason-Phrase.

    (...) The reason phrases (...) MAY be replaced by local equivalents without affecting the protocol.

    3.1.2. Status Line

    (...) A client SHOULD ignore the reason-phrase content.

    8.1.2.4. Response Pseudo-Header Fields

    (...) HTTP/2 does not define a way to carry the version or reason phrase that is included in an HTTP/1.1 status line.

    Need to know the meaning of a status code?

    See the complete list of status codes maintained by IANA.