io.undertow.server.protocol.http2.Http2ServerConnection Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
The newest version!
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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 io.undertow.server.protocol.http2;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import javax.net.ssl.SSLSession;
import io.undertow.UndertowLogger;
import io.undertow.UndertowOptions;
import io.undertow.protocols.http2.Http2HeadersStreamSinkChannel;
import io.undertow.server.ConduitWrapper;
import io.undertow.server.ExchangeCompletionListener;
import io.undertow.server.HttpHandler;
import io.undertow.server.XnioBufferPoolAdaptor;
import io.undertow.server.protocol.http.HttpContinue;
import io.undertow.util.ConduitFactory;
import io.undertow.util.DateUtils;
import io.undertow.util.Headers;
import io.undertow.util.ParameterLimitException;
import io.undertow.util.Protocols;
import org.xnio.ChannelListener;
import org.xnio.Option;
import org.xnio.OptionMap;
import io.undertow.connector.ByteBufferPool;
import org.xnio.Pool;
import org.xnio.StreamConnection;
import org.xnio.XnioIoThread;
import org.xnio.XnioWorker;
import org.xnio.channels.Configurable;
import org.xnio.channels.ConnectedChannel;
import org.xnio.conduits.ConduitStreamSinkChannel;
import org.xnio.conduits.ConduitStreamSourceChannel;
import org.xnio.conduits.EmptyStreamSourceConduit;
import org.xnio.conduits.StreamSinkChannelWrappingConduit;
import org.xnio.conduits.StreamSinkConduit;
import org.xnio.conduits.StreamSourceChannelWrappingConduit;
import org.xnio.conduits.StreamSourceConduit;
import org.xnio.conduits.WriteReadyHandler;
import io.undertow.UndertowMessages;
import io.undertow.protocols.http2.Http2Channel;
import io.undertow.protocols.http2.Http2DataStreamSinkChannel;
import io.undertow.protocols.http2.Http2StreamSourceChannel;
import io.undertow.server.Connectors;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.HttpUpgradeListener;
import io.undertow.server.SSLSessionInfo;
import io.undertow.server.ServerConnection;
import io.undertow.util.AttachmentKey;
import io.undertow.util.AttachmentList;
import io.undertow.util.BadRequestException;
import io.undertow.util.HeaderMap;
import io.undertow.util.HttpString;
import io.undertow.util.StatusCodes;
import io.undertow.util.URLUtils;
import static io.undertow.protocols.http2.Http2Channel.AUTHORITY;
import static io.undertow.protocols.http2.Http2Channel.METHOD;
import static io.undertow.protocols.http2.Http2Channel.PATH;
import static io.undertow.protocols.http2.Http2Channel.SCHEME;
/**
* A server connection. There is one connection per request
*
*
* TODO: how are we going to deal with attachments?
* @author Stuart Douglas
*/
public class Http2ServerConnection extends ServerConnection {
private static final HttpString STATUS = new HttpString(":status");
private final Http2Channel channel;
private final Http2StreamSourceChannel requestChannel;
private final Http2DataStreamSinkChannel responseChannel;
private final ConduitStreamSinkChannel conduitStreamSinkChannel;
private final ConduitStreamSourceChannel conduitStreamSourceChannel;
private final StreamSinkConduit originalSinkConduit;
private final StreamSourceConduit originalSourceConduit;
private final OptionMap undertowOptions;
private final int bufferSize;
private SSLSessionInfo sessionInfo;
private final HttpHandler rootHandler;
private HttpServerExchange exchange;
private boolean continueSent = false;
private XnioBufferPoolAdaptor poolAdaptor;
private final String protocolRequestId;
public Http2ServerConnection(Http2Channel channel, Http2StreamSourceChannel requestChannel, OptionMap undertowOptions, int bufferSize, HttpHandler rootHandler) {
this.channel = channel;
this.requestChannel = requestChannel;
this.undertowOptions = undertowOptions;
this.bufferSize = bufferSize;
this.rootHandler = rootHandler;
responseChannel = requestChannel.getResponseChannel();
originalSinkConduit = new StreamSinkChannelWrappingConduit(responseChannel);
originalSourceConduit = new StreamSourceChannelWrappingConduit(requestChannel);
this.conduitStreamSinkChannel = new ConduitStreamSinkChannel(responseChannel, originalSinkConduit);
this.conduitStreamSourceChannel = new ConduitStreamSourceChannel(channel, originalSourceConduit);
this.protocolRequestId = channel.getProtocolRequestId();
}
void setExchange(HttpServerExchange exchange) {
this.exchange = exchange;
}
/**
* Channel that is used when the request is already half closed
* @param channel
* @param undertowOptions
* @param bufferSize
* @param rootHandler
*/
public Http2ServerConnection(Http2Channel channel, Http2DataStreamSinkChannel sinkChannel, OptionMap undertowOptions, int bufferSize, HttpHandler rootHandler) {
this.channel = channel;
this.rootHandler = rootHandler;
this.requestChannel = null;
this.undertowOptions = undertowOptions;
this.bufferSize = bufferSize;
responseChannel = sinkChannel;
originalSinkConduit = new StreamSinkChannelWrappingConduit(responseChannel);
originalSourceConduit = new StreamSourceChannelWrappingConduit(requestChannel);
this.conduitStreamSinkChannel = new ConduitStreamSinkChannel(responseChannel, originalSinkConduit);
this.conduitStreamSourceChannel = new ConduitStreamSourceChannel(Configurable.EMPTY, new EmptyStreamSourceConduit(getIoThread()));
this.protocolRequestId = channel.getProtocolRequestId();
}
@Override
public Pool getBufferPool() {
if(poolAdaptor == null) {
poolAdaptor = new XnioBufferPoolAdaptor(getByteBufferPool());
}
return poolAdaptor;
}
public String getProtocolRequestId() {
return protocolRequestId;
}
public SSLSession getSslSession() {
return channel.getSslSession();
}
@Override
public ByteBufferPool getByteBufferPool() {
return channel.getBufferPool();
}
@Override
public XnioWorker getWorker() {
return channel.getWorker();
}
@Override
public XnioIoThread getIoThread() {
return channel.getIoThread();
}
@Override
public HttpServerExchange sendOutOfBandResponse(HttpServerExchange exchange) {
if (exchange == null || !HttpContinue.requiresContinueResponse(exchange)) {
throw UndertowMessages.MESSAGES.outOfBandResponseOnlyAllowedFor100Continue();
}
final HttpServerExchange newExchange = new HttpServerExchange(this);
for (HttpString header : exchange.getRequestHeaders().getHeaderNames()) {
newExchange.getRequestHeaders().putAll(header, exchange.getRequestHeaders().get(header));
}
newExchange.setProtocol(exchange.getProtocol());
newExchange.setRequestMethod(exchange.getRequestMethod());
exchange.setRequestURI(exchange.getRequestURI(), exchange.isHostIncludedInRequestURI());
exchange.setRequestPath(exchange.getRequestPath());
exchange.setRelativePath(exchange.getRelativePath());
newExchange.setPersistent(true);
Connectors.terminateRequest(newExchange);
newExchange.addResponseWrapper(new ConduitWrapper() {
@Override
public StreamSinkConduit wrap(ConduitFactory factory, HttpServerExchange exchange) {
HeaderMap headers = newExchange.getResponseHeaders();
DateUtils.addDateHeaderIfRequired(exchange);
headers.add(STATUS, exchange.getStatusCode());
Connectors.flattenCookies(exchange);
Http2HeadersStreamSinkChannel sink = new Http2HeadersStreamSinkChannel(channel, requestChannel.getStreamId(), headers);
StreamSinkChannelWrappingConduit ret = new StreamSinkChannelWrappingConduit(sink);
ret.setWriteReadyHandler(new WriteReadyHandler.ChannelListenerHandler(Connectors.getConduitSinkChannel(exchange)));
return ret;
}
});
continueSent = true;
return newExchange;
}
@Override
public boolean isContinueResponseSupported() {
return true;
}
@Override
public void terminateRequestChannel(HttpServerExchange exchange) {
if(HttpContinue.requiresContinueResponse(exchange.getRequestHeaders()) && !continueSent) {
if(requestChannel != null) { //can happen on upgrade
requestChannel.setIgnoreForceClose(true);
requestChannel.close();
//if this request requires a 100-continue and it was not sent we have to reset the stream
//we do it in a completion listener though, to make sure the response is sent first
exchange.addExchangeCompleteListener(new ExchangeCompletionListener() {
@Override
public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) {
try {
channel.sendRstStream(responseChannel.getStreamId(), Http2Channel.ERROR_CANCEL);
} finally {
nextListener.proceed();
}
}
});
}
}
}
@Override
public boolean isOpen() {
return channel.isOpen();
}
@Override
public boolean supportsOption(Option> option) {
return false;
}
@Override
public T getOption(Option option) throws IOException {
return null;
}
@Override
public T setOption(Option option, T value) throws IllegalArgumentException, IOException {
return null;
}
@Override
public void close() throws IOException {
channel.sendRstStream(requestChannel.getStreamId(), Http2Channel.ERROR_CANCEL);
}
@Override
public SocketAddress getPeerAddress() {
return channel.getPeerAddress();
}
@Override
public A getPeerAddress(Class type) {
return channel.getPeerAddress(type);
}
@Override
public ChannelListener.Setter extends ConnectedChannel> getCloseSetter() {
return channel.getCloseSetter();
}
@Override
public SocketAddress getLocalAddress() {
return channel.getLocalAddress();
}
@Override
public A getLocalAddress(Class type) {
return channel.getLocalAddress(type);
}
@Override
public OptionMap getUndertowOptions() {
return undertowOptions;
}
@Override
public int getBufferSize() {
return bufferSize;
}
@Override
public SSLSessionInfo getSslSessionInfo() {
return sessionInfo;
}
@Override
public void setSslSessionInfo(SSLSessionInfo sessionInfo) {
this.sessionInfo = sessionInfo;
}
@Override
public void addCloseListener(final CloseListener listener) {
channel.addCloseTask(new ChannelListener() {
@Override
public void handleEvent(Http2Channel channel) {
listener.closed(Http2ServerConnection.this);
}
});
}
@Override
protected StreamConnection upgradeChannel() {
throw UndertowMessages.MESSAGES.upgradeNotSupported();
}
@Override
protected ConduitStreamSinkChannel getSinkChannel() {
return conduitStreamSinkChannel;
}
@Override
protected ConduitStreamSourceChannel getSourceChannel() {
return conduitStreamSourceChannel;
}
@Override
protected StreamSinkConduit getSinkConduit(HttpServerExchange exchange, StreamSinkConduit conduit) {
HeaderMap headers = responseChannel.getHeaders();
DateUtils.addDateHeaderIfRequired(exchange);
headers.add(STATUS, exchange.getStatusCode());
Connectors.flattenCookies(exchange);
if(!Connectors.isEntityBodyAllowed(exchange)) {
//we are not allowed to send an entity body for some requests
exchange.getResponseHeaders().remove(Headers.CONTENT_LENGTH);
exchange.getResponseHeaders().remove(Headers.TRANSFER_ENCODING);
}
return originalSinkConduit;
}
@Override
protected boolean isUpgradeSupported() {
return false;
}
@Override
protected boolean isConnectSupported() {
return false;
}
@Override
protected void exchangeComplete(HttpServerExchange exchange) {
}
@Override
protected void setUpgradeListener(HttpUpgradeListener upgradeListener) {
throw UndertowMessages.MESSAGES.upgradeNotSupported();
}
@Override
protected void setConnectListener(HttpUpgradeListener connectListener) {
}
@Override
protected void maxEntitySizeUpdated(HttpServerExchange exchange) {
if(requestChannel != null) {
requestChannel.setMaxStreamSize(exchange.getMaxEntitySize());
}
}
@Override
public void addToAttachmentList(AttachmentKey> key, T value) {
channel.addToAttachmentList(key, value);
}
@Override
public T removeAttachment(AttachmentKey key) {
return channel.removeAttachment(key);
}
@Override
public T putAttachment(AttachmentKey key, T value) {
return channel.putAttachment(key, value);
}
@Override
public List getAttachmentList(AttachmentKey extends List> key) {
return channel.getAttachmentList(key);
}
@Override
public T getAttachment(AttachmentKey key) {
return channel.getAttachment(key);
}
@Override
public boolean isPushSupported() {
return channel.isPushEnabled()
&& !exchange.getRequestHeaders().contains(Headers.X_DISABLE_PUSH)
// push is not supported for already pushed streams, just for peer-initiated (odd) ids
&& responseChannel.getStreamId() % 2 != 0;
}
@Override
public boolean isRequestTrailerFieldsSupported() {
return true;
}
@Override
public boolean pushResource(String path, HttpString method, HeaderMap requestHeaders) {
return pushResource(path, method, requestHeaders, rootHandler);
}
@Override
public boolean pushResource(String path, HttpString method, HeaderMap requestHeaders, final HttpHandler handler) {
HeaderMap responseHeaders = new HeaderMap();
try {
requestHeaders.put(METHOD, method.toString());
requestHeaders.put(PATH, path.toString());
requestHeaders.put(AUTHORITY, exchange.getHostAndPort());
requestHeaders.put(SCHEME, exchange.getRequestScheme());
Http2HeadersStreamSinkChannel sink = channel.sendPushPromise(responseChannel.getStreamId(), requestHeaders, responseHeaders);
Http2ServerConnection newConnection = new Http2ServerConnection(channel, sink, getUndertowOptions(), getBufferSize(), rootHandler);
final HttpServerExchange exchange = new HttpServerExchange(newConnection, requestHeaders, responseHeaders, getUndertowOptions().get(UndertowOptions.MAX_ENTITY_SIZE, UndertowOptions.DEFAULT_MAX_ENTITY_SIZE));
newConnection.setExchange(exchange);
exchange.setRequestMethod(method);
exchange.setProtocol(Protocols.HTTP_1_1);
exchange.setRequestScheme(this.exchange.getRequestScheme());
try {
Connectors.setExchangeRequestPath(exchange, path, getUndertowOptions().get(UndertowOptions.URL_CHARSET, StandardCharsets.UTF_8.name()), getUndertowOptions().get(UndertowOptions.DECODE_URL, true), URLUtils.getSlashDecodingFlag(getUndertowOptions()), new StringBuilder(), getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_HEADERS));
} catch (ParameterLimitException | BadRequestException e) {
UndertowLogger.REQUEST_IO_LOGGER.debug("Too many parameters in HTTP/2 request", e);
exchange.setStatusCode(StatusCodes.BAD_REQUEST);
exchange.endExchange();
return false;
}
sink.setCompletionListener(new ChannelListener() {
@Override
public void handleEvent(Http2DataStreamSinkChannel channel) {
Connectors.terminateResponse(exchange);
}
});
Connectors.terminateRequest(exchange);
getIoThread().execute(new Runnable() {
@Override
public void run() {
Connectors.executeRootHandler(handler, exchange);
}
});
return true;
} catch (IOException e) {
UndertowLogger.REQUEST_IO_LOGGER.ioException(e);
return false;
}
}
@Override
public String getTransportProtocol() {
return channel.getProtocol();
}
}