0
votes

I'm using Spring Boot as a web server, and I want to response [HTTP/1.1 403 XXX] sometimes.

I tried ResponseEntity with @ResponseStatus, and response.sendError(403, "XXX"), but I sniffer packet found web server only response [HTTP/1.1 403]

CURL example:

[root@localhost ~]# curl -v 192.168.12.36:8080/test
* About to connect() to 192.168.12.36 port 8080 (#0) 
*   Trying 192.168.12.36...
* Connected to 192.168.12.36 (192.168.12.36) port 8080 (#0)
> GET /test HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 192.168.12.36:8080
> Accept: */*
> 
< HTTP/1.1 403 
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Thu, 09 Aug 2018 08:26:37 GMT
< 
* Connection #0 to host 192.168.12.36 left intact

The response what i need is:

< HTTP/1.1 403 XXX
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
...

How can I add custom message after HTTP status code ?

3
Thanks @john for correcting my question.ajianzheng

3 Answers

2
votes

You could have the body of the HTTP response contain a message which you can parse with any additional information.

The HTTP status message on the status code (in the response) can be anything you want and it won't effect any clients. HTTP clients usually ignore the message text.

Here is an alternative. Create a generic exception that takes a status code and a message. Then create an exception handler. Use the exception handler to retrieve the information out of the exception and return to the caller of the service.

http://javaninja.net/2016/06/throwing-exceptions-messages-spring-mvc-controller/

public class ResourceException extends RuntimeException {

    private HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

    public HttpStatus getHttpStatus() {
        return httpStatus;
    }

    /**
     * Constructs a new runtime exception with the specified detail message.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()}
     *                method.
     */
    public ResourceException(HttpStatus httpStatus, String message) {
        super(message);
        this.httpStatus = httpStatus;
    }
}

Then use an exception handler to retrieve the information and return it to the service caller.

@ControllerAdvice
public class ExceptionHandlerAdvice { 

    @ExceptionHandler(ResourceException.class)
    public ResponseEntity handleException(ResourceException e) {
        // log exception 
        return ResponseEntity.status(e.getHttpStatus()).body(e.getMessage());
    }         
} 

Then create an exception when you need to.

throw new ResourceException(HttpStatus.NOT_FOUND, "We were unable to find the specified resource.");
1
votes

You haven't said so in your question, but I guess that you are using Tomcat. By default, Tomcat only sends the status code and does not send the reason phrase. The reason phrase is an optional part of the HTTP spec so I would not recommend relying on it being sent.

If you really want to rely on a custom reason phrase, rather than using the response body as other answers have recommended, you will either have to configure Tomcat to send the reason phrase (something that is only supported in Tomcat 8.5) or switch to another container. In terms of embedded servlet containers supported by Spring Boot, both Jetty and Undertow will send the reason phrase.

Here's a small example that configures Tomcat to both send the reason phrase and enable the use of custom phrases:

package sample.tomcat;

import javax.servlet.http.HttpServletResponse;

import org.apache.coyote.AbstractProtocol;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@SpringBootApplication
public class SampleTomcatApplication {

    @Bean
    public EmbeddedServletContainerCustomizer tomcatCustomizer() {
        return (container) -> {
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                ((TomcatEmbeddedServletContainerFactory) container)
                        .addConnectorCustomizers((connector) -> {
                            ((AbstractProtocol<?>) connector.getProtocolHandler())
                                    .setSendReasonPhrase(true);
                        });
            }
        };
    }

    public static void main(String[] args) throws Exception {
        System.setProperty("org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER", "true");
        SpringApplication.run(SampleTomcatApplication.class, args);
    }

}

@Controller
class SampleController {

    @GetMapping("/")
    public void customReasonPhrase(HttpServletResponse response) throws Exception {
        response.setStatus(503, "Custom");
    }

}
0
votes

You can set a reason on your @ResponseStatus annotation :

@ResponseStatus(value = HttpStatus.FORBIDDEN, reason="You custom reason")