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.eclipse.jetty.http2.server.HttpTransportOverHTTP2 Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http2.server;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpTransport;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class HttpTransportOverHTTP2 implements HttpTransport
{
private static final Logger LOG = Log.getLogger(HttpTransportOverHTTP2.class);
private final AtomicBoolean commit = new AtomicBoolean();
private final TransportCallback transportCallback = new TransportCallback();
private final Connector connector;
private final HTTP2ServerConnection connection;
private IStream stream;
private MetaData metaData;
public HttpTransportOverHTTP2(Connector connector, HTTP2ServerConnection connection)
{
this.connector = connector;
this.connection = connection;
}
@Override
public boolean isOptimizedForDirectBuffers()
{
// Because sent buffers are passed directly to the endpoint without
// copying we can defer to the endpoint
return connection.getEndPoint().isOptimizedForDirectBuffers();
}
public IStream getStream()
{
return stream;
}
public void setStream(IStream stream)
{
if (LOG.isDebugEnabled())
LOG.debug("{} setStream {}", this, stream.getId());
this.stream = stream;
}
public void recycle()
{
this.stream = null;
commit.set(false);
}
@Override
public void send(MetaData.Response info, boolean isHeadRequest, ByteBuffer content, boolean lastContent, Callback callback)
{
boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest;
if (info != null)
{
metaData = info;
int status = info.getStatus();
boolean informational = HttpStatus.isInformational(status) && status != HttpStatus.SWITCHING_PROTOCOLS_101;
if (informational)
{
if (transportCallback.start(callback, false))
sendHeaders(info, false, transportCallback);
}
else
{
boolean needsCommit = commit.compareAndSet(false, true);
if (needsCommit)
{
Supplier trailers = info.getTrailerSupplier();
if (hasContent)
{
Callback nested = trailers == null || !lastContent ? callback : new SendTrailers(callback);
Callback commitCallback = new Callback.Nested(nested)
{
@Override
public void succeeded()
{
if (transportCallback.start(nested, false))
sendContent(content, lastContent, trailers == null && lastContent, transportCallback);
}
};
if (transportCallback.start(commitCallback, true))
sendHeaders(info, false, transportCallback);
}
else
{
Callback nested = trailers == null ? callback : new SendTrailers(callback);
if (transportCallback.start(nested, true))
sendHeaders(info, trailers == null && lastContent, transportCallback);
}
}
else
{
callback.failed(new IllegalStateException("committed"));
}
}
}
else
{
if (hasContent || lastContent)
{
Supplier trailers = metaData.getTrailerSupplier();
Callback nested = trailers == null ? callback : new SendTrailers(callback);
if (transportCallback.start(nested, false))
sendContent(content, lastContent, trailers == null && lastContent, transportCallback);
}
else
{
callback.succeeded();
}
}
}
@Override
public boolean isPushSupported()
{
return stream.getSession().isPushEnabled();
}
@Override
public void push(final MetaData.Request request)
{
if (!stream.getSession().isPushEnabled())
{
if (LOG.isDebugEnabled())
LOG.debug("HTTP/2 Push disabled for {}", request);
return;
}
if (LOG.isDebugEnabled())
LOG.debug("HTTP/2 Push {}", request);
stream.push(new PushPromiseFrame(stream.getId(), 0, request), new Promise()
{
@Override
public void succeeded(Stream pushStream)
{
connection.push(connector, (IStream)pushStream, request);
}
@Override
public void failed(Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not push " + request, x);
}
}, new Stream.Listener.Adapter()); // TODO: handle reset from the client ?
}
private void sendHeaders(MetaData.Response info, boolean endStream, Callback callback)
{
if (LOG.isDebugEnabled())
{
LOG.debug("HTTP2 Response #{}/{}:{}{} {}{}{}",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
System.lineSeparator(), HttpVersion.HTTP_2, info.getStatus(),
System.lineSeparator(), info.getFields());
}
HeadersFrame frame = new HeadersFrame(stream.getId(), info, null, endStream);
stream.headers(frame, callback);
}
private void sendContent(ByteBuffer content, boolean lastContent, boolean endStream, Callback callback)
{
if (LOG.isDebugEnabled())
{
LOG.debug("HTTP2 Response #{}/{}: {} content bytes{}",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
content.remaining(), lastContent ? " (last chunk)" : "");
}
DataFrame frame = new DataFrame(stream.getId(), content, endStream);
stream.data(frame, callback);
}
private void sendTrailers(MetaData metaData, Callback callback)
{
if (LOG.isDebugEnabled())
{
LOG.debug("HTTP2 Response #{}/{}: trailers",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()));
}
HeadersFrame frame = new HeadersFrame(stream.getId(), metaData, null, true);
stream.headers(frame, callback);
}
public void onStreamFailure(Throwable failure)
{
transportCallback.failed(failure);
}
public boolean onStreamTimeout(Throwable failure)
{
return transportCallback.onIdleTimeout(failure);
}
@Override
public void onCompleted()
{
// If the stream is not closed, it is still reading the request content.
// Send a reset to the other end so that it stops sending data.
if (!stream.isClosed())
{
if (LOG.isDebugEnabled())
LOG.debug("HTTP2 Response #{}: unconsumed request content, resetting stream", stream.getId());
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
}
// Consume the existing queued data frames to
// avoid stalling the session flow control.
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
if (channel != null)
channel.consumeInput();
}
@Override
public void abort(Throwable failure)
{
IStream stream = this.stream;
if (LOG.isDebugEnabled())
LOG.debug("HTTP2 Response #{}/{} aborted", stream == null ? -1 : stream.getId(),
stream == null ? -1 : Integer.toHexString(stream.getSession().hashCode()));
if (stream != null)
stream.reset(new ResetFrame(stream.getId(), ErrorCode.INTERNAL_ERROR.code), Callback.NOOP);
}
private class TransportCallback implements Callback
{
private State state = State.IDLE;
private Callback callback;
private Throwable failure;
private boolean commit;
public boolean start(Callback callback, boolean commit)
{
State state;
Throwable failure;
synchronized (this)
{
state = this.state;
failure = this.failure;
if (state == State.IDLE)
{
this.state = State.WRITING;
this.callback = callback;
this.commit = commit;
return true;
}
}
if (failure == null)
failure = new IllegalStateException("Invalid transport state: " + state);
callback.failed(failure);
return false;
}
@Override
public void succeeded()
{
boolean commit;
Callback callback = null;
synchronized (this)
{
commit = this.commit;
if (state == State.WRITING)
{
callback = this.callback;
this.callback = null;
this.state = State.IDLE;
}
}
if (LOG.isDebugEnabled())
LOG.debug("HTTP2 Response #{}/{} {} {}",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
commit ? "commit" : "flush",
callback == null ? "failure" : "success");
if (callback != null)
callback.succeeded();
}
@Override
public void failed(Throwable failure)
{
boolean commit;
Callback callback = null;
synchronized (this)
{
commit = this.commit;
// Only fail pending writes, as we
// may need to write an error page.
if (state == State.WRITING)
{
this.state = State.FAILED;
this.failure = failure;
callback = this.callback;
this.callback = null;
}
}
if (LOG.isDebugEnabled())
LOG.debug(String.format("HTTP2 Response #%d/%h failed to %s", stream.getId(), stream.getSession(), commit ? "commit" : "flush"), failure);
if (callback != null)
callback.failed(failure);
}
@Override
public InvocationType getInvocationType()
{
Callback callback;
synchronized (this)
{
callback = this.callback;
}
return callback != null ? callback.getInvocationType() : Callback.super.getInvocationType();
}
private boolean onIdleTimeout(Throwable failure)
{
boolean result;
Callback callback = null;
synchronized (this)
{
// Ignore idle timeouts if not writing,
// as the application may be suspended.
result = state == State.WRITING;
if (result)
{
this.state = State.TIMEOUT;
callback = this.callback;
this.callback = null;
this.failure = failure;
}
}
if (LOG.isDebugEnabled())
LOG.debug(String.format("HTTP2 Response #%d/%h idle timeout", stream.getId(), stream.getSession()), failure);
if (result)
callback.failed(failure);
return result;
}
}
private enum State
{
IDLE, WRITING, FAILED, TIMEOUT
}
private class SendTrailers extends Callback.Nested
{
private SendTrailers(Callback callback)
{
super(callback);
}
@Override
public void succeeded()
{
if (transportCallback.start(getCallback(), false))
sendTrailers(new MetaData(HttpVersion.HTTP_2, metaData.getTrailerSupplier().get()), transportCallback);
}
}
}