All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micronaut.http.netty.reactive.HandlerSubscriber Maven / Gradle / Ivy

There is a newer version: 4.7.5
Show newest version
/*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.http.netty.reactive;

import io.micronaut.core.annotation.Internal;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.concurrent.EventExecutor;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

import java.util.concurrent.atomic.AtomicBoolean;

import static io.micronaut.http.netty.reactive.HandlerSubscriber.State.CANCELLED;
import static io.micronaut.http.netty.reactive.HandlerSubscriber.State.COMPLETE;
import static io.micronaut.http.netty.reactive.HandlerSubscriber.State.INACTIVE;
import static io.micronaut.http.netty.reactive.HandlerSubscriber.State.NO_CONTEXT;
import static io.micronaut.http.netty.reactive.HandlerSubscriber.State.NO_SUBSCRIPTION;
import static io.micronaut.http.netty.reactive.HandlerSubscriber.State.NO_SUBSCRIPTION_OR_CONTEXT;
import static io.micronaut.http.netty.reactive.HandlerSubscriber.State.RUNNING;

/**
 * Subscriber that publishes received messages to the handler pipeline.
 *
 * @param  The subscriber type
 * @author Graeme Rocher
 * @since 1.0
 */
@SuppressWarnings("SubscriberImplementation")
@Internal
public class HandlerSubscriber extends ChannelDuplexHandler implements Subscriber {

    protected ChannelFuture lastWriteFuture;

    private final EventExecutor executor;
    private final AtomicBoolean hasSubscription = new AtomicBoolean();

    private volatile Subscription subscription;
    private volatile ChannelHandlerContext ctx;

    private State state = NO_SUBSCRIPTION_OR_CONTEXT;

    /**
     * Create a new handler subscriber with the default low and high watermarks.
     * 

* The supplied executor must be the same event loop as the event loop that this handler is eventually registered * with, if not, an exception will be thrown when the handler is registered. * * @param executor The executor to execute asynchronous events from the publisher on. */ public HandlerSubscriber(EventExecutor executor) { this.executor = executor; } /** * Override for custom error handling. By default, it closes the channel. * * @param error The error to handle. */ protected void error(Throwable error) { doClose(); } /** * Override for custom completion handling. By default, it closes the channel. */ protected void complete() { doClose(); } /** * The state. */ enum State { NO_SUBSCRIPTION_OR_CONTEXT, NO_SUBSCRIPTION, NO_CONTEXT, INACTIVE, RUNNING, CANCELLED, COMPLETE } @Override public void handlerAdded(ChannelHandlerContext ctx) { verifyRegisteredWithRightExecutor(ctx); switch (state) { case NO_SUBSCRIPTION_OR_CONTEXT: this.ctx = ctx; // We were in no subscription or context, now we just don't have a subscription. state = NO_SUBSCRIPTION; break; case NO_CONTEXT: this.ctx = ctx; // We were in no context, we're now fully initialised maybeStart(); break; case COMPLETE: // We are complete, close state = COMPLETE; ctx.close(); break; default: throw new IllegalStateException("This handler must only be added to a pipeline once " + state); } } @Override public void channelRegistered(ChannelHandlerContext ctx) { verifyRegisteredWithRightExecutor(ctx); ctx.fireChannelRegistered(); } @Override public void channelWritabilityChanged(ChannelHandlerContext ctx) { maybeRequestMore(); ctx.fireChannelWritabilityChanged(); } @Override public void channelActive(ChannelHandlerContext ctx) { if (state == INACTIVE) { state = RUNNING; maybeRequestMore(); } ctx.fireChannelActive(); } @Override public void channelInactive(ChannelHandlerContext ctx) { cancel(); ctx.fireChannelInactive(); } @Override public void handlerRemoved(ChannelHandlerContext ctx) { cancel(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cancel(); ctx.fireExceptionCaught(cause); } @Override public void onSubscribe(final Subscription subscription) { if (subscription == null) { throw new NullPointerException("Null subscription"); } else if (!hasSubscription.compareAndSet(false, true)) { subscription.cancel(); } else { this.subscription = subscription; executor.execute(this::provideSubscription); } } @Override public void onNext(T t) { // Publish straight to the context. onNext(t, ctx.newPromise()); } /** * Write the message with the supplied promise. * * @param t The message * @param promise The promise */ protected void onNext(T t, ChannelPromise promise) { // Publish straight to the context. lastWriteFuture = ctx.writeAndFlush(t, promise); lastWriteFuture.addListener(future -> maybeRequestMore() ); } @Override public void onError(final Throwable error) { if (error == null) { throw new NullPointerException("Null error published"); } error(error); } @Override public void onComplete() { if (lastWriteFuture == null) { complete(); } else { lastWriteFuture.addListener(channelFuture -> complete()); } } private void doClose() { executor.execute(() -> { switch (state) { case NO_SUBSCRIPTION: case INACTIVE: case RUNNING: ctx.close(); state = COMPLETE; break; default: // no-op } }); } private void maybeRequestMore() { if (ctx.channel().isWritable() && !(state == COMPLETE || state == CANCELLED)) { subscription.request(1); } } private void verifyRegisteredWithRightExecutor(ChannelHandlerContext ctx) { if (ctx.channel().isRegistered() && !executor.inEventLoop()) { throw new IllegalArgumentException("Channel handler MUST be registered with the same EventExecutor that it is created with."); } } private void cancel() { switch (state) { case NO_SUBSCRIPTION: state = CANCELLED; break; case RUNNING: case INACTIVE: subscription.cancel(); state = CANCELLED; break; default: // no-op } } private void provideSubscription() { switch (state) { case NO_SUBSCRIPTION_OR_CONTEXT: state = NO_CONTEXT; break; case NO_SUBSCRIPTION: maybeStart(); break; case CANCELLED: subscription.cancel(); break; default: // no-op } } private void maybeStart() { if (ctx.channel().isActive()) { state = RUNNING; maybeRequestMore(); } else { state = INACTIVE; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy