001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.view; 018 019import java.io.IOException; 020import java.io.UnsupportedEncodingException; 021import java.lang.reflect.Array; 022import java.net.URLEncoder; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.Iterator; 027import java.util.LinkedHashMap; 028import java.util.Map; 029import java.util.regex.Matcher; 030import java.util.regex.Pattern; 031import javax.servlet.http.HttpServletRequest; 032import javax.servlet.http.HttpServletResponse; 033 034import org.springframework.beans.BeanUtils; 035import org.springframework.http.HttpStatus; 036import org.springframework.util.CollectionUtils; 037import org.springframework.util.ObjectUtils; 038import org.springframework.util.StringUtils; 039import org.springframework.web.context.WebApplicationContext; 040import org.springframework.web.servlet.FlashMap; 041import org.springframework.web.servlet.FlashMapManager; 042import org.springframework.web.servlet.HandlerMapping; 043import org.springframework.web.servlet.SmartView; 044import org.springframework.web.servlet.View; 045import org.springframework.web.servlet.support.RequestContextUtils; 046import org.springframework.web.servlet.support.RequestDataValueProcessor; 047import org.springframework.web.util.UriComponents; 048import org.springframework.web.util.UriComponentsBuilder; 049import org.springframework.web.util.UriUtils; 050import org.springframework.web.util.WebUtils; 051 052/** 053 * View that redirects to an absolute, context relative, or current request 054 * relative URL. The URL may be a URI template in which case the URI template 055 * variables will be replaced with values available in the model. By default 056 * all primitive model attributes (or collections thereof) are exposed as HTTP 057 * query parameters (assuming they've not been used as URI template variables), 058 * but this behavior can be changed by overriding the 059 * {@link #isEligibleProperty(String, Object)} method. 060 * 061 * <p>A URL for this view is supposed to be an HTTP redirect URL, i.e. 062 * suitable for HttpServletResponse's {@code sendRedirect} method, which 063 * is what actually does the redirect if the HTTP 1.0 flag is on, or via sending 064 * back an HTTP 303 code - if the HTTP 1.0 compatibility flag is off. 065 * 066 * <p>Note that while the default value for the "contextRelative" flag is off, 067 * you will probably want to almost always set it to true. With the flag off, 068 * URLs starting with "/" are considered relative to the web server root, while 069 * with the flag on, they are considered relative to the web application root. 070 * Since most web applications will never know or care what their context path 071 * actually is, they are much better off setting this flag to true, and submitting 072 * paths which are to be considered relative to the web application root. 073 * 074 * <p><b>NOTE when using this redirect view in a Portlet environment:</b> Make sure 075 * that your controller respects the Portlet {@code sendRedirect} constraints. 076 * 077 * @author Rod Johnson 078 * @author Juergen Hoeller 079 * @author Colin Sampaleanu 080 * @author Sam Brannen 081 * @author Arjen Poutsma 082 * @author Rossen Stoyanchev 083 * @see #setContextRelative 084 * @see #setHttp10Compatible 085 * @see #setExposeModelAttributes 086 * @see javax.servlet.http.HttpServletResponse#sendRedirect 087 */ 088public class RedirectView extends AbstractUrlBasedView implements SmartView { 089 090 private static final Pattern URI_TEMPLATE_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); 091 092 093 private boolean contextRelative = false; 094 095 private boolean http10Compatible = true; 096 097 private boolean exposeModelAttributes = true; 098 099 private String encodingScheme; 100 101 private HttpStatus statusCode; 102 103 private boolean expandUriTemplateVariables = true; 104 105 private boolean propagateQueryParams = false; 106 107 private String[] hosts; 108 109 110 /** 111 * Constructor for use as a bean. 112 */ 113 public RedirectView() { 114 setExposePathVariables(false); 115 } 116 117 /** 118 * Create a new RedirectView with the given URL. 119 * <p>The given URL will be considered as relative to the web server, 120 * not as relative to the current ServletContext. 121 * @param url the URL to redirect to 122 * @see #RedirectView(String, boolean) 123 */ 124 public RedirectView(String url) { 125 super(url); 126 setExposePathVariables(false); 127 } 128 129 /** 130 * Create a new RedirectView with the given URL. 131 * @param url the URL to redirect to 132 * @param contextRelative whether to interpret the given URL as 133 * relative to the current ServletContext 134 */ 135 public RedirectView(String url, boolean contextRelative) { 136 super(url); 137 this.contextRelative = contextRelative; 138 setExposePathVariables(false); 139 } 140 141 /** 142 * Create a new RedirectView with the given URL. 143 * @param url the URL to redirect to 144 * @param contextRelative whether to interpret the given URL as 145 * relative to the current ServletContext 146 * @param http10Compatible whether to stay compatible with HTTP 1.0 clients 147 */ 148 public RedirectView(String url, boolean contextRelative, boolean http10Compatible) { 149 super(url); 150 this.contextRelative = contextRelative; 151 this.http10Compatible = http10Compatible; 152 setExposePathVariables(false); 153 } 154 155 /** 156 * Create a new RedirectView with the given URL. 157 * @param url the URL to redirect to 158 * @param contextRelative whether to interpret the given URL as 159 * relative to the current ServletContext 160 * @param http10Compatible whether to stay compatible with HTTP 1.0 clients 161 * @param exposeModelAttributes whether or not model attributes should be 162 * exposed as query parameters 163 */ 164 public RedirectView(String url, boolean contextRelative, boolean http10Compatible, boolean exposeModelAttributes) { 165 super(url); 166 this.contextRelative = contextRelative; 167 this.http10Compatible = http10Compatible; 168 this.exposeModelAttributes = exposeModelAttributes; 169 setExposePathVariables(false); 170 } 171 172 173 /** 174 * Set whether to interpret a given URL that starts with a slash ("/") 175 * as relative to the current ServletContext, i.e. as relative to the 176 * web application root. 177 * <p>Default is "false": A URL that starts with a slash will be interpreted 178 * as absolute, i.e. taken as-is. If "true", the context path will be 179 * prepended to the URL in such a case. 180 * @see javax.servlet.http.HttpServletRequest#getContextPath 181 */ 182 public void setContextRelative(boolean contextRelative) { 183 this.contextRelative = contextRelative; 184 } 185 186 /** 187 * Set whether to stay compatible with HTTP 1.0 clients. 188 * <p>In the default implementation, this will enforce HTTP status code 302 189 * in any case, i.e. delegate to {@code HttpServletResponse.sendRedirect}. 190 * Turning this off will send HTTP status code 303, which is the correct 191 * code for HTTP 1.1 clients, but not understood by HTTP 1.0 clients. 192 * <p>Many HTTP 1.1 clients treat 302 just like 303, not making any 193 * difference. However, some clients depend on 303 when redirecting 194 * after a POST request; turn this flag off in such a scenario. 195 * @see javax.servlet.http.HttpServletResponse#sendRedirect 196 */ 197 public void setHttp10Compatible(boolean http10Compatible) { 198 this.http10Compatible = http10Compatible; 199 } 200 201 /** 202 * Set the {@code exposeModelAttributes} flag which denotes whether 203 * or not model attributes should be exposed as HTTP query parameters. 204 * <p>Defaults to {@code true}. 205 */ 206 public void setExposeModelAttributes(final boolean exposeModelAttributes) { 207 this.exposeModelAttributes = exposeModelAttributes; 208 } 209 210 /** 211 * Set the encoding scheme for this view. 212 * <p>Default is the request's encoding scheme 213 * (which is ISO-8859-1 if not specified otherwise). 214 */ 215 public void setEncodingScheme(String encodingScheme) { 216 this.encodingScheme = encodingScheme; 217 } 218 219 /** 220 * Set the status code for this view. 221 * <p>Default is to send 302/303, depending on the value of the 222 * {@link #setHttp10Compatible(boolean) http10Compatible} flag. 223 */ 224 public void setStatusCode(HttpStatus statusCode) { 225 this.statusCode = statusCode; 226 } 227 228 /** 229 * Whether to treat the redirect URL as a URI template. 230 * Set this flag to {@code false} if the redirect URL contains open 231 * and close curly braces "{", "}" and you don't want them interpreted 232 * as URI variables. 233 * <p>Defaults to {@code true}. 234 */ 235 public void setExpandUriTemplateVariables(boolean expandUriTemplateVariables) { 236 this.expandUriTemplateVariables = expandUriTemplateVariables; 237 } 238 239 /** 240 * When set to {@code true} the query string of the current URL is appended 241 * and thus propagated through to the redirected URL. 242 * <p>Defaults to {@code false}. 243 * @since 4.1 244 */ 245 public void setPropagateQueryParams(boolean propagateQueryParams) { 246 this.propagateQueryParams = propagateQueryParams; 247 } 248 249 /** 250 * Whether to propagate the query params of the current URL. 251 * @since 4.1 252 */ 253 public boolean isPropagateQueryProperties() { 254 return this.propagateQueryParams; 255 } 256 257 /** 258 * Configure one or more hosts associated with the application. 259 * All other hosts will be considered external hosts. 260 * <p>In effect, this property provides a way turn off encoding via 261 * {@link HttpServletResponse#encodeRedirectURL} for URLs that have a 262 * host and that host is not listed as a known host. 263 * <p>If not set (the default) all URLs are encoded through the response. 264 * @param hosts one or more application hosts 265 * @since 4.3 266 */ 267 public void setHosts(String... hosts) { 268 this.hosts = hosts; 269 } 270 271 /** 272 * Return the configured application hosts. 273 * @since 4.3 274 */ 275 public String[] getHosts() { 276 return this.hosts; 277 } 278 279 /** 280 * Returns "true" indicating this view performs a redirect. 281 */ 282 @Override 283 public boolean isRedirectView() { 284 return true; 285 } 286 287 /** 288 * An ApplicationContext is not strictly required for RedirectView. 289 */ 290 @Override 291 protected boolean isContextRequired() { 292 return false; 293 } 294 295 296 /** 297 * Convert model to request parameters and redirect to the given URL. 298 * @see #appendQueryProperties 299 * @see #sendRedirect 300 */ 301 @Override 302 protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, 303 HttpServletResponse response) throws IOException { 304 305 String targetUrl = createTargetUrl(model, request); 306 targetUrl = updateTargetUrl(targetUrl, model, request, response); 307 308 FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request); 309 if (!CollectionUtils.isEmpty(flashMap)) { 310 UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build(); 311 flashMap.setTargetRequestPath(uriComponents.getPath()); 312 flashMap.addTargetRequestParams(uriComponents.getQueryParams()); 313 FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request); 314 if (flashMapManager == null) { 315 throw new IllegalStateException("FlashMapManager not found despite output FlashMap having been set"); 316 } 317 flashMapManager.saveOutputFlashMap(flashMap, request, response); 318 } 319 320 sendRedirect(request, response, targetUrl, this.http10Compatible); 321 } 322 323 /** 324 * Create the target URL by checking if the redirect string is a URI template first, 325 * expanding it with the given model, and then optionally appending simple type model 326 * attributes as query String parameters. 327 */ 328 protected final String createTargetUrl(Map<String, Object> model, HttpServletRequest request) 329 throws UnsupportedEncodingException { 330 331 // Prepare target URL. 332 StringBuilder targetUrl = new StringBuilder(); 333 if (this.contextRelative && getUrl().startsWith("/")) { 334 // Do not apply context path to relative URLs. 335 targetUrl.append(getContextPath(request)); 336 } 337 targetUrl.append(getUrl()); 338 339 String enc = this.encodingScheme; 340 if (enc == null) { 341 enc = request.getCharacterEncoding(); 342 } 343 if (enc == null) { 344 enc = WebUtils.DEFAULT_CHARACTER_ENCODING; 345 } 346 347 if (this.expandUriTemplateVariables && StringUtils.hasText(targetUrl)) { 348 Map<String, String> variables = getCurrentRequestUriVariables(request); 349 targetUrl = replaceUriTemplateVariables(targetUrl.toString(), model, variables, enc); 350 } 351 if (isPropagateQueryProperties()) { 352 appendCurrentQueryParams(targetUrl, request); 353 } 354 if (this.exposeModelAttributes) { 355 appendQueryProperties(targetUrl, model, enc); 356 } 357 358 return targetUrl.toString(); 359 } 360 361 private String getContextPath(HttpServletRequest request) { 362 String contextPath = request.getContextPath(); 363 while (contextPath.startsWith("//")) { 364 contextPath = contextPath.substring(1); 365 } 366 return contextPath; 367 } 368 369 /** 370 * Replace URI template variables in the target URL with encoded model 371 * attributes or URI variables from the current request. Model attributes 372 * referenced in the URL are removed from the model. 373 * @param targetUrl the redirect URL 374 * @param model a Map that contains model attributes 375 * @param currentUriVariables current request URI variables to use 376 * @param encodingScheme the encoding scheme to use 377 * @throws UnsupportedEncodingException if string encoding failed 378 */ 379 protected StringBuilder replaceUriTemplateVariables( 380 String targetUrl, Map<String, Object> model, Map<String, String> currentUriVariables, String encodingScheme) 381 throws UnsupportedEncodingException { 382 383 StringBuilder result = new StringBuilder(); 384 Matcher matcher = URI_TEMPLATE_VARIABLE_PATTERN.matcher(targetUrl); 385 int endLastMatch = 0; 386 while (matcher.find()) { 387 String name = matcher.group(1); 388 Object value = (model.containsKey(name) ? model.remove(name) : currentUriVariables.get(name)); 389 if (value == null) { 390 throw new IllegalArgumentException("Model has no value for key '" + name + "'"); 391 } 392 result.append(targetUrl, endLastMatch, matcher.start()); 393 result.append(UriUtils.encodePathSegment(value.toString(), encodingScheme)); 394 endLastMatch = matcher.end(); 395 } 396 result.append(targetUrl.substring(endLastMatch, targetUrl.length())); 397 return result; 398 } 399 400 @SuppressWarnings("unchecked") 401 private Map<String, String> getCurrentRequestUriVariables(HttpServletRequest request) { 402 String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; 403 Map<String, String> uriVars = (Map<String, String>) request.getAttribute(name); 404 return (uriVars != null) ? uriVars : Collections.<String, String> emptyMap(); 405 } 406 407 /** 408 * Append the query string of the current request to the target redirect URL. 409 * @param targetUrl the StringBuilder to append the properties to 410 * @param request the current request 411 * @since 4.1 412 */ 413 protected void appendCurrentQueryParams(StringBuilder targetUrl, HttpServletRequest request) { 414 String query = request.getQueryString(); 415 if (StringUtils.hasText(query)) { 416 // Extract anchor fragment, if any. 417 String fragment = null; 418 int anchorIndex = targetUrl.indexOf("#"); 419 if (anchorIndex > -1) { 420 fragment = targetUrl.substring(anchorIndex); 421 targetUrl.delete(anchorIndex, targetUrl.length()); 422 } 423 424 if (targetUrl.toString().indexOf('?') < 0) { 425 targetUrl.append('?').append(query); 426 } 427 else { 428 targetUrl.append('&').append(query); 429 } 430 // Append anchor fragment, if any, to end of URL. 431 if (fragment != null) { 432 targetUrl.append(fragment); 433 } 434 } 435 } 436 437 /** 438 * Append query properties to the redirect URL. 439 * Stringifies, URL-encodes and formats model attributes as query properties. 440 * @param targetUrl the StringBuilder to append the properties to 441 * @param model a Map that contains model attributes 442 * @param encodingScheme the encoding scheme to use 443 * @throws UnsupportedEncodingException if string encoding failed 444 * @see #queryProperties 445 */ 446 @SuppressWarnings("unchecked") 447 protected void appendQueryProperties(StringBuilder targetUrl, Map<String, Object> model, String encodingScheme) 448 throws UnsupportedEncodingException { 449 450 // Extract anchor fragment, if any. 451 String fragment = null; 452 int anchorIndex = targetUrl.indexOf("#"); 453 if (anchorIndex > -1) { 454 fragment = targetUrl.substring(anchorIndex); 455 targetUrl.delete(anchorIndex, targetUrl.length()); 456 } 457 458 // If there aren't already some parameters, we need a "?". 459 boolean first = (targetUrl.toString().indexOf('?') < 0); 460 for (Map.Entry<String, Object> entry : queryProperties(model).entrySet()) { 461 Object rawValue = entry.getValue(); 462 Iterator<Object> valueIter; 463 if (rawValue != null && rawValue.getClass().isArray()) { 464 valueIter = Arrays.asList(ObjectUtils.toObjectArray(rawValue)).iterator(); 465 } 466 else if (rawValue instanceof Collection) { 467 valueIter = ((Collection<Object>) rawValue).iterator(); 468 } 469 else { 470 valueIter = Collections.singleton(rawValue).iterator(); 471 } 472 while (valueIter.hasNext()) { 473 Object value = valueIter.next(); 474 if (first) { 475 targetUrl.append('?'); 476 first = false; 477 } 478 else { 479 targetUrl.append('&'); 480 } 481 String encodedKey = urlEncode(entry.getKey(), encodingScheme); 482 String encodedValue = (value != null ? urlEncode(value.toString(), encodingScheme) : ""); 483 targetUrl.append(encodedKey).append('=').append(encodedValue); 484 } 485 } 486 487 // Append anchor fragment, if any, to end of URL. 488 if (fragment != null) { 489 targetUrl.append(fragment); 490 } 491 } 492 493 /** 494 * Determine name-value pairs for query strings, which will be stringified, 495 * URL-encoded and formatted by {@link #appendQueryProperties}. 496 * <p>This implementation filters the model through checking 497 * {@link #isEligibleProperty(String, Object)} for each element, 498 * by default accepting Strings, primitives and primitive wrappers only. 499 * @param model the original model Map 500 * @return the filtered Map of eligible query properties 501 * @see #isEligibleProperty(String, Object) 502 */ 503 protected Map<String, Object> queryProperties(Map<String, Object> model) { 504 Map<String, Object> result = new LinkedHashMap<String, Object>(); 505 for (Map.Entry<String, Object> entry : model.entrySet()) { 506 if (isEligibleProperty(entry.getKey(), entry.getValue())) { 507 result.put(entry.getKey(), entry.getValue()); 508 } 509 } 510 return result; 511 } 512 513 /** 514 * Determine whether the given model element should be exposed 515 * as a query property. 516 * <p>The default implementation considers Strings and primitives 517 * as eligible, and also arrays and Collections/Iterables with 518 * corresponding elements. This can be overridden in subclasses. 519 * @param key the key of the model element 520 * @param value the value of the model element 521 * @return whether the element is eligible as query property 522 */ 523 protected boolean isEligibleProperty(String key, Object value) { 524 if (value == null) { 525 return false; 526 } 527 if (isEligibleValue(value)) { 528 return true; 529 } 530 if (value.getClass().isArray()) { 531 int length = Array.getLength(value); 532 if (length == 0) { 533 return false; 534 } 535 for (int i = 0; i < length; i++) { 536 Object element = Array.get(value, i); 537 if (!isEligibleValue(element)) { 538 return false; 539 } 540 } 541 return true; 542 } 543 if (value instanceof Collection) { 544 Collection<?> coll = (Collection<?>) value; 545 if (coll.isEmpty()) { 546 return false; 547 } 548 for (Object element : coll) { 549 if (!isEligibleValue(element)) { 550 return false; 551 } 552 } 553 return true; 554 } 555 return false; 556 } 557 558 /** 559 * Determine whether the given model element value is eligible for exposure. 560 * <p>The default implementation considers primitives, strings, numbers, dates, 561 * URIs, URLs etc as eligible, according to {@link BeanUtils#isSimpleValueType}. 562 * This can be overridden in subclasses. 563 * @param value the model element value 564 * @return whether the element value is eligible 565 * @see BeanUtils#isSimpleValueType 566 */ 567 protected boolean isEligibleValue(Object value) { 568 return (value != null && BeanUtils.isSimpleValueType(value.getClass())); 569 } 570 571 /** 572 * URL-encode the given input String with the given encoding scheme. 573 * <p>The default implementation uses {@code URLEncoder.encode(input, enc)}. 574 * @param input the unencoded input String 575 * @param encodingScheme the encoding scheme 576 * @return the encoded output String 577 * @throws UnsupportedEncodingException if thrown by the JDK URLEncoder 578 * @see java.net.URLEncoder#encode(String, String) 579 */ 580 protected String urlEncode(String input, String encodingScheme) throws UnsupportedEncodingException { 581 return (input != null ? URLEncoder.encode(input, encodingScheme) : null); 582 } 583 584 /** 585 * Find the registered {@link RequestDataValueProcessor}, if any, and allow 586 * it to update the redirect target URL. 587 * @param targetUrl the given redirect URL 588 * @return the updated URL or the same as URL as the one passed in 589 */ 590 protected String updateTargetUrl(String targetUrl, Map<String, Object> model, 591 HttpServletRequest request, HttpServletResponse response) { 592 593 WebApplicationContext wac = getWebApplicationContext(); 594 if (wac == null) { 595 wac = RequestContextUtils.findWebApplicationContext(request, getServletContext()); 596 } 597 598 if (wac != null && wac.containsBean(RequestContextUtils.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)) { 599 RequestDataValueProcessor processor = wac.getBean( 600 RequestContextUtils.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME, RequestDataValueProcessor.class); 601 return processor.processUrl(request, targetUrl); 602 } 603 604 return targetUrl; 605 } 606 607 /** 608 * Send a redirect back to the HTTP client. 609 * @param request current HTTP request (allows for reacting to request method) 610 * @param response current HTTP response (for sending response headers) 611 * @param targetUrl the target URL to redirect to 612 * @param http10Compatible whether to stay compatible with HTTP 1.0 clients 613 * @throws IOException if thrown by response methods 614 */ 615 protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, 616 String targetUrl, boolean http10Compatible) throws IOException { 617 618 String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl)); 619 if (http10Compatible) { 620 HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE); 621 if (this.statusCode != null) { 622 response.setStatus(this.statusCode.value()); 623 response.setHeader("Location", encodedURL); 624 } 625 else if (attributeStatusCode != null) { 626 response.setStatus(attributeStatusCode.value()); 627 response.setHeader("Location", encodedURL); 628 } 629 else { 630 // Send status code 302 by default. 631 response.sendRedirect(encodedURL); 632 } 633 } 634 else { 635 HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl); 636 response.setStatus(statusCode.value()); 637 response.setHeader("Location", encodedURL); 638 } 639 } 640 641 /** 642 * Whether the given targetUrl has a host that is a "foreign" system in which 643 * case {@link HttpServletResponse#encodeRedirectURL} will not be applied. 644 * This method returns {@code true} if the {@link #setHosts(String[])} 645 * property is configured and the target URL has a host that does not match. 646 * @param targetUrl the target redirect URL 647 * @return {@code true} the target URL has a remote host, {@code false} if it 648 * the URL does not have a host or the "host" property is not configured. 649 * @since 4.3 650 */ 651 protected boolean isRemoteHost(String targetUrl) { 652 if (ObjectUtils.isEmpty(getHosts())) { 653 return false; 654 } 655 String targetHost = UriComponentsBuilder.fromUriString(targetUrl).build().getHost(); 656 if (StringUtils.isEmpty(targetHost)) { 657 return false; 658 } 659 for (String host : getHosts()) { 660 if (targetHost.equals(host)) { 661 return false; 662 } 663 } 664 return true; 665 } 666 667 /** 668 * Determines the status code to use for HTTP 1.1 compatible requests. 669 * <p>The default implementation returns the {@link #setStatusCode(HttpStatus) statusCode} 670 * property if set, or the value of the {@link #RESPONSE_STATUS_ATTRIBUTE} attribute. 671 * If neither are set, it defaults to {@link HttpStatus#SEE_OTHER} (303). 672 * @param request the request to inspect 673 * @param response the servlet response 674 * @param targetUrl the target URL 675 * @return the response status 676 */ 677 protected HttpStatus getHttp11StatusCode( 678 HttpServletRequest request, HttpServletResponse response, String targetUrl) { 679 680 if (this.statusCode != null) { 681 return this.statusCode; 682 } 683 HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE); 684 if (attributeStatusCode != null) { 685 return attributeStatusCode; 686 } 687 return HttpStatus.SEE_OTHER; 688 } 689 690}