diff options
Diffstat (limited to 'spring-web/src/main/java/org/springframework/web/context/request')
7 files changed, 151 insertions, 85 deletions
diff --git a/spring-web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java b/spring-web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java index 73301c34..bfbf032b 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -217,7 +217,7 @@ public class FacesRequestAttributes implements RequestAttributes { Object session = getExternalContext().getSession(true); try { // Both HttpSession and PortletSession have a getId() method. - Method getIdMethod = session.getClass().getMethod("getId", new Class<?>[0]); + Method getIdMethod = session.getClass().getMethod("getId"); return ReflectionUtils.invokeMethod(getIdMethod, session).toString(); } catch (NoSuchMethodException ex) { @@ -227,12 +227,12 @@ public class FacesRequestAttributes implements RequestAttributes { @Override public Object getSessionMutex() { - // Enforce presence of a session first to allow listeners - // to create the mutex attribute, if any. - Object session = getExternalContext().getSession(true); - Object mutex = getExternalContext().getSessionMap().get(WebUtils.SESSION_MUTEX_ATTRIBUTE); + // Enforce presence of a session first to allow listeners to create the mutex attribute + ExternalContext externalContext = getExternalContext(); + Object session = externalContext.getSession(true); + Object mutex = externalContext.getSessionMap().get(WebUtils.SESSION_MUTEX_ATTRIBUTE); if (mutex == null) { - mutex = session; + mutex = (session != null ? session : externalContext); } return mutex; } diff --git a/spring-web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java b/spring-web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java index 4182342a..6eb5de18 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -108,15 +108,24 @@ public class ServletRequestAttributes extends AbstractRequestAttributes { */ protected final HttpSession getSession(boolean allowCreate) { if (isRequestActive()) { - return this.request.getSession(allowCreate); + HttpSession session = this.request.getSession(allowCreate); + this.session = session; + return session; } else { // Access through stored session reference, if any... - if (this.session == null && allowCreate) { - throw new IllegalStateException( - "No session found and request already completed - cannot create new session!"); + HttpSession session = this.session; + if (session == null) { + if (allowCreate) { + throw new IllegalStateException( + "No session found and request already completed - cannot create new session!"); + } + else { + session = this.request.getSession(false); + this.session = session; + } } - return this.session; + return session; } } @@ -251,25 +260,26 @@ public class ServletRequestAttributes extends AbstractRequestAttributes { */ @Override protected void updateAccessedSessionAttributes() { - // Store session reference for access after request completion. - this.session = this.request.getSession(false); - // Update all affected session attributes. - if (this.session != null) { - try { - for (Map.Entry<String, Object> entry : this.sessionAttributesToUpdate.entrySet()) { - String name = entry.getKey(); - Object newValue = entry.getValue(); - Object oldValue = this.session.getAttribute(name); - if (oldValue == newValue && !isImmutableSessionAttribute(name, newValue)) { - this.session.setAttribute(name, newValue); + if (!this.sessionAttributesToUpdate.isEmpty()) { + // Update all affected session attributes. + HttpSession session = getSession(false); + if (session != null) { + try { + for (Map.Entry<String, Object> entry : this.sessionAttributesToUpdate.entrySet()) { + String name = entry.getKey(); + Object newValue = entry.getValue(); + Object oldValue = session.getAttribute(name); + if (oldValue == newValue && !isImmutableSessionAttribute(name, newValue)) { + session.setAttribute(name, newValue); + } } } + catch (IllegalStateException ex) { + // Session invalidated - shouldn't usually happen. + } } - catch (IllegalStateException ex) { - // Session invalidated - shouldn't usually happen. - } + this.sessionAttributesToUpdate.clear(); } - this.sessionAttributesToUpdate.clear(); } /** diff --git a/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java index 6cc8e052..97a227af 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.util.Date; import java.util.Iterator; import java.util.Locale; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @@ -47,6 +49,8 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ private static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since"; + private static final String HEADER_IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + private static final String HEADER_IF_NONE_MATCH = "If-None-Match"; private static final String HEADER_LAST_MODIFIED = "Last-Modified"; @@ -55,6 +59,18 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ private static final String METHOD_HEAD = "HEAD"; + private static final String METHOD_POST = "POST"; + + private static final String METHOD_PUT = "PUT"; + + private static final String METHOD_DELETE = "DELETE"; + + /** + * Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match" + * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a> + */ + private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?"); + /** Checking for Servlet 3.0+ HttpServletResponse.getHeader(String) */ private static final boolean servlet3Present = @@ -183,11 +199,18 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ if (isCompatibleWithConditionalRequests(response)) { this.notModified = isTimestampNotModified(lastModifiedTimestamp); if (response != null) { - if (this.notModified && supportsNotModifiedStatus()) { - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + if (supportsNotModifiedStatus()) { + if (this.notModified) { + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + } + if (isHeaderAbsent(response, HEADER_LAST_MODIFIED)) { + response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp); + } } - if (isHeaderAbsent(response, HEADER_LAST_MODIFIED)) { - response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp); + else if (supportsConditionalUpdate()) { + if (this.notModified) { + response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED); + } } } } @@ -201,7 +224,9 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ if (StringUtils.hasLength(etag) && !this.notModified) { if (isCompatibleWithConditionalRequests(response)) { etag = addEtagPadding(etag); - this.notModified = isEtagNotModified(etag); + if (hasRequestHeader(HEADER_IF_NONE_MATCH)) { + this.notModified = isEtagNotModified(etag); + } if (response != null) { if (this.notModified && supportsNotModifiedStatus()) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); @@ -221,16 +246,28 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ if (StringUtils.hasLength(etag) && !this.notModified) { if (isCompatibleWithConditionalRequests(response)) { etag = addEtagPadding(etag); - this.notModified = isEtagNotModified(etag) && isTimestampNotModified(lastModifiedTimestamp); + if (hasRequestHeader(HEADER_IF_NONE_MATCH)) { + this.notModified = isEtagNotModified(etag); + } + else if (hasRequestHeader(HEADER_IF_MODIFIED_SINCE)) { + this.notModified = isTimestampNotModified(lastModifiedTimestamp); + } if (response != null) { - if (this.notModified && supportsNotModifiedStatus()) { - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - } - if (isHeaderAbsent(response, HEADER_ETAG)) { - response.setHeader(HEADER_ETAG, etag); + if (supportsNotModifiedStatus()) { + if (this.notModified) { + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + } + if (isHeaderAbsent(response, HEADER_ETAG)) { + response.setHeader(HEADER_ETAG, etag); + } + if (isHeaderAbsent(response, HEADER_LAST_MODIFIED)) { + response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp); + } } - if (isHeaderAbsent(response, HEADER_LAST_MODIFIED)) { - response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp); + else if (supportsConditionalUpdate()) { + if (this.notModified) { + response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED); + } } } } @@ -250,7 +287,8 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ return true; } return HttpStatus.valueOf(response.getStatus()).is2xxSuccessful(); - } catch (IllegalArgumentException e) { + } + catch (IllegalArgumentException e) { return true; } } @@ -263,47 +301,65 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ return (response.getHeader(header) == null); } + private boolean hasRequestHeader(String headerName) { + return StringUtils.hasLength(getHeader(headerName)); + } + private boolean supportsNotModifiedStatus() { String method = getRequest().getMethod(); return (METHOD_GET.equals(method) || METHOD_HEAD.equals(method)); } - @SuppressWarnings("deprecation") + private boolean supportsConditionalUpdate() { + String method = getRequest().getMethod(); + return (METHOD_POST.equals(method) || METHOD_PUT.equals(method) || METHOD_DELETE.equals(method)) + && hasRequestHeader(HEADER_IF_UNMODIFIED_SINCE); + } + private boolean isTimestampNotModified(long lastModifiedTimestamp) { - long ifModifiedSince = -1; + long ifModifiedSince = parseDateHeader(HEADER_IF_MODIFIED_SINCE); + if (ifModifiedSince != -1) { + return (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000)); + } + long ifUnmodifiedSince = parseDateHeader(HEADER_IF_UNMODIFIED_SINCE); + if (ifUnmodifiedSince != -1) { + return (ifUnmodifiedSince < (lastModifiedTimestamp / 1000 * 1000)); + } + return false; + } + + @SuppressWarnings("deprecation") + private long parseDateHeader(String headerName) { + long dateValue = -1; try { - ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE); + dateValue = getRequest().getDateHeader(headerName); } catch (IllegalArgumentException ex) { - String headerValue = getRequest().getHeader(HEADER_IF_MODIFIED_SINCE); + String headerValue = getHeader(headerName); // Possibly an IE 10 style value: "Wed, 09 Apr 2014 09:57:42 GMT; length=13774" int separatorIndex = headerValue.indexOf(';'); if (separatorIndex != -1) { String datePart = headerValue.substring(0, separatorIndex); try { - ifModifiedSince = Date.parse(datePart); + dateValue = Date.parse(datePart); } catch (IllegalArgumentException ex2) { // Giving up } } } - return (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000)); + return dateValue; } private boolean isEtagNotModified(String etag) { - if (StringUtils.hasLength(etag)) { - String ifNoneMatch = getRequest().getHeader(HEADER_IF_NONE_MATCH); - if (StringUtils.hasLength(ifNoneMatch)) { - String[] clientEtags = StringUtils.delimitedListToStringArray(ifNoneMatch, ",", " "); - for (String clientEtag : clientEtags) { - // compare weak/strong ETag as per https://tools.ietf.org/html/rfc7232#section-2.3 - if (StringUtils.hasLength(clientEtag) && - (clientEtag.replaceFirst("^W/", "").equals(etag.replaceFirst("^W/", "")) || - clientEtag.equals("*"))) { - return true; - } - } + String ifNoneMatch = getHeader(HEADER_IF_NONE_MATCH); + // compare weak/strong ETag as per https://tools.ietf.org/html/rfc7232#section-2.3 + String serverETag = etag.replaceFirst("^W/", ""); + Matcher eTagMatcher = ETAG_HEADER_VALUE_PATTERN.matcher(ifNoneMatch); + while (eTagMatcher.find()) { + if ("*".equals(eTagMatcher.group()) + || serverETag.equals(eTagMatcher.group(3))) { + return true; } } return false; @@ -316,7 +372,6 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ return etag; } - @Override public String getDescription(boolean includeClientInfo) { HttpServletRequest request = getRequest(); diff --git a/spring-web/src/main/java/org/springframework/web/context/request/WebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/WebRequest.java index cea9fa70..c4c11eb4 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/WebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/WebRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -126,10 +126,10 @@ public interface WebRequest extends RequestAttributes { boolean isSecure(); /** - * Check whether the request qualifies as not modified given the + * Check whether the requested resource has been modified given the * supplied last-modified timestamp (as determined by the application). - * <p>This will also transparently set the appropriate response headers, - * for both the modified case and the not-modified case. + * <p>This will also transparently set the "Last-Modified" response header + * and HTTP status when applicable. * <p>Typical usage: * <pre class="code"> * public String myHandleMethod(WebRequest webRequest, Model model) { @@ -142,6 +142,8 @@ public interface WebRequest extends RequestAttributes { * model.addAttribute(...); * return "myViewName"; * }</pre> + * <p>This method works with conditional GET/HEAD requests, but + * also with conditional POST/PUT/DELETE requests. * <p><strong>Note:</strong> you can use either * this {@code #checkNotModified(long)} method; or * {@link #checkNotModified(String)}. If you want enforce both @@ -151,8 +153,9 @@ public interface WebRequest extends RequestAttributes { * <p>If the "If-Modified-Since" header is set but cannot be parsed * to a date value, this method will ignore the header and proceed * with setting the last-modified timestamp on the response. - * @param lastModifiedTimestamp the last-modified timestamp that - * the application determined for the underlying resource + * @param lastModifiedTimestamp the last-modified timestamp in + * milliseconds that the application determined for the underlying + * resource * @return whether the request qualifies as not modified, * allowing to abort request processing and relying on the response * telling the client that the content has not been modified @@ -160,10 +163,10 @@ public interface WebRequest extends RequestAttributes { boolean checkNotModified(long lastModifiedTimestamp); /** - * Check whether the request qualifies as not modified given the + * Check whether the requested resource has been modified given the * supplied {@code ETag} (entity tag), as determined by the application. - * <p>This will also transparently set the appropriate response headers, - * for both the modified case and the not-modified case. + * <p>This will also transparently set the "ETag" response header + * and HTTP status when applicable. * <p>Typical usage: * <pre class="code"> * public String myHandleMethod(WebRequest webRequest, Model model) { @@ -185,18 +188,16 @@ public interface WebRequest extends RequestAttributes { * @param etag the entity tag that the application determined * for the underlying resource. This parameter will be padded * with quotes (") if necessary. - * @return whether the request qualifies as not modified, - * allowing to abort request processing and relying on the response - * telling the client that the content has not been modified + * @return true if the request does not require further processing. */ boolean checkNotModified(String etag); /** - * Check whether the request qualifies as not modified given the + * Check whether the requested resource has been modified given the * supplied {@code ETag} (entity tag) and last-modified timestamp, * as determined by the application. * <p>This will also transparently set the "ETag" and "Last-Modified" - * response headers, for both the modified case and the not-modified case. + * response headers, and HTTP status when applicable. * <p>Typical usage: * <pre class="code"> * public String myHandleMethod(WebRequest webRequest, Model model) { @@ -210,6 +211,8 @@ public interface WebRequest extends RequestAttributes { * model.addAttribute(...); * return "myViewName"; * }</pre> + * <p>This method works with conditional GET/HEAD requests, but + * also with conditional POST/PUT/DELETE requests. * <p><strong>Note:</strong> The HTTP specification recommends * setting both ETag and Last-Modified values, but you can also * use {@code #checkNotModified(String)} or @@ -217,11 +220,10 @@ public interface WebRequest extends RequestAttributes { * @param etag the entity tag that the application determined * for the underlying resource. This parameter will be padded * with quotes (") if necessary. - * @param lastModifiedTimestamp the last-modified timestamp that - * the application determined for the underlying resource - * @return whether the request qualifies as not modified, - * allowing to abort request processing and relying on the response - * telling the client that the content has not been modified + * @param lastModifiedTimestamp the last-modified timestamp in + * milliseconds that the application determined for the underlying + * resource + * @return true if the request does not require further processing. * @since 4.2 */ boolean checkNotModified(String etag, long lastModifiedTimestamp); diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java index 638068a3..89e5745f 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java @@ -25,7 +25,7 @@ import org.springframework.web.context.request.NativeWebRequest; * * <p>A {@code DeferredResultProcessingInterceptor} is invoked before the start * of async processing, after the {@code DeferredResult} is set as well as on - * timeout, or or after completing for any reason including a timeout or network + * timeout, or after completing for any reason including a timeout or network * error. * * <p>As a general rule exceptions raised by interceptor methods will cause diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java index fc0bb983..9bd4ac5c 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java @@ -34,7 +34,7 @@ import org.springframework.web.context.request.ServletWebRequest; * * <p>The servlet and all filters involved in an async request must have async * support enabled using the Servlet API or by adding an - * {@code <async-support>true</async-support>} element to servlet and filter + * {@code <async-supported>true</async-supported>} element to servlet and filter * declarations in {@code web.xml}. * * @author Rossen Stoyanchev diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java index a20cf23d..83913f02 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -105,7 +105,6 @@ public final class WebAsyncManager { */ public void setAsyncWebRequest(final AsyncWebRequest asyncWebRequest) { Assert.notNull(asyncWebRequest, "AsyncWebRequest must not be null"); - Assert.state(!isConcurrentHandlingStarted(), "Can't set AsyncWebRequest with concurrent handling in progress"); this.asyncWebRequest = asyncWebRequest; this.asyncWebRequest.addCompletionHandler(new Runnable() { @Override |