How to get request's URI from WebRequest in Spring?
Asked Answered
I

7

31

I am handling REST exceptions using @ControllerAdvice and ResponseEntityExceptionHandler in a spring Rest webservice. So far everything was working fine until I decided to add the URI path(for which exception has occurred) into the BAD_REQUEST response.

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
        HttpHeaders headers, HttpStatus status, WebRequest request) {
    logger.info(request.toString());
    return handleExceptionInternal(ex, errorMessage(HttpStatus.BAD_REQUEST, ex, request), headers, HttpStatus.BAD_REQUEST, request);
}

private ApiError errorMessage(HttpStatus httpStatus, Exception ex, WebRequest request) {
    final String message = ex.getMessage() == null ? ex.getClass().getName() : ex.getMessage();
    final String developerMessage = ex.getCause() == null ? ex.toString() : ex.getCause().getMessage();
    return new ApiError(httpStatus.value(), message, developerMessage, System.currentTimeMillis(), request.getDescription(false));
}

ApiError is just a Pojo class:

public class ApiError {

    private Long timeStamp;
    private int status;
    private String message;
    private String developerMessage;
    private String path;
}

But WebRequest has not given any api to get the path for which the request failed. I tried: request.toString() returns -> ServletWebRequest: uri=/signup;client=0:0:0:0:0:0:0:1
request.getDescription(false) returns -> uri=/signup
getDescription is pretty close to the requirement, but doesn't meet it. Is there any way to get only the uri part?

Isometropia answered 19/8, 2018 at 20:34 Comment(0)
I
65

Found the solution. Casting WebRequest to ServletWebRequest solved the purpose.

((ServletWebRequest)request).getRequest().getRequestURI().toString()

returns the complete path - http://localhost:8080/signup

Isometropia answered 22/8, 2018 at 5:56 Comment(3)
Instead of getRequestURL(), getRequestURI() be used to get the URI in the question.Pappas
getRequestURI() returns String alreadyVirtuous
Are you sure that you want to return the full path protocol://host:port:uri? In containerized environment it might return the host:port of container, that is not equal to the host:port that is actually called?Vu
C
12

There are multiple solutions to this problem.

1) One can get request URI and client information from WebRequest using webRequest.getDescription(true).

true will show user's information such as client id and false will just print URI.

2) Instead of WebRequest of Use HttpServletRequest directly in method definition as

@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
        HttpHeaders headers, HttpStatus status, WebRequest request, HttpServletRequest httpRequest) {
    logger.info(httpRequest.getRequestURI());
    return handleExceptionInternal(ex, errorMessage(HttpStatus.BAD_REQUEST, ex, request), headers, HttpStatus.BAD_REQUEST, request);
}
Chancellor answered 15/1, 2020 at 8:53 Comment(0)
T
3

ResponseEntityExceptionHandler explains A convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods. here

In Spring Boot 2.1.6, You can write as below:

RestExceptionHandler.java

@Order(Ordered.HIGHEST_PRECEDENCE)
@RestControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
 
private static final Logger logger = LoggerFactory.getLogger(RestExceptionHandler.class);

@ExceptionHandler(ResourceNotFoundException.class)
protected ResponseEntity<Object> handleEntityNotFound(ResourceNotFoundException ex, final HttpServletRequest httpServletRequest) {
    ApiError apiError = new ApiError(HttpStatus.NOT_FOUND);
    apiError.setMessage("Resource not found");
    apiError.setDebugMessage(ex.getMessage());
    apiError.setPath(httpServletRequest.getRequestURI());
    return buildResponseEntity(apiError);
}

private ResponseEntity<Object> buildResponseEntity(ApiError apiError) {
    return new ResponseEntity<>(apiError, apiError.getStatus());
}


 
 @Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        ApiError apiError = new ApiError(HttpStatus.METHOD_NOT_ALLOWED);
        apiError.setMessage(ex.getMessage());
        apiError.setPath(((ServletWebRequest)request).getRequest().getRequestURI().toString());
        logger.warn(ex.getMessage());
        return buildResponseEntity(apiError);
}
}

Let's start by implementing a simple structure for sending errors:

ApiError.java

public class ApiError {
// 4xx and 5xx
private HttpStatus status;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private LocalDateTime timestamp;

// holds a user-friendly message about the error.
private String message;

// holds a system message describing the error in more detail.
@JsonInclude(value = Include.NON_EMPTY)
private String debugMessage;

// returns the part of this request's URL
private String path;

@JsonInclude(value = Include.NON_EMPTY)
private List<String> details=new ArrayList<>();

// setters & getters
}

ResourceNotFoundException.java

public class ResourceNotFoundException extends RuntimeException {

private static final long serialVersionUID = 1L;

public ResourceNotFoundException() {
    super();
}

public ResourceNotFoundException(String msg) {
    super(msg);
}
Trichinosis answered 31/7, 2020 at 16:40 Comment(0)
V
3

Access the attribute of WebRequest object:

Object obj = webRequest.getAttribute("org.springframework.web.util.UrlPathHelper.PATH", 0)
String uri = String.valueOf(obj);
webRequest.getAttribute(String attributeName, int scope);

// scope can be either:
//   0: request
//   1: session

// valid attribute names can be fetched with call:
String[] attributeNames = webRequest.getAttributeNames(0);   //scope is request

Valid attribute names are:

org.springframework.web.util.UrlPathHelper.PATH
org.springframework.web.context.request.async.WebAsyncManager.WEB_ASYNC_MANAGER
org.springframework.web.servlet.HandlerMapping.bestMatchingHandler
org.springframework.web.servlet.DispatcherServlet.CONTEXT
org.springframework.web.servlet.resource.ResourceUrlProvider
characterEncodingFilter.FILTERED
org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR
org.springframework.web.servlet.DispatcherServlet.THEME_SOURCE
org.springframework.web.servlet.DispatcherServlet.LOCALE_RESOLVER
formContentFilter.FILTERED
org.springframework.web.servlet.HandlerMapping.bestMatchingPattern
requestContextFilter.FILTERED
org.springframework.web.servlet.DispatcherServlet.OUTPUT_FLASH_MAP
org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping
org.springframework.web.servlet.DispatcherServlet.FLASH_MAP_MANAGER
org.springframework.web.servlet.HandlerMapping.uriTemplateVariables
org.springframework.web.servlet.DispatcherServlet.THEME_RESOLVER
org.springframework.core.convert.ConversionService
Vu answered 20/5, 2021 at 6:55 Comment(0)
G
1

I am using SpringBoot 2.5.3 and globalExceptionHandler. short snipit. used "TheCoder" answer and went from there. You do not have to use header, status, ... WebRequest as input args if you don't need them. This gives just the endpoint of the url and not hostname.

@ExceptionHandler(value = NotFound.class)
ResponseEntity<...> httpNotFoundException(NotFound exc, HttpServletRequest req ) {
//use req.getRequestURI();
}

@ExceptionHandler(value = HttpClientErrorException.class)
ResponseEntity<...> httpClientException(HttpClientErrorException exc, HttpServletRequest req ) {
exc.getRawStatusCode()  //to get status code
//I am using this to check for 404 and handling here with other stuff instead of using NotFound.class above.
// Use req.getRequestURI();
}
Gaunt answered 15/10, 2021 at 23:11 Comment(0)
R
0

You could use request.getDescription(false).

Retroversion answered 24/6, 2021 at 9:53 Comment(0)
V
0

Implementing other solutions would possibly lead you to the following exception

java.lang.IllegalStateException: getInputStream() has already been called for this request

To read HttpServletRequest, following needs to be implemented.

Background:

To get the request body from the request, HttpServletRequest is provided with and InputStream class. The getReader() is what is usually used to stream the request. This function internally calls getInputStream() function, which returns us the stream for us to read the request. Now, note that its a stream, and can be opened only once. Hence, while reading this (i.e. implementing the solutions given in this thread) it usually throws "stream is already closed." exception. Now this happens because, the tomcat server has already opened and read the request once. Hence, we need to implement a wrapper here, which helps us to re-open an already read stream by keeping an instance of it. This wrapper again, cannot be used directly, instead, needs to be added at spring filter level, while the tomcat server is reading it.

Code:

Servlet Request Wrapper Class:

import lombok.extern.slf4j.Slf4j;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
@Slf4j
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final String body;public MyHttpServletRequestWrapper(HttpServletRequest request) {
    super(request);

    StringBuilder stringBuilder = new StringBuilder();
    BufferedReader bufferedReader = null;

    try {
        InputStream inputStream = request.getInputStream();

        if (inputStream != null) {
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

            char[] charBuffer = new char[128];
            int bytesRead = -1;

            while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                stringBuilder.append(charBuffer, 0, bytesRead);
            }
        } else {
            stringBuilder.append("");
        }
    } catch (IOException ex) {
        log.error("Error reading the request body...");
    } finally {
        if (bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (IOException ex) {
                log.error("Error closing bufferedReader...");
            }
        }
    }

    body = stringBuilder.toString();
}

@Override
public ServletInputStream getInputStream () {
    final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());

    ServletInputStream inputStream = new ServletInputStream() {
        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener readListener) {

        }

        public int read () throws IOException {
            return byteArrayInputStream.read();
        }
    };

    return inputStream;
}

@Override
public BufferedReader getReader(){
    return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}

Now we need to use the wrapper my implementing it in a filter, as shown below.

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
@Component
@Order(1)
@Slf4j
public class ServletFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (servletRequest instanceof HttpServletRequest) {
            requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest) servletRequest);
        }
        if (Objects.isNull(requestWrapper)){
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            filterChain.doFilter(requestWrapper, servletResponse);
        }
}
}

Then, the suggested implementations can be used as follows to get the request body as following:

    private String getRequestBody(HttpServletRequest request) {
    try {
        return request.getReader().lines().collect(Collectors.joining());
    }catch (Exception e){
        e.printStackTrace();
        return "{}";
    }
}
Villosity answered 9/6, 2023 at 7:53 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.