summaryrefslogtreecommitdiff
path: root/spring-web/src/main/java/org/springframework/web/context/request
diff options
context:
space:
mode:
Diffstat (limited to 'spring-web/src/main/java/org/springframework/web/context/request')
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/FacesRequestAttributes.java14
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/ServletRequestAttributes.java52
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java123
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/WebRequest.java40
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResultProcessingInterceptor.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/async/StandardServletAsyncWebRequest.java2
-rw-r--r--spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java3
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