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

org.zodiac.sdk.nio.http.server.Server Maven / Gradle / Ivy

There is a newer version: 1.6.8
Show newest version
package org.zodiac.sdk.nio.http.server;

import io.vavr.control.Either;

import static org.zodiac.sdk.nio.core.Packet.State.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zodiac.sdk.nio.common.ErrorFactory;
import org.zodiac.sdk.nio.core.Packet;
import org.zodiac.sdk.nio.core.PacketUtil;
import org.zodiac.sdk.nio.http.ApplicationProtocol;
import org.zodiac.sdk.nio.http.NioHttpConstants;
import org.zodiac.sdk.nio.http.TransportProtocol;
import org.zodiac.sdk.nio.http.TransportProtocol.Type;
import org.zodiac.sdk.nio.http.common.*;

public class Server {

    private final Logger log = LoggerFactory.getLogger(getClass());

    private final ExecutorService executorService;

    private volatile boolean isRunning = false;

    private final Server.Configuration configuration;

    public Server(final Server.Configuration configuration) {
        this.configuration = configuration;
        executorService = Executors.newFixedThreadPool(configuration.threadPoolSize());
    }

    public synchronized void run() {
        if (isRunning) {
            return;
        }

        log.info("starting server");
        log.info("using port {}", configuration.port());
        try {
            isRunning = true;
            new ServerThread().start();
            log.debug("server started");
        } catch (final IOException e) {
            log.error(e.getMessage());
            e.printStackTrace();
        }
    }

    public void stop() {
        isRunning = false;
    }

    class ServerThread extends Thread {

        private final ConcurrentHashMap> clientPacketQueueTable =
            new ConcurrentHashMap<>();

        private DatagramChannel channel;

        private ServerSocketChannel tcpChannel;

        private ByteBuffer buffer;

        ServerThread() throws IOException {
            super("listener-thread");

            log.debug("starting server with listener thread");

            if (configuration.transportProtocolType == TransportProtocol.Type.TCP) {
                tcpChannel = ServerSocketChannel.open();
                tcpChannel.bind(new InetSocketAddress(configuration.port()));
            } else {
                final Selector selector = Selector.open();
                channel = DatagramChannel.open();
                channel.configureBlocking(false);
                channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                channel.bind(new InetSocketAddress(configuration.port()));
                buffer = transportProtocol().emptyBuffer();
            }
        }

        @Override
        public void run() {
            while (isRunning) {
                try {
                    if (configuration.transportProtocolType == TransportProtocol.Type.TCP) { // TCP
                        executorService.execute(handler(tcpChannel.socket().accept(), null, null, null));
                    } else { // UDP
                        buffer.clear();
                        final SocketAddress router = channel.receive(buffer);
                        buffer.flip();
                        if (router != null) {
                            final Packet packet = Packet.of(buffer);
                            final InetSocketAddress client = packet.getPeerAddress();
                            log.debug("incoming packet={}", packet);
                            log.debug("client={}", client);

                            final BlockingQueue existingQueue =
                                clientPacketQueueTable.putIfAbsent(client, new ArrayBlockingQueue<>(100));
                            final BlockingQueue clientPacketQueue =
                                Objects.requireNonNull(clientPacketQueueTable.get(client),
                                    "client incoming packet queue should have been present in client table");

                            clientPacketQueue.put(packet);
                            if (packet.is(SYN) && existingQueue == null) {
                                log.debug("{} wishes to establish connection", client);
                                executorService.execute(handler(null, channel, client, clientPacketQueue));
                            } else {
                                log.debug("received {} added to queue {}", packet, clientPacketQueue);
                            }
                        }
                    }
                } catch (final IOException | InterruptedException e) {
                    log.error(e.getMessage());
                    e.printStackTrace();
                }
            }
        }
    }

    private Runnable handler(final Socket socket, final DatagramChannel channel, final SocketAddress client,
        final BlockingQueue queue) throws IOException {
        switch (configuration.getTransportProtocolType()) {
            case UDP:
                return new UDPHandler(channel, client, queue, configuration, transportProtocol(),
                    applicationProtocol());
            case TCP:
                return new TCPHandler(socket, configuration, transportProtocol(), applicationProtocol());
            default:
                throw ErrorFactory.invalidTransportProtocol(configuration.getTransportProtocolType().name());
        }
    }

    private TransportProtocol transportProtocol() {
        switch (configuration.getTransportProtocolType()) {
            case UDP:
                return new UDPSRProtocol();
            case TCP:
                return null;
            default:
                throw ErrorFactory.invalidTransportProtocol(configuration.getTransportProtocolType().name());
        }
    }

    private ApplicationProtocol.Response applicationProtocol() throws IOException {
        switch (configuration.getApplicationProtocolType()) {
            case FILESERVER:
                return new FileServerProtocol(configuration.getDirectory());
            default:
                throw ErrorFactory.invalidApplicationProtocol(configuration.getApplicationProtocolType().name());
        }
    }

    private static class UDPHandler implements Runnable, UDPSRProtocol.Agent {

        private final Logger log;

        DatagramChannel channel;

        Selector selector;

        InetSocketAddress client;

        BlockingQueue queue;

        TransportProtocol transportProtocol;

        private final ApplicationProtocol.Response applicationProtocol;

        private final Configuration configuration;

        private final InetSocketAddress router;

        public UDPHandler(final DatagramChannel channel, final SocketAddress client, final BlockingQueue queue,
            final Configuration configuration, final TransportProtocol transportProtocol,
            final ApplicationProtocol.Response applicationProtocol) throws UnknownHostException {
            this(null, channel, client, queue, configuration, transportProtocol, applicationProtocol);
        }

        public UDPHandler(final Logger log, final DatagramChannel channel, final SocketAddress client, final BlockingQueue queue,
            final Configuration configuration, final TransportProtocol transportProtocol,
            final ApplicationProtocol.Response applicationProtocol) throws UnknownHostException {
            this.log = null != log ? log : LoggerFactory.getLogger(getClass());
            this.channel = channel;
            this.client = (InetSocketAddress)client;
            this.queue = queue;
            this.transportProtocol = transportProtocol;
            this.applicationProtocol = applicationProtocol;
            this.configuration = configuration;
            router = configuration.router();
        }

        @Override
        public void run() {
            try {
                configure();
                if (handshake()) {
                    final HTTPRequest request = transportProtocol.receive(this);
                    if (request != null) {
                        log.info("request received, preparing response");
                        final HTTPResponse response = applicationProtocol.response(request);
                        final ByteBuffer[] buffers = PacketUtil.split(response.toString().getBytes());
                        final Packet[] packets = new Packet[buffers.length];
                        for (int i = 0; i < buffers.length; i++) {
                            packets[i] = Packet.builder().state(BFRD).sequenceNumber(i).peerAddress(client)
                                .payload(buffers[i].array()).build();
                        }
                        if (transportProtocol.send(this, packets)) {
                            log.info("response sent!");
                        } else {
                            log.error("unable to confirm response delivery after {} attempts",
                                transportProtocol.maxConsecutiveRetries());
                        }
                        log.info("response=\n{}", response.toString());
                    }
                }
            } catch (final Exception e) {
                log.error("{}: {}", e.getClass().getSimpleName(), e.getMessage());
            } finally {
                log.debug("disconnecting");
                try {
                    selector.close();
                } catch (IOException e) {
                    log.error("{}: {}", e.getClass().getSimpleName(), e.getMessage());
                }
                log.debug("exiting thread");
            }
        }

        private void configure() throws IOException {
            selector = Selector.open();
            channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        }

        private boolean handshake() throws InterruptedException, IOException {
            final Packet synPacket =
                Objects.requireNonNull(queue.poll(), "synPacket should have been in client queue but none were found");
            log.debug("received syn {}", synPacket);

            final Packet synAckPacket =
                synPacket.toBuilder().state(SYNACK).sequenceNumber(1).peerAddress(client).build();

            write(synAckPacket);
            log.debug("sent synack {}", synAckPacket);

            final Packet ackPacket = queue.poll(transportProtocol.packetTimeoutMs(), TimeUnit.MILLISECONDS);

            if (ackPacket == null) {
                log.error("did not receive ack after timeout, assuming was sent");
            } else if (!ackPacket.is(ACK)) {
                Packet packet = ackPacket;
                if (packet.is(SYN)) {
                    while (packet != null && packet.is(SYN)) {
                        log.warn("received again syn {}", packet);
                        write(packet.toBuilder().state(SYNACK).build());
                        log.debug("sent synack {}", synAckPacket);
                        packet = queue.poll(transportProtocol.packetTimeoutMs(), TimeUnit.MILLISECONDS);
                    }
                    if (packet == null) {
                        log.error("did not receive ack after timeout, assuming was sent");
                    } else {
                        log.error("received a packet but was not ack, will offer other packet back to queue");
                        final boolean offeredToQueue =
                            queue.offer(ackPacket, transportProtocol.packetTimeoutMs(), TimeUnit.MILLISECONDS);
                        log.info("{} was successfully to queue", offeredToQueue);
                    }
                } else {
                    log.error("received a packet but was not ack, will offer other packet back to queue");
                    final boolean offeredToQueue =
                        queue.offer(ackPacket, transportProtocol.packetTimeoutMs(), TimeUnit.MILLISECONDS);
                    log.info("{} was successfully to queue", offeredToQueue);
                }
            } else {
                log.debug("received ackPacket {}", ackPacket);
                log.debug("connection established");
            }

            return true;
        }

        private Packet read(final int timeout) throws InterruptedException {
            log.info("blocking up to {}ms for response", timeout);
            final Packet packet = queue.poll(timeout, TimeUnit.MILLISECONDS);
            log.info("polled {}", packet);
            return packet;
        }

        @Override
        public Packet read() throws InterruptedException {
            return read(transportProtocol.packetTimeoutMs());
        }

        @Override
        public void write(final Packet packet) throws IOException {
            log.info("sent {}", packet);
            channel.send(packet.buffer(), router);
        }

        @Override
        public  T make(final List packets) {
            final String combinedPayload =
                packets.stream().filter(Objects::nonNull).map(Packet::payload).collect(Collectors.joining(""));
            try {
                final Either request = HTTPRequest.of(combinedPayload);
                if (request.isLeft()) {
                    log.info("request successfully created");
                    return (T)request.getLeft();
                } else {
                    log.error("request invalid: {}", request.get());
                    log.info("\nrequest: \n{}", combinedPayload);
                    return null;
                }
            } catch (final Exception e) {
                log.error("{}: {}", e.getClass().getSimpleName(), e.getMessage());
                log.debug("request: \n{}", combinedPayload);
                return null;
            }
        }
    }

    private static class TCPHandler implements Runnable {

        private final Logger log;
        private final Socket socket;

        private final TransportProtocol transportProtocol;

        private final ApplicationProtocol.Response applicationProtocol;

        private final Server.Configuration configuration;

        public TCPHandler(final Socket socket, final Configuration configuration,
            final TransportProtocol transportProtocol, final ApplicationProtocol.Response applicationProtocol) {
            this(null, socket, configuration, transportProtocol, applicationProtocol);
        }

        public TCPHandler(final Logger log, final Socket socket, final Configuration configuration,
            final TransportProtocol transportProtocol, final ApplicationProtocol.Response applicationProtocol) {
            this.log = null != log ? log : LoggerFactory.getLogger(getClass());
            this.socket = socket;
            this.transportProtocol = transportProtocol;
            this.applicationProtocol = applicationProtocol;
            this.configuration = configuration;
        }

        @Override
        public void run() {
            try (final PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
                final BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
                log.debug("connection accepted");
                boolean keepAlive = true;
                int contentLength = 0;

                while (keepAlive) {
                    HTTPRequest request = null;
                    final StringBuilder requestBuilder = new StringBuilder();
                    String line;

                    while ((line = in.readLine()) != null) {
                        requestBuilder.append(line);
                        requestBuilder.append(NioHttpConstants.CRLF);

                        if (line.toLowerCase().contains(NioHttpConstants.Headers.CONTENT_LENGTH.toLowerCase())) {
                            final String[] keyValue = line.split(":");
                            contentLength = Integer.parseInt(keyValue[1].trim());
                        }

                        log.debug("line: {}", line);
                        if (line.equalsIgnoreCase("")) {
                            for (int i = 0; i < contentLength; i++) {
                                requestBuilder.append((char)in.read());
                            }

                            break;
                        }
                    }

                    log.debug("builder: {}", requestBuilder.toString());
                    final Either requestAttempt = HTTPRequest.of(requestBuilder.toString());

                    if (requestAttempt.isLeft()) {
                        request = requestAttempt.getLeft();
                    } else {
                        throw new HTTPRequest.RequestError(requestAttempt.get());
                    }

                    if (request != null) {
                        log.debug("request:");
                        log.debug(request.toString());
                        final String responseText = applicationProtocol.response(request).toString();
                        log.debug("response:");
                        log.debug(responseText);
                        out.print(responseText);
                        out.flush();

                        if (request.headers().containsKey(NioHttpConstants.Headers.CONNECTION)) {
                            //final String connection = request.headers().getFirst(NioHttpConstants.Headers.CONNECTION);
                            final String connection = request.headers().getFirstConnection();
                            keepAlive = connection.equalsIgnoreCase(NioHttpConstants.Headers.KEEP_ALIVE);
                        } else {
                            keepAlive = false;
                        }
                    } else {
                        log.debug("connection terminated");
                        keepAlive = false;
                    }
                }
            } catch (final IOException | HTTPRequest.RequestError e) {
                log.error("{}: {}", e.getClass().getSimpleName(), e.getMessage());
            }
        }
    }

    // @Accessors(fluent = true)
    static final class Configuration {

        private final TransportProtocol.Type transportProtocolType;

        private final ApplicationProtocol.Type applicationProtocolType;

        private final int port;

        private final boolean verbose;

        private final String directory;

        public Configuration(Type transportProtocolType,
            org.zodiac.sdk.nio.http.ApplicationProtocol.Type applicationProtocolType, int port, boolean verbose,
            String directory) {
            super();
            this.transportProtocolType = transportProtocolType;
            this.applicationProtocolType = applicationProtocolType;
            this.port = port;
            this.verbose = verbose;
            this.directory = directory;
        }

        public TransportProtocol.Type getTransportProtocolType() {
            return transportProtocolType;
        }

        public ApplicationProtocol.Type getApplicationProtocolType() {
            return applicationProtocolType;
        }

        public int getPort() {
            return port;
        }

        public boolean isVerbose() {
            return verbose;
        }

        public String getDirectory() {
            return directory;
        }

        public final int port() {
            return port == 0 || port == -1 ? NioHttpConstants.DEFAULT_SERVER_PORT : port;
        }

        public final InetSocketAddress router() throws UnknownHostException {
            return new InetSocketAddress(InetAddress.getByName(NioHttpConstants.DEFAULT_ROUTER_HOST), NioHttpConstants.DEFAULT_ROUTER_PORT);
        }

        public final int threadPoolSize() {
            return NioHttpConstants.DEFAULT_THREAD_POOL_SIZE;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy