001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.request.http; 018 019import java.io.IOException; 020import java.nio.charset.StandardCharsets; 021import java.time.Duration; 022import java.time.Instant; 023import javax.servlet.http.Cookie; 024import org.apache.wicket.request.Response; 025import org.apache.wicket.util.encoding.UrlEncoder; 026import org.apache.wicket.util.lang.Args; 027import org.apache.wicket.util.string.Strings; 028 029/** 030 * Base class for web-related responses. 031 * 032 * @author Matej Knopp 033 */ 034public abstract class WebResponse extends Response 035{ 036 /** Recommended value for cache duration */ 037 // one year, maximum recommended cache duration in RFC-2616 038 public static final Duration MAX_CACHE_DURATION = Duration.ofDays(365); 039 040 /** 041 * Add a cookie to the web response 042 * 043 * @param cookie 044 */ 045 public abstract void addCookie(final Cookie cookie); 046 047 /** 048 * Convenience method for clearing a cookie. 049 * 050 * @param cookie 051 * The cookie to set 052 * @see WebResponse#addCookie(Cookie) 053 */ 054 public abstract void clearCookie(final Cookie cookie); 055 056 /** 057 * Indicates if the response supports setting headers. When this method returns 058 * false, {@link #setHeader(String, String)} and its variations will thrown an 059 * {@code UnsupportedOperationException}. 060 * 061 * @return True when this {@code WebResponse} supports setting headers. 062 */ 063 public abstract boolean isHeaderSupported(); 064 065 /** 066 * Set a header to the string value in the servlet response stream. 067 * 068 * @param name 069 * @param value 070 */ 071 public abstract void setHeader(String name, String value); 072 073 /** 074 * Add a value to the servlet response stream. 075 * 076 * @param name 077 * @param value 078 */ 079 public abstract void addHeader(String name, String value); 080 081 /** 082 * Set a header to the date value in the servlet response stream. 083 * 084 * @param name 085 * @param date 086 */ 087 public abstract void setDateHeader(String name, Instant date); 088 089 /** 090 * Set the content length on the response, if appropriate in the subclass. This default 091 * implementation does nothing. 092 * 093 * @param length 094 * The length of the content 095 */ 096 public abstract void setContentLength(final long length); 097 098 /** 099 * Set the content type on the response, if appropriate in the subclass. This default 100 * implementation does nothing. 101 * 102 * @param mimeType 103 * The mime type 104 */ 105 public abstract void setContentType(final String mimeType); 106 107 /** 108 * Sets the content range of the response. If no content range is set the client assumes the 109 * whole content. Please note that if the content range is set, the content length, the status 110 * code and the accept range must be set right, too. 111 * 112 * @param contentRange 113 * the content range 114 */ 115 public void setContentRange(final String contentRange) 116 { 117 setHeader("Content-Range", contentRange); 118 } 119 120 121 /** 122 * Sets the accept range (e.g. bytes) 123 * 124 * @param acceptRange 125 * the accept range header information 126 */ 127 public void setAcceptRange(final String acceptRange) 128 { 129 setHeader("Accept-Range", acceptRange); 130 131 } 132 133 /** 134 * Set the contents last modified time, if appropriate in the subclass. 135 * 136 * @param time 137 * The last modified time 138 */ 139 public void setLastModifiedTime(final Instant time) 140 { 141 setDateHeader("Last-Modified", time); 142 } 143 144 /** 145 * Convenience method for setting the content-disposition:attachment header. This header is used 146 * if the response should prompt the user to download it as a file instead of opening in a 147 * browser. 148 * <p> 149 * The file name will be <a href="http://greenbytes.de/tech/tc2231/">encoded</a> 150 * 151 * @param filename 152 * file name of the attachment 153 */ 154 public void setAttachmentHeader(final String filename) 155 { 156 setHeader("Content-Disposition", "attachment" + encodeDispositionHeaderValue(filename)); 157 } 158 159 /** 160 * Convenience method for setting the content-disposition:inline header. This header is used if 161 * the response should be shown embedded in browser window while having custom file name when 162 * user saves the response. browser. 163 * <p> 164 * The file name will be <a href="http://greenbytes.de/tech/tc2231/">encoded</a> 165 * 166 * @param filename 167 * file name of the attachment 168 */ 169 public void setInlineHeader(final String filename) 170 { 171 setHeader("Content-Disposition", "inline" + encodeDispositionHeaderValue(filename)); 172 } 173 174 /** 175 * <a href="http://greenbytes.de/tech/tc2231/">Encodes</a> the value of the filename used in 176 * "Content-Disposition" response header 177 * 178 * @param filename 179 * the non-encoded file name 180 * @return encoded filename 181 */ 182 private String encodeDispositionHeaderValue(final String filename) 183 { 184 return (Strings.isEmpty(filename) ? "" : String.format( 185 "; filename=\"%1$s\"; filename*=UTF-8''%1$s", 186 UrlEncoder.HEADER_INSTANCE.encode(filename, StandardCharsets.UTF_8))); 187 } 188 189 /** 190 * Sets the status code for this response. 191 * 192 * @param sc 193 * status code 194 */ 195 public abstract void setStatus(int sc); 196 197 /** 198 * Send error status code with optional message. 199 * 200 * @param sc 201 * @param msg 202 */ 203 public abstract void sendError(int sc, String msg); 204 205 /** 206 * Encodes urls used to redirect. Sometimes rules for encoding URLs for redirecting differ from 207 * encoding URLs for links, so this method is broken out away form 208 * {@link #encodeURL(CharSequence)}. 209 * 210 * @param url 211 * @return encoded URL 212 */ 213 public abstract String encodeRedirectURL(CharSequence url); 214 215 /** 216 * Redirects the response to specified URL. The implementation is responsible for properly 217 * encoding the URL. Implementations of this method should run passed in {@code url} parameters 218 * through the {@link #encodeRedirectURL(CharSequence)} method. 219 * 220 * @param url 221 */ 222 public abstract void sendRedirect(String url); 223 224 /** 225 * @return <code>true</code> is {@link #sendRedirect(String)} was called, <code>false</code> 226 * otherwise. 227 */ 228 public abstract boolean isRedirect(); 229 230 /** 231 * Flushes the response. 232 */ 233 public abstract void flush(); 234 235 /** 236 * Make this response non-cacheable 237 */ 238 public void disableCaching() 239 { 240 setDateHeader("Date", Instant.now()); 241 setDateHeader("Expires", Instant.EPOCH); 242 setHeader("Pragma", "no-cache"); 243 setHeader("Cache-Control", "no-cache, no-store"); 244 } 245 246 /** 247 * Make this response cacheable 248 * <p/> 249 * when trying to enable caching for web pages check this out: <a 250 * href="https://issues.apache.org/jira/browse/WICKET-4357">WICKET-4357</a> 251 * 252 * @param duration 253 * maximum duration before the response must be invalidated by any caches. It should 254 * not exceed one year, based on <a 255 * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">RFC-2616</a>. 256 * @param scope 257 * controls which caches are allowed to cache the response 258 * 259 * @see WebResponse#MAX_CACHE_DURATION 260 */ 261 public void enableCaching(Duration duration, final WebResponse.CacheScope scope) 262 { 263 Args.notNull(duration, "duration"); 264 Args.notNull(scope, "scope"); 265 266 // do not exceed the maximum recommended value from RFC-2616 267 if (duration.compareTo(MAX_CACHE_DURATION) > 0) 268 { 269 duration = MAX_CACHE_DURATION; 270 } 271 272 // Get current time 273 Instant now = Instant.now(); 274 275 // Time of message generation 276 setDateHeader("Date", now); 277 278 // Time for cache expiry = now + duration 279 setDateHeader("Expires", now.plus(duration)); 280 281 // Set cache scope 282 setHeader("Cache-Control", scope.cacheControl); 283 284 // Set maximum age for caching in seconds (rounded) 285 addHeader("Cache-Control", "max-age=" + Math.round(duration.getSeconds())); 286 287 // Though 'cache' is not an official value it will eliminate an eventual 'no-cache' header 288 setHeader("Pragma", "cache"); 289 } 290 291 /** 292 * caching scope for data 293 * <p/> 294 * Unless the data is confidential, session-specific or user-specific the general advice is to 295 * prefer value <code>PUBLIC</code> for best network performance. 296 * <p/> 297 * This value will basically affect the header [Cache-Control]. Details can be found <a 298 * href="http://palisade.plynt.com/issues/2008Jul/cache-control-attributes">here</a> or in <a 299 * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">RFC-2616</a>. 300 */ 301 public static enum CacheScope 302 { 303 /** 304 * use all caches (private + public) 305 * <p/> 306 * Use this value for caching if the data is not confidential or session-specific. It will 307 * allow public caches to cache the data. In some versions of Firefox this will enable 308 * caching of resources over SSL (details can be found <a 309 * href="http://blog.pluron.com/2008/07/why-you-should.html">here</a>). 310 */ 311 PUBLIC("public"), 312 /** 313 * only use non-public caches 314 * <p/> 315 * Use this setting if the response is session-specific or confidential and you don't want 316 * it to be cached on public caches or proxies. On some versions of Firefox this will 317 * disable caching of any resources in over SSL connections. 318 */ 319 PRIVATE("private"); 320 321 // value for Cache-Control header 322 private final String cacheControl; 323 324 CacheScope(final String cacheControl) 325 { 326 this.cacheControl = cacheControl; 327 } 328 } 329}