package com.webpieces.http2engine.impl.client;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import org.webpieces.javasm.api.Memento;
import org.webpieces.util.logging.Logger;
import org.webpieces.util.logging.LoggerFactory;
import com.webpieces.hpack.api.dto.Http2Headers;
import com.webpieces.hpack.api.dto.Http2Push;
import com.webpieces.http2engine.api.ConnectionClosedException;
import com.webpieces.http2engine.api.client.Http2Config;
import com.webpieces.http2engine.api.client.Http2ResponseListener;
import com.webpieces.http2engine.api.client.PushPromiseListener;
import com.webpieces.http2engine.impl.shared.HeaderSettings;
import com.webpieces.http2engine.impl.shared.Level4AbstractStreamMgr;
import com.webpieces.http2engine.impl.shared.Level6LocalFlowControl;
import com.webpieces.http2engine.impl.shared.Level6RemoteFlowControl;
import com.webpieces.http2engine.impl.shared.Stream;
import com.webpieces.http2engine.impl.shared.StreamState;
import com.webpieces.http2parser.api.ConnectionException;
import com.webpieces.http2parser.api.ParseFailReason;
import com.webpieces.http2parser.api.dto.PriorityFrame;
import com.webpieces.http2parser.api.dto.RstStreamFrame;
import com.webpieces.http2parser.api.dto.lib.PartialStream;
import com.webpieces.util.locking.PermitQueue;
public class Level4ClientStreams extends Level4AbstractStreamMgr {
private static final Logger log = LoggerFactory.getLogger(Level4ClientStreams.class);
private Level5ClientStateMachine clientSm;
private HeaderSettings localSettings;
private PermitQueue<Stream> permitQueue;
//purely for logging!!! do not use for something else
private AtomicInteger acquiredCnt = new AtomicInteger(0);
private AtomicInteger releasedCnt = new AtomicInteger(0);
private int afterResetExpireSeconds;
public Level4ClientStreams(
StreamState state,
Level5ClientStateMachine clientSm,
Level6LocalFlowControl localFlowControl,
Level6RemoteFlowControl level5FlowControl,
Http2Config config,
HeaderSettings remoteSettings
) {
super(clientSm, level5FlowControl, localFlowControl, remoteSettings, state);
this.clientSm = clientSm;
this.localSettings = config.getLocalSettings();
this.remoteSettings = remoteSettings;
afterResetExpireSeconds = config.getAfterResetExpireSeconds();
permitQueue = new PermitQueue<>(config.getInitialRemoteMaxConcurrent());
}
public CompletableFuture<Stream> createStreamAndSend(Http2Headers frame, Http2ResponseListener responseListener) {
if(closedReason != null) {
return createExcepted("send request headers").thenApply((s) -> null);
}
return permitQueue.runRequest(() -> createStreamSendImpl(frame, responseListener));
}
private CompletableFuture<Stream> createStreamSendImpl(Http2Headers frame, Http2ResponseListener responseListener) {
int val = acquiredCnt.incrementAndGet();
log.info("got permit(cause="+frame+"). size="+permitQueue.availablePermits()+" acquired="+val);
Stream stream = createStream(frame.getStreamId(), responseListener, null);
return clientSm.fireToSocket(stream, frame)
.thenApply(s -> stream);
}
@Override
protected CompletableFuture<Void> fireRstToSocket(Stream stream, RstStreamFrame frame) {
return fireToSocket(stream, frame, true);
}
private Stream createStream(int streamId, Http2ResponseListener responseListener, PushPromiseListener pushListener) {
Memento initialState = clientSm.createStateMachine("stream" + streamId);
long localWindowSize = localSettings.getInitialWindowSize();
long remoteWindowSize = remoteSettings.getInitialWindowSize();
Stream stream = new Stream(streamId, initialState, responseListener, pushListener, localWindowSize, remoteWindowSize);
return streamState.create(stream);
}
@Override
public CompletableFuture<Void> sendPriorityFrame(PriorityFrame frame) {
if(closedReason != null) {
log.info("ignoring incoming frame="+frame+" since socket is shutting down");
return CompletableFuture.completedFuture(null);
}
Stream stream;
try {
stream = streamState.getStream(frame);
} catch(ConnectionException e) {
//per spec, priority frames can be received on closed stream but ignore it
return CompletableFuture.completedFuture(null);
}
return clientSm.fireToClient(stream, frame, null)
.thenApply(s -> null);
}
@Override
public CompletableFuture<Void> sendPayloadToApp(PartialStream frame) {
if(closedReason != null) {
log.info("ignoring incoming frame="+frame+" since socket is shutting down");
return CompletableFuture.completedFuture(null);
}
if(frame instanceof Http2Push) {
return sendPushPromiseToClient((Http2Push) frame);
}
Stream stream = streamState.getStream(frame);
return clientSm.fireToClient(stream, frame, () -> checkForClosedState(stream, frame, false))
.thenApply(s -> null);
}
public CompletableFuture<Void> sendPushPromiseToClient(Http2Push fullPromise) {
if(closedReason != null) {
log.info("ignoring incoming push="+fullPromise+" since socket is shutting down");
return CompletableFuture.completedFuture(null);
}
int newStreamId = fullPromise.getPromisedStreamId();
if(newStreamId % 2 == 1)
throw new ConnectionException(ParseFailReason.INVALID_STREAM_ID, newStreamId,
"Server sent bad push promise="+fullPromise+" as new stream id is incorrect and is an odd number");
Stream causalStream = streamState.getStream(fullPromise);
Http2ResponseListener listener = causalStream.getResponseListener();
PushPromiseListener pushListener = listener.newIncomingPush(newStreamId);
Stream stream = createStream(newStreamId, null, pushListener);
return clientSm.fireToClient(stream, fullPromise, null).thenApply(s -> null);
}
@Override
protected void modifyMaxConcurrentStreams(long value) {
int permitCount = permitQueue.totalPermits();
if(value == permitCount)
return;
else if (value > Integer.MAX_VALUE)
throw new IllegalArgumentException("remote setting too large");
int modifyPermitsCnt = (int) (value - permitCount);
permitQueue.modifyPermitPoolSize(modifyPermitsCnt);
}
@Override
protected void release(Stream stream, PartialStream cause) {
//request stream, so increase permits
permitQueue.releasePermit();
int val = releasedCnt.decrementAndGet();
log.info("release permit(cause="+cause+"). size="+permitQueue.availablePermits()+" releasedCnt="+val+" stream="+stream.getStreamId());
}
}