blob: 58f12a04afc3211d9673b469af4322404c252c14 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.wicket.request.http;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import jakarta.servlet.http.Cookie;
import org.apache.wicket.request.Response;
import org.apache.wicket.util.encoding.UrlEncoder;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.string.Strings;
/**
* Base class for web-related responses.
*
* @author Matej Knopp
*/
public abstract class WebResponse extends Response
{
/** Recommended value for cache duration */
// one year, maximum recommended cache duration in RFC-2616
public static final Duration MAX_CACHE_DURATION = Duration.ofDays(365);
/**
* Add a cookie to the web response
*
* @param cookie
*/
public abstract void addCookie(final Cookie cookie);
/**
* Convenience method for clearing a cookie.
*
* @param cookie
* The cookie to set
* @see WebResponse#addCookie(Cookie)
*/
public abstract void clearCookie(final Cookie cookie);
/**
* Indicates if the response supports setting headers. When this method returns
* false, {@link #setHeader(String, String)} and its variations will thrown an
* {@code UnsupportedOperationException}.
*
* @return True when this {@code WebResponse} supports setting headers.
*/
public abstract boolean isHeaderSupported();
/**
* Set a header to the string value in the servlet response stream.
*
* @param name
* @param value
*/
public abstract void setHeader(String name, String value);
/**
* Add a value to the servlet response stream.
*
* @param name
* @param value
*/
public abstract void addHeader(String name, String value);
/**
* Set a header to the date value in the servlet response stream.
*
* @param name
* @param date
*/
public abstract void setDateHeader(String name, Instant date);
/**
* Set the content length on the response, if appropriate in the subclass. This default
* implementation does nothing.
*
* @param length
* The length of the content
*/
public abstract void setContentLength(final long length);
/**
* Set the content type on the response, if appropriate in the subclass. This default
* implementation does nothing.
*
* @param mimeType
* The mime type
*/
public abstract void setContentType(final String mimeType);
/**
* Sets the content range of the response. If no content range is set the client assumes the
* whole content. Please note that if the content range is set, the content length, the status
* code and the accept range must be set right, too.
*
* @param contentRange
* the content range
*/
public void setContentRange(final String contentRange)
{
setHeader("Content-Range", contentRange);
}
/**
* Sets the accept range (e.g. bytes)
*
* @param acceptRange
* the accept range header information
*/
public void setAcceptRange(final String acceptRange)
{
setHeader("Accept-Range", acceptRange);
}
/**
* Set the contents last modified time, if appropriate in the subclass.
*
* @param time
* The last modified time
*/
public void setLastModifiedTime(final Instant time)
{
setDateHeader("Last-Modified", time);
}
/**
* Convenience method for setting the content-disposition:attachment header. This header is used
* if the response should prompt the user to download it as a file instead of opening in a
* browser.
* <p>
* The file name will be <a href="http://greenbytes.de/tech/tc2231/">encoded</a>
*
* @param filename
* file name of the attachment
*/
public void setAttachmentHeader(final String filename)
{
setHeader("Content-Disposition", "attachment" + encodeDispositionHeaderValue(filename));
}
/**
* Convenience method for setting the content-disposition:inline header. This header is used if
* the response should be shown embedded in browser window while having custom file name when
* user saves the response. browser.
* <p>
* The file name will be <a href="http://greenbytes.de/tech/tc2231/">encoded</a>
*
* @param filename
* file name of the attachment
*/
public void setInlineHeader(final String filename)
{
setHeader("Content-Disposition", "inline" + encodeDispositionHeaderValue(filename));
}
/**
* <a href="http://greenbytes.de/tech/tc2231/">Encodes</a> the value of the filename used in
* "Content-Disposition" response header
*
* @param filename
* the non-encoded file name
* @return encoded filename
*/
private String encodeDispositionHeaderValue(final String filename)
{
return (Strings.isEmpty(filename) ? "" : String.format(
"; filename=\"%1$s\"; filename*=UTF-8''%1$s",
UrlEncoder.HEADER_INSTANCE.encode(filename, StandardCharsets.UTF_8)));
}
/**
* Sets the status code for this response.
*
* @param sc
* status code
*/
public abstract void setStatus(int sc);
/**
* Send error status code with optional message.
*
* @param sc
* @param msg
* @throws IOException
*/
public abstract void sendError(int sc, String msg);
/**
* Encodes urls used to redirect. Sometimes rules for encoding URLs for redirecting differ from
* encoding URLs for links, so this method is broken out away form
* {@link #encodeURL(CharSequence)}.
*
* @param url
* @return encoded URL
*/
public abstract String encodeRedirectURL(CharSequence url);
/**
* Redirects the response to specified URL. The implementation is responsible for properly
* encoding the URL. Implementations of this method should run passed in {@code url} parameters
* through the {@link #encodeRedirectURL(CharSequence)} method.
*
* @param url
*/
public abstract void sendRedirect(String url);
/**
* @return <code>true</code> is {@link #sendRedirect(String)} was called, <code>false</code>
* otherwise.
*/
public abstract boolean isRedirect();
/**
* Flushes the response.
*/
public abstract void flush();
/**
* Make this response non-cacheable
*/
public void disableCaching()
{
setDateHeader("Date", Instant.now());
setDateHeader("Expires", Instant.EPOCH);
setHeader("Pragma", "no-cache");
setHeader("Cache-Control", "no-cache, no-store");
}
/**
* Make this response cacheable
* <p/>
* when trying to enable caching for web pages check this out: <a
* href="https://issues.apache.org/jira/browse/WICKET-4357">WICKET-4357</a>
*
* @param duration
* maximum duration before the response must be invalidated by any caches. It should
* not exceed one year, based on <a
* href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">RFC-2616</a>.
* @param scope
* controls which caches are allowed to cache the response
*
* @see WebResponse#MAX_CACHE_DURATION
*/
public void enableCaching(Duration duration, final WebResponse.CacheScope scope)
{
Args.notNull(duration, "duration");
Args.notNull(scope, "scope");
// do not exceed the maximum recommended value from RFC-2616
if (duration.compareTo(MAX_CACHE_DURATION) > 0)
{
duration = MAX_CACHE_DURATION;
}
// Get current time
Instant now = Instant.now();
// Time of message generation
setDateHeader("Date", now);
// Time for cache expiry = now + duration
setDateHeader("Expires", now.plus(duration));
// Set cache scope
setHeader("Cache-Control", scope.cacheControl);
// Set maximum age for caching in seconds (rounded)
addHeader("Cache-Control", "max-age=" + Math.round(duration.getSeconds()));
// Though 'cache' is not an official value it will eliminate an eventual 'no-cache' header
setHeader("Pragma", "cache");
}
/**
* caching scope for data
* <p/>
* Unless the data is confidential, session-specific or user-specific the general advice is to
* prefer value <code>PUBLIC</code> for best network performance.
* <p/>
* This value will basically affect the header [Cache-Control]. Details can be found <a
* href="http://palisade.plynt.com/issues/2008Jul/cache-control-attributes">here</a> or in <a
* href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">RFC-2616</a>.
*/
public static enum CacheScope
{
/**
* use all caches (private + public)
* <p/>
* Use this value for caching if the data is not confidential or session-specific. It will
* allow public caches to cache the data. In some versions of Firefox this will enable
* caching of resources over SSL (details can be found <a
* href="http://blog.pluron.com/2008/07/why-you-should.html">here</a>).
*/
PUBLIC("public"),
/**
* only use non-public caches
* <p/>
* Use this setting if the response is session-specific or confidential and you don't want
* it to be cached on public caches or proxies. On some versions of Firefox this will
* disable caching of any resources in over SSL connections.
*/
PRIVATE("private");
// value for Cache-Control header
private final String cacheControl;
CacheScope(final String cacheControl)
{
this.cacheControl = cacheControl;
}
}
}