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

au.gov.amsa.streams.StringSockets Maven / Gradle / Ivy

The newest version!
package au.gov.amsa.streams;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.davidmoten.guavamini.annotations.VisibleForTesting;
import com.github.davidmoten.rx.Checked;
import com.github.davidmoten.rx.RetryWhen;
import com.github.davidmoten.util.Preconditions;

import rx.Observable;
import rx.Scheduler;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

public final class StringSockets {

    private static final Logger log = LoggerFactory.getLogger(StringSockets.class);

    /**
     * Returns an Observable sequence of strings (not lines) from the given host
     * and port. If the stream is quiet for quietTimeoutMs then a
     * reconnect will be attempted. Note that this is a good idea with TCPIP
     * connections as for instance a firewall can simply drop a quiet connection
     * without the client being aware of it. If any exception occurs a reconnect
     * will be attempted after reconnectDelayMs. If the socket is
     * closed by the server (the end of the input stream is reached) then a
     * reconnect is attempted after reconnectDelayMs.
     * 
     * @param host
     * @param port
     * @param quietTimeoutMs
     * @param reconnectDelayMs
     * @param charset
     * @param scheduler
     * @return Observable sequence of strings (not lines).
     */
    public static Observable from(final String host, final int port, long quietTimeoutMs,
            long reconnectDelayMs, Charset charset, Scheduler scheduler) {
        // delay connect by delayMs so that if server closes
        // stream on every connect we won't be in a mad loop of
        // failing connections
        return from(socketCreator(host, port, (int) quietTimeoutMs), quietTimeoutMs,
                reconnectDelayMs, charset, scheduler); //
    }

    public static Observable from(Func0 socketCreator, long quietTimeoutMs,
            long reconnectDelayMs, Charset charset, Scheduler scheduler) {
        return strings(socketCreator, charset) //
                // additional timeout appears to be necessary for certain use
                // cases like when the server side does not close the socket
                .timeout(quietTimeoutMs + 100, TimeUnit.MILLISECONDS) //
                .subscribeOn(scheduler) //
                // if any exception occurs retry
                .retryWhen(RetryWhen //
                        .delay(reconnectDelayMs, TimeUnit.MILLISECONDS) //
                        .build()) //
                // all subscribers use the same stream
                .share();
    }

    public static class Builder {
        private final String host;
        private int port = 6564;
        private long quietTimeoutMs = 60000;
        private long reconnectDelayMs = 30000;
        private Charset charset = StandardCharsets.UTF_8;
        private Scheduler scheduler = Schedulers.io();

        Builder(String host) {
            this.host = host;
        }

        public Builder port(int port) {
            this.port = port;
            return this;
        }

        public Builder quietTimeoutMs(long quietTimeoutMs) {
            this.quietTimeoutMs = quietTimeoutMs;
            return this;
        }

        public Builder quietTimeout(long duration, TimeUnit unit) {
            return quietTimeoutMs(unit.toMillis(duration));
        }

        public Builder reconnectDelayMs(long reconnectDelayMs) {
            this.reconnectDelayMs = reconnectDelayMs;
            return this;
        }

        public Builder reconnectDelay(long duration, TimeUnit unit) {
            return reconnectDelayMs(unit.toMillis(duration));
        }

        public Builder charset(Charset charset) {
            this.charset = charset;
            return this;
        }

        public Builder subscribeOn(Scheduler scheduler) {
            this.scheduler = scheduler;
            return this;
        }

        public Observable create() {
            return from(host, port, quietTimeoutMs, reconnectDelayMs, charset, scheduler);
        }

    }

    /**
     * Returns a builder for converting a socket read to an Observable. Defaults
     * to port=6564, quietTimeoutMs=60000, reconnectDelayMs=30000, charset=
     * UTF-8.
     * 
     * @param host
     * @return
     */
    public static Builder from(String host) {
        return new Builder(host);
    }

    public static Observable strings(final String host, final int port, int quietTimeoutMs,
            final Charset charset) {
        Preconditions.checkNotNull(host);
        Preconditions.checkArgument(port >= 0 && port <= 65535, "port must be between 0 and 65535");
        Preconditions.checkArgument(quietTimeoutMs > 0, "quietTimeoutMs must be > 0");
        Preconditions.checkNotNull(charset);
        return strings(socketCreator(host, port, quietTimeoutMs), charset);
    }

    public static Observable strings(Func0 socketCreator, final Charset charset) {
        Preconditions.checkNotNull(socketCreator);
        Preconditions.checkNotNull(charset);
        return Observable
                // create a stream from a socket and dispose of socket
                // appropriately
                .using(socketCreator, socketObservableFactory(charset), socketDisposer(), true);
    }

    @VisibleForTesting
    static Func0 socketCreator(final String host, final int port, long quietTimeoutMs) {
        return Checked.f0(() -> {
            Socket socket = new Socket(host, port);
            socket.setSoTimeout((int) quietTimeoutMs);
            return socket;
        });
    }

    @VisibleForTesting
    static Func1> socketObservableFactory(final Charset charset) {
        return Checked.f1(
                socket -> Strings.from(new InputStreamReader(socket.getInputStream(), charset)));
    }

    @VisibleForTesting
    static Action1 socketDisposer() {
        return socket -> {
            try {
                log.info("closing socket " + socket.getInetAddress().getHostAddress() + ":"
                        + socket.getPort());
                socket.close();
            } catch (IOException e) {
                // don't really care if socket could not be closed cleanly
                log.info("messageOnSocketClose=" + e.getMessage(), e);
            }
        };
    }

    public static Observable mergeLinesFrom(Observable hostPorts,
            Scheduler scheduler) {
        return hostPorts
                //
                .map(hp -> StringSockets
                        .from(hp.getHost(), hp.getPort(), hp.getQuietTimeoutMs(),
                                hp.getReconnectDelayMs(), StandardCharsets.UTF_8, scheduler)
                        // split by new line character
                        .compose(o -> Strings.split(o, "\n")))
                // merge streams of lines
                .compose(o -> Observable.merge(o));
    }

    public static Observable mergeLinesFrom(Observable hostPorts, int maxLineLength) {
        return mergeLinesFrom(hostPorts, Schedulers.io(), maxLineLength);
    }
    
    public static Observable mergeLinesFrom(Observable hostPorts,
            Scheduler scheduler, int maxLineLength) {
        return hostPorts
                //
                .map(hp -> StringSockets
                        .from(hp.getHost(), hp.getPort(), hp.getQuietTimeoutMs(),
                                hp.getReconnectDelayMs(), StandardCharsets.UTF_8, scheduler)
                        // split by new line character
                        .compose(o -> Strings.split(o, maxLineLength,  "\n", 1)))
                // merge streams of lines
                .compose(o -> Observable.merge(o));
    }

    public static Observable mergeLinesFrom(Observable hostPorts) {
        return mergeLinesFrom(hostPorts, Schedulers.io());
    }

    public static void main(String[] args) {
        Observable hostPorts = Observable.just(
                HostPort.create("sarapps", 9010, 1000, 1000),
                HostPort.create("sarapps", 9100, 1000, 1000));
        StringSockets.mergeLinesFrom(hostPorts, Schedulers.io()).doOnNext(System.out::println)
                .toBlocking().last();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy