How to setup default success status codes for mapped HttpMethods Spring MVC #RestControllers? - spring

Hi there Spring Gurus,
Currently we are annotating every mapped #RestController method with it's own success #ResponseStatus. I wonder, if there is a possibility for a global setup, based on the mapped #RequestMapping method. On Success we'd like to have:
GET -> HttpStatus.OK
POST -> HttpStatus.CREATED
PUT -> HttpStatus.OK
PATCH -> HttpStatus.OK
DELETE -> HttpStatus.NO_CONTENT
If possible at all, what do we have to do?
Best regards,
Marius

You could do it using a Filter:
public class ResponseStatusConverterFilter implements javax.servlet.Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
chain.doFilter(request, response);
switch (request.getMethod()) {
case "GET": response.setStatus(200);
break;
case "POST": response.setStatus(201);
break;
...
}
}
And add filter in web.xml
<filter>
<filter-name>responseStatusConverterFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>responseStatusConverterFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Just be careful with error handling because this could overwrite error response code. You should check response code before overwriting it (e.g check if it's some bad code like 500 or 404 and then do not set it to 200 or 201).
Another solution:
Create bean that extends HandlerInterceptorAdapter override
void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView)
throws Exception
and add logic similar to my first solution.
Add bean definition to:
<mvc:interceptors>
<bean class="com.your.web.ResponseConverterInterceptor" />
</mvc:interceptors>
Addendum: You will need to wrap HttpServletResponse with your class that extends HttpServletResponseWrapper :
public class StatusConvertingServletResponse extends HttpServletResponseWrapper {
private int httpStatus;
public StatusConvertingServletResponse(HttpServletResponse response) {
super(response);
}
#Override
public void setStatus(int sc) {
httpStatus = sc;
super.setStatus(sc);
}
#Override
public int getStatus() {
return httpStatus;
}
}
And use it like:
StatusConvertingServletResponse res = new StatusConvertingServletResponse((HttpServletResponse)response);
chain.doFilter(request, res);
if (res.getStatus() == 200) {
res.setStatus(n);
}

Just use annotation on above your methods
#ResponseStatus(value=org.springframework.http.HttpStatus.OK)
#ResponseStatus(value=org.springframework.http.HttpStatus.CREATED)
#ResponseStatus(value=org.springframework.http.HttpStatus.OK)
#ResponseStatus(value=org.springframework.http.HttpStatus.OK)
#ResponseStatus(value=org.springframework.http.HttpStatus.NO_CONTENT)

You could extend the Spring #PostMapping and add the default #ResponseStatus i.e.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#PostMapping
#ResponseStatus(value = HttpStatus.CREATED)
public #interface CreatedPostMapping {
}
and then use that annotation instead of the spring ones e.g.
#CreatedPostMapping
public String create() {
return "blah";
}
Similarly for the other http verb annotations.

Related

How to make a controller for all extension type requests?

Have a spring boot project and a default controller:
#Controller
public class GenericController
{
#RequestMapping(value= {"/**.html", "/"})
public String httpRequest(Model model, HttpServletRequest request)
{
But works only with /*.html routes. How to catch all .html routes with any folder source? example: /abc.html, /abc/def.html, /abc/def/ghi.html, etc.
I learn about:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html#path--
Learning Ant path style
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/AntPathMatcher.html
And try with:
#RequestMapping(value= {"/**/*.html", "/"})
But does not works, when call http://localhost/abc/def/ghi.html returns an http status 404.
I don't know why you want to do that but you can hack path params to do it for you. But its a dirty way and can cause conflicts with other mappings.
By using path params like below you can do /abc.html, /abc/def.html, /abc/def/ghi.html.
#RequestMapping(value = { "/**.html" , "/{path2}/**.html" ,"/{path}/{path2}/**.html" })
public String httpRequest(Model model) {
//You can also check which path variables are present and work accordingly
System.out.println("index");
return "index";
}
If you want to create a single entry point for your API then I would suggest you to read about GraphQL
Another approach can be using a Filter, that redirects your response according to incoming URI:
#Component
#Order(1)
public class AFilter implements Filter {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
if(httpServletRequest.getRequestURI()...){ // use regex ?
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
((HttpServletResponse) servletResponse).sendRedirect("/some/path/to/your/thingy");
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
And some controller:
#RequestMapping(value = "/some/path/to/your/thingy", method = RequestMethod.GET)
public ResponseEntity<Object> aMethod() throws Exception {
return ResponseEntity.ok("ok");
}

Spring REST (MVC). How to modify http request before handling in the controller

I'd like to ask. How to modify http request before handling in the Spring REST (MVC) controller?
As far as I found I can make with servlets through filter and custom request wrapper. So does anyone know how to make it in Spring (e.g. via interceptors)?
My aim is to add new request parameter to request to use it into controller
Thx in advance
You can use Filter to modify httprequestparameter value. Watchout the implementation:
Web.xml
<filter>
<description>check request for service token</description>
<filter-name>CustomFilter</filter-name>
<filter-class>com.filters.CustomFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CustomFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Create a Filter Class:
public class CustomFilter implements Filter {
String encoding;
public void init(FilterConfig config) throws ServletException {
encoding = config.getInitParameter("requestEncoding");
if (encoding == null) {
encoding = "UTF-8";
}
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// next handler
System.out.println("accessed");
filterChain.doFilter(new CustomFilterWrapper((HttpServletRequest)request),(HttpServletResponse)response);
}
public void destroy() {
}
}
Create a CustomFilterWrapper where you override getParameter method to modify certain request parameter. In my case I change value if the request param is maverick.
public class CustomFilterWrapper extends HttpServletRequestWrapper {
public CustomFilterWrapper(HttpServletRequest request) {
super(request);
}
#Override
public String getParameter(String name) {
String[] vals = getParameterMap().get(name);
if (name.equalsIgnoreCase("maverick")) {
return "modify value";
} else if (vals != null && vals.length > 0) {
return vals[0];
} else {
return null;
}
}
}

Spring Boot: Inject a custom context path

I'm running a Spring Boot 1.2.3 application with embedded Tomcat.
I'd like to inject a custom contextPath on every request, based on the first part of the URL.
Examples:
http://localhost:8080/foo has by default contextPath="" and should get contextPath="foo"
http://localhost:8080/foo/bar has by default contextPath="" and should get contextPath="foo"
(URLs without path should stay as is)
I tried to write a custom javax.servlet.Filter with #Order(Ordered.HIGHEST_PRECEDENCE), but it seems like I'm missing something. Here's the code:
#Component #Order(Ordered.HIGHEST_PRECEDENCE)
public class MultiTenancyFilter implements Filter {
private final static Pattern pattern = Pattern.compile("^/(?<contextpath>[^/]+).*$");
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest req = (HttpServletRequest) request;
final String requestURI = req.getRequestURI();
Matcher matcher = pattern.matcher(requestURI);
if(matcher.matches()) {
chain.doFilter(new HttpServletRequestWrapper(req) {
#Override
public String getContextPath() {
return "/"+matcher.group("contextpath");
}
}, response);
}
}
#Override public void init(FilterConfig filterConfig) throws ServletException {}
#Override public void destroy() {}
}
This should simply take the String after the first / and before (if any) the second / and then use it as return value for getContextPath().
But Spring #Controller #RequestMapping and Spring Security's antMatchers("/") does not seem to respect it. Both still work as if contextPath="".
How can I dynamically override the context path for each request?
Got it working!
Spring Security docs ( http://docs.spring.io/spring-security/site/docs/3.1.x/reference/security-filter-chain.html ) say: "Spring Security is only interested in securing paths within the application, so the contextPath is ignored. Unfortunately, the servlet spec does not define exactly what the values of servletPath and pathInfo will contain for a particular request URI. [...] The strategy is implemented in the class AntPathRequestMatcher which uses Spring's AntPathMatcher to perform a case-insensitive match of the pattern against the concatenated servletPath and pathInfo, ignoring the queryString."
So I just did override servletPath and contextPath (even if it's not used by Spring Security). Additionally I added some small redirect, because normally when hitting http://localhost:8080/myContext you get redirected to http://localhost:8080/myContext/ and Spring Securities Ant Matcher did not like the missing trailing slash.
So here's my MultiTenancyFilter code:
#Component #Order(Ordered.HIGHEST_PRECEDENCE)
public class MultiTenancyFilter extends OncePerRequestFilter {
private final static Pattern pattern = Pattern.compile("^(?<contextPath>/[^/]+)(?<servletPath>.*)$");
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Matcher matcher = pattern.matcher(request.getServletPath());
if(matcher.matches()) {
final String contextPath = matcher.group("contextPath");
final String servletPath = matcher.group("servletPath");
if(servletPath.trim().isEmpty()) {
response.sendRedirect(contextPath+"/");
return;
}
filterChain.doFilter(new HttpServletRequestWrapper(request) {
#Override
public String getContextPath() {
return contextPath;
}
#Override
public String getServletPath() {
return servletPath;
}
}, response);
} else {
filterChain.doFilter(request, response);
}
}
#Override
protected String getAlreadyFilteredAttributeName() {
return "multiTenancyFilter" + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX;
}
}
It simply extracts the contextPath and servletPath using the URL schema mentioned here: https://theholyjava.wordpress.com/2014/03/24/httpservletrequest-requesturirequesturlcontextpathservletpathpathinfoquerystring/
Additionally I had to provide a custom getAlreadyFilteredAttributeName method, because else the filter got called twice. (This resulted in stripping the contextPath twice)

Make simple servlet filter work with #ControllerAdvice

I've a simple filter just to check if a request contains a special header with static key - no user auth - just to protect endpoints. The idea is to throw an AccessForbiddenException if the key does not match which then will be mapped to response with a class annotated with #ControllerAdvice. However I can't make it work. My #ExceptionHandler isn't called.
ClientKeyFilter
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Controller
import javax.servlet.*
import javax.servlet.http.HttpServletRequest
#Controller //I know that #Component might be here
public class ClientKeyFilter implements Filter {
#Value('${CLIENT_KEY}')
String clientKey
public void init(FilterConfig filterConfig) {}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
req = (HttpServletRequest) req
def reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey)) {
throw new AccessForbiddenException('Invalid API key')
}
chain.doFilter(req, res)
}
public void destroy() {}
}
AccessForbiddenException
public class AccessForbiddenException extends RuntimeException {
AccessForbiddenException(String message) {
super(message)
}
}
ExceptionController
#ControllerAdvice
class ExceptionController {
static final Logger logger = LoggerFactory.getLogger(ExceptionController)
#ExceptionHandler(AccessForbiddenException)
public ResponseEntity handleException(HttpServletRequest request, AccessForbiddenException e) {
logger.error('Caught exception.', e)
return new ResponseEntity<>(e.getMessage(), I_AM_A_TEAPOT)
}
}
Where I'm wrong? Can simple servlet filter work with spring-boot's exception mapping?
As specified by the java servlet specification Filters execute always before a Servlet is invoked. Now a #ControllerAdvice is only useful for controller which are executed inside the DispatcherServlet. So using a Filter and expecting a #ControllerAdvice or in this case the #ExceptionHandler, to be invoked isn't going to happen.
You need to either put the same logic in the filter (for writing a JSON response) or instead of a filter use a HandlerInterceptor which does this check. The easiest way is to extend the HandlerInterceptorAdapter and just override and implement the preHandle method and put the logic from the filter into that method.
public class ClientKeyInterceptor extends HandlerInterceptorAdapter {
#Value('${CLIENT_KEY}')
String clientKey
#Override
public boolean preHandle(ServletRequest req, ServletResponse res, Object handler) {
String reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey)) {
throw new AccessForbiddenException('Invalid API key')
}
return true;
}
}
You can't use #ControllerAdvice, because it gets called in case of an exception in some controller, but your ClientKeyFilter is not a #Controller.
You should replace the #Controller annotation with the #Component and just set response body and status like this:
#Component
public class ClientKeyFilter implements Filter {
#Value('${CLIENT_KEY}')
String clientKey
public void init(FilterConfig filterConfig) {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String reqClientKey = request.getHeader("Client-Key");
if (!clientKey.equals(reqClientKey)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid API key");
return;
}
chain.doFilter(req, res);
}
public void destroy() {
}
}
Servlet Filters in Java classes are used for the following purposes:
To check requests from client before they access resources at backend.
To check responses from server before sent back to the client.
Exception throw from Filter may not be catch by #ControllerAdvice because in may not reach DispatcherServlet. I am handling in my project as below:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
String token = null;
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && (bearerToken.contains("Bearer "))) {
if (bearerToken.startsWith("Bearer "))
token = bearerToken.substring(7, bearerToken.length());
try {
AuthenticationInfo authInfo = TokenHandler.validateToken(token);
logger.debug("Found id:{}", authInfo.getId());
authInfo.uri = request.getRequestURI();
AuthPersistenceBean persistentBean = new AuthPersistenceBean(authInfo);
SecurityContextHolder.getContext().setAuthentication(persistentBean);
logger.debug("Found id:'{}', added into SecurityContextHolder", authInfo.getId());
} catch (AuthenticationException authException) {
logger.error("User Unauthorized: Invalid token provided");
raiseException(request, response);
return;
} catch (Exception e) {
raiseException(request, response);
return;
}
// Wrapping the error response
private void raiseException(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ApiError apiError = new ApiError(HttpStatus.UNAUTHORIZED);
apiError.setMessage("User Unauthorized: Invalid token provided");
apiError.setPath(request.getRequestURI());
byte[] body = new ObjectMapper().writeValueAsBytes(apiError);
response.getOutputStream().write(body);
}
// ApiError class
public class ApiError {
// 4xx and 5xx
private HttpStatus status;
// holds a user-friendly message about the error.
private String message;
// holds a system message describing the error in more detail.
private String debugMessage;
// returns the part of this request's URL
private String path;
public ApiError(HttpStatus status) {
this();
this.status = status;
}
//setter and getters

Modify request URI in spring mvc

I have a spring mvc based application. I want to modify the request URI before it reaches controller. For example, RequestMapping for controller is "abc/xyz" but the request coming is "abc/1/xyz". I want to modify incoming request to map it to controller.
Solution1: Implement interceptor and modify incoming request URI. But the problem here is that as there is no controller matching the URI pattern "abc/1/xyz", it does not even goes to interceptor.(I might be missing something to enable it if its there)
Get around for it could be to have both of URI as request mapping for controller.
What other solutions could be there? Is there a way to handle this request even before it comes to spring. As in handle it at filter in web.xml, i am just making it up.
You could write a servlet Filter which wraps the HttpServletRequest and returns a different value for the method getRequestURI. Something like that:
public class RequestURIOverriderServletFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(new HttpServletRequestWrapper((HttpServletRequest) request) {
#Override
public String getRequestURI() {
// return what you want
}
}, response);
}
// ...
}
The servlet filter configuration must be added into the web.xml.
But sincerly, there is probably other way to solve your problems and you should not do this unless you have very good reasons.
in order to achieve this you should replace every place that affected when you calling uri.
the place that not mentioned is INCLUDE_SERVLET_PATH_ATTRIBUTE which is internally is accessed when going deeper.
public class AuthFilter implements Filter {
private final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
private final String API_PREFIX = "/api";
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
if (requestURI.startsWith(API_PREFIX)) {
String redirectURI = requestURI.substring(API_PREFIX.length());
StringBuffer redirectURL = new StringBuffer(((HttpServletRequest) request).getRequestURL().toString().replaceFirst(API_PREFIX, ""));
filterChain.doFilter(new HttpServletRequestWrapper((HttpServletRequest) request) {
#Override
public String getRequestURI() {
return redirectURI;
}
#Override
public StringBuffer getRequestURL() {
return redirectURL;
}
#Override
public Object getAttribute(String name) {
if(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE.equals(name))
return redirectURI;
return super.getAttribute(name);
}
}, response);
} else {
filterChain.doFilter(request, response);
}
}
}
You can use a URL Re-Write which are specifically meant for this purpose i.e. transform one request URI to another URI based on some regex.

Resources