Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I prevent Spring MVC from doing a redirect?

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;
            }
    });
}
like image 977
Michael Borgwardt Avatar asked Jun 06 '14 16:06

Michael Borgwardt


People also ask

What is the difference between forward and redirect in Spring MVC?

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.

What is the use of redirect in Spring MVC?

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.

What object will Spring use behind the scenes to issue a redirect command?

Behind the scenes, RedirectView will trigger an HttpServletResponse. sendRedirect(), which will perform the actual redirect.


3 Answers

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.

like image 196
Sotirios Delimanolis Avatar answered Oct 29 '22 08:10

Sotirios Delimanolis


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>
like image 38
dharam Avatar answered Oct 29 '22 10:10

dharam


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.

like image 39
Serge Ballesta Avatar answered Oct 29 '22 08:10

Serge Ballesta