io.reactivesocket.cli.Main.java Source code

Java tutorial

Introduction

Here is the source code for io.reactivesocket.cli.Main.java

Source

/**
 * Copyright 2015 Netflix, Inc.
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.reactivesocket.cli;

import com.google.common.base.Charsets;
import com.google.common.io.CharSource;
import com.google.common.io.Files;
import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import io.airlift.airline.Help;
import io.airlift.airline.Option;
import io.airlift.airline.ParseException;
import io.airlift.airline.SingleCommand;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.util.internal.logging.Slf4JLoggerFactory;
import io.reactivesocket.AbstractReactiveSocket;
import io.reactivesocket.ConnectionSetupPayload;
import io.reactivesocket.Payload;
import io.reactivesocket.ReactiveSocket;
import io.reactivesocket.client.ReactiveSocketClient;
import io.reactivesocket.frame.ByteBufferUtil;
import io.reactivesocket.lease.DisabledLeaseAcceptingSocket;
import io.reactivesocket.reactivestreams.extensions.Px;
import io.reactivesocket.server.ReactiveSocketServer;
import io.reactivesocket.transport.TransportServer;
import io.reactivesocket.util.PayloadImpl;
import io.reactivex.Flowable;
import io.reactivex.netty.client.ClientState;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Completable;
import rx.Observable;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import static io.reactivesocket.client.KeepAliveProvider.never;
import static io.reactivesocket.client.SetupProvider.keepAlive;
import static java.util.stream.Collectors.toList;
import static java.util.stream.IntStream.range;
import static rx.RxReactiveStreams.toObservable;

/**
 * Simple command line tool to make a ReactiveSocket connection and send/receive elements.
 * <p>
 * Currently limited in features, only supports a text/line based approach.
 */
@Command(name = Main.NAME, description = "CLI for ReactiveSocket.")
public class Main {
    static final String NAME = "reactivesocket-cli";

    @Option(name = { "-h", "--help" }, description = "Display help information")
    public boolean help = false;

    @Option(name = "--sub", description = "Request Subscription")
    public boolean subscription;

    @Option(name = "--str", description = "Request Stream")
    public boolean stream;

    @Option(name = "--rr", description = "Request Response")
    public boolean requestResponse;

    @Option(name = "--fnf", description = "Fire and Forget")
    public boolean fireAndForget;

    @Option(name = "--channel", description = "Channel")
    public boolean channel;

    @Option(name = "--metadata", description = "Metadata Push")
    public boolean metadataPush;

    @Option(name = "--server", description = "Start server instead of client")
    public boolean serverMode = false;

    @Option(name = { "-i", "--input" }, description = "String input or @path/to/file")
    public String input;

    @Option(name = "--debug", description = "Debug Output")
    public boolean debug;

    @Option(name = "--ops", description = "Operation Count")
    public int operations = 1;

    @Arguments(title = "target", description = "Endpoint URL", required = true)
    public List<String> arguments = new ArrayList<>();

    public ReactiveSocket client;

    public OutputHandler outputHandler;
    private TransportServer.StartedServer server;

    private Logger retainedLogger;

    public void run() throws IOException, URISyntaxException, InterruptedException {
        System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", debug ? "debug" : "warn");

        retainedLogger = LoggerFactory.getLogger("");

        InternalLoggerFactory.setDefaultFactory(Slf4JLoggerFactory.INSTANCE);

        if (outputHandler == null) {
            outputHandler = new ConsoleOutputHandler();
        }

        try {
            URI uri = new URI(arguments.get(0));

            if (serverMode) {
                server = ReactiveSocketServer.create(ConnectionHelper.buildServerConnection(uri))
                        .start((setupPayload, reactiveSocket) -> new DisabledLeaseAcceptingSocket(
                                createServerRequestHandler(setupPayload)));

                server.awaitShutdown();
            } else {
                client = Flowable.fromPublisher(ReactiveSocketClient
                        .create(ConnectionHelper.buildClientConnection(uri), keepAlive(never()).disableLease())
                        .connect()).blockingFirst();

                Completable run = run(client);
                run.await();
            }
        } catch (Exception e) {
            outputHandler.error("error", e);
        } finally {
            ClientState.defaultEventloopGroup().shutdownGracefully();
        }
    }

    public ReactiveSocket createServerRequestHandler(ConnectionSetupPayload setupPayload) {
        LoggerFactory.getLogger(Main.class).debug("setup payload " + setupPayload);

        return new AbstractReactiveSocket() {
            @Override
            public Publisher<Void> fireAndForget(Payload payload) {
                outputHandler.showOutput(ByteBufferUtil.toUtf8String(payload.getData()));
                return Px.empty();
            }

            @Override
            public Publisher<Payload> requestResponse(Payload payload) {
                return handleIncomingPayload(payload);
            }

            @Override
            public Publisher<Payload> requestStream(Payload payload) {
                return handleIncomingPayload(payload);
            }

            @Override
            public Publisher<Payload> requestSubscription(Payload payload) {
                return handleIncomingPayload(payload);
            }

            @Override
            public Publisher<Payload> requestChannel(Publisher<Payload> payloads) {
                // TODO implement channel functionality
                return super.requestChannel(payloads);
            }

            @Override
            public Publisher<Void> metadataPush(Payload payload) {
                outputHandler.showOutput(ByteBufferUtil.toUtf8String(payload.getMetadata()));
                return Px.empty();
            }
        };
    }

    private Publisher<Payload> handleIncomingPayload(Payload payload) {
        outputHandler.showOutput(ByteBufferUtil.toUtf8String(payload.getData()));
        return inputPublisher();
    }

    public Completable run(ReactiveSocket client) {
        try {
            return runAllOperations(client);
        } catch (Exception e) {
            outputHandler.error("error", e);
        }

        return Completable.complete();
    }

    private Completable runAllOperations(ReactiveSocket client) {
        List<Completable> l = range(0, operations).mapToObj(i -> runSingleOperation(client)).collect(toList());

        return Completable.merge(l);
    }

    private Completable runSingleOperation(ReactiveSocket client) {
        if (fireAndForget) {
            return toObservable(client.fireAndForget(singleInputPayload())).toCompletable();
        } else if (metadataPush) {
            return toObservable(client.metadataPush(singleInputPayload())).toCompletable();
        }

        Observable<Payload> source;
        if (requestResponse) {
            source = toObservable(client.requestResponse(singleInputPayload()));
        } else if (subscription) {
            source = toObservable(client.requestSubscription(singleInputPayload()));
        } else if (stream) {
            source = toObservable(client.requestStream(singleInputPayload()));
        } else {
            // Defaults to channel for interactive mode.
            //TODO: We should have the mode as a group defaulting to channel?
            if (!channel) {
                outputHandler
                        .info("Using request-channel interaction mode, choose an option to use a different mode.");
            }
            outputHandler.info("Type commands to send to the server.");
            source = toObservable(client.requestChannel(inputPublisher()));
        }

        return source.map(Payload::getData).map(ByteBufferUtil::toUtf8String).doOnNext(outputHandler::showOutput)
                .doOnError(e -> outputHandler.error("error from server", e))
                .onExceptionResumeNext(Observable.empty()).toCompletable();
    }

    private Publisher<Payload> inputPublisher() {
        CharSource is;

        if (input == null) {
            is = SystemInCharSource.INSTANCE;
        } else if (input.startsWith("@")) {
            is = Files.asCharSource(inputFile(), Charsets.UTF_8);
        } else {
            is = CharSource.wrap(input);
        }

        return ObservableIO.lines(is);
    }

    private PayloadImpl singleInputPayload() {
        String inputString;

        if (input == null) {
            Scanner in = new Scanner(System.in);
            inputString = in.nextLine();
        } else if (input.startsWith("@")) {
            try {
                inputString = Files.toString(inputFile(), StandardCharsets.UTF_8);
            } catch (IOException e) {
                throw new UsageException(e.toString());
            }
        } else {
            inputString = input;
        }

        return new PayloadImpl(inputString, "");
    }

    private File inputFile() {
        File file = new File(input.substring(1));

        if (!file.isFile()) {
            throw new UsageException("file not found: " + file);
        }

        return file;
    }

    private static Main fromArgs(String... args) {
        SingleCommand<Main> cmd = SingleCommand.singleCommand(Main.class);
        try {
            return cmd.parse(args);
        } catch (ParseException e) {
            System.err.println(e.getMessage());
            Help.help(cmd.getCommandMetadata());
            System.exit(-1);
            return null;
        }
    }

    public static void main(String... args) throws Exception {
        fromArgs(args).run();
    }
}