28
28
import org .springframework .util .FileCopyUtils ;
29
29
import org .springframework .util .StringUtils ;
30
30
import org .springframework .web .HttpRequestHandler ;
31
+ import org .springframework .web .context .request .ServletWebRequest ;
31
32
import org .springframework .web .servlet .HandlerMapping ;
32
33
import org .springframework .web .servlet .support .WebContentGenerator ;
33
34
34
35
/**
35
36
* {@link HttpRequestHandler} that serves static resources optimized for superior browser performance
36
- * (according to the guidelines of Page Speed, YSlow, etc.) by adding far future cache expiration headers.
37
+ * (according to the guidelines of Page Speed, YSlow, etc.) by allowing for flexible cache settings
38
+ * ({@link #setCacheSeconds "cacheSeconds" property}, last-modified support).
37
39
*
38
- * <p>The constructor takes a list of Spring {@link Resource} locations from which static resources are allowed
39
- * to be served by this handler. For a given request, the list of locations will be consulted in order for the
40
- * presence of the requested resource, and the first found match will be written to the response, with {@code
41
- * Expires} and {@code Cache-Control} headers set for one year in the future. The handler also properly evaluates
42
- * the {@code Last-Modified} header (if present) so that a {@code 304} status code will be returned as appropriate,
43
- * avoiding unnecessary overhead for resources that are already cached by the client. The use of {@code Resource}
44
- * locations allows resource requests to easily be mapped to locations other than the web application root. For
40
+ * <p>The {@link #setLocations "locations" property takes a list of Spring {@link Resource} locations
41
+ * from which static resources are allowed to be served by this handler. For a given request, the
42
+ * list of locations will be consulted in order for the presence of the requested resource, and the
43
+ * first found match will be written to the response, with {@code Expires} and {@code Cache-Control}
44
+ * headers set as configured. The handler also properly evaluates the {@code Last-Modified} header
45
+ * (if present) so that a {@code 304} status code will be returned as appropriate, avoiding unnecessary
46
+ * overhead for resources that are already cached by the client. The use of {@code Resource} locations
47
+ * allows resource requests to easily be mapped to locations other than the web application root. For
45
48
* example, resources could be served from a classpath location such as "classpath:/META-INF/public-web-resources/",
46
49
* allowing convenient packaging and serving of resources such as a JavaScript library from within jar files.
47
50
*
48
- * <p>To ensure that users with a primed browser cache get the latest changes to application-specific resources
49
- * upon deployment of new versions of the application, it is recommended that a version string is used in the URL
50
- * mapping pattern that selects this handler. Such patterns can be easily parameterized using Spring EL. See the
51
- * reference manual for further examples of this approach.
51
+ * <p>To ensure that users with a primed browser cache get the latest changes to application-specific
52
+ * resources upon deployment of new versions of the application, it is recommended that a version string
53
+ * is used in the URL mapping pattern that selects this handler. Such patterns can be easily parameterized
54
+ * using Spring EL. See the reference manual for further examples of this approach.
52
55
*
53
- * <p>Rather than being directly configured as a bean, this handler will typically be configured through use of
54
- * the <code><mvc:resources/></code> Spring configuration tag.
56
+ * <p>Rather than being directly configured as a bean, this handler will typically be configured
57
+ * through use of the <code><mvc:resources/></code> XML configuration element.
55
58
*
56
59
* @author Keith Donald
57
60
* @author Jeremy Grelle
@@ -64,7 +67,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
64
67
65
68
66
69
public ResourceHttpRequestHandler () {
67
- super (METHOD_GET );
70
+ super (METHOD_GET , METHOD_HEAD );
68
71
}
69
72
70
73
/**
@@ -98,13 +101,15 @@ public void handleRequest(HttpServletRequest request, HttpServletResponse respon
98
101
response .sendError (HttpServletResponse .SC_NOT_FOUND );
99
102
return ;
100
103
}
101
- if (checkNotModified (resource , request , response )) {
104
+ setHeaders (resource , response );
105
+ if (new ServletWebRequest (request , response ).checkNotModified (resource .lastModified ()) ||
106
+ METHOD_HEAD .equals (request .getMethod ())) {
102
107
return ;
103
108
}
104
- writeResponse (resource , response );
109
+ writeContent (resource , response );
105
110
}
106
111
107
- private Resource getResource (HttpServletRequest request ) {
112
+ protected Resource getResource (HttpServletRequest request ) {
108
113
String path = (String ) request .getAttribute (HandlerMapping .PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE );
109
114
if (path == null ) {
110
115
throw new IllegalStateException ("Required request attribute '" +
@@ -128,22 +133,7 @@ private Resource getResource(HttpServletRequest request) {
128
133
return null ;
129
134
}
130
135
131
- private boolean checkNotModified (Resource resource ,HttpServletRequest request , HttpServletResponse response )
132
- throws IOException {
133
-
134
- long ifModifiedSince = request .getDateHeader ("If-Modified-Since" );
135
- long lastModified = resource .lastModified ();
136
- boolean notModified = ifModifiedSince >= (lastModified / 1000 * 1000 );
137
- if (notModified ) {
138
- response .setStatus (HttpServletResponse .SC_NOT_MODIFIED );
139
- }
140
- else {
141
- response .setDateHeader ("Last-Modified" , lastModified );
142
- }
143
- return notModified ;
144
- }
145
-
146
- private void writeResponse (Resource resource , HttpServletResponse response ) throws IOException {
136
+ protected void setHeaders (Resource resource , HttpServletResponse response ) throws IOException {
147
137
MediaType mediaType = getMediaType (resource );
148
138
if (mediaType != null ) {
149
139
response .setContentType (mediaType .toString ());
@@ -153,12 +143,15 @@ private void writeResponse(Resource resource, HttpServletResponse response) thro
153
143
throw new IOException ("Resource content too long (beyond Integer.MAX_VALUE): " + resource );
154
144
}
155
145
response .setContentLength ((int ) length );
156
- FileCopyUtils .copy (resource .getInputStream (), response .getOutputStream ());
157
146
}
158
147
159
148
protected MediaType getMediaType (Resource resource ) {
160
149
String mimeType = getServletContext ().getMimeType (resource .getFilename ());
161
150
return (StringUtils .hasText (mimeType ) ? MediaType .parseMediaType (mimeType ) : null );
162
151
}
163
152
153
+ protected void writeContent (Resource resource , HttpServletResponse response ) throws IOException {
154
+ FileCopyUtils .copy (resource .getInputStream (), response .getOutputStream ());
155
+ }
156
+
164
157
}
0 commit comments