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

kvd.server.ClientHandler Maven / Gradle / Ivy

/*
 * Copyright 2020 Andre Gebers
 *
 * 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
 *
 * http://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 kvd.server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

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

import kvd.common.HelloPacket;
import kvd.common.KvdException;
import kvd.common.Packet;
import kvd.common.PacketType;
import kvd.common.Utils;
import kvd.server.storage.StorageBackend;

public class ClientHandler implements Runnable, AutoCloseable {

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

  private long clientId;

  private Socket socket;

  private DataInputStream in;

  private AtomicBoolean closed = new AtomicBoolean(false);

  private Map channels = new HashMap<>();

  private StorageBackend storage;

  private ClientResponseHandler client;

  private Thread clientThread;

  public ClientHandler(long clientId, Socket socket, StorageBackend storage) {
    this.clientId = clientId;
    this.socket = socket;
    this.storage = storage;
  }

  private synchronized void setupResponseHandler(DataOutputStream out) {
    if(client == null) {
      client = new ClientResponseHandler(out);
      clientThread = new Thread(client, "client-resp-" + clientId);
      clientThread.start();
    } else {
      log.warn("client response handler already setup");
    }
  }

  public void run() {
    try {
      // block on socket read for 1 sec only
      socket.setSoTimeout(1000);
      log.info("client connect, id '{}'", clientId);
      in = new DataInputStream(socket.getInputStream());
      setupResponseHandler(new DataOutputStream(socket.getOutputStream()));
      Utils.receiveHello(in);
      client.sendAsync(new HelloPacket());
      long lastReceiveNs = System.nanoTime();
      while(!closed.get()) {
        Packet packet = Packet.readNext(in);
        if(packet != null) {
          lastReceiveNs = System.nanoTime();
          log.trace("received packet " + packet.getType());
          handlePacket(packet);
        } else {
          if(Utils.isTimeout(lastReceiveNs, 10)) {
            log.info("client '{}' timeout", clientId);
            break;
          }
        }
      }
    } catch(Exception e) {
      log.error("client connection failed", e);
    } finally {
      try {
        client.close();
        clientThread.join();
        log.trace("client thread joined");
      } catch(Exception clientCloseException) {
        log.warn("close client sender failed", clientCloseException);
      }
      Utils.closeQuietly(this);
      log.info("client id '{}' disconnect", clientId);
    }
  }

  @SuppressWarnings("resource")
  private void handlePacket(Packet packet) {
    if(PacketType.PING.equals(packet.getType())) {
      client.sendAsync(new Packet(PacketType.PONG));
    } else if(PacketType.BYE.equals(packet.getType())) {
      log.debug("client '{}' close received", clientId);
      client.sendAsync(new Packet(PacketType.BYE));
      closed.set(true);
    } else if(PacketType.PUT_INIT.equals(packet.getType())) {
      createChannel(packet, new PutConsumer(storage, client));
    } else if(
        PacketType.PUT_DATA.equals(packet.getType()) ||
        PacketType.PUT_FINISH.equals(packet.getType()) ||
        PacketType.PUT_ABORT.equals(packet.getType())) {
      int channel = packet.getChannel();
      ChannelConsumer c = channels.get(channel);
      if(c != null) {
        c.accept(packet);
      } else {
        throw new KvdException("channel does not exist " + channel);
      }
    } else if(PacketType.GET_INIT.equals(packet.getType())) {
      createChannel(packet, new GetConsumer(clientId, packet.getChannel(), storage, client));
    } else if(PacketType.CLOSE_CHANNEL.equals(packet.getType())) {
      closeChannel(packet.getChannel());
    } else if(PacketType.CONTAINS_REQUEST.equals(packet.getType())) {
      String key = Utils.fromUTF8(packet.getBody());
      boolean contains = storage.contains(key);
      client.sendAsync(new Packet(PacketType.CONTAINS_RESPONSE,
          packet.getChannel(), new byte[] {(contains?(byte)1:(byte)0)}));
    } else if(PacketType.REMOVE_REQUEST.equals(packet.getType())) {
      String key = Utils.fromUTF8(packet.getBody());
      boolean removed = storage.remove(key);
      client.sendAsync(new Packet(PacketType.REMOVE_RESPONSE,
          packet.getChannel(), new byte[] {(removed?(byte)1:(byte)0)}));
    } else {
      log.error("can't handle packet type '{}' (not implemented)", packet.getType());
      throw new KvdException("server error, can't handle packet type " + packet.getType());
    }
  }

  private void createChannel(Packet packet, ChannelConsumer c) {
    int channel = packet.getChannel();
    if(!channels.containsKey(channel)) {
      log.trace("channel opened '{}'", channel);
      channels.put(channel, c);
      c.accept(packet);
    } else {
      throw new KvdException("client error, channel already exists " + channel);
    }
  }

  private void closeChannel(int channel) {
    ChannelConsumer c = channels.remove(channel);
    if(c != null) {
      try {
        c.close();
      } catch(Exception e) {
        log.debug("failed to close channel", e);
      }
    }
  }

  public long getClientId() {
    return clientId;
  }

  @Override
  public void close() throws Exception {
    channels.values().forEach(c -> {
      Utils.closeQuietly(c);
    });
    channels.clear();
    Utils.closeQuietly(in);
    Utils.closeQuietly(client);
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy