Improve ETag & Last-Modifed support in WebRequest
This change improves the following use cases with `WebRequest.checkNotModified(String etag)` and `WebRequest.checkNotModified(long lastModifiedTimeStamp)`: 1) Allow weak comparisons for ETags Per rfc7232 section-2.3, ETags can be strong or weak; this change allows comparing weak forms `W/"etagvalue"` but does not make a difference between strong and weak comparisons. 2) Allow multiple ETags in client requests HTTP clients can send multiple ETags values in a single header such as: `If-None-Match: "firstvalue", "secondvalue"` This change makes sure each value is compared to the one provided by the application side. 3) Extended support for ETag values This change adds padding `"` to the ETag value provided by the application, if not already done: `etagvalue` => `"etagvalue"` It also supports wildcard values `*` that can be sent by HTTP clients. 4) Sending validation headers for 304 responses As defined in https://tools.ietf.org/html/rfc7232#section-4.1 `304 Not Modified` reponses must generate `Etag` and `Last-Modified` HTTP headers, as they would have for a `200 OK` response. 5) Providing a new method to validate both Etag & Last-Modified Also, this change adds a new method `WebRequest.checkNotModified(String etag, long lastModifiedTimeStamp)` in order to support validation of both `If-None-Match` and `Last-Modified` headers sent by HTTP clients, if both values are supported by the application code. Even though this approach is recommended by the HTTP rfc (setting both Etag and Last-Modified headers in the response), this requires more application logic and may not apply to all resources produced by the application. Issue: SPR-11324
This commit is contained in:
		
							parent
							
								
									e086a637d5
								
							
						
					
					
						commit
						953608ec49
					
				| 
						 | 
				
			
			@ -155,6 +155,17 @@ public class FacesWebRequest extends FacesRequestAttributes implements NativeWeb
 | 
			
		|||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Last-modified handling not supported for portlet requests:
 | 
			
		||||
	 * As a consequence, this method always returns {@code false}.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @since 4.2
 | 
			
		||||
	 */
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean checkNotModified(String etag, long lastModifiedTimestamp) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public String getDescription(boolean includeClientInfo) {
 | 
			
		||||
		ExternalContext externalContext = getExternalContext();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2014 the original author or authors.
 | 
			
		||||
 * Copyright 2002-2015 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,7 @@ import java.util.Date;
 | 
			
		|||
import java.util.Iterator;
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
import javax.servlet.http.HttpSession;
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +36,9 @@ import org.springframework.web.util.WebUtils;
 | 
			
		|||
 * {@link WebRequest} adapter for an {@link javax.servlet.http.HttpServletRequest}.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Juergen Hoeller
 | 
			
		||||
 * @author Brian Clozel
 | 
			
		||||
 * @author Markus Malkusch
 | 
			
		||||
 *
 | 
			
		||||
 * @since 2.0
 | 
			
		||||
 */
 | 
			
		||||
public class ServletWebRequest extends ServletRequestAttributes implements NativeWebRequest {
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +76,6 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
 | 
			
		|||
		super(request, response);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Object getNativeRequest() {
 | 
			
		||||
		return getRequest();
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +96,6 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
 | 
			
		|||
		return WebUtils.getNativeResponse(getResponse(), requiredType);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Return the HTTP method of the request.
 | 
			
		||||
	 * @since 4.0.2
 | 
			
		||||
| 
						 | 
				
			
			@ -169,11 +171,24 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	@SuppressWarnings("deprecation")
 | 
			
		||||
	public boolean checkNotModified(long lastModifiedTimestamp) {
 | 
			
		||||
		HttpServletResponse response = getResponse();
 | 
			
		||||
		if (lastModifiedTimestamp >= 0 && !this.notModified &&
 | 
			
		||||
				(response == null || !response.containsHeader(HEADER_LAST_MODIFIED))) {
 | 
			
		||||
		if (lastModifiedTimestamp >= 0 && !this.notModified) {
 | 
			
		||||
			if (response == null || !response.containsHeader(HEADER_LAST_MODIFIED)) {
 | 
			
		||||
				this.notModified = isTimeStampNotModified(lastModifiedTimestamp);
 | 
			
		||||
				if (response != null) {
 | 
			
		||||
					if (this.notModified && supportsNotModifiedStatus()) {
 | 
			
		||||
						response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
 | 
			
		||||
					}
 | 
			
		||||
					response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return this.notModified;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SuppressWarnings("deprecation")
 | 
			
		||||
	private boolean isTimeStampNotModified(long lastModifiedTimestamp) {
 | 
			
		||||
		long ifModifiedSince = -1;
 | 
			
		||||
		try {
 | 
			
		||||
			ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE);
 | 
			
		||||
| 
						 | 
				
			
			@ -192,31 +207,20 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
 | 
			
		|||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
			this.notModified = (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000));
 | 
			
		||||
			if (response != null) {
 | 
			
		||||
				if (this.notModified && supportsNotModifiedStatus()) {
 | 
			
		||||
					response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
 | 
			
		||||
				}
 | 
			
		||||
				else {
 | 
			
		||||
					response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return this.notModified;
 | 
			
		||||
		return (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean checkNotModified(String etag) {
 | 
			
		||||
		HttpServletResponse response = getResponse();
 | 
			
		||||
		if (StringUtils.hasLength(etag) && !this.notModified &&
 | 
			
		||||
				(response == null || !response.containsHeader(HEADER_ETAG))) {
 | 
			
		||||
			String ifNoneMatch = getRequest().getHeader(HEADER_IF_NONE_MATCH);
 | 
			
		||||
			this.notModified = etag.equals(ifNoneMatch);
 | 
			
		||||
		if (StringUtils.hasLength(etag) && !this.notModified) {
 | 
			
		||||
			if (response == null || !response.containsHeader(HEADER_ETAG)) {
 | 
			
		||||
				etag = addEtagPadding(etag);
 | 
			
		||||
				this.notModified = isETagNotModified(etag);
 | 
			
		||||
				if (response != null) {
 | 
			
		||||
					if (this.notModified && supportsNotModifiedStatus()) {
 | 
			
		||||
						response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
 | 
			
		||||
					}
 | 
			
		||||
				else {
 | 
			
		||||
					response.setHeader(HEADER_ETAG, etag);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -224,11 +228,56 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
 | 
			
		|||
		return this.notModified;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private String addEtagPadding(String etag) {
 | 
			
		||||
		if (!(etag.startsWith("\"") || etag.startsWith("W/\"")) || !etag.endsWith("\"")) {
 | 
			
		||||
			etag = "\"" + etag + "\"";
 | 
			
		||||
		}
 | 
			
		||||
		return etag;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 ETags 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;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private boolean supportsNotModifiedStatus() {
 | 
			
		||||
		String method = getRequest().getMethod();
 | 
			
		||||
		return (METHOD_GET.equals(method) || METHOD_HEAD.equals(method));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean checkNotModified(String etag, long lastModifiedTimestamp) {
 | 
			
		||||
		HttpServletResponse response = getResponse();
 | 
			
		||||
		if (StringUtils.hasLength(etag) && !this.notModified) {
 | 
			
		||||
			if (response == null ||
 | 
			
		||||
					(!response.containsHeader(HEADER_ETAG) && !response.containsHeader(HEADER_LAST_MODIFIED))) {
 | 
			
		||||
				etag = addEtagPadding(etag);
 | 
			
		||||
				this.notModified = isETagNotModified(etag) && isTimeStampNotModified(lastModifiedTimestamp);
 | 
			
		||||
				if (response != null) {
 | 
			
		||||
					if (this.notModified && supportsNotModifiedStatus()) {
 | 
			
		||||
						response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
 | 
			
		||||
					}
 | 
			
		||||
					response.setHeader(HEADER_ETAG, etag);
 | 
			
		||||
					response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return this.notModified;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean isNotModified() {
 | 
			
		||||
		return this.notModified;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2014 the original author or authors.
 | 
			
		||||
 * Copyright 2002-2015 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -141,9 +141,12 @@ public interface WebRequest extends RequestAttributes {
 | 
			
		|||
	 *   model.addAttribute(...);
 | 
			
		||||
	 *   return "myViewName";
 | 
			
		||||
	 * }</pre>
 | 
			
		||||
	 * <p><strong>Note:</strong> that you typically want to use either
 | 
			
		||||
	 * <p><strong>Note:</strong> you can use either
 | 
			
		||||
	 * this {@code #checkNotModified(long)} method; or
 | 
			
		||||
	 * {@link #checkNotModified(String)}, but not both.
 | 
			
		||||
	 * {@link #checkNotModified(String)}. If you want enforce both
 | 
			
		||||
	 * a strong entity tag and a Last-Modified value,
 | 
			
		||||
	 * as recommended by the HTTP specification,
 | 
			
		||||
	 * then you should use {@link #checkNotModified(String, long)}.
 | 
			
		||||
	 * <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.
 | 
			
		||||
| 
						 | 
				
			
			@ -172,9 +175,12 @@ public interface WebRequest extends RequestAttributes {
 | 
			
		|||
	 *   model.addAttribute(...);
 | 
			
		||||
	 *   return "myViewName";
 | 
			
		||||
	 * }</pre>
 | 
			
		||||
	 * <p><strong>Note:</strong> that you typically want to use either
 | 
			
		||||
	 * <p><strong>Note:</strong> you can use either
 | 
			
		||||
	 * this {@code #checkNotModified(String)} method; or
 | 
			
		||||
	 * {@link #checkNotModified(long)}, but not both.
 | 
			
		||||
	 * {@link #checkNotModified(long)}. If you want enforce both
 | 
			
		||||
	 * a strong entity tag and a Last-Modified value,
 | 
			
		||||
	 * as recommended by the HTTP specification,
 | 
			
		||||
	 * then you should use {@link #checkNotModified(String, long)}.
 | 
			
		||||
	 * @param etag the entity tag that the application determined
 | 
			
		||||
	 * for the underlying resource. This parameter will be padded
 | 
			
		||||
	 * with quotes (") if necessary.
 | 
			
		||||
| 
						 | 
				
			
			@ -184,6 +190,42 @@ public interface WebRequest extends RequestAttributes {
 | 
			
		|||
	 */
 | 
			
		||||
	boolean checkNotModified(String etag);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Check whether the request qualifies as not modified given the
 | 
			
		||||
	 * supplied {@code ETag} (entity tag) and 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>Typical usage:
 | 
			
		||||
	 * <pre class="code">
 | 
			
		||||
	 * public String myHandleMethod(WebRequest webRequest, Model model) {
 | 
			
		||||
	 *   String eTag = // application-specific calculation
 | 
			
		||||
	 *   long lastModified = // application-specific calculation
 | 
			
		||||
	 *   if (request.checkNotModified(eTag, lastModified)) {
 | 
			
		||||
	 *     // shortcut exit - no further processing necessary
 | 
			
		||||
	 *     return null;
 | 
			
		||||
	 *   }
 | 
			
		||||
	 *   // further request processing, actually building content
 | 
			
		||||
	 *   model.addAttribute(...);
 | 
			
		||||
	 *   return "myViewName";
 | 
			
		||||
	 * }</pre>
 | 
			
		||||
	 * <p><strong>Note:</strong> The HTTP specification recommends
 | 
			
		||||
	 * setting both ETag and Last-Modified values, but you can also
 | 
			
		||||
	 * use {@code #checkNotModified(String)} or
 | 
			
		||||
	 * {@link #checkNotModified(long)}.
 | 
			
		||||
	 * @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
 | 
			
		||||
	 *
 | 
			
		||||
	 * @since 4.2
 | 
			
		||||
	 */
 | 
			
		||||
	boolean checkNotModified(String etag, long lastModifiedTimestamp);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Get a short description of this request,
 | 
			
		||||
	 * typically containing request URI and session id.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,258 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2015 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.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.springframework.web.context.request;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.*;
 | 
			
		||||
 | 
			
		||||
import java.text.SimpleDateFormat;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.junit.runners.Parameterized;
 | 
			
		||||
import org.junit.runners.Parameterized.Parameters;
 | 
			
		||||
 | 
			
		||||
import org.springframework.mock.web.test.MockHttpServletRequest;
 | 
			
		||||
import org.springframework.mock.web.test.MockHttpServletResponse;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Parameterized tests for ServletWebRequest
 | 
			
		||||
 * @author Juergen Hoeller
 | 
			
		||||
 * @author Brian Clozel
 | 
			
		||||
 * @author Markus Malkusch
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(Parameterized.class)
 | 
			
		||||
public class ServletWebRequestHttpMethodsTests {
 | 
			
		||||
 | 
			
		||||
	private SimpleDateFormat dateFormat;
 | 
			
		||||
 | 
			
		||||
	private MockHttpServletRequest servletRequest;
 | 
			
		||||
 | 
			
		||||
	private MockHttpServletResponse servletResponse;
 | 
			
		||||
 | 
			
		||||
	private ServletWebRequest request;
 | 
			
		||||
 | 
			
		||||
	private String method;
 | 
			
		||||
 | 
			
		||||
	@Parameters
 | 
			
		||||
	static public Iterable<Object[]> safeMethods() {
 | 
			
		||||
		return Arrays.asList(new Object[][] {
 | 
			
		||||
				{"GET"},
 | 
			
		||||
				{"HEAD"}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public ServletWebRequestHttpMethodsTests(String method) {
 | 
			
		||||
		this.method = method;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Before
 | 
			
		||||
	public void setUp() {
 | 
			
		||||
		dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
 | 
			
		||||
		servletRequest = new MockHttpServletRequest(method, "http://example.org");
 | 
			
		||||
		servletResponse = new MockHttpServletResponse();
 | 
			
		||||
		request = new ServletWebRequest(servletRequest, servletResponse);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedTimestamp() {
 | 
			
		||||
		long currentTime = new Date().getTime();
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", currentTime);
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(currentTime));
 | 
			
		||||
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
		assertEquals("" + currentTime, servletResponse.getHeader("Last-Modified"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkModifiedTimestamp() {
 | 
			
		||||
		long currentTime = new Date().getTime();
 | 
			
		||||
		long oneMinuteAgo = currentTime - (1000 * 60);
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", oneMinuteAgo);
 | 
			
		||||
 | 
			
		||||
		assertFalse(request.checkNotModified(currentTime));
 | 
			
		||||
 | 
			
		||||
		assertEquals(200, servletResponse.getStatus());
 | 
			
		||||
		assertEquals("" + currentTime, servletResponse.getHeader("Last-Modified"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedETag() {
 | 
			
		||||
		String eTag = "\"Foo\"";
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", eTag);
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(eTag));
 | 
			
		||||
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(eTag, servletResponse.getHeader("ETag"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkModifiedETag() {
 | 
			
		||||
		String currentETag = "\"Foo\"";
 | 
			
		||||
		String oldEtag = "Bar";
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", oldEtag);
 | 
			
		||||
 | 
			
		||||
		assertFalse(request.checkNotModified(currentETag));
 | 
			
		||||
 | 
			
		||||
		assertEquals(200, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(currentETag, servletResponse.getHeader("ETag"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedUnpaddedETag() {
 | 
			
		||||
		String eTag = "Foo";
 | 
			
		||||
		String paddedEtag = String.format("\"%s\"", eTag);
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", paddedEtag);
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(eTag));
 | 
			
		||||
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(paddedEtag, servletResponse.getHeader("ETag"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkModifiedUnpaddedETag() {
 | 
			
		||||
		String currentETag = "Foo";
 | 
			
		||||
		String oldEtag = "Bar";
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", oldEtag);
 | 
			
		||||
 | 
			
		||||
		assertFalse(request.checkNotModified(currentETag));
 | 
			
		||||
 | 
			
		||||
		assertEquals(200, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(String.format("\"%s\"", currentETag), servletResponse.getHeader("ETag"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedWildcardETag() {
 | 
			
		||||
		String eTag = "\"Foo\"";
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", "*");
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(eTag));
 | 
			
		||||
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(eTag, servletResponse.getHeader("ETag"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedETagAndTimestamp() {
 | 
			
		||||
		String eTag = "\"Foo\"";
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", eTag);
 | 
			
		||||
		long currentTime = new Date().getTime();
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", currentTime);
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(eTag, currentTime));
 | 
			
		||||
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(eTag, servletResponse.getHeader("ETag"));
 | 
			
		||||
		assertEquals("" + currentTime, servletResponse.getHeader("Last-Modified"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedETagAndModifiedTimestamp() {
 | 
			
		||||
		String eTag = "\"Foo\"";
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", eTag);
 | 
			
		||||
		long currentTime = new Date().getTime();
 | 
			
		||||
		long oneMinuteAgo = currentTime - (1000 * 60);
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", oneMinuteAgo);
 | 
			
		||||
 | 
			
		||||
		assertFalse(request.checkNotModified(eTag, currentTime));
 | 
			
		||||
 | 
			
		||||
		assertEquals(200, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(eTag, servletResponse.getHeader("ETag"));
 | 
			
		||||
		assertEquals("" + currentTime, servletResponse.getHeader("Last-Modified"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkModifiedETagAndNotModifiedTimestamp() {
 | 
			
		||||
		String currentETag = "\"Foo\"";
 | 
			
		||||
		String oldEtag = "\"Bar\"";
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", oldEtag);
 | 
			
		||||
		long currentTime = new Date().getTime();
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", currentTime);
 | 
			
		||||
 | 
			
		||||
		assertFalse(request.checkNotModified(currentETag, currentTime));
 | 
			
		||||
 | 
			
		||||
		assertEquals(200, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(currentETag, servletResponse.getHeader("ETag"));
 | 
			
		||||
		assertEquals("" + currentTime, servletResponse.getHeader("Last-Modified"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedETagWeakStrong() {
 | 
			
		||||
		String eTag = "\"Foo\"";
 | 
			
		||||
		String weakEtag = String.format("W/%s", eTag);
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", eTag);
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(weakEtag));
 | 
			
		||||
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(weakEtag, servletResponse.getHeader("ETag"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedETagStrongWeak() {
 | 
			
		||||
		String eTag = "\"Foo\"";
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", String.format("W/%s", eTag));
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(eTag));
 | 
			
		||||
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(eTag, servletResponse.getHeader("ETag"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedMultipleETags() {
 | 
			
		||||
		String eTag = "\"Bar\"";
 | 
			
		||||
		String multipleETags = String.format("\"Foo\", %s", eTag);
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", multipleETags);
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(eTag));
 | 
			
		||||
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(eTag, servletResponse.getHeader("ETag"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedTimestampWithLengthPart() throws Exception {
 | 
			
		||||
		long currentTime = dateFormat.parse("Wed, 09 Apr 2014 09:57:42 GMT").getTime();
 | 
			
		||||
		servletRequest.setMethod("GET");
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", "Wed, 09 Apr 2014 09:57:42 GMT; length=13774");
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(currentTime));
 | 
			
		||||
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
		assertEquals("" + currentTime, servletResponse.getHeader("Last-Modified"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkModifiedTimestampWithLengthPart() throws Exception {
 | 
			
		||||
		long currentTime = dateFormat.parse("Wed, 09 Apr 2014 09:57:42 GMT").getTime();
 | 
			
		||||
		servletRequest.setMethod("GET");
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", "Wed, 08 Apr 2014 09:57:42 GMT; length=13774");
 | 
			
		||||
 | 
			
		||||
		assertFalse(request.checkNotModified(currentTime));
 | 
			
		||||
 | 
			
		||||
		assertEquals(200, servletResponse.getStatus());
 | 
			
		||||
		assertEquals("" + currentTime, servletResponse.getHeader("Last-Modified"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2002-2014 the original author or authors.
 | 
			
		||||
 * Copyright 2002-2015 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -16,9 +16,11 @@
 | 
			
		|||
 | 
			
		||||
package org.springframework.web.context.request;
 | 
			
		||||
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import static org.junit.Assert.*;
 | 
			
		||||
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.ServletRequest;
 | 
			
		||||
import javax.servlet.ServletResponse;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
| 
						 | 
				
			
			@ -33,12 +35,8 @@ import org.springframework.mock.web.test.MockHttpServletRequest;
 | 
			
		|||
import org.springframework.mock.web.test.MockHttpServletResponse;
 | 
			
		||||
import org.springframework.web.multipart.MultipartRequest;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Juergen Hoeller
 | 
			
		||||
 * @author Markus Malkusch
 | 
			
		||||
 * @since 26.07.2006
 | 
			
		||||
 */
 | 
			
		||||
public class ServletWebRequestTests {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -116,113 +114,4 @@ public class ServletWebRequestTests {
 | 
			
		|||
		assertNull(request.getNativeResponse(MultipartRequest.class));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedTimestampForGET() {
 | 
			
		||||
		long currentTime = new Date().getTime();
 | 
			
		||||
		servletRequest.setMethod("GET");
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", currentTime);
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(currentTime));
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkModifiedTimestampForGET() {
 | 
			
		||||
		long currentTime = new Date().getTime();
 | 
			
		||||
		long oneMinuteAgo  = currentTime - (1000 * 60);
 | 
			
		||||
		servletRequest.setMethod("GET");
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", oneMinuteAgo);
 | 
			
		||||
 | 
			
		||||
		assertFalse(request.checkNotModified(currentTime));
 | 
			
		||||
		assertEquals(200, servletResponse.getStatus());
 | 
			
		||||
		assertEquals("" + currentTime, servletResponse.getHeader("Last-Modified"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedTimestampForHEAD() {
 | 
			
		||||
		long currentTime = new Date().getTime();
 | 
			
		||||
		servletRequest.setMethod("HEAD");
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", currentTime);
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(currentTime));
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkModifiedTimestampForHEAD() {
 | 
			
		||||
		long currentTime = new Date().getTime();
 | 
			
		||||
		long oneMinuteAgo  = currentTime - (1000 * 60);
 | 
			
		||||
		servletRequest.setMethod("HEAD");
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", oneMinuteAgo);
 | 
			
		||||
 | 
			
		||||
		assertFalse(request.checkNotModified(currentTime));
 | 
			
		||||
		assertEquals(200, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(""+currentTime, servletResponse.getHeader("Last-Modified"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedTimestampWithLengthPart() {
 | 
			
		||||
		long currentTime = Date.parse("Wed, 09 Apr 2014 09:57:42 GMT");
 | 
			
		||||
		servletRequest.setMethod("GET");
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", "Wed, 09 Apr 2014 09:57:42 GMT; length=13774");
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(currentTime));
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkModifiedTimestampWithLengthPart() {
 | 
			
		||||
		long currentTime = Date.parse("Wed, 09 Apr 2014 09:57:42 GMT");
 | 
			
		||||
		servletRequest.setMethod("GET");
 | 
			
		||||
		servletRequest.addHeader("If-Modified-Since", "Wed, 08 Apr 2014 09:57:42 GMT; length=13774");
 | 
			
		||||
 | 
			
		||||
		assertFalse(request.checkNotModified(currentTime));
 | 
			
		||||
		assertEquals(200, servletResponse.getStatus());
 | 
			
		||||
		assertEquals("" + currentTime, servletResponse.getHeader("Last-Modified"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedETagForGET() {
 | 
			
		||||
		String eTag = "\"Foo\"";
 | 
			
		||||
		servletRequest.setMethod("GET");
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", eTag );
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(eTag));
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkModifiedETagForGET() {
 | 
			
		||||
		String currentETag = "\"Foo\"";
 | 
			
		||||
		String oldEtag = "Bar";
 | 
			
		||||
		servletRequest.setMethod("GET");
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", oldEtag);
 | 
			
		||||
 | 
			
		||||
		assertFalse(request.checkNotModified(currentETag));
 | 
			
		||||
		assertEquals(200, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(currentETag, servletResponse.getHeader("ETag"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkNotModifiedETagForHEAD() {
 | 
			
		||||
		String eTag = "\"Foo\"";
 | 
			
		||||
		servletRequest.setMethod("HEAD");
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", eTag );
 | 
			
		||||
 | 
			
		||||
		assertTrue(request.checkNotModified(eTag));
 | 
			
		||||
		assertEquals(304, servletResponse.getStatus());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void checkModifiedETagForHEAD() {
 | 
			
		||||
		String currentETag = "\"Foo\"";
 | 
			
		||||
		String oldEtag = "Bar";
 | 
			
		||||
		servletRequest.setMethod("HEAD");
 | 
			
		||||
		servletRequest.addHeader("If-None-Match", oldEtag);
 | 
			
		||||
 | 
			
		||||
		assertFalse(request.checkNotModified(currentETag));
 | 
			
		||||
		assertEquals(200, servletResponse.getStatus());
 | 
			
		||||
		assertEquals(currentETag, servletResponse.getHeader("ETag"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -162,6 +162,17 @@ public class PortletWebRequest extends PortletRequestAttributes implements Nativ
 | 
			
		|||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Last-modified handling not supported for portlet requests:
 | 
			
		||||
	 * As a consequence, this method always returns {@code false}.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @since 4.2
 | 
			
		||||
	 */
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean checkNotModified(String etag, long lastModifiedTimestamp) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public String getDescription(boolean includeClientInfo) {
 | 
			
		||||
		PortletRequest request = getRequest();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue