Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.glassfish.grizzly.http.server.Response Maven / Gradle / Ivy
/*
* Copyright (c) 2008, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright 2004 The Apache Software Foundation
*
* 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.glassfish.grizzly.http.server;
import static org.glassfish.grizzly.http.util.Constants.DEFAULT_HTTP_CHARACTER_ENCODING;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.grizzly.CloseListener;
import org.glassfish.grizzly.CloseType;
import org.glassfish.grizzly.Closeable;
import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.GenericCloseListener;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.http.Cookie;
import org.glassfish.grizzly.http.Cookies;
import org.glassfish.grizzly.http.HttpContext;
import org.glassfish.grizzly.http.HttpResponsePacket;
import org.glassfish.grizzly.http.Protocol;
import org.glassfish.grizzly.http.io.InputBuffer;
import org.glassfish.grizzly.http.io.NIOOutputStream;
import org.glassfish.grizzly.http.io.NIOWriter;
import org.glassfish.grizzly.http.io.OutputBuffer;
import org.glassfish.grizzly.http.server.io.ServerOutputBuffer;
import org.glassfish.grizzly.http.server.util.Globals;
import org.glassfish.grizzly.http.server.util.HtmlHelper;
import org.glassfish.grizzly.http.util.CharChunk;
import org.glassfish.grizzly.http.util.ContentType;
import org.glassfish.grizzly.http.util.CookieSerializerUtils;
import org.glassfish.grizzly.http.util.FastHttpDateFormat;
import org.glassfish.grizzly.http.util.Header;
import org.glassfish.grizzly.http.util.HeaderValue;
import org.glassfish.grizzly.http.util.HttpRequestURIDecoder;
import org.glassfish.grizzly.http.util.HttpStatus;
import org.glassfish.grizzly.http.util.MimeHeaders;
import org.glassfish.grizzly.http.util.UEncoder;
import org.glassfish.grizzly.localization.LogMessages;
import org.glassfish.grizzly.utils.DelayedExecutor;
import org.glassfish.grizzly.utils.DelayedExecutor.DelayQueue;
/**
* Wrapper object for the Coyote response.
*
* @author Remy Maucherat
* @author Craig R. McClanahan
* @version $Revision: 1.2 $ $Date: 2006/11/02 20:01:44 $
*/
public class Response {
enum SuspendState {
NONE, SUSPENDED, RESUMING, RESUMED, CANCELLING, CANCELLED
}
private static final Logger LOGGER = Grizzly.logger(Response.class);
static DelayQueue createDelayQueue(final DelayedExecutor delayedExecutor) {
return delayedExecutor.createDelayQueue(new DelayQueueWorker(), new DelayQueueResolver());
}
// ----------------------------------------------------------- Constructors
protected Response() {
urlEncoder.addSafeCharacter('/');
}
// ----------------------------------------------------- Instance Variables
private boolean cacheEnabled = false;
/**
* Default locale as mandated by the spec.
*/
private static final Locale DEFAULT_LOCALE = Locale.getDefault();
private static final String HTTP_RESPONSE_DATE_HEADER = "EEE, dd MMM yyyy HH:mm:ss zzz";
/**
* The date format we will use for creating date headers.
*/
protected SimpleDateFormat format = null;
/**
* Descriptive information about this Response implementation.
*/
protected static final String info = "org.glassfish.grizzly.http.server.Response/2.0";
// ------------------------------------------------------------- Properties
/**
* The request with which this response is associated.
*/
protected Request request = null;
/**
* Coyote response.
*/
protected HttpResponsePacket response;
/**
* Grizzly {@link org.glassfish.grizzly.filterchain.FilterChain} context, related to this HTTP request/response
*/
protected FilterChainContext ctx;
/**
* Grizzly {@link HttpContext} associated with the current Request/Response processing.
*/
protected HttpContext httpContext;
/**
* The associated output buffer.
*/
protected final ServerOutputBuffer outputBuffer = new ServerOutputBuffer();
/**
* The associated output stream.
*/
private final NIOOutputStreamImpl outputStream = new NIOOutputStreamImpl();
/**
* The associated writer.
*/
private final NIOWriterImpl writer = new NIOWriterImpl();
/**
* The application commit flag.
*/
protected boolean appCommitted = false;
/**
* The error flag.
*/
protected boolean error = false;
/**
* Using output stream flag.
*/
protected boolean usingOutputStream = false;
/**
* Using writer flag.
*/
protected boolean usingWriter = false;
/**
* URL encoder.
*/
protected final UEncoder urlEncoder = new UEncoder();
/**
* Recyclable buffer to hold the redirect URL.
*/
protected final CharChunk redirectURLCC = new CharChunk();
protected DelayedExecutor.DelayQueue delayQueue;
SuspendState suspendState = SuspendState.NONE;
private final SuspendedContextImpl suspendedContext = new SuspendedContextImpl();
private SuspendStatus suspendStatus;
private boolean sendFileEnabled;
private ErrorPageGenerator errorPageGenerator;
// --------------------------------------------------------- Public Methods
public void initialize(final Request request, final HttpResponsePacket response, final FilterChainContext ctx,
final DelayedExecutor.DelayQueue delayQueue, final HttpServerFilter serverFilter) {
this.request = request;
this.response = response;
sendFileEnabled = serverFilter != null && serverFilter.getConfiguration().isSendFileEnabled();
outputBuffer.initialize(this, ctx);
this.ctx = ctx;
this.httpContext = HttpContext.get(ctx);
this.delayQueue = delayQueue;
}
SuspendStatus initSuspendStatus() {
suspendStatus = SuspendStatus.create();
return suspendStatus;
}
/**
* Return the Request with which this Response is associated.
*/
public Request getRequest() {
return request;
}
/**
* Get the {@link HttpResponsePacket}.
*/
public HttpResponsePacket getResponse() {
return response;
}
/**
* Release all object references, and initialize instance variables, in preparation for reuse of this object.
*/
protected void recycle() {
delayQueue = null;
outputBuffer.recycle();
outputStream.recycle();
writer.recycle();
usingOutputStream = false;
usingWriter = false;
appCommitted = false;
error = false;
errorPageGenerator = null;
request = null;
response.recycle();
sendFileEnabled = false;
response = null;
ctx = null;
suspendState = SuspendState.NONE;
cacheEnabled = false;
}
// ------------------------------------------------------- Response Methods
/**
* Set the supplier of trailer headers. The supplier will be called within the scope of whatever thread/call causes the
* response content to be completed. Typically this will be any thread calling close() on the output stream or writer.
*
* The trailers that run afoul of the provisions of section 4.1.2 of RFC 7230 are ignored.
*
* @param trailerSupplier the supplier of trailer headers
*
* @throws IllegalStateException if it is invoked after the response has has been committed, or trailers cannot be
* supported given the current protocol and/or configuration (chunked transfer encoding disabled in HTTP/1.1 as an
* example).
*
* @since 2.4.0
*/
public void setTrailers(Supplier> trailerSupplier) {
if (isCommitted()) {
throw new IllegalStateException("Response has already been committed.");
}
final Protocol protocol = request.getProtocol();
if (protocol.equals(Protocol.HTTP_0_9) || protocol.equals(Protocol.HTTP_1_0)) {
throw new IllegalStateException("Trailers not supported by response protocol version " + protocol);
}
if (protocol.equals(Protocol.HTTP_1_1)) {
if (!response.isChunkingAllowed()) {
throw new IllegalStateException("Chunked transfer-encoding disabled.");
}
response.setChunked(true);
}
outputBuffer.setTrailers(trailerSupplier);
}
/**
* @return the trailers supplier, if any.
*
* @since 2.4.0
*/
public Supplier> getTrailers() {
return outputBuffer.getTrailers();
}
/**
* Encode the session identifier associated with this response into the specified URL, if necessary.
*
* @param url URL to be encoded
*/
public String encodeURL(String url) {
String absolute = toAbsolute(url, false);
if (isEncodeable(absolute)) {
// W3c spec clearly said
if (url.equalsIgnoreCase("")) {
url = absolute;
}
return toEncoded(url, request.getSession().getIdInternal());
} else {
return url;
}
}
/**
* Encode the session identifier associated with this response into the specified redirect URL, if necessary.
*
* @param url URL to be encoded
*/
public String encodeRedirectURL(String url) {
if (isEncodeable(toAbsolute(url, false))) {
return toEncoded(url, request.getSession().getIdInternal());
} else {
return url;
}
}
/**
* Return true if the specified URL should be encoded with a session identifier. This will be true if all of
* the following conditions are met:
*
* The request we are responding to asked for a valid session
* The requested session ID was not received via a cookie
* The specified URL points back to somewhere within the web application that is responding to this request
*
*
* @param location Absolute URL to be validated
*/
protected boolean isEncodeable(final String location) {
if (location == null) {
return false;
}
// Is this an intra-document reference?
if (location.startsWith("#")) {
return false;
}
final Session session = request.getSession(false);
return session != null && !request.isRequestedSessionIdFromCookie() && doIsEncodeable(request, session, location);
}
private static boolean doIsEncodeable(Request request, Session session, String location) {
// Is this a valid absolute URL?
URL url;
try {
url = new URL(location);
} catch (MalformedURLException e) {
return false;
}
// Does this URL match down to (and including) the context path?
if (!request.getScheme().equalsIgnoreCase(url.getProtocol())) {
return false;
}
if (!request.getServerName().equalsIgnoreCase(url.getHost())) {
return false;
}
int serverPort = request.getServerPort();
if (serverPort == -1) {
if ("https".equals(request.getScheme())) {
serverPort = 443;
} else {
serverPort = 80;
}
}
int urlPort = url.getPort();
if (urlPort == -1) {
if ("https".equals(url.getProtocol())) {
urlPort = 443;
} else {
urlPort = 80;
}
}
if (serverPort != urlPort) {
return false;
}
String contextPath = "/";
String file = url.getFile();
if (file == null || !file.startsWith(contextPath)) {
return false;
}
if (file.contains(";jsessionid=" + session.getIdInternal())) {
return false;
}
// This URL belongs to our web application, so it is encodeable
return true;
}
/**
* Return descriptive information about this Response implementation and the corresponding version number, in the format
* <description>/<version>
.
*/
public String getInfo() {
return info;
}
/**
* Set the error flag.
*/
public void setError() {
error = true;
}
/**
* Error flag accessor.
*/
public boolean isError() {
return error;
}
/**
* @return the {@link ErrorPageGenerator} to be used by {@link #sendError(int)} or
* {@link #sendError(int, java.lang.String)}.
*/
public ErrorPageGenerator getErrorPageGenerator() {
return errorPageGenerator;
}
/**
* Sets the {@link ErrorPageGenerator} to be used by {@link #sendError(int)} or
* {@link #sendError(int, java.lang.String)}.
*
* @param errorPageGenerator the custom {@link ErrorPageGenerator}.
*/
public void setErrorPageGenerator(ErrorPageGenerator errorPageGenerator) {
this.errorPageGenerator = errorPageGenerator;
}
// BEGIN S1AS 4878272
/**
* Sets detail error message.
*
* @param message detail error message
*/
public void setDetailMessage(String message) {
checkResponse();
response.setReasonPhrase(message);
}
/**
* Gets detail error message.
*
* @return the detail error message
*/
@SuppressWarnings("unused")
public String getDetailMessage() {
checkResponse();
return response.getReasonPhrase();
}
// END S1AS 4878272
/**
* Perform whatever actions are required to flush and close the output stream or writer, in a single operation.
*/
public void finish() {
// Writing leftover bytes
try {
outputBuffer.endRequest();
} catch (IOException e) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, LogMessages.WARNING_GRIZZLY_HTTP_SERVER_RESPONSE_FINISH_ERROR(), e);
}
} catch (Throwable t) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, LogMessages.WARNING_GRIZZLY_HTTP_SERVER_RESPONSE_FINISH_ERROR(), t);
}
}
}
/**
* Return the content length that was set or calculated for this Response.
*/
public int getContentLength() {
checkResponse();
return (int) response.getContentLength();
}
/**
* Return the content length that was set or calculated for this Response.
*/
public long getContentLengthLong() {
checkResponse();
return response.getContentLength();
}
/**
* Return the content type that was set or calculated for this response, or null
if no content type was
* set.
*/
public String getContentType() {
checkResponse();
return response.getContentType();
}
// ------------------------------------------------ ServletResponse Methods
/**
* Return the actual buffer size used for this Response.
*/
public int getBufferSize() {
return outputBuffer.getBufferSize();
}
/**
* Return the character encoding used for this Response.
*/
public String getCharacterEncoding() {
checkResponse();
final String characterEncoding = response.getCharacterEncoding();
if (characterEncoding == null) {
return DEFAULT_HTTP_CHARACTER_ENCODING;
}
return characterEncoding;
}
/*
* Overrides the name of the character encoding used in the body of the request. This method must be called prior to
* reading request parameters or reading input using getReader().
*
* @param charset String containing the name of the chararacter encoding.
*/
public void setCharacterEncoding(String charset) {
checkResponse();
if (isCommitted()) {
return;
}
// Ignore any call made after the getWriter has been invoked
// The default should be used
if (usingWriter) {
return;
}
response.setCharacterEncoding(charset);
}
/**
* Create and return a ServletOutputStream to write the content associated with this Response.
*/
public NIOOutputStream createOutputStream() {
outputStream.setOutputBuffer(outputBuffer);
return outputStream;
}
/**
*
* Return the {@link NIOOutputStream} associated with this {@link Response}. This {@link NIOOutputStream} will write
* content in a non-blocking manner.
*
*
* @throws IllegalStateException if {@link #getWriter()} or {@link #getNIOWriter()} were already invoked.
*/
public NIOOutputStream getNIOOutputStream() {
if (usingWriter) {
throw new IllegalStateException("Illegal attempt to call getOutputStream() after getWriter() has already been called.");
}
usingOutputStream = true;
outputStream.setOutputBuffer(outputBuffer);
return outputStream;
}
/**
*
* Return the {@link OutputStream} associated with this {@link Response}.
*
*
* By default the returned {@link NIOOutputStream} will work as blocking {@link java.io.OutputStream}, but it will be
* possible to call {@link NIOOutputStream#canWrite()} or
* {@link NIOOutputStream#notifyCanWrite(org.glassfish.grizzly.WriteHandler)} to avoid blocking.
*
* @return the {@link NIOOutputStream} associated with this {@link Response}.
*
* @throws IllegalStateException if {@link #getWriter()} or {@link #getNIOWriter()} were already invoked.
*
* @since 2.1.2
*/
public OutputStream getOutputStream() {
return getNIOOutputStream();
}
/**
* Return the Locale assigned to this response.
*/
public Locale getLocale() {
checkResponse();
Locale locale = response.getLocale();
if (locale == null) {
locale = DEFAULT_LOCALE;
response.setLocale(locale);
}
return locale;
}
/**
*
* Return the {@link NIOWriter} associated with this {@link Response}.
*
*
* By default the returned {@link NIOWriter} will work as blocking {@link java.io.Writer}, but it will be possible to
* call {@link NIOWriter#canWrite()} or {@link NIOWriter#notifyCanWrite(org.glassfish.grizzly.WriteHandler)} to avoid
* blocking.
*
* @throws IllegalStateException if {@link #getOutputStream()} or {@link #getNIOOutputStream()} were already invoked.
*/
public Writer getWriter() {
return getNIOWriter();
}
/**
*
* Return the {@link NIOWriter} associated with this {@link Response}. The {@link NIOWriter} will write content in a
* non-blocking manner.
*
*
* @return the {@link NIOWriter} associated with this {@link Response}.
*
* @throws IllegalStateException if {@link #getOutputStream()} or {@link #getNIOOutputStream()} were already invoked.
*
* @since 2.1.2
*/
public NIOWriter getNIOWriter() {
if (usingOutputStream) {
throw new IllegalStateException("Illegal attempt to call getWriter() after getOutputStream() has already been called.");
}
/*
* If the response's character encoding has not been specified as described in getCharacterEncoding
(i.e.,
* the method just returns the default value ISO-8859-1
), getWriter
updates it to
* ISO-8859-1
(with the effect that a subsequent call to getContentType() will include a charset=ISO-8859-1
* component which will also be reflected in the Content-Type response header, thereby satisfying the Servlet spec
* requirement that containers must communicate the character encoding used for the servlet response's writer to the
* client).
*/
setCharacterEncoding(getCharacterEncoding());
usingWriter = true;
outputBuffer.prepareCharacterEncoder();
writer.setOutputBuffer(outputBuffer);
return writer;
}
/**
* Has the output of this response already been committed?
*/
public boolean isCommitted() {
checkResponse();
return response.isCommitted();
}
/**
* Flush the current buffered content to the network.
*
* @throws IOException if an occur occurs flushing to the wire.
*/
public void flush() throws IOException {
outputBuffer.flush();
}
/**
* @return the {@link OutputBuffer} associated with this Response
.
*/
public OutputBuffer getOutputBuffer() {
return outputBuffer;
}
/**
* Clears any data that exists in the buffer as well as the status code and headers.
*
* @exception IllegalStateException if this response has already been committed
*/
public void reset() {
checkResponse();
if (isCommitted()) {
throw new IllegalStateException();
}
response.getHeaders().clear();
response.setContentLanguage(null);
if (response.getContentLength() > 0) {
response.setContentLengthLong(-1L);
}
response.setCharacterEncoding(null);
response.setStatus(null);
response.setContentType((String) null);
response.setLocale(null);
outputBuffer.reset();
usingWriter = false;
usingOutputStream = false;
}
/**
* Reset the data buffer but not any status or header information.
*
* @exception IllegalStateException if the response has already been committed
*/
public void resetBuffer() {
resetBuffer(false);
}
/**
* Reset the data buffer and the using Writer/Stream flags but not any status or header information.
*
* @param resetWriterStreamFlags true
if the internal usingWriter
,
* usingOutputStream
, isCharacterEncodingSet
flags should also be reset
*
* @exception IllegalStateException if the response has already been committed
*/
public void resetBuffer(final boolean resetWriterStreamFlags) {
if (isCommitted()) {
throw new IllegalStateException("Cannot reset buffer after response has been committed.");
}
outputBuffer.reset();
if (resetWriterStreamFlags) {
usingOutputStream = false;
usingWriter = false;
}
}
/**
* Set the buffer size to be used for this Response.
*
* @param size The new buffer size
*
* @exception IllegalStateException if this method is called after output has been committed for this response
*/
public void setBufferSize(final int size) {
if (isCommitted()) {
throw new IllegalStateException("Unable to change buffer size as the response has been committed");
}
outputBuffer.setBufferSize(size);
}
/**
* Set the content length (in bytes) for this Response.
*
* If the length
argument is negative - then {@link org.glassfish.grizzly.http.HttpPacket} content-length
* value will be reset to -1 and Content-Length header (if present) will be removed.
*
* @param length The new content length
*/
public void setContentLengthLong(final long length) {
checkResponse();
if (isCommitted()) {
return;
}
if (usingWriter) {
return;
}
response.setContentLengthLong(length);
}
/**
* Set the content length (in bytes) for this Response.
*
* If the length
argument is negative - then {@link org.glassfish.grizzly.http.HttpPacket} content-length
* value will be reset to -1 and Content-Length header (if present) will be removed.
*
* @param length The new content length
*/
public void setContentLength(final int length) {
setContentLengthLong(length);
}
/**
* Set the content type for this Response.
*
* @param type The new content type
*/
public void setContentType(String type) {
checkResponse();
if (isCommitted()) {
return;
}
// Ignore charset if getWriter() has already been called
if (usingWriter) {
if (type != null) {
int index = type.indexOf(";");
if (index != -1) {
type = type.substring(0, index);
}
}
}
response.setContentType(type);
}
/**
* Set the content type for this Response.
*
* @param type The new content type
*/
public void setContentType(final ContentType type) {
checkResponse();
if (isCommitted()) {
return;
}
if (type == null) {
response.setContentType((String) null);
return;
}
if (!usingWriter) {
response.setContentType(type);
} else {
// Ignore charset if getWriter() has already been called
response.setContentType(type.getMimeType());
}
}
/**
* Set the Locale that is appropriate for this response, including setting the appropriate character encoding.
*
* @param locale The new locale
*/
public void setLocale(final Locale locale) {
checkResponse();
if (isCommitted()) {
return;
}
response.setLocale(locale);
}
// --------------------------------------------------- HttpResponsePacket Methods
/**
* Return an array of all cookies set for this response, or a zero-length array if no cookies have been set.
*/
public Cookie[] getCookies() {
final Cookies cookies = new Cookies();
cookies.setHeaders(response.getHeaders(), false);
return cookies.get();
}
/**
* Return the value for the specified header, or null
if this header has not been set. If more than one
* value was added for this name, only the first is returned; use getHeaderValues() to retrieve all of them.
*
* @param name Header name to look up
*/
public String getHeader(String name) {
checkResponse();
return response.getHeader(name);
}
/**
* Return an array of all the header names set for this response, or a zero-length array if no headers have been set.
*/
public String[] getHeaderNames() {
checkResponse();
MimeHeaders headers = response.getHeaders();
int n = headers.size();
String[] result = new String[n];
for (int i = 0; i < n; i++) {
result[i] = headers.getName(i).toString();
}
return result;
}
/**
* Return an array of all the header values associated with the specified header name, or an zero-length array if there
* are no such header values.
*
* @param name Header name to look up
*/
public String[] getHeaderValues(final String name) {
checkResponse();
final Collection result = new LinkedList<>();
for (final String headerValue : response.getHeaders().values(name)) {
result.add(headerValue);
}
return result.toArray(new String[result.size()]);
}
/**
* Return the error message that was set with sendError()
for this Response.
*/
public String getMessage() {
checkResponse();
return response.getReasonPhrase();
}
/**
* Return the HTTP status code associated with this Response.
*/
public int getStatus() {
checkResponse();
return response.getStatus();
}
/**
* Reset this response, and specify the values for the HTTP status code and corresponding message.
*
* @exception IllegalStateException if this response has already been committed
*/
public void reset(final int status, final String message) {
reset();
setStatus(status, message);
}
// -------------------------------------------- HttpServletResponse Methods
/**
* Add the specified Cookie to those that will be included with this Response.
*
* @param cookie Cookie to be added
*/
@SuppressWarnings({ "unchecked" })
public void addCookie(final Cookie cookie) {
if (isCommitted()) {
return;
}
final StringBuilder sb = new StringBuilder();
// web application code can receive a IllegalArgumentException
// from the appendCookieValue invokation
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Object run() {
CookieSerializerUtils.serializeServerCookie(sb, cookie);
return null;
}
});
} else {
CookieSerializerUtils.serializeServerCookie(sb, cookie);
}
// if we reached here, no exception, cookie is valid
// the header name is Set-Cookie for both "old" and v.1 ( RFC2109 )
// RFC2965 is not supported by browsers and the Servlet spec
// asks for 2109.
addHeader(Header.SetCookie, sb.toString());
}
/**
* Special method for adding a session cookie as we should be overriding any previous
*/
protected void addSessionCookieInternal(final Cookie cookie) {
if (isCommitted()) {
return;
}
String name = cookie.getName();
final String headername = Header.SetCookie.toString();
final String startsWith = name + "=";
final StringBuilder sb = new StringBuilder();
// web application code can receive a IllegalArgumentException
// from the appendCookieValue invokation
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Object run() {
CookieSerializerUtils.serializeServerCookie(sb, cookie);
return null;
}
});
} else {
CookieSerializerUtils.serializeServerCookie(sb, cookie);
}
final String cookieString = sb.toString();
boolean set = false;
MimeHeaders headers = response.getHeaders();
int n = headers.size();
for (int i = 0; i < n; i++) {
if (headers.getName(i).toString().equals(headername)) {
if (headers.getValue(i).toString().startsWith(startsWith)) {
headers.getValue(i).setString(cookieString);
set = true;
}
}
}
if (!set) {
addHeader(headername, cookieString);
}
}
/**
* Removes any Set-Cookie response headers whose value contains the string "JSESSIONID=" or "JSESSIONIDSSO="
*/
@SuppressWarnings("unused")
protected void removeSessionCookies() {
final String sessionCookieName = request.getSessionCookieName();
final String pattern = sessionCookieName != null ? '^' + sessionCookieName + "(?:SSO)?=.*" : Globals.SESSION_COOKIE_PATTERN;
response.getHeaders().removeHeaderMatches(Header.SetCookie, pattern);
}
/**
* Add the specified date header to the specified value.
*
* @param name Name of the header to set
* @param value Date value to be set
*/
public void addDateHeader(final String name, final long value) {
if (isCommitted()) {
return;
}
if (format == null) {
format = new SimpleDateFormat(HTTP_RESPONSE_DATE_HEADER, Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
}
addHeader(name, FastHttpDateFormat.formatDate(value, format));
}
/**
* Add the specified date header to the specified value.
*
* @param header the {@link Header} to set
* @param value Date value to be set
*
* @since 2.1.2
*/
public void addDateHeader(final Header header, final long value) {
if (isCommitted()) {
return;
}
if (format == null) {
format = new SimpleDateFormat(HTTP_RESPONSE_DATE_HEADER, Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
}
addHeader(header, FastHttpDateFormat.formatDate(value, format));
}
/**
* Add the specified header to the specified value.
*
* @param name Name of the header to set
* @param value Value to be set
*/
public void addHeader(final String name, final String value) {
checkResponse();
if (isCommitted()) {
return;
}
response.addHeader(name, value);
}
/**
* Add the specified header to the specified value.
*
* @param name Name of the header to set
* @param value Value to be set
*
* @since 2.3.8
*/
public void addHeader(final String name, final HeaderValue value) {
checkResponse();
if (isCommitted()) {
return;
}
response.addHeader(name, value);
}
/**
* Add the specified header to the specified value.
*
* @param header the {@link Header} to set
* @param value Value to be set
*
* @since 2.1.2
*/
public void addHeader(final Header header, final String value) {
checkResponse();
if (isCommitted()) {
return;
}
response.addHeader(header, value);
}
/**
* Add the specified header to the specified value.
*
* @param header the {@link Header} to set
* @param value Value to be set
*
* @since 2.3.8
*/
public void addHeader(final Header header, final HeaderValue value) {
checkResponse();
if (isCommitted()) {
return;
}
response.addHeader(header, value);
}
/**
* Add the specified integer header to the specified value.
*
* @param name Name of the header to set
* @param value Integer value to be set
*/
public void addIntHeader(final String name, final int value) {
if (isCommitted()) {
return;
}
addHeader(name, "" + value);
}
/**
* Add the specified integer header to the specified value.
*
* @param header the {@link Header} to set
* @param value Integer value to be set
*
* @since 2.1.2
*/
@SuppressWarnings("unused")
public void addIntHeader(final Header header, final int value) {
if (isCommitted()) {
return;
}
addHeader(header, Integer.toString(value));
}
/**
* Has the specified header been set already in this response?
*
* @param name Name of the header to check
*/
public boolean containsHeader(final String name) {
checkResponse();
return response.containsHeader(name);
}
/**
* Has the specified header been set already in this response?
*
* @param header the {@link Header} to check
*
* @since 2.1.2
*/
@SuppressWarnings("unused")
public boolean containsHeader(final Header header) {
checkResponse();
return response.containsHeader(header);
}
/**
* Send an acknowledgment of a request. An acknowledgment in this case is simply an HTTP response status line, i.e.
* HTTP/1.1 [STATUS] [REASON-PHRASE].
*
* @exception java.io.IOException if an input/output error occurs
*/
public void sendAcknowledgement() throws IOException {
if (isCommitted() || !request.requiresAcknowledgement()) {
return;
}
response.setAcknowledgement(true);
outputBuffer.acknowledge();
}
/**
* Send an error response with the specified status and a default message.
*
* @param status HTTP status code to send
*
* @exception IllegalStateException if this response has already been committed
* @exception java.io.IOException if an input/output error occurs
*/
public void sendError(int status) throws IOException {
sendError(status, null);
}
/**
* Send an error response with the specified status and message.
*
* @param status HTTP status code to send
* @param message Corresponding message to send
*
* @exception IllegalStateException if this response has already been committed
* @exception java.io.IOException if an input/output error occurs
*/
public void sendError(final int status, final String message) throws IOException {
checkResponse();
if (isCommitted()) {
throw new IllegalStateException("Illegal attempt to call sendError() after the response has been committed.");
}
setError();
response.getHeaders().removeHeader(Header.TransferEncoding);
response.setContentLanguage(null);
response.setContentLengthLong(-1L);
response.setChunked(false);
response.setCharacterEncoding(null);
response.setContentType((String) null);
response.setLocale(null);
outputBuffer.reset();
usingWriter = false;
usingOutputStream = false;
setStatus(status, message);
String nonNullMsg = message;
if (nonNullMsg == null) {
final HttpStatus httpStatus = HttpStatus.getHttpStatus(status);
if (httpStatus != null && httpStatus.getReasonPhrase() != null) {
nonNullMsg = httpStatus.getReasonPhrase();
} else {
nonNullMsg = "Unknown Error";
}
}
HtmlHelper.sendErrorPage(request, this, getErrorPageGenerator(), status, nonNullMsg, nonNullMsg, null);
finish();
}
/**
* Send a temporary redirect to the specified redirect location URL.
*
* @param location Location URL to redirect to
*
* @exception IllegalStateException if this response has already been committed
* @exception java.io.IOException if an input/output error occurs
*/
public void sendRedirect(String location) throws IOException {
if (isCommitted()) {
throw new IllegalStateException("Illegal attempt to redirect the response as the response has been committed.");
}
// Clear any data content that has been buffered
resetBuffer();
// Generate a temporary redirect to the specified location
try {
String absolute = toAbsolute(location, true);
// END RIMOD 4642650
setStatus(HttpStatus.FOUND_302);
setHeader(Header.Location, absolute);
// According to RFC2616 section 10.3.3 302 Found,
// the response SHOULD contain a short hypertext note with
// a hyperlink to the new URI.
setContentType("text/html");
setLocale(Locale.getDefault());
String filteredMsg = filter(absolute);
StringBuilder sb = new StringBuilder(150 + absolute.length());
sb.append("\r\n");
sb.append("Document moved \r\n");
sb.append("Document moved \r\n");
sb.append("This document has moved here .\r\n");
sb.append("\r\n");
sb.append("\r\n");
try {
getWriter().write(sb.toString());
getWriter().flush();
} catch (IllegalStateException ise1) {
try {
getOutputStream().write(sb.toString().getBytes(org.glassfish.grizzly.http.util.Constants.DEFAULT_HTTP_CHARSET));
} catch (IllegalStateException ise2) {
// ignore; the RFC says "SHOULD" so it is acceptable
// to omit the body in case of an error
}
}
} catch (IllegalArgumentException e) {
sendError(404);
}
finish();
}
/**
* Set the specified date header to the specified value.
*
* @param name Name of the header to set
* @param value Date value to be set
*/
public void setDateHeader(String name, long value) {
if (isCommitted()) {
return;
}
if (format == null) {
format = new SimpleDateFormat(HTTP_RESPONSE_DATE_HEADER, Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
}
setHeader(name, FastHttpDateFormat.formatDate(value, format));
}
/**
* Set the specified date header to the specified value.
*
* @param header the {@link Header} to set
* @param value Date value to be set
*
* @since 2.1.2
*/
@SuppressWarnings("unused")
public void setDateHeader(final Header header, long value) {
if (isCommitted()) {
return;
}
if (format == null) {
format = new SimpleDateFormat(HTTP_RESPONSE_DATE_HEADER, Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
}
setHeader(header, FastHttpDateFormat.formatDate(value, format));
}
/**
* Set the specified header to the specified value.
*
* @param name Name of the header to set
* @param value Value to be set
*/
public void setHeader(final String name, final String value) {
checkResponse();
if (isCommitted()) {
return;
}
response.setHeader(name, value);
}
/**
* Set the specified header to the specified value.
*
* @param name Name of the header to set
* @param value Value to be set
*
* @since 2.3.8
*/
public void setHeader(final String name, final HeaderValue value) {
checkResponse();
if (isCommitted()) {
return;
}
response.setHeader(name, value);
}
/**
* Set the specified header to the specified value.
*
* @param header the {@link Header} to set
* @param value Value to be set
*
* @since 2.1.2
*/
public void setHeader(final Header header, final String value) {
checkResponse();
if (isCommitted()) {
return;
}
response.setHeader(header, value);
}
/**
* Set the specified header to the specified value.
*
* @param header the {@link Header} to set
* @param value Value to be set
*
* @since 2.3.8
*/
public void setHeader(final Header header, final HeaderValue value) {
checkResponse();
if (isCommitted()) {
return;
}
response.setHeader(header, value);
}
/**
* Set the specified integer header to the specified value.
*
* @param name Name of the header to set
* @param value Integer value to be set
*/
public void setIntHeader(String name, int value) {
if (isCommitted()) {
return;
}
setHeader(name, "" + value);
}
/**
* Set the specified integer header to the specified value.
*
* @param header the {@link Header} to set
* @param value Integer value to be set
*
* @since 2.1.2
*/
@SuppressWarnings("unused")
public void setIntHeader(final Header header, final int value) {
if (isCommitted()) {
return;
}
setHeader(header, Integer.toString(value));
}
/**
* Set the HTTP status to be returned with this response.
*
* @param status The new HTTP status
*/
public void setStatus(int status) {
setStatus(status, null);
}
/**
* Set the HTTP status and message to be returned with this response.
*
* @param status The new HTTP status
* @param message The associated text message
*
*/
public void setStatus(int status, String message) {
checkResponse();
if (isCommitted()) {
return;
}
response.setStatus(status);
response.setReasonPhrase(message);
}
/**
* Set the HTTP status and message to be returned with this response.
*
* @param status {@link HttpStatus} to set
*/
public void setStatus(HttpStatus status) {
checkResponse();
if (isCommitted()) {
return;
}
status.setValues(response);
}
// ------------------------------------------------------ Protected Methods
/**
* Convert (if necessary) and return the absolute URL that represents the resource referenced by this possibly relative
* URL. If this URL is already absolute, return it unchanged.
*
* @param location URL to be (possibly) converted and then returned
*
* @exception IllegalArgumentException if a MalformedURLException is thrown when converting the relative URL to an
* absolute one
*/
@SuppressWarnings({ "unchecked" })
protected String toAbsolute(final String location, final boolean normalize) {
if (location == null) {
return null;
}
final boolean leadingSlash = location.startsWith("/");
if (leadingSlash || !location.contains("://")) {
final String scheme = request.getScheme();
final String name = request.getServerName();
final int port = request.getServerPort();
redirectURLCC.recycle();
final CharChunk cc = redirectURLCC;
try {
cc.append(scheme, 0, scheme.length());
cc.append("://", 0, 3);
cc.append(name, 0, name.length());
if (scheme.equals("http") && port != 80 || scheme.equals("https") && port != 443) {
cc.append(':');
String portS = port + "";
cc.append(portS, 0, portS.length());
}
if (!leadingSlash) {
String relativePath = request.getDecodedRequestURI();
final int pos = relativePath.lastIndexOf('/');
relativePath = relativePath.substring(0, pos);
final String encodedURI;
if (System.getSecurityManager() != null) {
try {
final String frelativePath = relativePath;
encodedURI = AccessController.doPrivileged(new PrivilegedExceptionAction() {
@Override
public String run() throws IOException {
return urlEncoder.encodeURL(frelativePath);
}
});
} catch (PrivilegedActionException pae) {
throw new IllegalArgumentException(location, pae.getCause());
}
} else {
encodedURI = urlEncoder.encodeURL(relativePath);
}
cc.append(encodedURI, 0, encodedURI.length());
cc.append('/');
}
cc.append(location, 0, location.length());
} catch (IOException e) {
throw new IllegalArgumentException(location, e);
}
if (normalize) {
HttpRequestURIDecoder.normalizeChars(cc);
}
return cc.toString();
} else {
return location;
}
}
/**
* Filter the specified message string for characters that are sensitive in HTML. This avoids potential attacks caused
* by including JavaScript codes in the request URL that is often reported in error messages.
*
* @param message The message string to be filtered
*/
public static String filter(String message) {
if (message == null) {
return null;
}
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
final StringBuilder result = new StringBuilder(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[i]);
}
}
return result.toString();
}
/**
* Return the specified URL with the specified session identifier suitably encoded.
*
* @param url URL to be encoded with the session id
* @param sessionId Session id to be included in the encoded URL
*/
protected String toEncoded(String url, String sessionId) {
if (url == null || sessionId == null) {
return url;
}
String path = url;
String query = "";
String anchor = "";
int question = url.indexOf('?');
if (question >= 0) {
path = url.substring(0, question);
query = url.substring(question);
}
int pound = path.indexOf('#');
if (pound >= 0) {
anchor = path.substring(pound);
path = path.substring(0, pound);
}
StringBuilder sb = new StringBuilder(path);
if (sb.length() > 0) { // jsessionid can't be first.
sb.append(";jsessionid=");
sb.append(sessionId);
}
String jrouteId = request.getHeader(Constants.PROXY_JROUTE);
if (jrouteId != null) {
sb.append(":");
sb.append(jrouteId);
}
sb.append(anchor);
sb.append(query);
return sb.toString();
}
/**
* Is the file cache enabled?
*/
@SuppressWarnings("unused")
public boolean isCacheEnabled() {
return cacheEnabled;
}
/**
* Get the context of the suspended Response .
*
* @return the context of the suspended Response .
*/
public SuspendContext getSuspendContext() {
return suspendedContext;
}
/**
* Return true/tt> if that {@link Response#suspend()} has been
* invoked and set to true
*
* @return true/tt> if that {@link Response#suspend()} has been
* invoked and set to true
*/
public boolean isSuspended() {
checkResponse();
final SuspendState state;
synchronized (suspendedContext) {
state = suspendState;
}
return state == SuspendState.SUSPENDED || state == SuspendState.RESUMING || state == SuspendState.CANCELLING;
}
/**
* Suspend the {@link Response}. Suspending a {@link Response} will tell the underlying container to avoid recycling
* objects associated with the current instance, and also to avoid committing response.
*/
@SuppressWarnings("deprecation")
public void suspend() {
suspend(DelayedExecutor.UNSET_TIMEOUT, TimeUnit.MILLISECONDS);
}
/**
* Suspend the {@link Response}. Suspending a {@link Response} will tell the underlying container to avoid recycling
* objects associated with the current instance, and also to avoid committing response.
*
* @param timeout The maximum amount of time, a {@link Response} can be suspended. When the timeout expires (because
* nothing has been written or because the {@link Response#resume()} or {@link Response#cancel()}), the {@link Response}
* will be automatically resumed and committed. Usage of any methods of a {@link Response} that times out will throw an
* {@link IllegalStateException}.
* @param timeunit timeout units
*
* @deprecated timeout parameters don't make any sense without CompletionHandler
*/
@Deprecated
public void suspend(final long timeout, final TimeUnit timeunit) {
suspend(timeout, timeunit, null);
}
/**
* Suspend the {@link Response}. Suspending a {@link Response} will tell the underlying container to avoid recycling
* objects associated with the current instance, and also to avoid committing response. When the
* {@link Response#resume()} is invoked, the container will make sure {@link CompletionHandler#completed(Object)} is
* invoked with the original attachment . When the {@link Response#cancel()} is invoked, the container will make
* sure {@link org.glassfish.grizzly.CompletionHandler#cancelled()} is invoked with the original attachment . If
* the timeout expires, the {@link org.glassfish.grizzly.CompletionHandler#cancelled()} is invoked with the original
* attachment and the {@link Response} committed.
*
* @param timeout The maximum amount of time the {@link Response} can be suspended. When the timeout expires (because
* nothing has been written or because the {@link Response#resume()} or {@link Response#cancel()}), the {@link Response}
* will be automatically resumed and committed. Usage of any methods of a {@link Response} that times out will throw an
* {@link IllegalStateException}.
* @param timeunit timeout units
* @param completionHandler a {@link org.glassfish.grizzly.CompletionHandler}
*/
public void suspend(final long timeout, final TimeUnit timeunit, final CompletionHandler completionHandler) {
suspend(timeout, timeunit, completionHandler, null);
}
/**
* Suspend the {@link Response}. Suspending a {@link Response} will tell the underlying container to avoid recycling
* objects associated with the current instance, and also to avoid committing response. When the
* {@link Response#resume()} is invoked, the container will make sure {@link CompletionHandler#completed(Object)} is
* invoked with the original attachment . When the {@link Response#cancel()} is invoked, the container will make
* sure {@link org.glassfish.grizzly.CompletionHandler#cancelled()} is invoked with the original attachment . If
* the timeout expires, the {@link org.glassfish.grizzly.CompletionHandler#cancelled()} is invoked with the original
* attachment and the {@link Response} committed.
*
* @param timeout The maximum amount of time the {@link Response} can be suspended. When the timeout expires (because
* nothing has been written or because the {@link Response#resume()} or {@link Response#cancel()}), the {@link Response}
* will be automatically resumed and committed. Usage of any methods of a {@link Response} that times out will throw an
* {@link IllegalStateException}.
* @param timeunit timeout units
* @param completionHandler a {@link org.glassfish.grizzly.CompletionHandler}
* @param timeoutHandler {@link TimeoutHandler} to customize the suspended Response timeout logic.
*/
public void suspend(final long timeout, final TimeUnit timeunit, final CompletionHandler completionHandler, final TimeoutHandler timeoutHandler) {
checkResponse();
if (suspendState != SuspendState.NONE) {
throw new IllegalStateException("Already Suspended");
}
suspendState = SuspendState.SUSPENDED;
suspendStatus.suspend();
suspendedContext.init(completionHandler, timeoutHandler);
HttpServerProbeNotifier.notifyRequestSuspend(request.httpServerFilter, ctx.getConnection(), request);
httpContext.getCloseable().addCloseListener(suspendedContext.closeListener);
if (timeout > 0) {
final long timeoutMillis = TimeUnit.MILLISECONDS.convert(timeout, timeunit);
delayQueue.add(suspendedContext.suspendTimeout, timeoutMillis, TimeUnit.MILLISECONDS);
suspendedContext.suspendTimeout.delayMillis = timeoutMillis;
}
}
/**
* Complete the {@link Response} and finish/commit it. If a {@link CompletionHandler} has been defined, its
* {@link CompletionHandler#completed(Object)} will first be invoked, then the {@link Response#finish()}. Those
* operations commit the response.
*/
@SuppressWarnings({ "unchecked" })
public void resume() {
checkResponse();
suspendedContext.markResumed();
ctx.resume();
}
/**
* Cancel the {@link Response} and finish/commit it. If a {@link CompletionHandler} has been defined, its
* {@link CompletionHandler#cancelled()} will first be invoked, then the {@link Response#finish()}. Those operations
* commit the response.
*
* @deprecated pls. use {@link #resume()}
*/
@Deprecated
public void cancel() {
checkResponse();
// noinspection deprecation
suspendedContext.markCancelled();
ctx.resume();
}
/**
* Make sure the {@link Response} object has been set.
*/
final void checkResponse() {
if (response == null) {
throw new IllegalStateException("Internal " + "org.glassfish.grizzly.http.server.Response has not been set");
}
}
public boolean isSendFileEnabled() {
return sendFileEnabled;
}
public final class SuspendedContextImpl implements SuspendContext {
private int modCount;
CompletionHandler completionHandler;
SuspendTimeout suspendTimeout;
private CloseListener closeListener;
/**
* Marks {@link Response} as resumed, but doesn't resume associated {@link FilterChainContext} invocation.
*/
public synchronized boolean markResumed() {
modCount++;
if (suspendState != SuspendState.SUSPENDED) {
if (suspendState == SuspendState.CANCELLED || suspendState == SuspendState.CANCELLING) { // Siletly return if processing has been cancelled
return false;
}
throw new IllegalStateException("Not Suspended");
}
suspendState = SuspendState.RESUMING;
httpContext.getCloseable().removeCloseListener(closeListener);
if (completionHandler != null) {
completionHandler.completed(Response.this);
}
reset();
suspendState = SuspendState.RESUMED;
HttpServerProbeNotifier.notifyRequestResume(request.httpServerFilter, ctx.getConnection(), request);
return true;
}
/**
* Marks {@link Response} as cancelled, if expectedModCount corresponds to the current modCount. This method doesn't
* resume associated {@link FilterChainContext} invocation.
*/
protected synchronized boolean markCancelled(final int expectedModCount) {
if (modCount != expectedModCount) {
return false;
}
modCount++;
if (suspendState != SuspendState.SUSPENDED) {
throw new IllegalStateException("Not Suspended");
}
suspendState = SuspendState.CANCELLING;
httpContext.getCloseable().removeCloseListener(closeListener);
if (completionHandler != null) {
completionHandler.cancelled();
}
suspendState = SuspendState.CANCELLED;
reset();
HttpServerProbeNotifier.notifyRequestCancel(request.httpServerFilter, ctx.getConnection(), request);
final InputBuffer inputBuffer = request.getInputBuffer();
if (!inputBuffer.isFinished()) {
inputBuffer.terminate();
}
return true;
}
/**
* Marks {@link Response} as cancelled, but doesn't resume associated {@link FilterChainContext} invocation.
*
* @deprecated
*/
@Deprecated
public synchronized void markCancelled() {
markCancelled(modCount);
}
private void init(final CompletionHandler completionHandler, final TimeoutHandler timeoutHandler) {
this.completionHandler = completionHandler;
this.suspendTimeout = new SuspendTimeout(modCount, timeoutHandler);
closeListener = new SuspendCloseListener(modCount);
}
void reset() {
suspendTimeout.reset();
suspendTimeout = null;
completionHandler = null;
closeListener = null;
}
@Override
public CompletionHandler getCompletionHandler() {
return completionHandler;
}
@Override
public TimeoutHandler getTimeoutHandler() {
return suspendTimeout.timeoutHandler;
}
@Override
public long getTimeout(final TimeUnit timeunit) {
return suspendTimeout.getTimeout(timeunit);
}
@Override
public void setTimeout(final long timeout, final TimeUnit timeunit) {
synchronized (suspendedContext) {
if (suspendState != SuspendState.SUSPENDED || suspendTimeout == null) {
return;
}
suspendTimeout.setTimeout(timeout, timeunit);
}
}
@Override
public boolean isSuspended() {
return Response.this.isSuspended();
}
public SuspendStatus getSuspendStatus() {
return suspendStatus;
}
@SuppressWarnings("deprecation")
private class SuspendCloseListener implements GenericCloseListener {
private final int expectedModCount;
public SuspendCloseListener(int expectedModCount) {
this.expectedModCount = expectedModCount;
}
@Override
public void onClosed(final Closeable connection, final CloseType closeType) throws IOException {
checkResponse();
if (suspendedContext.markCancelled(expectedModCount)) {
// ctx.resume();
ctx.completeAndRelease();
}
}
}
}
protected class SuspendTimeout {
private final int expectedModCount;
TimeoutHandler timeoutHandler;
long delayMillis;
volatile long timeoutTimeMillis;
private SuspendTimeout(int modCount, TimeoutHandler timeoutHandler) {
this.expectedModCount = modCount;
this.timeoutHandler = timeoutHandler;
}
boolean onTimeout() {
timeoutTimeMillis = DelayedExecutor.UNSET_TIMEOUT;
final TimeoutHandler localTimeoutHandler = timeoutHandler;
if (localTimeoutHandler == null || localTimeoutHandler.onTimeout(Response.this)) {
HttpServerProbeNotifier.notifyRequestTimeout(request.httpServerFilter, ctx.getConnection(), request);
try {
checkResponse();
// noinspection StatementWithEmptyBody
if (suspendedContext.markCancelled(expectedModCount)) {
// ctx.resume();
}
} catch (Exception ignored) {
}
return true;
} else {
return false;
}
}
private long getTimeout(TimeUnit timeunit) {
if (delayMillis > 0) {
return timeunit.convert(delayMillis, TimeUnit.MILLISECONDS);
} else {
return delayMillis;
}
}
private void setTimeout(long timeout, TimeUnit timeunit) {
if (timeout > 0) {
delayMillis = TimeUnit.MILLISECONDS.convert(timeout, timeunit);
} else {
delayMillis = DelayedExecutor.UNSET_TIMEOUT;
}
delayQueue.add(this, delayMillis, TimeUnit.MILLISECONDS);
}
private void reset() {
timeoutTimeMillis = DelayedExecutor.UNSET_TIMEOUT;
timeoutHandler = null;
}
}
private static class DelayQueueWorker implements DelayedExecutor.Worker {
@Override
public boolean doWork(final SuspendTimeout element) {
return element.onTimeout();
}
}
private static class DelayQueueResolver implements DelayedExecutor.Resolver {
@Override
public boolean removeTimeout(final SuspendTimeout element) {
if (element.timeoutTimeMillis != DelayedExecutor.UNSET_TIMEOUT) {
element.timeoutTimeMillis = DelayedExecutor.UNSET_TIMEOUT;
return true;
}
return false;
}
@Override
public long getTimeoutMillis(final SuspendTimeout element) {
return element.timeoutTimeMillis;
}
@Override
public void setTimeoutMillis(final SuspendTimeout element, final long timeoutMillis) {
element.timeoutTimeMillis = timeoutMillis;
}
}
}