private void receivedDemand(long demand) { switch (state) { case BUFFERING: case DRAINING: if (addDemand(demand)) { flushBuffer(); } break; case DEMANDING: addDemand(demand); break; case IDLE: if (addDemand(demand)) { // Important to change state to demanding before doing a read, in case we get a synchronous // read back. state = DEMANDING; requestDemand(); } break; default: } }
@Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { complete(); }
private void flushBuffer() { while (!buffer.isEmpty() && (outstandingDemand > 0 || outstandingDemand == Long.MAX_VALUE)) { publishMessage(buffer.remove()); } if (buffer.isEmpty()) { if (outstandingDemand > 0) { if (state == BUFFERING) { state = DEMANDING; } // otherwise we're draining requestDemand(); } else if (state == BUFFERING) { state = IDLE; } } }
@Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { if (state == DEMANDING) { requestDemand(); } }
@Override public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception { if (acceptInboundMessage(message)) { switch (state) { case IDLE: buffer.add(message); state = BUFFERING; break; case NO_SUBSCRIBER: case BUFFERING: buffer.add(message); break; case DEMANDING: publishMessage(message); break; case DRAINING: case DONE: ReferenceCountUtil.release(message); break; case NO_CONTEXT: case NO_SUBSCRIBER_OR_CONTEXT: throw new IllegalStateException("Message received before added to the channel context"); } } else { ctx.fireChannelRead(message); } }
private void handleWebSocketResponse(ChannelHandlerContext ctx, Outgoing out) { WebSocketHttpResponse response = (WebSocketHttpResponse) out.message; WebSocketServerHandshaker handshaker = response.handshakerFactory().newHandshaker(lastRequest); if (handshaker == null) { HttpResponse res = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.UPGRADE_REQUIRED); res.headers().set(HttpHeaderNames.SEC_WEBSOCKET_VERSION, WebSocketVersion.V13.toHttpHeaderValue()); HttpUtil.setContentLength(res, 0); super.unbufferedWrite(ctx, new Outgoing(res, out.promise)); response.subscribe(new CancelledSubscriber<>()); } else { // First, insert new handlers in the chain after us for handling the websocket ChannelPipeline pipeline = ctx.pipeline(); HandlerPublisher<WebSocketFrame> publisher = new HandlerPublisher<>(ctx.executor(), WebSocketFrame.class); HandlerSubscriber<WebSocketFrame> subscriber = new HandlerSubscriber<>(ctx.executor()); pipeline.addAfter(ctx.executor(), ctx.name(), "websocket-subscriber", subscriber); pipeline.addAfter(ctx.executor(), ctx.name(), "websocket-publisher", publisher); // Now remove ourselves from the chain ctx.pipeline().remove(ctx.name()); // Now do the handshake // Wrap the request in an empty request because we don't need the WebSocket handshaker ignoring the body, // we already have handled the body. handshaker.handshake(ctx.channel(), new EmptyHttpRequest(lastRequest)); // And hook up the subscriber/publishers response.subscribe(subscriber); publisher.subscribe(response); } }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { switch (state) { case NO_SUBSCRIBER: noSubscriberError = cause; state = NO_SUBSCRIBER_ERROR; cleanup(); break; case BUFFERING: case DEMANDING: case IDLE: case DRAINING: state = DONE; cleanup(); subscriber.onError(cause); break; } }
@Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { provideChannelContext(ctx); ctx.fireChannelRegistered(); }
private boolean addDemand(long demand) { if (demand <= 0) { illegalDemand(); return false; } else { if (outstandingDemand < Long.MAX_VALUE) { outstandingDemand += demand; if (outstandingDemand < 0) { outstandingDemand = Long.MAX_VALUE; } } return true; } }
@Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // If we subscribed before the channel was active, then our read would have been ignored. if (state == DEMANDING) { requestDemand(); } ctx.fireChannelActive(); }
private void illegalDemand() { cleanup(); subscriber.onError(new IllegalArgumentException("Request for 0 or negative elements in violation of Section 3.9 of the Reactive Streams specification")); ctx.close(); state = DONE; }
@Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // If the channel is not yet registered, then it's not safe to invoke any methods on it, eg read() or close() // So don't provide the context until it is registered. if (ctx.channel().isRegistered()) { provideChannelContext(ctx); } }
@Override protected void requestDemand() { bodyRequested(ctx); super.requestDemand(); } };
private void provideSubscriber(Subscriber<? super T> subscriber) { this.subscriber = subscriber; switch (state) { case NO_SUBSCRIBER_OR_CONTEXT: state = NO_CONTEXT; break; case NO_SUBSCRIBER: if (buffer.isEmpty()) { state = IDLE; } else { state = BUFFERING; } subscriber.onSubscribe(new ChannelSubscription()); break; case DRAINING: subscriber.onSubscribe(new ChannelSubscription()); break; case NO_SUBSCRIBER_ERROR: cleanup(); state = DONE; subscriber.onSubscribe(new ChannelSubscription()); subscriber.onError(noSubscriberError); break; } }
@Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { complete(); }