package com.webpieces.http2engine.impl.shared; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; import org.webpieces.util.logging.Logger; import org.webpieces.util.logging.LoggerFactory; import com.webpieces.http2engine.api.ConnectionClosedException; import com.webpieces.http2engine.api.ConnectionReset; import com.webpieces.http2engine.api.client.Http2ResponseListener; import com.webpieces.http2engine.api.client.PushPromiseListener; import com.webpieces.http2engine.api.server.StreamReference; import com.webpieces.http2parser.api.ConnectionException; import com.webpieces.http2parser.api.StreamException; import com.webpieces.http2parser.api.dto.PriorityFrame; import com.webpieces.http2parser.api.dto.RstStreamFrame; import com.webpieces.http2parser.api.dto.WindowUpdateFrame; import com.webpieces.http2parser.api.dto.lib.PartialStream; public abstract class Level4AbstractStreamMgr { private final static Logger log = LoggerFactory.getLogger(Level4AbstractStreamMgr.class); protected StreamState streamState; protected HeaderSettings remoteSettings; private Level6RemoteFlowControl remoteFlowControl; private Level6LocalFlowControl localFlowControl; protected ConnectionReset closedReason; private Level5AbstractStateMachine stateMachine; public Level4AbstractStreamMgr( Level5AbstractStateMachine stateMachine, Level6RemoteFlowControl level5RemoteFlow, Level6LocalFlowControl localFlowControl, HeaderSettings remoteSettings2, StreamState streamState) { this.stateMachine = stateMachine; this.remoteFlowControl = level5RemoteFlow; this.localFlowControl = localFlowControl; this.remoteSettings = remoteSettings2; this.streamState = streamState; } public abstract CompletableFuture<Void> sendPayloadToApp(PartialStream msg); protected abstract CompletableFuture<Void> fireRstToSocket(Stream stream, RstStreamFrame frame); public CompletableFuture<Void> sendRstToServerAndClient(StreamException e) { if(closedReason != null) { log.info("ignoring incoming reset since socket is shutting down"); return CompletableFuture.completedFuture(null); } RstStreamFrame frame = new RstStreamFrame(); frame.setKnownErrorCode(e.getReason().getErrorCode()); frame.setStreamId(e.getStreamId()); Stream stream = streamState.getStream(frame); return fireRstToSocket(stream, frame) .thenCompose(t -> localFlowControl.fireToClient(stream, frame)); } public CompletableFuture<Void> sendClientResetsAndSvrGoAway(ConnectionException reset) { return sendClientResetsAndSvrGoAway(new ConnectionReset(reset)); } public CompletableFuture<Void> sendClientResetsAndSvrGoAway(ConnectionReset reset) { closedReason = reset; ConcurrentMap<Integer, Stream> streams = streamState.closeEngine(); for(Stream stream : streams.values()) { Http2ResponseListener responseListener = stream.getResponseListener(); if(responseListener != null) fireReset((c) -> responseListener.incomingPartialResponse(c)); PushPromiseListener pushListener = stream.getPushListener(); if(pushListener != null) fireReset((c) -> pushListener.incomingPushPromise(c)); StreamReference writer = stream.getStreamWriter(); if(writer != null) fireReset((c) -> writer.cancel(c).thenApply((w) -> null)); } if(!reset.isFarEndClosed()) return remoteFlowControl.goAway(reset.getCause()); return CompletableFuture.completedFuture(null); } private void fireReset(Function<ConnectionReset, CompletableFuture<Void>> clientFunction) { CompletableFuture<Void> future = new CompletableFuture<Void>(); try { future = clientFunction.apply(closedReason); } catch(Throwable t) { future.completeExceptionally(t); } future.exceptionally((t) -> { log.error("when trying inform client of connection error, client had an exception", t); return null; }); } public CompletableFuture<Void> updateWindowSize(WindowUpdateFrame msg) { if(closedReason != null) { log.info("ignoring incoming window update since socket is shutting down"); return CompletableFuture.completedFuture(null); } else if(msg.getStreamId() == 0) { return remoteFlowControl.updateConnectionWindowSize(msg); } else { Stream stream = streamState.getStream(msg); if(stream != null) return remoteFlowControl.updateStreamWindowSize(stream, msg); return CompletableFuture.completedFuture(null); } } public CompletableFuture<Void> sendMoreStreamData(Stream stream, PartialStream data) { if(closedReason != null) { return createExcepted("sending "+data.getClass().getSimpleName()); } return fireToSocket(stream, data, false); } public CompletableFuture<Void> createExcepted(String extra) { log.info("returning CompletableFuture.exception since this socket is closed(while doing '"+extra+"'):"+closedReason.getReason()); CompletableFuture<Void> future = new CompletableFuture<>(); ConnectionClosedException exception = new ConnectionClosedException("Connection closed or closing:"+closedReason.getReason()); if(closedReason.getCause() != null) exception.initCause(closedReason.getCause()); future.completeExceptionally(exception); return future; } protected CompletableFuture<Void> fireToSocket(Stream stream, PartialStream frame, boolean keepDelayedState) { return stateMachine.fireToSocket(stream, frame).thenApply(v -> { checkForClosedState(stream, frame, keepDelayedState); return null; }); } protected void checkForClosedState(Stream stream, PartialStream cause, boolean keepDelayedState) { //If a stream ends up in closed state, for request type streams, we must release a permit on //max concurrent streams boolean isClosed = stateMachine.isInClosedState(stream); if(!isClosed) return; //do nothing log.info("stream closed="+stream.getStreamId()); if(!keepDelayedState) { Stream removedStream = streamState.remove(stream); if(removedStream == null) return; //someone else closed the stream. they beat us to it so just return } else { //streamState.addDelayedRemove(stream, afterResetExpireSeconds); throw new UnsupportedOperationException("not supported"); } release(stream, cause); return; //we closed the stream } protected abstract void release(Stream stream, PartialStream cause); public void setMaxConcurrentStreams(long value) { remoteSettings.setMaxConcurrentStreams(value); modifyMaxConcurrentStreams(value); } protected abstract void modifyMaxConcurrentStreams(long value); public abstract CompletableFuture<Void> sendPriorityFrame(PriorityFrame msg); }