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.
de.mklinger.qetcher.client.jetty.http2.HTTP2Stream Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995-2018 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;
import java.io.EOFException;
import java.io.IOException;
import java.nio.channels.WritePendingException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.Frame;
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.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.io.IdleTimeout;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpable
{
private static final Logger LOG = Log.getLogger(HTTP2Stream.class);
private final AtomicReference attachment = new AtomicReference<>();
private final AtomicReference> attributes = new AtomicReference<>();
private final AtomicReference closeState = new AtomicReference<>(CloseState.NOT_CLOSED);
private final AtomicReference writing = new AtomicReference<>();
private final AtomicInteger sendWindow = new AtomicInteger();
private final AtomicInteger recvWindow = new AtomicInteger();
private final long timeStamp = System.nanoTime();
private final ISession session;
private final int streamId;
private final boolean local;
private volatile Listener listener;
private volatile boolean localReset;
private volatile boolean remoteReset;
public HTTP2Stream(Scheduler scheduler, ISession session, int streamId, boolean local)
{
super(scheduler);
this.session = session;
this.streamId = streamId;
this.local = local;
}
@Override
public int getId()
{
return streamId;
}
@Override
public Object getAttachment()
{
return attachment.get();
}
@Override
public void setAttachment(Object attachment)
{
this.attachment.set(attachment);
}
@Override
public boolean isLocal()
{
return local;
}
@Override
public ISession getSession()
{
return session;
}
@Override
public void headers(HeadersFrame frame, Callback callback)
{
if (startWrite(callback))
session.frames(this, this, frame, Frame.EMPTY_ARRAY);
}
@Override
public void push(PushPromiseFrame frame, Promise promise, Listener listener)
{
session.push(this, promise, frame, listener);
}
@Override
public void data(DataFrame frame, Callback callback)
{
if (startWrite(callback))
session.data(this, this, frame);
}
@Override
public void reset(ResetFrame frame, Callback callback)
{
if (isReset())
return;
localReset = true;
session.frames(this, callback, frame, Frame.EMPTY_ARRAY);
}
private boolean startWrite(Callback callback)
{
if (writing.compareAndSet(null, callback))
return true;
callback.failed(new WritePendingException());
return false;
}
@Override
public Object getAttribute(String key)
{
return attributes().get(key);
}
@Override
public void setAttribute(String key, Object value)
{
attributes().put(key, value);
}
@Override
public Object removeAttribute(String key)
{
return attributes().remove(key);
}
@Override
public boolean isReset()
{
return localReset || remoteReset;
}
@Override
public boolean isClosed()
{
return closeState.get() == CloseState.CLOSED;
}
@Override
public boolean isRemotelyClosed()
{
return closeState.get() == CloseState.REMOTELY_CLOSED;
}
public boolean isLocallyClosed()
{
return closeState.get() == CloseState.LOCALLY_CLOSED;
}
@Override
public boolean isOpen()
{
return !isClosed();
}
@Override
protected void onIdleExpired(TimeoutException timeout)
{
if (LOG.isDebugEnabled())
LOG.debug("Idle timeout {}ms expired on {}", getIdleTimeout(), this);
// Notify the application.
if (notifyIdleTimeout(this, timeout))
{
// Tell the other peer that we timed out.
reset(new ResetFrame(getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
}
}
private ConcurrentMap attributes()
{
ConcurrentMap map = attributes.get();
if (map == null)
{
map = new ConcurrentHashMap<>();
if (!attributes.compareAndSet(null, map))
{
map = attributes.get();
}
}
return map;
}
@Override
public Listener getListener()
{
return listener;
}
@Override
public void setListener(Listener listener)
{
this.listener = listener;
}
@Override
public void process(Frame frame, Callback callback)
{
notIdle();
switch (frame.getType())
{
case HEADERS:
{
onHeaders((HeadersFrame)frame, callback);
break;
}
case DATA:
{
onData((DataFrame)frame, callback);
break;
}
case RST_STREAM:
{
onReset((ResetFrame)frame, callback);
break;
}
case PUSH_PROMISE:
{
onPush((PushPromiseFrame)frame, callback);
break;
}
case WINDOW_UPDATE:
{
onWindowUpdate((WindowUpdateFrame)frame, callback);
break;
}
default:
{
throw new UnsupportedOperationException();
}
}
}
private void onHeaders(HeadersFrame frame, Callback callback)
{
if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED))
session.removeStream(this);
callback.succeeded();
}
private void onData(DataFrame frame, Callback callback)
{
if (getRecvWindow() < 0)
{
// It's a bad client, it does not deserve to be
// treated gently by just resetting the stream.
session.close(ErrorCode.FLOW_CONTROL_ERROR.code, "stream_window_exceeded", Callback.NOOP);
callback.failed(new IOException("stream_window_exceeded"));
return;
}
// SPEC: remotely closed streams must be replied with a reset.
if (isRemotelyClosed())
{
reset(new ResetFrame(streamId, ErrorCode.STREAM_CLOSED_ERROR.code), Callback.NOOP);
callback.failed(new EOFException("stream_closed"));
return;
}
if (isReset())
{
// Just drop the frame.
callback.failed(new IOException("stream_reset"));
return;
}
if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED))
session.removeStream(this);
notifyData(this, frame, callback);
}
private void onReset(ResetFrame frame, Callback callback)
{
remoteReset = true;
close();
session.removeStream(this);
notifyReset(this, frame, callback);
}
private void onPush(PushPromiseFrame frame, Callback callback)
{
// Pushed streams are implicitly locally closed.
// They are closed when receiving an end-stream DATA frame.
updateClose(true, CloseState.Event.AFTER_SEND);
callback.succeeded();
}
private void onWindowUpdate(WindowUpdateFrame frame, Callback callback)
{
callback.succeeded();
}
@Override
public boolean updateClose(boolean update, CloseState.Event event)
{
if (LOG.isDebugEnabled())
LOG.debug("Update close for {} update={} event={}", this, update, event);
if (!update)
return false;
switch (event)
{
case RECEIVED:
return updateCloseAfterReceived();
case BEFORE_SEND:
return updateCloseBeforeSend();
case AFTER_SEND:
return updateCloseAfterSend();
default:
return false;
}
}
private boolean updateCloseAfterReceived()
{
while (true)
{
CloseState current = closeState.get();
switch (current)
{
case NOT_CLOSED:
{
if (closeState.compareAndSet(current, CloseState.REMOTELY_CLOSED))
return false;
break;
}
case LOCALLY_CLOSING:
{
if (closeState.compareAndSet(current, CloseState.CLOSING))
{
updateStreamCount(0, 1);
return false;
}
break;
}
case LOCALLY_CLOSED:
{
close();
return true;
}
default:
{
return false;
}
}
}
}
private boolean updateCloseBeforeSend()
{
while (true)
{
CloseState current = closeState.get();
switch (current)
{
case NOT_CLOSED:
{
if (closeState.compareAndSet(current, CloseState.LOCALLY_CLOSING))
return false;
break;
}
case REMOTELY_CLOSED:
{
if (closeState.compareAndSet(current, CloseState.CLOSING))
{
updateStreamCount(0, 1);
return false;
}
break;
}
default:
{
return false;
}
}
}
}
private boolean updateCloseAfterSend()
{
while (true)
{
CloseState current = closeState.get();
switch (current)
{
case NOT_CLOSED:
case LOCALLY_CLOSING:
{
if (closeState.compareAndSet(current, CloseState.LOCALLY_CLOSED))
return false;
break;
}
case REMOTELY_CLOSED:
case CLOSING:
{
close();
return true;
}
default:
{
return false;
}
}
}
}
public int getSendWindow()
{
return sendWindow.get();
}
public int getRecvWindow()
{
return recvWindow.get();
}
@Override
public int updateSendWindow(int delta)
{
return sendWindow.getAndAdd(delta);
}
@Override
public int updateRecvWindow(int delta)
{
return recvWindow.getAndAdd(delta);
}
@Override
public void close()
{
CloseState oldState = closeState.getAndSet(CloseState.CLOSED);
if (oldState != CloseState.CLOSED)
{
int deltaClosing = oldState == CloseState.CLOSING ? -1 : 0;
updateStreamCount(-1, deltaClosing);
onClose();
}
}
private void updateStreamCount(int deltaStream, int deltaClosing)
{
((HTTP2Session)session).updateStreamCount(isLocal(), deltaStream, deltaClosing);
}
@Override
public void succeeded()
{
Callback callback = endWrite();
if (callback != null)
callback.succeeded();
}
@Override
public void failed(Throwable x)
{
Callback callback = endWrite();
if (callback != null)
callback.failed(x);
}
private Callback endWrite()
{
return writing.getAndSet(null);
}
private void notifyData(Stream stream, DataFrame frame, Callback callback)
{
final Listener listener = this.listener;
if (listener == null)
return;
try
{
listener.onData(stream, frame, callback);
}
catch (Throwable x)
{
LOG.info("Failure while notifying listener " + listener, x);
}
}
private void notifyReset(Stream stream, ResetFrame frame, Callback callback)
{
final Listener listener = this.listener;
if (listener == null)
return;
try
{
listener.onReset(stream, frame, callback);
}
catch (Throwable x)
{
LOG.info("Failure while notifying listener " + listener, x);
}
}
private boolean notifyIdleTimeout(Stream stream, Throwable failure)
{
Listener listener = this.listener;
if (listener == null)
return true;
try
{
return listener.onIdleTimeout(stream, failure);
}
catch (Throwable x)
{
LOG.info("Failure while notifying listener " + listener, x);
return true;
}
}
@Override
public String dump()
{
return ContainerLifeCycle.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
out.append(toString()).append(System.lineSeparator());
}
@Override
public String toString()
{
return String.format("%s@%x#%d{sendWindow=%s,recvWindow=%s,reset=%b,%s,age=%d,attachment=%s}",
getClass().getSimpleName(),
hashCode(),
getId(),
sendWindow,
recvWindow,
isReset(),
closeState,
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - timeStamp),
attachment);
}
}