package com.webpieces.http2engine.impl.svr; 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.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.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 Level4ServerStreams extends Level4AbstractStreamMgr { private final static Logger log = LoggerFactory.getLogger(Level4ServerStreams.class); private Level5ServerStateMachine serverSm; private HeaderSettings localSettings; private volatile int streamsInProcess = 0; private PermitQueue<Void> permitQueue = new PermitQueue<>(100); //purely for logging!!! do not use for something else private AtomicInteger acquiredCnt = new AtomicInteger(0); private AtomicInteger releasedCnt = new AtomicInteger(0); public Level4ServerStreams(StreamState streamState, Level5ServerStateMachine serverSm, Level6LocalFlowControl localFlowControl, Level6RemoteFlowControl remoteFlowCtrl, HeaderSettings localSettings, HeaderSettings remoteSettings) { super(serverSm, remoteFlowCtrl, localFlowControl, remoteSettings, streamState); this.serverSm = serverSm; this.localSettings = localSettings; } @Override public CompletableFuture<Void> sendPayloadToApp(PartialStream frame) { if(frame instanceof Http2Headers && !streamState.isStreamExist(frame)) { return incomingHeadersToApp((Http2Headers) frame); } else { //this copied from client but for server this should not occur and if does is a connection error? // if(closedReason != null) { // log.info("ignoring incoming frame="+frame+" since socket is shutting down"); // return CompletableFuture.completedFuture(null); // } Stream stream = streamState.getStream(frame); return serverSm.fireToClient(stream, frame, () -> checkForClosedState(stream, frame, false)) .thenApply(s -> null); } } @Override public CompletableFuture<Void> sendMoreStreamData(Stream stream, PartialStream data) { if(stream.isPushStream() && data instanceof Http2Headers && !stream.isHeadersSent()) { stream.setHeadersSent(true); return permitQueue.runRequest(() -> { int val = acquiredCnt.incrementAndGet(); log.info("got permit(cause="+data+"). size="+permitQueue.availablePermits()+" acquired="+val); return super.fireToSocket(stream, data, false); }); } return super.sendMoreStreamData(stream, data); } private CompletableFuture<Void> incomingHeadersToApp(Http2Headers msg) { Stream stream = createStream(msg.getStreamId()); return serverSm.fireToClient(stream, msg, null).thenApply(s -> null); } private Stream createStream(int streamId) { Memento initialState = serverSm.createStateMachine("stream" + streamId); long localWindowSize = localSettings.getInitialWindowSize(); long remoteWindowSize = remoteSettings.getInitialWindowSize(); Stream stream = new Stream(streamId, initialState, null, null, localWindowSize, remoteWindowSize); return streamState.create(stream); } public CompletableFuture<Stream> sendResponseHeaderToSocket(Stream origStream, Http2Headers frame) { if(closedReason != null) { return createExcepted("sending response headers").thenApply((s) -> null); } Stream stream = streamState.getStream(frame); return serverSm.fireToSocket(stream, frame) .thenApply(s -> stream); } public CompletableFuture<Stream> sendPush(Http2Push push) { int newStreamId = push.getPromisedStreamId(); Stream stream = createStream(newStreamId); return serverSm.fireToSocket(stream, push) .thenApply(s -> stream); } @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 public CompletableFuture<Void> sendPriorityFrame(PriorityFrame msg) { throw new UnsupportedOperationException("not supported yet"); } @Override protected CompletableFuture<Void> fireRstToSocket(Stream stream, RstStreamFrame frame) { // TODO Auto-generated method stub return null; } @Override protected void release(Stream stream, PartialStream cause) { if(stream.isPushStream()) { permitQueue.releasePermit(); int val = releasedCnt.decrementAndGet(); log.info("release permit(cause="+cause+"). size="+permitQueue.availablePermits()+" releasedCnt="+val+" stream="+stream.getStreamId()); } } }