responseCookies;
private String protocol;
/**
* The security context
*/
private SecurityContext securityContext;
// mutable state
private int state = 200;
private String requestMethod;
private String requestScheme;
/**
* The original request URI. This will include the host name if it was specified by the client.
*
* This is not decoded in any way, and does not include the query string.
*
* Examples:
* GET http://localhost:8080/myFile.jsf?foo=bar HTTP/1.1 -> 'http://localhost:8080/myFile.jsf'
* POST /my+File.jsf?foo=bar HTTP/1.1 -> '/my+File.jsf'
*/
private String requestURI;
/**
* The request path. This will be decoded by the server, and does not include the query string.
*
* This path is not canonicalised, so care must be taken to ensure that escape attacks are not possible.
*
* Examples:
* GET http://localhost:8080/b/../my+File.jsf?foo=bar HTTP/1.1 -> '/b/../my+File.jsf'
* POST /my+File.jsf?foo=bar HTTP/1.1 -> '/my File.jsf'
*/
private String requestPath;
/**
* The remaining unresolved portion of request path. If a {@link io.undertow.server.handlers.CanonicalPathHandler} is
* installed this will be canonicalised.
*
* Initially this will be equal to {@link #requestPath}, however it will be modified as handlers resolve the path.
*/
private String relativePath;
/**
* The resolved part of the canonical path.
*/
private String resolvedPath = "";
/**
* the query string
*/
private String queryString = "";
private long requestStartTime = -1;
/**
* The maximum entity size. This can be modified before the request stream is obtained, however once the request
* stream is obtained this cannot be modified further.
*
* The default value for this is determined by the {@link UndertowOptions#MAX_ENTITY_SIZE} option. A value
* of 0 indicates that this is unbounded.
*
* If this entity size is exceeded the request channel will be forcibly closed.
*
* TODO: integrate this with HTTP 100-continue responses, to make it possible to send a 417 rather than just forcibly
* closing the channel.
*
* @see UndertowOptions#MAX_ENTITY_SIZE
*/
private long maxEntitySize;
/**
* When the call stack return this task will be executed by the executor specified in {@link #dispatchExecutor}.
* If the executor is null then it will be executed by the worker.
*/
private Runnable dispatchTask;
/**
* The executor that is to be used to dispatch the {@link #dispatchTask}. Note that this is not cleared
* between dispatches, so once a request has been dispatched once then all subsequent dispatches will use
* the same executor.
*/
private Executor dispatchExecutor;
/**
* Flag that is set when the response sending begins
*/
private static final int FLAG_RESPONSE_SENT = 1 << 10;
/**
* Flag that is set if this is a persistent connection, and the
* connection should be re-used.
*/
private static final int FLAG_PERSISTENT = 1 << 14;
/**
* If this flag is set it means that the request has been dispatched,
* and will not be ending when the call stack returns.
*
* This could be because it is being dispatched to a worker thread from
* an IO thread, or because resume(Reads/Writes) has been called.
*/
private static final int FLAG_DISPATCHED = 1 << 15;
/**
* Flag that is set if the {@link #requestURI} field contains the hostname.
*/
private static final int FLAG_URI_CONTAINS_HOST = 1 << 16;
/**
* Flag that indicates the user has started to read data from the request
*/
private static final int FLAG_REQUEST_READ = 1 << 20;
/**
* Flag that indicates that the request channel has been reset, can be called again
*/
private static final int FLAG_REQUEST_RESET = 1 << 21;
/**
* Flag that indicates that the last data has been queued
*/
private static final int FLAG_LAST_DATA_QUEUED = 1 << 22;
/**
* The source address for the request. If this is null then the actual source address from the channel is used
*/
private InetSocketAddress sourceAddress;
/**
* The destination address for the request. If this is null then the actual source address from the channel is used
*/
private InetSocketAddress destinationAddress;
private SSLSessionInfo sslSessionInfo;
final HttpExchange delegate;
private boolean executingHandlerChain;
private static final AtomicLong REQUEST_ID_GENERATOR = new AtomicLong(0);
private final String requestId = Long.toString(REQUEST_ID_GENERATOR.incrementAndGet());
public HttpServerExchange(final HttpExchange delegate, long maxEntitySize) {
this.maxEntitySize = maxEntitySize;
this.delegate = delegate;
delegate.setCompletedListener(this);
delegate.setPreCommitListener(this);
}
/**
* Get the request getProtocol string. Normally this is one of the strings listed in {@link HttpProtocolNames}.
*
* @return the request getProtocol string
*/
public String getProtocol() {
if (protocol != null) {
return protocol;
} else {
return delegate.getProtocol();
}
}
/**
* Sets the http getProtocol
*
* @param protocol
*/
public HttpServerExchange protocol(final String protocol) {
this.protocol = protocol;
return this;
}
public boolean isSecure() {
Boolean secure = getAttachment(SECURE_REQUEST);
if (secure != null && secure) {
return true;
}
String scheme = getRequestScheme();
if (scheme != null && scheme.equalsIgnoreCase(HTTPS)) {
return true;
}
return false;
}
/**
* Get the HTTP request method. Normally this is one of the strings listed in {@link HttpMethodNames}.
*
* @return the HTTP request method
*/
public String getRequestMethod() {
if (requestMethod == null) {
return delegate.getRequestMethod();
}
return requestMethod;
}
/**
* Set the HTTP request method.
*
* @param requestMethod the HTTP request method
*/
public HttpServerExchange requestMethod(final String requestMethod) {
this.requestMethod = requestMethod;
return this;
}
/**
* Get the request URI scheme. Normally this is one of {@code http} or {@code https}.
*
* @return the request URI scheme
*/
public String getRequestScheme() {
if (requestScheme == null) {
return delegate.getRequestScheme();
}
return requestScheme;
}
/**
* Set the request URI scheme.
*
* @param requestScheme the request URI scheme
*/
public HttpServerExchange setRequestScheme(final String requestScheme) {
this.requestScheme = requestScheme;
return this;
}
public String getRequestId() {
return requestId;
}
/**
* The original request URI. This will include the host name, getProtocol etc
* if it was specified by the client.
*
* This is not decoded in any way, and does not include the query string.
*
* Examples:
* GET http://localhost:8080/myFile.jsf?foo=bar HTTP/1.1 -> 'http://localhost:8080/myFile.jsf'
* POST /my+File.jsf?foo=bar HTTP/1.1 -> '/my+File.jsf'
*/
public String getRequestURI() {
if (requestURI == null) {
return delegate.getRequestURI();
}
return requestURI;
}
/**
* Sets the request URI
*
* @param requestURI The new request URI
*/
public HttpServerExchange setRequestURI(final String requestURI) {
this.requestURI = requestURI;
return this;
}
/**
* Sets the request URI
*
* @param requestURI The new request URI
* @param containsHost If this is true the request URI contains the host part
*/
public HttpServerExchange setRequestURI(final String requestURI, boolean containsHost) {
this.requestURI = requestURI;
if (containsHost) {
this.state |= FLAG_URI_CONTAINS_HOST;
} else {
this.state &= ~FLAG_URI_CONTAINS_HOST;
}
return this;
}
/**
* If a request was submitted to the server with a full URI instead of just a path this
* will return true. For example:
*
* GET http://localhost:8080/b/../my+File.jsf?foo=bar HTTP/1.1 -> true
* POST /my+File.jsf?foo=bar HTTP/1.1 -> false
*
* @return true
If the request URI contains the host part of the URI
*/
public boolean isHostIncludedInRequestURI() {
return anyAreSet(state, FLAG_URI_CONTAINS_HOST);
}
/**
* The request path. This will be decoded by the server, and does not include the query string.
*
* This path is not canonicalised, so care must be taken to ensure that escape attacks are not possible.
*
* Examples:
* GET http://localhost:8080/b/../my+File.jsf?foo=bar HTTP/1.1 -> '/b/../my+File.jsf'
* POST /my+File.jsf?foo=bar HTTP/1.1 -> '/my File.jsf'
*/
public String getRequestPath() {
return requestPath;
}
/**
* Set the request URI path.
*
* @param requestPath the request URI path
*/
public HttpServerExchange setRequestPath(final String requestPath) {
this.requestPath = requestPath;
return this;
}
/**
* Get the request relative path. This is the path which should be evaluated by the current handler.
*
* If the {@link io.undertow.server.handlers.CanonicalPathHandler} is installed in the current chain
* then this path with be canonicalized
*
* @return the request relative path
*/
public String getRelativePath() {
return relativePath;
}
/**
* Set the request relative path.
*
* @param relativePath the request relative path
*/
public HttpServerExchange setRelativePath(final String relativePath) {
this.relativePath = relativePath;
return this;
}
/**
* Get the resolved path.
*
* @return the resolved path
*/
public String getResolvedPath() {
return resolvedPath;
}
/**
* Set the resolved path.
*
* @param resolvedPath the resolved path
*/
public HttpServerExchange setResolvedPath(final String resolvedPath) {
this.resolvedPath = resolvedPath;
return this;
}
/**
* @return The query string, without the leading ?
*/
public String getQueryString() {
return queryString;
}
public HttpServerExchange setQueryString(final String queryString) {
this.queryString = queryString;
return this;
}
/**
* Reconstructs the complete URL as seen by the user. This includes scheme, host name etc,
* but does not include query string.
*
* This is not decoded.
*/
public String getRequestURL() {
if (isHostIncludedInRequestURI()) {
return getRequestURI();
} else {
return getRequestScheme() + "://" + getHostAndPort() + getRequestURI();
}
}
public long getRequestContentLength() {
return delegate.getRequestContentLength();
}
/**
* @return The content length of the response, or -1
if it has not been set
*/
public long getResponseContentLength() {
return delegate.getResponseContentLength();
}
/**
* Return the host that this request was sent to, in general this will be the
* value of the Host header, minus the port specifier.
*
* If this resolves to an IPv6 address it will not be enclosed by square brackets.
* Care must be taken when constructing URLs based on this method to ensure IPv6 URLs
* are handled correctly.
*
* @return The host part of the destination address
*/
public String getHostName() {
String host = getRequestHeader(HttpHeaderNames.HOST);
if (host == null) {
host = getDestinationAddress().getHostString();
} else {
if (host.startsWith("[")) {
host = host.substring(1, host.indexOf(']'));
} else if (host.indexOf(':') != -1) {
host = host.substring(0, host.indexOf(':'));
}
}
return host;
}
/**
* Return the host, and also the port if this request was sent to a non-standard port. In general
* this will just be the value of the Host header.
*
* If this resolves to an IPv6 address it *will* be enclosed by square brackets. The return
* value of this method is suitable for inclusion in a URL.
*
* @return The host and port part of the destination address
*/
public String getHostAndPort() {
String host = getRequestHeader(HttpHeaderNames.HOST);
if (host == null) {
InetSocketAddress address = getDestinationAddress();
host = NetworkUtils.formatPossibleIpv6Address(address.getHostString());
int port = address.getPort();
if (!((getRequestScheme().equals("http") && port == 80)
|| (getRequestScheme().equals("https") && port == 443))) {
host = host + ":" + port;
}
}
return host;
}
public HttpExchange getDelegate() {
return delegate;
}
/**
* Return the port that this request was sent to. In general this will be the value of the Host
* header, minus the host name.
*
* @return The port part of the destination address
*/
public int getHostPort() {
String host = getRequestHeader(HttpHeaderNames.HOST);
if (host != null) {
//for ipv6 addresses we make sure we take out the first part, which can have multiple occurrences of :
final int colonIndex;
if (host.startsWith("[")) {
colonIndex = host.indexOf(':', host.indexOf(']'));
} else {
colonIndex = host.indexOf(':');
}
if (colonIndex != -1) {
try {
return Integer.parseInt(host.substring(colonIndex + 1));
} catch (NumberFormatException ignore) {
}
}
if (getRequestScheme().equals("https")) {
return 443;
} else if (getRequestScheme().equals("http")) {
return 80;
}
}
return getDestinationAddress().getPort();
}
public boolean isPersistent() {
return anyAreSet(state, FLAG_PERSISTENT);
}
/**
* @return true
If the current thread in the IO thread for the exchange
*/
public boolean isInIoThread() {
return getIoThread().inEventLoop();
}
/**
* @return The number of bytes sent in the entity body
*/
public long getResponseBytesSent() {
return delegate.getResponseBytesSent();
}
public HttpServerExchange setPersistent(final boolean persistent) {
if (persistent) {
this.state = this.state | FLAG_PERSISTENT;
} else {
this.state = this.state & ~FLAG_PERSISTENT;
}
return this;
}
public boolean isDispatched() {
return anyAreSet(state, FLAG_DISPATCHED);
}
public HttpServerExchange unDispatch() {
state &= ~FLAG_DISPATCHED;
dispatchTask = null;
return this;
}
/**
* Dispatches this request to the XNIO worker thread pool. Once the call stack returns
* the given runnable will be submitted to the executor.
*
* In general handlers should first check the value of {@link #isInIoThread()} before
* calling this method, and only dispatch if the request is actually running in the IO
* thread.
*
* @param runnable The task to run
* @throws IllegalStateException If this exchange has already been dispatched
*/
public HttpServerExchange dispatch(final Runnable runnable) {
dispatch(null, runnable);
return this;
}
/**
* Dispatches this request to the given executor. Once the call stack returns
* the given runnable will be submitted to the executor.
*
* In general handlers should first check the value of {@link #isInIoThread()} before
* calling this method, and only dispatch if the request is actually running in the IO
* thread.
*
* @param runnable The task to run
* @throws IllegalStateException If this exchange has already been dispatched
*/
public HttpServerExchange dispatch(final Executor executor, final Runnable runnable) {
if (isExecutingHandlerChain()) {
if (executor != null) {
this.dispatchExecutor = executor;
}
state |= FLAG_DISPATCHED;
if (delegate.isIoOperationQueued()) {
throw UndertowMessages.MESSAGES.resumedAndDispatched();
}
this.dispatchTask = runnable;
} else {
if (executor == null) {
delegate.getWorker().execute(runnable);
} else {
executor.execute(runnable);
}
}
return this;
}
boolean isExecutingHandlerChain() {
return executingHandlerChain;
}
public HttpServerExchange dispatch(final HttpHandler handler) {
dispatch(null, handler);
return this;
}
public HttpServerExchange dispatch(final Executor executor, final HttpHandler handler) {
final Runnable runnable = new Runnable() {
@Override
public void run() {
Connectors.executeRootHandler(handler, HttpServerExchange.this);
}
};
dispatch(executor, runnable);
return this;
}
/**
* Sets the executor that is used for dispatch operations where no executor is specified.
*
* @param executor The executor to use
*/
public HttpServerExchange setDispatchExecutor(final Executor executor) {
if (executor == null) {
dispatchExecutor = null;
} else {
dispatchExecutor = executor;
}
return this;
}
/**
* Gets the current executor that is used for dispatch operations. This may be null
*
* @return The current dispatch executor
*/
public Executor getDispatchExecutor() {
return dispatchExecutor;
}
/**
* @return The current dispatch task
*/
Runnable getDispatchTask() {
return dispatchTask;
}
public HttpServerExchange addExchangeCompleteListener(final ExchangeCompletionListener listener) {
if (isComplete() || this.exchangeCompletionListenersCount == -1) {
throw UndertowMessages.MESSAGES.exchangeAlreadyComplete();
}
final int exchangeCompletionListenersCount = this.exchangeCompletionListenersCount++;
ExchangeCompletionListener[] exchangeCompleteListeners = this.exchangeCompleteListeners;
if (exchangeCompleteListeners == null || exchangeCompleteListeners.length == exchangeCompletionListenersCount) {
ExchangeCompletionListener[] old = exchangeCompleteListeners;
this.exchangeCompleteListeners = exchangeCompleteListeners = new ExchangeCompletionListener[exchangeCompletionListenersCount + 2];
if (old != null) {
System.arraycopy(old, 0, exchangeCompleteListeners, 0, exchangeCompletionListenersCount);
}
}
exchangeCompleteListeners[exchangeCompletionListenersCount] = listener;
return this;
}
public HttpServerExchange addDefaultResponseListener(final DefaultResponseListener listener) {
int i = 0;
if (defaultResponseListeners == null) {
defaultResponseListeners = new DefaultResponseListener[2];
} else {
while (i != defaultResponseListeners.length && defaultResponseListeners[i] != null) {
++i;
}
if (i == defaultResponseListeners.length) {
DefaultResponseListener[] old = defaultResponseListeners;
defaultResponseListeners = new DefaultResponseListener[defaultResponseListeners.length + 2];
System.arraycopy(old, 0, defaultResponseListeners, 0, old.length);
}
}
defaultResponseListeners[i] = listener;
return this;
}
/**
* Get the source address of the HTTP request.
*
* @return the source address of the HTTP request
*/
public InetSocketAddress getSourceAddress() {
if (sourceAddress != null) {
return sourceAddress;
}
return delegate.getSourceAddress();
}
public boolean isComplete() {
return delegate.isComplete();
}
public boolean isRequestComplete() {
return delegate.isRequestComplete();
}
public boolean isResponseComplete() {
return delegate.isResponseComplete();
}
/**
* Sets the source address of the HTTP request. If this is not explicitly set
* the actual source address of the channel is used.
*
* @param sourceAddress The address
*/
public HttpServerExchange setSourceAddress(InetSocketAddress sourceAddress) {
this.sourceAddress = sourceAddress;
return this;
}
/**
* Get the destination address of the HTTP request.
*
* @return the destination address of the HTTP request
*/
public InetSocketAddress getDestinationAddress() {
if (destinationAddress != null) {
return destinationAddress;
}
return delegate.getDestinationAddress();
}
/**
* Sets the destination address of the HTTP request. If this is not explicitly set
* the actual destination address of the channel is used.
*
* @param destinationAddress The address
*/
public HttpServerExchange setDestinationAddress(InetSocketAddress destinationAddress) {
this.destinationAddress = destinationAddress;
return this;
}
/**
* Sets the response content length
*
* @param length The content length
*/
public HttpServerExchange setResponseContentLength(long length) {
if (length == -1) {
delegate.removeResponseHeader(HttpHeaderNames.CONTENT_LENGTH);
} else {
delegate.setResponseHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(length));
}
return this;
}
/**
* Returns a mutable map of query parameters.
*
* @return The query parameters
*/
public Map> getQueryParameters() {
if (queryParameters == null) {
queryParameters = new TreeMap<>();
}
return queryParameters;
}
public HttpServerExchange addQueryParam(final String name, final String param) {
if (queryParameters == null) {
queryParameters = new TreeMap<>();
}
Deque list = queryParameters.get(name);
if (list == null) {
queryParameters.put(name, list = new ArrayDeque<>(2));
}
list.add(param);
return this;
}
/**
* Returns a mutable map of path parameters
*
* @return The path parameters
*/
public Map> getPathParameters() {
if (pathParameters == null) {
pathParameters = new TreeMap<>();
}
return pathParameters;
}
public HttpServerExchange addPathParam(final String name, final String param) {
if (pathParameters == null) {
pathParameters = new TreeMap<>();
}
Deque list = pathParameters.get(name);
if (list == null) {
pathParameters.put(name, list = new ArrayDeque<>(2));
}
list.add(param);
return this;
}
/**
* @return A mutable map of request cookies
*/
public Map getRequestCookies() {
if (requestCookies == null) {
requestCookies = Cookies.parseRequestCookies(
delegate.getUndertowOptions().get(UndertowOptions.MAX_COOKIES, 200),
delegate.getUndertowOptions().get(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false),
delegate.getRequestHeaders(HttpHeaderNames.COOKIE));
}
return requestCookies;
}
/**
* Sets a response cookie
*
* @param cookie The cookie
*/
public HttpServerExchange setResponseCookie(final Cookie cookie) {
if (delegate.getUndertowOptions().get(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, UndertowOptions.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION)) {
if (cookie.getValue() != null && !cookie.getValue().isEmpty()) {
Rfc6265CookieSupport.validateCookieValue(cookie.getValue());
}
if (cookie.getPath() != null && !cookie.getPath().isEmpty()) {
Rfc6265CookieSupport.validatePath(cookie.getPath());
}
if (cookie.getDomain() != null && !cookie.getDomain().isEmpty()) {
Rfc6265CookieSupport.validateDomain(cookie.getDomain());
}
}
if (responseCookies == null) {
responseCookies = new TreeMap<>(); //hashmap is slow to allocate in JDK7
}
responseCookies.put(cookie.getName(), cookie);
return this;
}
/**
* @return A mutable map of response cookies
*/
public Map getResponseCookies() {
if (responseCookies == null) {
responseCookies = new TreeMap<>();
}
return responseCookies;
}
/**
* For internal use only
*
* @return The response cookies, or null if they have not been set yet
*/
Map getResponseCookiesInternal() {
return responseCookies;
}
/**
* @return true
If the response has already been started
*/
public boolean isResponseStarted() {
return allAreSet(state, FLAG_RESPONSE_SENT);
}
/**
* Reads some data. If all data has been read it will return null.
*
* @return
* @throws IOException on failure
*/
public ByteBuf readBlocking() throws IOException {
if (isRequestComplete()) {
return null;
}
return delegate.getInputChannel().readBlocking();
}
public void send1ContinueIfRequired() {
if (HttpContinue.requiresContinueResponse(this)) {
delegate.sendContinue();
}
}
/**
* Writes the given UTF-8 data and ends the exchange
*
* @param data The data to write
*/
public void writeAsync(String data) {
writeAsync(Unpooled.copiedBuffer(data, StandardCharsets.UTF_8), true, IoCallback.END_EXCHANGE, null);
}
/**
* Writes the given data in the provided charset and ends the exchange
*
* @param data The data to write
*/
public void writeAsync(String data, Charset charset) {
writeAsync(Unpooled.copiedBuffer(data, charset), true, IoCallback.END_EXCHANGE, null);
}
/**
* Writes the given data in the provided charset and invokes the provided callback on completion
*
* @param data The data to write
*/
public void writeAsync(String data, Charset charset, boolean last, IoCallback callback, T context) {
writeAsync(Unpooled.copiedBuffer(data, charset), last, callback, context);
}
public void writeAsync(ByteBuf data, boolean last, IoCallback callback, T context) {
if (data == null && !last) {
throw new IllegalArgumentException("cannot call write with a null buffer and last being false");
}
if (isResponseComplete() || anyAreSet(state, FLAG_LAST_DATA_QUEUED)) {
if (last && data == null) {
callback.onComplete(delegate, context);
return;
}
callback.onException(delegate, context, new IOException(UndertowMessages.MESSAGES.responseComplete()));
return;
}
if (last) {
state |= FLAG_LAST_DATA_QUEUED;
}
delegate.getOutputChannel().writeAsync(data, last, callback, context);
}
public void writeBlocking(ByteBuf data, boolean last) throws IOException {
if (data == null && !last) {
throw new IllegalArgumentException("cannot call write with a null buffer and last being false");
}
if (getIoThread().inEventLoop()) {
throw UndertowMessages.MESSAGES.blockingIoFromIOThread();
}
if (isResponseComplete() || anyAreSet(state, FLAG_LAST_DATA_QUEUED)) {
if (last && data == null) {
return;
}
throw UndertowMessages.MESSAGES.responseComplete();
}
if (last) {
state |= FLAG_LAST_DATA_QUEUED;
}
delegate.getOutputChannel().writeBlocking(data, last);
}
private void invokeExchangeCompleteListeners() {
int count = exchangeCompletionListenersCount;
exchangeCompletionListenersCount = -1;
for (int i = count - 1; i >= 0; i--) {
ExchangeCompletionListener next = exchangeCompleteListeners[i];
next.exchangeEvent(this);
}
}
/**
* Get the status code.
*
* @return the status code
*/
public int getStatusCode() {
return delegate.getStatusCode();
}
public String getRequestHeader(String name) {
return delegate.getRequestHeader(name);
}
public List getRequestHeaders(String name) {
return delegate.getRequestHeaders(name);
}
public boolean containsRequestHeader(String name) {
return delegate.containsRequestHeader(name);
}
public void removeRequestHeader(String name) {
delegate.removeRequestHeader(name);
}
public void setRequestHeader(String name, String value) {
delegate.setRequestHeader(name, value);
}
public Collection getRequestHeaderNames() {
return delegate.getRequestHeaderNames();
}
public void addRequestHeader(String name, String value) {
delegate.addRequestHeader(name, value);
}
public void clearRequestHeaders() {
delegate.clearRequestHeaders();
}
public void clearResponseHeaders() {
delegate.clearResponseHeaders();
}
public String getResponseHeader(String name) {
return delegate.getResponseHeader(name);
}
public List getResponseHeaders(String name) {
return delegate.getResponseHeaders(name);
}
public boolean containsResponseHeader(String name) {
return delegate.containsResponseHeader(name);
}
public void removeResponseHeader(String name) {
delegate.removeResponseHeader(name);
}
public void setResponseHeader(String name, String value) {
delegate.setResponseHeader(name, value);
}
public Collection getResponseHeaderNames() {
return delegate.getResponseHeaderNames();
}
public void addResponseHeader(String name, String value) {
delegate.addResponseHeader(name, value);
}
/**
* Change the status code for this response. If not specified, the code will be a {@code 200}. Setting
* the status code after the response headers have been transmitted has no effect.
*
* @param statusCode the new code
* @throws IllegalStateException if a response or upgrade was already sent
*/
public HttpServerExchange setStatusCode(final int statusCode) {
if (statusCode < 0 || statusCode > 999) {
throw new IllegalArgumentException("Invalid response code");
}
int oldVal = state;
if (allAreSet(oldVal, FLAG_RESPONSE_SENT)) {
throw UndertowMessages.MESSAGES.responseAlreadyStarted();
}
if (statusCode >= 500) {
if (UndertowLogger.ERROR_RESPONSE.isDebugEnabled()) {
UndertowLogger.ERROR_RESPONSE.debugf(new RuntimeException(), "Setting error code %s for exchange %s", statusCode, this);
}
}
delegate.setStatusCode(statusCode);
return this;
}
/**
* Calling this method puts the exchange in blocking mode, using the given
* blocking exchange as the source of the streams.
*
* When an exchange is in blocking mode the input stream methods become
* available, other than that there is presently no major difference
* between blocking an non-blocking modes.
*
* Note that this method may be called multiple times with different
* exchange objects, to allow handlers to modify the streams
* that are being used.
*
*/
public void startBlocking(final BlockingHttpExchange httpExchange) {
delegate.setBlockingHttpExchange(httpExchange);
}
/**
* @return The input stream
*/
public InputStream getInputStream() {
return delegate.getInputStream();
}
public OutputStream getOutputStream() {
return delegate.getOutputStream();
}
/**
* @return The request start time, or -1 if this was not recorded
*/
public long getRequestStartTime() {
return requestStartTime;
}
HttpServerExchange setRequestStartTime(long requestStartTime) {
this.requestStartTime = requestStartTime;
return this;
}
/**
* Ends the exchange by fully draining the request channel, and flushing the response channel.
*
* This can result in handoff to an XNIO worker, so after this method is called the exchange should
* not be modified by the caller.
*
* If the exchange is already complete this method is a noop
*/
public HttpServerExchange endExchange() {
if (defaultResponseListeners != null && !isResponseStarted()) {
int i = defaultResponseListeners.length - 1;
while (i >= 0) {
DefaultResponseListener listener = defaultResponseListeners[i];
if (listener != null) {
defaultResponseListeners[i] = null;
try {
if (listener.handleDefaultResponse(this)) {
return this;
}
} catch (Throwable e) {
UndertowLogger.REQUEST_LOGGER.debug("Exception running default response listener", e);
}
}
i--;
}
}
delegate.endExchange();
return this;
}
public EventExecutor getIoThread() {
return delegate.getIoThread();
}
/**
* @return The maximum entity size for this exchange
*/
public long getMaxEntitySize() {
return maxEntitySize;
}
/**
* Sets the max entity size for this exchange. This cannot be modified after the request channel has been obtained.
*
* @param maxEntitySize The max entity size
*/
public HttpServerExchange setMaxEntitySize(final long maxEntitySize) {
if (anyAreSet(state, FLAG_REQUEST_READ)) {
throw UndertowMessages.MESSAGES.requestChannelAlreadyProvided();
}
this.maxEntitySize = maxEntitySize;
delegate.setMaxEntitySize(maxEntitySize);
return this;
}
public SecurityContext getSecurityContext() {
return securityContext;
}
public void setSecurityContext(SecurityContext securityContext) {
this.securityContext = securityContext;
}
/**
* Adds a listener that will be invoked on response commit
*
* @param listener The response listener
*/
public void addResponseCommitListener(final ResponseCommitListener listener) {
if (isComplete() || this.responseCommitListenerCount == -1) {
throw UndertowMessages.MESSAGES.responseAlreadyStarted();
}
final int responseCommitListenerCount = this.responseCommitListenerCount++;
ResponseCommitListener[] responseCommitListeners = this.responseCommitListeners;
if (responseCommitListeners == null || responseCommitListeners.length == responseCommitListenerCount) {
ResponseCommitListener[] old = responseCommitListeners;
this.responseCommitListeners = responseCommitListeners = new ResponseCommitListener[responseCommitListenerCount + 2];
if (old != null) {
System.arraycopy(old, 0, responseCommitListeners, 0, responseCommitListenerCount);
}
}
responseCommitListeners[responseCommitListenerCount] = listener;
}
@Override
public ByteBuf readAsync() throws IOException {
return delegate.getInputChannel().readAsync();
}
@Override
public boolean isReadable() {
return delegate.getInputChannel().isReadable();
}
@Override
public void setReadHandler(BiConsumer handler, T context) {
delegate.getInputChannel().setReadHandler(handler, context);
}
public int readBytesAvailable() {
return delegate.getInputChannel().readBytesAvailable();
}
@Override
public ByteBuf allocateBuffer() {
return delegate.getBufferAllocator().allocateBuffer();
}
@Override
public ByteBuf allocateBuffer(boolean direct) {
return delegate.getBufferAllocator().allocateBuffer(direct);
}
@Override
public ByteBuf allocateBuffer(int bufferSize) {
return delegate.getBufferAllocator().allocateBuffer(bufferSize);
}
@Override
public ByteBuf allocateBuffer(boolean direct, int bufferSize) {
return delegate.getBufferAllocator().allocateBuffer(direct, bufferSize);
}
@Override
public int getBufferSize() {
return delegate.getBufferAllocator().getBufferSize();
}
/**
* Used to terminate the request in an async manner, the actual mechanism will depend on the underlying getProtocol,
* for example HTTP/2 may send a RST_STREAM stream if there is more data coming.
*
* This may result in an unclean close if it is called on a non multiplexed getProtocol
*/
public void discardRequest() {
if (isRequestComplete()) {
return;
}
delegate.discardRequest();
}
/**
* Upgrade the channel to a raw socket. This method set the response code to 101, and then marks both the
* request and response as terminated, which means that once the current request is completed the raw channel
* can be obtained
*
* @throws IllegalStateException if a response or upgrade was already sent, or if the request body is already being
* read
*/
public HttpServerExchange upgradeChannel(final Consumer listener) {
if (!delegate.isUpgradeSupported()) {
throw UndertowMessages.MESSAGES.upgradeNotSupported();
}
if (!delegate.containsRequestHeader(HttpHeaderNames.UPGRADE)) {
throw UndertowMessages.MESSAGES.notAnUpgradeRequest();
}
UndertowLogger.REQUEST_LOGGER.debugf("Upgrading request %s", this);
setStatusCode(StatusCodes.SWITCHING_PROTOCOLS);
delegate.setResponseHeader(HttpHeaderNames.CONNECTION, HttpHeaderNames.UPGRADE);
delegate.setUpgradeListener(listener);
return this;
}
/**
* Upgrade the channel to a raw socket. This method set the response code to 101, and then marks both the
* request and response as terminated, which means that once the current request is completed the raw channel
* can be obtained
*
* @param productName the product name to report to the client
* @throws IllegalStateException if a response or upgrade was already sent, or if the request body is already being
* read
*/
public HttpServerExchange upgradeChannel(String productName, final Consumer listener) {
if (!delegate.isUpgradeSupported()) {
throw UndertowMessages.MESSAGES.upgradeNotSupported();
}
UndertowLogger.REQUEST_LOGGER.debugf("Upgrading request %s", this);
delegate.setUpgradeListener(listener);
setStatusCode(StatusCodes.SWITCHING_PROTOCOLS);
delegate.setResponseHeader(HttpHeaderNames.UPGRADE, productName);
delegate.setResponseHeader(HttpHeaderNames.CONNECTION, HttpHeaderNames.UPGRADE);
return this;
}
public void resetRequestChannel() {
state |= FLAG_REQUEST_RESET;
}
public void setSslSessionInfo(SSLSessionInfo info) {
this.sslSessionInfo = info;
}
public SSLSessionInfo getSslSessionInfo() {
if (sslSessionInfo == null) {
return delegate.getSslSessionInfo();
}
return sslSessionInfo;
}
@Override
public void close() {
endExchange();
}
public Executor getWorker() {
return delegate.getWorker();
}
public UndertowOptionMap getUndertowOptions() {
return delegate.getUndertowOptions();
}
public boolean isPushSupported() {
return delegate.isPushSupported();
}
public void pushResource(String path, String method, Map> requestHeaders) {
delegate.pushResource(path, method, requestHeaders);
}
public boolean isRequestTrailerFieldsSupported() {
return delegate.isRequestTrailerFieldsSupported();
}
@Override
public void completed(HttpExchange exchange) {
invokeExchangeCompleteListeners();
}
public void beginExecutingHandlerChain() {
executingHandlerChain = true;
}
public void endExecutingHandlerChain() {
executingHandlerChain = false;
}
public void runResumeReadWrite() {
//TODO: hold off on queuing IO
}
public OutputChannel getOutputChannel() {
return this;
}
public InputChannel getInputChannel() {
return this;
}
@Override
public String toString() {
return "HttpServerExchange{ " + getRequestMethod() + " " + getRequestURI() + " delegate " + delegate + '}';
}
@Override
public void preCommit(HttpExchange exchange) {
for (int i = responseCommitListenerCount - 1; i >= 0; --i) {
responseCommitListeners[i].beforeCommit(this);
}
state |= FLAG_RESPONSE_SENT;
Connectors.flattenCookies(this);
}
public void addWriteFunction(WriteFunction function) {
delegate.addWriteFunction(function);
}
}