I want to handle an AJAX request that updates an entity. I don't really need it to return anything. The problem is that Spring MVC insists on sending a redirect to the same URL (apparently doing its post-redirect-get thing), which the browser dutifully follows.
How can I have a Spring MVC controller method just complete and return something without sending a redirect? Searching on the web only leads to countrless discussions of how to do a redirect, not how to avoid one.
It's a PUT request to http://localhost:9090/pex/api/testrun/f0a80b46-84b1-462a-af47-d1eadd779f59e
with these headers:
Host: localhost:9090
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0
Accept: */*
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Length: 20
Content-Type: application/json
Referer: http://localhost:9090/pex/api/testrun/f0a80b46-84b1-462a-af47-d1eadd779f59e/visualizations/common-api?slas=lp,internal,external
X-Requested-With: XMLHttpRequest
Connection: keep-alive
Authorization: Basic xxxx
The response has status code "302 Found", no body content and these headers:
Content-Language: "de"
Content-Length: "0"
Location: "http://localhost:9090/pex/api/testrun/f0a80b46-84b1-462a-af47-d1eadd779f59e"
Server: "Jetty(6.1.10)"
access-control-allow-origin: "*"
Here's the Server-side code:
@RequestMapping(value = "/api/testrun/{testrunId}", method = RequestMethod.PUT, consumes = "application/json")
@ResponseBody
public Testrun updateOverview(@PathVariable("testrunId") final String testrunId, @RequestBody final String body) {
return testrunService.updateOverview(testrunId, body);
}
Here's the Javascript code that makes the AJAX call:
$(document).ready(function() {
$("#update_name_form").submit(function (e) {
update_testrun($("#name"));
return false;
});
}
function update_testrun(element) {
var name = element.attr('name');
var new_value = element.val().trim();
var data = {};
data[name] = new_value;
$.ajax({url: config.urls.api.testrun + testrun.id,
type: "PUT",
contentType: "application/json",
data: JSON.stringify(data),
error: function(jqXHR, textStatus, errorThrown) {
alert(errorThrown);
},
success: function (data, textStatus, jqXHR) {
testrun.overview[name] = new_value;
}
});
}
Forward: is faster, the client browser is not involved, the browser displays the original URL, the request is transfered do the forwarded URL. Redirect: is slower, the client browser is involved, the browser displays the redirected URL, it creates a new request to the redirected URL.
Using the redirect prefix in your controller will generate a HTTP response with the 302 status code and the location header pointing to the redirection URL. The browser will then redirect to that URL (model exposed in the first request will be lost, and the browser URL will be the second one). Save this answer.
Behind the scenes, RedirectView will trigger an HttpServletResponse. sendRedirect(), which will perform the actual redirect.
Spring MVC is built on top of the Servlet API. As such, any component that has access to the HttpServletResponse
can theoretically use it to sendRedirect(String)
or set the response code manually. (I say theoretically because the response must not have yet been committed when those calls are made.)
Typically, in a Spring MVC application, a @Controller
can receive the HttpServletResponse
(or ServletResponse
) in a @RequestMapping
method as an argument.
A HandlerInterceptor
receives it three times as part of the DispatcherServlet
's request handling lifecycle.
Any registered Servlet Filter
instances also have access to the ServletResponse
before (and after) Spring's DispatcherServlet
since filters act before servlets.
Spring tries to hide all these dependencies to the Servlet API to make programming web server easier. It therefore provides other ways to cause a redirect. These mostly depend on the supported handler method return types. More specifically, we care about String
, View
, ModelAndView
, and ResponseEntity
.
The following are all default cases:
When you return a String
, Spring will use a ViewResolver
to resolve a View
based on the String
value, which identifies the name of the view. Spring's UrlBasedViewResolver
will detect a redirect:
prefix in String
view names and consider it as an indication to send a redirect response. It will create a RedirectView
(part of this is actually done in ViewNameMethodReturnValueHandler
, but UrlBasedViewResolver
creates the View
) which will be in charge of doing the redirect with the HttpServletResponse
.
This is an implementation detail, but most of Spring's default ViewResolver
classes do this.
With View
, you can simply create and return a RedirectView
yourself. You can also implement your own View
class that will do the same. Spring will use the appropriate HandlerMethodReturnValueHandler
to handle it.
With ModelAndView
, it's a mix of the two previous options since you can provide either a view name or a View
itself.
With ResponseEntity
it gets more interesting since you control the whole response. That is, you can set a status code, headers, body, everything. All you need to do is therefore set the status code to 302 and put a Location
header with the URL to redirect to.
Finally, you have similar behavior in @ExceptionHandler
methods (with similar return types) which you can also mix with @ResponseStatus
and modifying the headers manually.
These are all the basic cases, but since Spring MVC is almost completely customizable, there are other components to be aware of. These are HandlerMethodArgumentResolver
, HandlerMethodReturnValueHandler
, HandlerAdapter
, HandlerExceptionResolver
and ExceptionHandler
, and more. Note that you'll rarely play with these and those that come with Spring pretty much do the whole job.
So,
I took your code and created an application with that, tried sending a PUT request using browser plugin POSTMAN and got a response but no redirection. See if this works. I am attaching the complete classes, you can copy and directly use it in ur application.
Here are the network headers:
Remote Address:::1:8080
Request URL:http://localhost:8080/test/api/testrun/hello
Request Method:PUT
Status Code:200 OK
**Request Headers**
Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-GB,en-US;q=0.8,en;q=0.6,pl;q=0.4
Authorization:Basic ZWFzeWwtbWFpbi1pbnQ6cHJnc3VzZXI=
Cache-Control:no-cache
Connection:keep-alive
Content-Length:0
Content-Type:application/json
Host:localhost:8080
Origin:chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36
**Response Headers**
Cache-Control:private
Content-Length:5
Content-Type:text/plain;charset=ISO-8859-1
Date:Tue, 10 Jun 2014 10:58:50 GMT
Expires:Thu, 01 Jan 1970 05:30:00 IST
Server:Apache-Coyote/1.1
ConsoleSearchEmulationRendering
And here is the code: Configuration to boot up spring
@Configuration
@EnableWebMvc
@Profile("production")
@ComponentScan(basePackages = "com", excludeFilters = { @ComponentScan.Filter(Configuration.class) })
public class WebConfig extends WebMvcConfigurationSupport {
@Override
protected void configureContentNegotiation(
ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).favorParameter(true)
.parameterName("mediaType").ignoreAcceptHeader(true)
.useJaf(false).defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML)
.mediaType("json", MediaType.APPLICATION_JSON);
}
@Bean(name = "viewResolver")
public InternalResourceViewResolver viewResolver() throws Exception {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
Controller
@Controller
public class TestController {
@RequestMapping(value = "/api/testrun/{testrunId}", method = RequestMethod.PUT, consumes = "application/json")
@ResponseBody
public String updateOverview(@PathVariable("testrunId") final String testrunId) {
System.out.println(testrunId);
return "hello";
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>test</display-name>
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.WebConfig</param-value>
</context-param>
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>production</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>ui</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ui</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Spring recommends using the post-redirect patterns but id does not do anything by default.
In my controllers I have to explicetely end the methods dealing with posts by return "redirect:/url";
I suspect the redirect to be done in the testrunService.updateOverview(testrunId, body);
call.
Edit : in reality, nothing in testrunService.updateOverview(testrunId, body);
could cause a redirect because of the @ResponseBody
annotation. With code like that, only interceptors or filters could do a redirection.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With