Custom HTTP Methods in Spring MVC - java

I am trying to create a custom Spring MVC Controller for a resource that would handle the COPY HTTP method.
#RequestMapping accepts only the following RequestMethod values: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS and TRACE.
Is there any recommended way of handling custom HTTP methods in Spring MVC Controller?

The Servlet specification allows only for GET, HEAD, POST, PUT, DELETE, OPTIONS or TRACE HTTP methods. This can be seen in the Apache Tomcat implementation of the Servlet API.
And this is reflected in the Spring API's RequestMethod enumeration.
You can cheat your way around those by implementing your own DispatcherServlet overriding the service method to allow COPY HTTP method - changing it to POST method, and customize the RequestMappingHandlerAdapter bean to allow it as well.
Something like this, using spring-boot:
#Controller
#EnableAutoConfiguration
#Configuration
public class HttpMethods extends WebMvcConfigurationSupport {
public static class CopyMethodDispatcher extends DispatcherServlet {
private static final long serialVersionUID = 1L;
#Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
if ("COPY".equals(request.getMethod())) {
super.doPost(request, response);
}
else {
super.service(request, response);
}
}
}
public static void main(final String[] args) throws Exception {
SpringApplication.run(HttpMethods.class, args);
}
#RequestMapping("/method")
#ResponseBody
public String customMethod(final HttpServletRequest request) {
return request.getMethod();
}
#Override
#Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
final RequestMappingHandlerAdapter requestMappingHandlerAdapter = super.requestMappingHandlerAdapter();
requestMappingHandlerAdapter.setSupportedMethods("COPY", "POST", "GET"); // add all methods your controllers need to support
return requestMappingHandlerAdapter;
}
#Bean
DispatcherServlet dispatcherServlet() {
return new CopyMethodDispatcher();
}
}
Now you can invoke the /method endpoint by using COPY HTTP method. Using curl this would be:
curl -v -X COPY http://localhost:8080/method

Related

Changing httpservletrequest to restcontroller in Spring

I have a servlet class which has a method "process", over ridden from HttpServlet
#override
protected void process(HttpServletRequest request, HttpServletResponse response) {
InputStream in = null;
OutputStream out = null;
String inXml = null;
//some more code..
}
It is reading whatever is coming into the servlet.
How can I rewrite this as rest controller in spring??
Just code it as:
#RestController
public ReturnType process(HttpServletRequest request, HttpServletResponse response {
//...
}
and check this part of the Spring MVC documentation as well:
#RequestMapping handler methods have a flexible signature and can choose from a range of supported controller method arguments and return values.
Note, that:
whatever you return from your Rest-Controller turns into HTTP Response Body;
you can define #RestController on the class level.

How to log RequestBody in async spring controller?

I added an async endpoint to a existing spring-mvc application:
#RestController
public class MyController {
#PostMapping("/")
public Mono<String> post(Object body) {
return Mono.just("test");
//webClient.retrieve().bodyToMono(String.class);
}
}
I want to create a global interceptor/filter that will log the request body payload. But how can I get access to it?
I tried adding a HandlerInterceptorAdapter, but the payload is always empty:
static class LoggingInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request);
byte[] buf = wrapper.getContentAsByteArray();
System.out.println(buf);
System.out.println(buf.length);
return true;
}
}
Maybe the payload is not yet present in the request, or has already been read. So how can I access the body in this async case?
Unfortunately in Webflux you cannot use HandlerInterceptorAdapter because it came from web mvc module and works only with the servlets.
I found a good article with solutions.
P.S. You must to remove spring-mvc dependencies if going to use reactive endpoins.

How to log JSON or XML request in a database or log file before processing in Spring boot using #Requestbody annotation

How do I log a JSON or XML request in a database or log file before processing in Spring boot using #RequestBody annotation?
Using which class can I perform this?
Or any link would be helpful.
You can use filter (CommonsRequestLoggingFilter class) approach or you can use below code with custom implementation
#Component
public class AppRequestInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) {
HttpServletRequest requestCacheWrapperObject = new ContentCachingRequestWrapper(request);
//your implementation
//sample method you can use: requestCacheWrapperObject.getParameterMap(); requestCacheWrapperObject.getContentAsByteArray();
return true;
}
#Override
public void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
//your implementation
}
}
#Configuration
public class AppMVCConfig implements WebMvcConfigurer {
#Autowired
private AppRequestInterceptor appRequestInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(appRequestInterceptor)
.addPathPatterns("/**");
}
}
In order to log request payloads, you can use Spring provided filter CommonsRequestLoggingFilter.
Add following bean into your Spring-Boot config and change log level of org.springframework.web.filter package to DEBUG.
#Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
CommonsRequestLoggingFilter rlFilter = new CommonsRequestLoggingFilter();
rlFilter.setIncludePayload(true);
return rlFilter;
}
Also, Spring Boot provides Actuator Endpoint (/actuator/httptrace) for HTTP request logging out of the box. Check the below link for more details on this:
Spring Boot Actuator

Spring Boot-Angular - Entering Url in Address Bar results in 404

Need help on the basics -
I have integrated Angular and Spring Boot.
I made production build of the Angular app and copied the 6 files in the Spring boot static resource folder.
By default when I hit localhost:8080 index.html is rendered as Spring boot Automatically registers it as welcome page.
Now when i am inside angular i can navigate to different component via ANGULAR ROUTER and the url is also changing.
But when i copy the same URL for example - localhost:8080/myTask and enter it in url address bar it throws 404 resource not found.
Because it hits the Spring controller first and since there is no mapping for that it fails.
In the class where you have extended WebMvcConfigurerAdapter in Spring Boot, inside addViewControllers method, you should do something like this
#Override
public void addViewControllers(final ViewControllerRegistry registry) {
super.addViewControllers(registry);
registry.addViewController("/myTask").setViewName("forward:/");
}
for forwarding, all request, you can do registry.addViewController("/**").setViewName("forward:/");
Update Thanks Jonas for the Suggestion. Since WebMvcConfigurerAdapter is deprecated in Spring 5.0, you can implement the above logic by extending WebMvcConfigurer
// the perfect solution(from jhipster)
#Controller
public class ClientForwardController {
#GetMapping(value = "/**/{path:[^\\.]*}")
public String forward() {
return "forward:/";
}
}
If you don't use Spring MVC (for example, you are using Jersey), you can also solve this by using a javax.servlet.Filter:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class AngularRoutingFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = ((HttpServletRequest) request);
String requestURI = httpServletRequest.getRequestURI();
if (shouldDispatch(requestURI)) {
request.getRequestDispatcher("/").forward(request, response);
} else {
chain.doFilter(request, response);
}
}
#Override
public void init(FilterConfig filterConfig) {}
#Override
public void destroy() {}
private boolean shouldDispatch(String requestURI) {
/* Exclude/Inlclude URLs here */
return !(requestURI.startsWith("/api") || requestURI.equals("/"));
}
}

How to enable HTTP response caching in Spring Boot

I have implemented a REST server using Spring Boot 1.0.2. I'm having trouble preventing Spring from setting HTTP headers that disable HTTP caching.
My controller is as following:
#Controller
public class MyRestController {
#RequestMapping(value = "/someUrl", method = RequestMethod.GET)
public #ResponseBody ResponseEntity<String> myMethod(
HttpServletResponse httpResponse) throws SQLException {
return new ResponseEntity<String>("{}", HttpStatus.OK);
}
}
All HTTP responses contain the following headers:
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Expires: 0
Pragma: no-cache
I've tried the following to remove or change those headers:
Call setCacheSeconds(-1) in the controller.
Call httpResponse.setHeader("Cache-Control", "max-age=123") in the controller.
Define #Bean that returns WebContentInterceptor for which I've called setCacheSeconds(-1).
Set property spring.resources.cache-period to -1 or a positive value in application.properties.
None of the above have had any effect. How do I disable or change these headers for all or individual requests in Spring Boot?
Turns out the no-cache HTTP headers are set by Spring Security. This is discussed in http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#headers.
The following disables the HTTP response header Pragma: no-cache, but doesn't otherwise solve the problem:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// Prevent the HTTP response header of "Pragma: no-cache".
http.headers().cacheControl().disable();
}
}
I ended up disabling Spring Security completely for public static resources as following (in the same class as above):
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/public/**");
}
This requires configuring two resource handlers to get cache control headers right:
#Configuration
public class MvcConfigurer extends WebMvcConfigurerAdapter
implements EmbeddedServletContainerCustomizer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Resources without Spring Security. No cache control response headers.
registry.addResourceHandler("/static/public/**")
.addResourceLocations("classpath:/static/public/");
// Resources controlled by Spring Security, which
// adds "Cache-Control: must-revalidate".
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(3600*24);
}
}
See also Serving static web resources in Spring Boot & Spring Security application.
There are a lot of ways in spring boot for http caching. Using spring boot 2.1.1 and additionally spring security 5.1.1.
1. For resources using resourcehandler in code:
You can add customized extensions of resources this way.
registry.addResourceHandler
Is for adding the uri path where to get the resource
.addResourceLocations
Is for setting the location in the filesystem where the resources are located(
given is a relative with classpath but absolute path with file::// is also possible.)
.setCacheControl
Is for setting the cache headers (self explanatory.)
Resourcechain and resolver are optional (in this case exactly as the default values.)
#Configuration
public class CustomWebMVCConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/*.js", "/*.css", "/*.ttf", "/*.woff", "/*.woff2", "/*.eot",
"/*.svg")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)
.cachePrivate()
.mustRevalidate())
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
}
2. For resources using application properties config file
Same as above, minus the specific patterns, but now as config.
This configuration is applied to all resources in the static-locations listed.
spring.resources.cache.cachecontrol.cache-private=true
spring.resources.cache.cachecontrol.must-revalidate=true
spring.resources.cache.cachecontrol.max-age=31536000
spring.resources.static-locations=classpath:/static/
3. At controller level
Response here is the HttpServletResponse injected in the controller method as parameter.
no-cache, must-revalidate, private
getHeaderValue will output the cache options as string. e.g.
response.setHeader(HttpHeaders.CACHE_CONTROL,
CacheControl.noCache()
.cachePrivate()
.mustRevalidate()
.getHeaderValue());
I have found this Spring extension: https://github.com/foo4u/spring-mvc-cache-control.
You just have to do three steps.
Step 1 (pom.xml):
<dependency>
<groupId>net.rossillo.mvc.cache</groupId>
<artifactId>spring-mvc-cache-control</artifactId>
<version>1.1.1-RELEASE</version>
<scope>compile</scope>
</dependency>
Step 2 (WebMvcConfiguration.java):
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CacheControlHandlerInterceptor());
}
}
Step 3 (Controller):
#Controller
public class MyRestController {
#CacheControl(maxAge=31556926)
#RequestMapping(value = "/someUrl", method = RequestMethod.GET)
public #ResponseBody ResponseEntity<String> myMethod(
HttpServletResponse httpResponse) throws SQLException {
return new ResponseEntity<String>("{}", HttpStatus.OK);
}
}
Overriding of default caching behavior for a particular method can be done in the below way:
#Controller
public class MyRestController {
#RequestMapping(value = "/someUrl", method = RequestMethod.GET)
public #ResponseBody ResponseEntity<String> myMethod(
HttpServletResponse httpResponse) throws SQLException {
return new ResponseEntity.ok().cacheControl(CacheControl.maxAge(100, TimeUnit.SECONDS)).body(T)
}
}
The CacheControl class is a fluent builder, which makes it easy for us to create different types of caching:
#GetMapping("/users/{name}")
public ResponseEntity<UserDto> getUser(#PathVariable String name) {
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
.body(new UserDto(name));
}
Let's hit this endpoint in our test, and assert that we have changed the headers:
given()
.when()
.get(getBaseUrl() + "/users/Michael")
.then()
.header("Cache-Control", "max-age=60");
I run into similar problem. I wanted to get just some of dynamic resources (images) cached in the browser. If image changes (not very often) I change the part of uri... This is my sollution
http.headers().cacheControl().disable();
http.headers().addHeaderWriter(new HeaderWriter() {
CacheControlHeadersWriter originalWriter = new CacheControlHeadersWriter();
#Override
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
Collection<String> headerNames = response.getHeaderNames();
String requestUri = request.getRequestURI();
if(!requestUri.startsWith("/web/eventImage")) {
originalWriter.writeHeaders(request, response);
} else {
//write header here or do nothing if it was set in the code
}
}
});
#Configuration
#EnableAutoConfiguration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/resources/")
.setCachePeriod(31556926);
}
}
If you don't care to have your static resources authenticated, you could do this:
import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toStaticResources;
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity
.ignoring()
.requestMatchers(toStaticResources().atCommonLocations());
}
...
}
and in your application.properties:
spring.resources.cache.cachecontrol.max-age=43200
See ResourceProperties.java for more properties that can be set.
I used below lines in my controller.
ResponseEntity.ok().cacheControl(CacheControl.maxAge(secondWeWantTobeCached, TimeUnit.SECONDS)).body(objToReturnInResponse);
Please note that Response will have header Cache-Control with value secondWeWantTobeCached. However if we are typing url in addressbar and pressing enter, Request will always be sent from Chrome to server. However if we hit url from some link, browser will not send a new request and it will be taken from cache.

Categories