private void updateSendWindowSize( final ChannelHandlerContext ctx, int streamId, int deltaWindowSize) { spdySession.updateSendWindowSize(streamId, deltaWindowSize); while (true) { // Check if we have unblocked a stalled stream SpdySession.PendingWrite pendingWrite = spdySession.getPendingWrite(streamId); if (pendingWrite == null) { return; } SpdyDataFrame spdyDataFrame = pendingWrite.spdyDataFrame; int dataFrameSize = spdyDataFrame.content().readableBytes(); int writeStreamId = spdyDataFrame.streamId(); int sendWindowSize = spdySession.getSendWindowSize(writeStreamId); int sessionSendWindowSize = spdySession.getSendWindowSize(SPDY_SESSION_STREAM_ID); sendWindowSize = Math.min(sendWindowSize, sessionSendWindowSize); if (sendWindowSize <= 0) { return; } else if (sendWindowSize < dataFrameSize) { // We can send a partial frame spdySession.updateSendWindowSize(writeStreamId, -1 * sendWindowSize); spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * sendWindowSize); // Create a partial data frame whose length is the current window size SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame( writeStreamId, spdyDataFrame.content().readSlice(sendWindowSize).retain()); // The transfer window size is pre-decremented when sending a data frame downstream. // Close the session on write failures that leave the transfer window in a corrupt state. ctx.writeAndFlush(partialDataFrame) .addListener( new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { issueSessionError(ctx, SpdySessionStatus.INTERNAL_ERROR); } } }); } else { // Window size is large enough to send entire data frame spdySession.removePendingWrite(writeStreamId); spdySession.updateSendWindowSize(writeStreamId, -1 * dataFrameSize); spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * dataFrameSize); // Close the local side of the stream if this is the last frame if (spdyDataFrame.isLast()) { halfCloseStream(writeStreamId, false, pendingWrite.promise); } // The transfer window size is pre-decremented when sending a data frame downstream. // Close the session on write failures that leave the transfer window in a corrupt state. ctx.writeAndFlush(spdyDataFrame, pendingWrite.promise) .addListener( new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { issueSessionError(ctx, SpdySessionStatus.INTERNAL_ERROR); } } }); } } }
private void handleOutboundMessage(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (msg instanceof SpdyDataFrame) { SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg; int streamId = spdyDataFrame.streamId(); // Frames must not be sent on half-closed streams if (spdySession.isLocalSideClosed(streamId)) { spdyDataFrame.release(); promise.setFailure(PROTOCOL_EXCEPTION); return; } /* * SPDY Data frame flow control processing requirements: * * Sender must not send a data frame with data length greater * than the transfer window size. * * After sending each data frame, the sender decrements its * transfer window size by the amount of data transmitted. * * When the window size becomes less than or equal to 0, the * sender must pause transmitting data frames. */ int dataLength = spdyDataFrame.content().readableBytes(); int sendWindowSize = spdySession.getSendWindowSize(streamId); int sessionSendWindowSize = spdySession.getSendWindowSize(SPDY_SESSION_STREAM_ID); sendWindowSize = Math.min(sendWindowSize, sessionSendWindowSize); if (sendWindowSize <= 0) { // Stream is stalled -- enqueue Data frame and return spdySession.putPendingWrite(streamId, new SpdySession.PendingWrite(spdyDataFrame, promise)); return; } else if (sendWindowSize < dataLength) { // Stream is not stalled but we cannot send the entire frame spdySession.updateSendWindowSize(streamId, -1 * sendWindowSize); spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * sendWindowSize); // Create a partial data frame whose length is the current window size SpdyDataFrame partialDataFrame = new DefaultSpdyDataFrame( streamId, spdyDataFrame.content().readSlice(sendWindowSize).retain()); // Enqueue the remaining data (will be the first frame queued) spdySession.putPendingWrite(streamId, new SpdySession.PendingWrite(spdyDataFrame, promise)); // The transfer window size is pre-decremented when sending a data frame downstream. // Close the session on write failures that leave the transfer window in a corrupt state. final ChannelHandlerContext context = ctx; ctx.write(partialDataFrame) .addListener( new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { issueSessionError(context, SpdySessionStatus.INTERNAL_ERROR); } } }); return; } else { // Window size is large enough to send entire data frame spdySession.updateSendWindowSize(streamId, -1 * dataLength); spdySession.updateSendWindowSize(SPDY_SESSION_STREAM_ID, -1 * dataLength); // The transfer window size is pre-decremented when sending a data frame downstream. // Close the session on write failures that leave the transfer window in a corrupt state. final ChannelHandlerContext context = ctx; promise.addListener( new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { issueSessionError(context, SpdySessionStatus.INTERNAL_ERROR); } } }); } // Close the local side of the stream if this is the last frame if (spdyDataFrame.isLast()) { halfCloseStream(streamId, false, promise); } } else if (msg instanceof SpdySynStreamFrame) { SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg; int streamId = spdySynStreamFrame.streamId(); if (isRemoteInitiatedId(streamId)) { promise.setFailure(PROTOCOL_EXCEPTION); return; } byte priority = spdySynStreamFrame.priority(); boolean remoteSideClosed = spdySynStreamFrame.isUnidirectional(); boolean localSideClosed = spdySynStreamFrame.isLast(); if (!acceptStream(streamId, priority, remoteSideClosed, localSideClosed)) { promise.setFailure(PROTOCOL_EXCEPTION); return; } } else if (msg instanceof SpdySynReplyFrame) { SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg; int streamId = spdySynReplyFrame.streamId(); // Frames must not be sent on half-closed streams if (!isRemoteInitiatedId(streamId) || spdySession.isLocalSideClosed(streamId)) { promise.setFailure(PROTOCOL_EXCEPTION); return; } // Close the local side of the stream if this is the last frame if (spdySynReplyFrame.isLast()) { halfCloseStream(streamId, false, promise); } } else if (msg instanceof SpdyRstStreamFrame) { SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg; removeStream(spdyRstStreamFrame.streamId(), promise); } else if (msg instanceof SpdySettingsFrame) { SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg; int settingsMinorVersion = spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MINOR_VERSION); if (settingsMinorVersion >= 0 && settingsMinorVersion != minorVersion) { // Settings frame had the wrong minor version promise.setFailure(PROTOCOL_EXCEPTION); return; } int newConcurrentStreams = spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS); if (newConcurrentStreams >= 0) { localConcurrentStreams = newConcurrentStreams; } // Persistence flag are inconsistent with the use of SETTINGS to communicate // the initial window size. Remove flags from the sender requesting that the // value be persisted. Remove values that the sender indicates are persisted. if (spdySettingsFrame.isPersisted(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE)) { spdySettingsFrame.removeValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE); } spdySettingsFrame.setPersistValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE, false); int newInitialWindowSize = spdySettingsFrame.getValue(SpdySettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE); if (newInitialWindowSize >= 0) { updateInitialReceiveWindowSize(newInitialWindowSize); } } else if (msg instanceof SpdyPingFrame) { SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg; if (isRemoteInitiatedId(spdyPingFrame.id())) { ctx.fireExceptionCaught( new IllegalArgumentException("invalid PING ID: " + spdyPingFrame.id())); return; } pings.getAndIncrement(); } else if (msg instanceof SpdyGoAwayFrame) { // Why is this being sent? Intercept it and fail the write. // Should have sent a CLOSE ChannelStateEvent promise.setFailure(PROTOCOL_EXCEPTION); return; } else if (msg instanceof SpdyHeadersFrame) { SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg; int streamId = spdyHeadersFrame.streamId(); // Frames must not be sent on half-closed streams if (spdySession.isLocalSideClosed(streamId)) { promise.setFailure(PROTOCOL_EXCEPTION); return; } // Close the local side of the stream if this is the last frame if (spdyHeadersFrame.isLast()) { halfCloseStream(streamId, false, promise); } } else if (msg instanceof SpdyWindowUpdateFrame) { // Why is this being sent? Intercept it and fail the write. promise.setFailure(PROTOCOL_EXCEPTION); return; } ctx.write(msg, promise); }