Package se.rupy.http

Source Code of se.rupy.http.Daemon

package se.rupy.http;

import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import java.nio.channels.*;

/**
* A tiny HTTP daemon. The whole server is non-static so that you can launch
* multiple contained HTTP servers in one application on different ports.
*
* @author marc
*/

public class Daemon implements Runnable {
  protected Properties properties;
  protected boolean verbose, debug, host, alive, panel;
  protected int threads, timeout, cookie, delay, size, port;
  private int selected, valid, accept, readwrite; // panel stats
  private HashMap archive, service;
  protected ConcurrentHashMap events, session;
  private Chain workers, queue;
  private Heart heart;
  private Selector selector;
  private String pass;
  protected PrintStream out, access, error;
  private static DateFormat DATE;

  /**
   * Don't forget to call {@link #start()}.
   */
  public Daemon() {
    this(new Properties());
  }

  /**
   * Don't forget to call {@link #start()}. The parameters below
   * should be in the properties argument.
   *
   * @param <br><b>host</b> (false)
   *            <i>if you want to host multiple domains on one rupy server you need
   *            to enable hosts, and add host attributes to your deploy jar
   *            manifest files.</i><br><br>
   * @param <b>pass</b>
   *            <i>the pass used to deploy services via HTTP POST or null/"" to
   *            disable remote hot-deploy</i><br><br>
   * @param <b>port</b> (8000)
   *            <i>which TCP port</i><br><br>
   * @param <b>threads</b> (5)
   *            <i>how many worker threads, the daemon also starts one selector
   *            and one heartbeat thread.</i><br><br>
   * @param <b>timeout</b> (5 minutes)
   *            <i>session timeout in seconds or 0 to disable sessions</i><br><br>
   * @param <b>cookie</b> (4 characters)
   *            <i>session key length; default and minimum is 4, > 10 can be
   *            considered secure</i><br><br>
   * @param <b>delay</b> (5000 ms.)
   *            <i>time in milliseconds before started event gets dropped due to
   *            inactivity. Increase this if your users will download
   *            content with a 'open/save/cancel' + save location dialog,
   *            since this will timeout otherwise.
   *            This is also the dead socket worker cleanup variable, so if a worker
   *            has a socket that hasn't been active for longer than this the worker
   *            will be released and the socket deemed as dead.</i><br><br>
   * @param <b>size</b> (1024 bytes)
   *            <i>IO buffer size, should be proportional to the data sizes
   *            received/sent by the server currently this is input/output-
   *            buffer, chunk-buffer, post-body-max and header-max lengths! :P</i><br><br>
   * @param <b>live</b> (false)
   *            <i>uses expires header to cache static files.</i><br><br>
   * @param <b>verbose</b> (false)
   *            <i>to log information about these startup parameters,
   *            high-level info for each request and deployed services overview.</i><br><br>
   * @param <b>debug</b> (false)
   *            <i>to log low-level NIO info for each request and class
   *            loading info.</i><br><br>
   * @param <b>log</b> (false)
   *            <i>simple log of each synchronous request in the access.txt file.</i><br><br>
   */
  public Daemon(Properties properties) {
    this.properties = properties;

    threads = Integer.parseInt(properties.getProperty("threads", "5"));
    cookie = Integer.parseInt(properties.getProperty("cookie", "4"));
    port = Integer.parseInt(properties.getProperty("port", "8000"));
    timeout = Integer.parseInt(properties.getProperty("timeout", "300")) * 1000;
    delay = Integer.parseInt(properties.getProperty("delay", "5000"));
    size = Integer.parseInt(properties.getProperty("size", "1024"));

    verbose = properties.getProperty("verbose", "false").toLowerCase()
    .equals("true");
    debug = properties.getProperty("debug", "false").toLowerCase().equals(
    "true");
    host = properties.getProperty("host", "false").toLowerCase().equals(
    "true");
    panel = properties.getProperty("panel", "false").toLowerCase().equals(
    "true");

    if (!verbose) {
      debug = false;
    }

    archive = new HashMap();
    service = new HashMap();
    session = new ConcurrentHashMap();
    events = new ConcurrentHashMap();

    workers = new Chain();
    queue = new Chain();

    try {
      out = new PrintStream(System.out, true, "UTF-8");

      if(properties.getProperty("log") != null || properties.getProperty("test", "false").toLowerCase().equals(
      "true")) {
        log();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public Properties properties() {
    return properties;
  }

  protected void log() throws IOException {
    File file = new File("log");

    if(!file.exists()) {
      file.mkdir();
    }

    access = new PrintStream(new FileOutputStream(new File("log/access.txt")), true, "UTF-8");
    error = new PrintStream(new FileOutputStream(new File("log/error.txt")), true, "UTF-8");

    DATE = new SimpleDateFormat("yy-MM-dd HH:mm:ss.SSS");
  }

  protected void error(Event event, Throwable t) throws IOException {
    if (error != null && t != null && !(t instanceof Failure.Close)) {
      Calendar date = Calendar.getInstance();
      StringBuilder b = new StringBuilder();

      b.append(DATE.format(date.getTime()));
      b.append(' ');
      b.append(event.remote());
      b.append(' ');
      b.append(event.query().path());

      String parameters = event.query().parameters();

      if(parameters != null) {
        b.append(' ');
        b.append(parameters);
      }

      b.append(Output.EOL);

      error.write(b.toString().getBytes("UTF-8"));

      t.printStackTrace(error);
    }
  }

  protected String access(Event event) throws IOException {
    if (access != null && !event.reply().push()) {
      Calendar date = Calendar.getInstance();
      StringBuilder b = new StringBuilder();

      b.append(DATE.format(date.getTime()));
      b.append(' ');
      b.append(event.remote());
      b.append(' ');
      b.append(event.query().path());
      b.append(' ');
      b.append(event.reply().code());

      int length = event.reply().length();

      if(length > 0) {
        b.append(' ');
        b.append(length);
      }

      return b.toString();
    }

    return null;
  }

  protected void access(String row, boolean push) throws IOException {
    if (access != null) {
      StringBuilder b = new StringBuilder();

      b.append(row);

      if(push) {
        b.append(' ');
        b.append('>');
      }

      b.append(Output.EOL);

      access.write(b.toString().getBytes("UTF-8"));
    }
  }


  /**
   * Starts the selector, heartbeat and worker threads.
   */
  public void start() {
    try {
      heart = new Heart();

      int threads = Integer.parseInt(properties.getProperty("threads",
      "5"));

      for (int i = 0; i < threads; i++) {
        workers.add(new Worker(this, i));
      }

      alive = true;

      new Thread(this).start();
    } catch (Exception e) {
      e.printStackTrace(out);
    }
  }

  /**
   * Stops the selector, heartbeat and worker threads.
   */
  public void stop() {
    Iterator it = workers.iterator();

    while(it.hasNext()) {
      Worker worker = (Worker) it.next();
      worker.stop();
    }

    workers.clear();
    alive = false;
    heart.stop();

    selector.wakeup();
  }

  public ConcurrentHashMap session() {
    return session;
  }

  protected Selector selector() {
    return selector;
  }

  protected void chain(Deploy.Archive archive) throws Exception {
    Deploy.Archive old = (Deploy.Archive) this.archive.get(archive.name());

    if (old != null) {
      Iterator it = old.service().iterator();

      while (it.hasNext()) {
        Service service = (Service) it.next();

        try {
          service.destroy();
        } catch (Exception e) {
          e.printStackTrace(out);
        }
      }
    }

    Iterator it = archive.service().iterator();

    while (it.hasNext()) {
      Service service = (Service) it.next();
      add(archive.chain(), service);
    }

    this.archive.put(archive.name(), archive);
  }

  /**
   * Get archive.
   * @return If null the archive is not deployed.
   */
  public Deploy.Archive archive(String name) {
    if(!name.endsWith(".jar")) {
      name += ".jar";
    }

    return (Deploy.Archive) this.archive.get(name);
  }

  /*
   * Listener - Cross class-loader communication interface. So that a class
   * deployed in one archive can send messages to a class deployed in another.
   */

  private Listener listener;

  /**
   * Send Object to listener. We recommend you only send bootclasspath loaded
   * classes here otherwise hotdeploy will fail.
   *
   * @param message
   * @return
   * @throws Exception
   */
  public Object send(Object message) throws Exception {
    if(listener == null) {
      return message;
    }

    return listener.receive(message);
  }

  /**
   * Register your listener here.
   *
   * @param listener
   */
  public void set(Listener listener) {
    this.listener = listener;
  }

  /**
   * The listener filters Object messages, for example JSONObject.
   * @author Marc
   */
  public interface Listener {
    /**
     * Forward, alter or swallow message.
     * @param message
     * @return The forwarded, altered message or null to swallow.
     * @throws Exception
     */
    public Object receive(Object message) throws Exception;
  }

  /*
   * Listener
   */

  public void add(Service service) throws Exception {
    add(this.service, service);
  }

  protected void add(HashMap map, Service service) throws Exception {
    String path = service.path();

    if(path == null) {
      path = "null";
    }

    StringTokenizer paths = new StringTokenizer(path, ":");

    while (paths.hasMoreTokens()) {
      path = paths.nextToken();
      Chain chain = (Chain) map.get(path);

      if (chain == null) {
        chain = new Chain();
        map.put(path, chain);
      }

      Service old = (Service) chain.put(service);

      if (old != null) {
        throw new Exception(service.getClass().getName()
            + " with path '" + path + "' and index ["
            + service.index() + "] is conflicting with "
            + old.getClass().getName()
            + " for the same path and index.");
      }

      if (verbose)
        out.println(path + padding(path) + chain);

      try {
        service.create(this);
      } catch (Exception e) {
        e.printStackTrace(out);
      }
    }
  }

  protected String padding(String path) {
    StringBuffer buffer = new StringBuffer();

    for(int i = 0; i < 10 - path.length(); i++) {
      buffer.append(' ');
    }

    return buffer.toString();
  }

  protected void verify(Deploy.Archive archive) throws Exception {
    Iterator it = archive.chain().keySet().iterator();

    while (it.hasNext()) {
      String path = (String) it.next();
      Chain chain = (Chain) archive.chain().get(path);

      for (int i = 0; i < chain.size(); i++) {
        Service service = (Service) chain.get(i);

        if (i != service.index()) {
          this.archive.remove(archive.name());
          throw new Exception(service.getClass().getName()
              + " with path '" + path + "' has index ["
              + service.index() + "] which is too high.");
        }
      }
    }
  }

  protected Deploy.Stream content(Query query) {
    if(host) {
      return content(query.header("host"), query.path());
    }
    else {
      return content(query.path());
    }
  }

  protected Deploy.Stream content(String path) {
    return content("content", path);
  }

  protected Deploy.Stream content(String host, String path) {
    File file = new File("app" + File.separator + host + File.separator + path);

    if(file.exists() && !file.isDirectory()) {
      return new Deploy.Big(file);
    }

    return null;
  }

  protected Chain chain(Query query) {
    if(host) {
      return chain(query.header("host"), query.path());
    }
    else {
      return chain(query.path());
    }
  }

  public Chain chain(String path) {
    return chain("content", path);
  }

  public Chain chain(String host, String path) {
    synchronized (this.service) {
      Chain chain = (Chain) this.service.get(path);

      if (chain != null) {
        return chain;
      }
    }

    synchronized (this.archive) {
      Iterator it = this.archive.values().iterator();

      while (it.hasNext()) {
        Deploy.Archive archive = (Deploy.Archive) it.next();

        if (archive.host().equals(host)) {
          Chain chain = (Chain) archive.chain().get(path);

          if (chain != null) {
            return chain;
          }
        }
      }
    }

    return null;
  }

  protected synchronized Event next(Worker worker) {
    synchronized (this.queue) {
      if (queue.size() > 0) {
        if (Event.LOG) {
          if (debug)
            out.println("worker " + worker.index()
                + " found work " + queue);
        }

        return (Event) queue.remove(0);
      }
    }
    return null;
  }

  public void run() {
    String pass = properties.getProperty("pass", "");
    ServerSocketChannel server = null;

    try {
      selector = Selector.open();
      server = ServerSocketChannel.open();
      server.socket().bind(new InetSocketAddress(port));
      server.configureBlocking(false);
      server.register(selector, SelectionKey.OP_ACCEPT);

      DecimalFormat decimal = (DecimalFormat) DecimalFormat.getInstance();
      decimal.applyPattern("#.##");

      if (verbose)
        out.println("daemon started\n" + "- pass       \t"
            + pass + "\n" + "- port       \t" + port + "\n"
            + "- worker(s)  \t" + threads + " thread"
            + (threads > 1 ? "s" : "") + "\n" +
            "- session    \t" + cookie + " characters\n" +
            "- timeout    \t"
            + decimal.format((double) timeout / 60000) + " minute"
            + (timeout / 60000 > 1 ? "s" : "") + "\n"
            + "- IO timeout \t" + delay + " ms." + "\n"
            + "- IO buffer  \t" + size + " bytes\n"
            + "- debug      \t" + debug + "\n"
            + "- live       \t" + properties.getProperty("live", "false").toLowerCase()
            .equals("true"));

      if (pass != null && pass.length() > 0) {
        add(new Deploy("app" + File.separator, pass));

        File[] app = new File(Deploy.path).listFiles(new Filter());

        if (app != null) {
          for (int i = 0; i < app.length; i++) {
            Deploy.deploy(this, app[i]);
          }
        }
      }

      /*
       * Used to debug thread locks and file descriptor leaks.
       */

      if(panel) {
        add(new Service() {
          public String path() { return "/panel"; }
          public void filter(Event event) throws Event, Exception {
            Iterator it = workers.iterator();
            event.output().println("<pre>workers: {size: " + workers.size() + ", ");
            while(it.hasNext()) {
              Worker worker = (Worker) it.next();
              event.output().print(" worker: {index: " + worker.index() + ", busy: " + worker.busy() + ", lock: " + worker.lock());

              if(worker.event() != null) {
                event.output().println(", ");
                event.output().println("  event: {index: " + worker.event() + ", init: " + worker.event().reply().output.init + ", done: " + worker.event().reply().output.done + "}");
                event.output().println(" }");
              }
              else {
                event.output().println("}");
              }
            }
            event.output().println("}");
            event.output().println("events: {size: " + events.size() + ", selected: " + selected + ", valid: " + valid + ", accept: " + accept + ", readwrite: " + readwrite + ", ");
            it = events.values().iterator();
            while(it.hasNext()) {
              Event e = (Event) it.next();
              event.output().println(" event: {index: " + e + ", last: " + (System.currentTimeMillis() - e.last()) + "}");
            }
            event.output().println("}</pre>");
          }
        });
      }

      if (properties.getProperty("test", "false").toLowerCase().equals(
      "true")) {
        new Test(this, 1);
      }
    } catch (Exception e) {
      e.printStackTrace(out);
      System.exit(1);
    }

    int index = 0;
    Event event = null;
    SelectionKey key = null;

    while (alive) {
      try {
        selector.select();
        Set set = selector.selectedKeys();
        int valid = 0, accept = 0, readwrite = 0, selected = set.size();
        Iterator it = set.iterator();

        while (it.hasNext()) {
          key = (SelectionKey) it.next();
          it.remove();

          if (key.isValid()) {
            valid++;
            if (key.isAcceptable()) {
              accept++;
              event = new Event(this, key, index++);
              events.put(new Integer(event.index()), event);

              if (Event.LOG) {
                event.log("accept ---");
              }
            } else if (key.isReadable() || key.isWritable()) {
              readwrite++;
              key.interestOps(0);

              event = (Event) key.attachment();
              Worker worker = event.worker();

              if (Event.LOG) {
                if (debug) {
                  if (key.isReadable())
                    event.log("read ---");
                  if (key.isWritable())
                    event.log("write ---");
                }
              }

              if (key.isReadable() && event.push()) {
                event.disconnect(null);
              } else if (worker == null) {
                employ(event);
              } else {
                worker.wakeup();
              }
            }
          }
        }

        this.valid = valid;
        this.accept = accept;
        this.readwrite = readwrite;
        this.selected = selected;
      } catch (Exception e) {
        /*
         * Here we get mostly ClosedChannelExceptions and
         * java.io.IOException: 'Too many open files' when the server is
         * taking a beating. Better to drop connections than to drop the
         * server.
         */
        if(event == null) {
          System.out.println(events + " " + key);
        }
        else {
          event.disconnect(e);
        }
      }
    }

    try {
      if(selector != null) {
        selector.close();
      }
      if(server != null) {
        server.close();
      }
    } catch (IOException e) {
      e.printStackTrace(out);
    }
  }

  protected void queue(Event event) {
    synchronized (this.queue) {
      queue.add(event);
    }

    if (Event.LOG) {
      if (debug)
        out.println("queue " + queue.size());
    }
  }

  protected synchronized void employ(Event event) {
    if(queue.size() > 0) {
      queue(event);
      return;
    }

    workers.reset();
    Worker worker = (Worker) workers.next();

    if (worker == null) {
      queue(event);
      return;
    }

    while (worker.busy()) {
      worker = (Worker) workers.next();

      if (worker == null) {
        queue(event);
        return;
      }
    }

    if (Event.LOG) {
      if (debug)
        out.println("worker " + worker.index() + " hired. (" + queue.size() + ")");
    }

    event.worker(worker);
    worker.event(event);
    worker.wakeup();
  }

  class Filter implements FilenameFilter {
    public boolean accept(File dir, String name) {
      if (name.endsWith(".jar")) {
        return true;
      }

      return false;
    }
  }

  protected void log(PrintStream out) {
    if(out != null) {
      this.out = out;
    }
  }

  protected void log(Object o) {
    if(out != null) {
      out.println(o);
    }
  }

  class Heart implements Runnable {
    boolean alive;

    Heart() {
      alive = true;
      new Thread(this).start();
    }

    protected void stop() {
      alive = false;
    }

    public void run() {
      while (alive) {
        try {
          Thread.sleep(1000);

          Iterator it = session.values().iterator();

          while (it.hasNext()) {
            Session se = (Session) it.next();

            if (System.currentTimeMillis() - se.date() > timeout) {
              it.remove();
              se.remove();

              if (Event.LOG) {
                if (debug)
                  out.println("session timeout "
                      + se.key());
              }
            }
          }

          it = workers.iterator();

          while(it.hasNext()) {
            Worker worker = (Worker) it.next();
            worker.busy();
          }

          it = events.values().iterator();

          while(it.hasNext()) {
            Event event = (Event) it.next();

            if(System.currentTimeMillis() - event.last() > 300000) {
              event.disconnect(null);
            }
          }
        } catch (Exception e) {
          e.printStackTrace(out);
        }
      }
    }
  }

  public static void main(String[] args) {
    Properties properties = new Properties();

    for (int i = 0; i < args.length; i++) {
      String flag = args[i];
      String value = null;

      if (flag.startsWith("-") && ++i < args.length) {
        value = args[i];

        if (value.startsWith("-")) {
          i--;
          value = null;
        }
      }

      if (value == null) {
        properties.put(flag.substring(1).toLowerCase(), "true");
      } else {
        properties.put(flag.substring(1).toLowerCase(), value);
      }
    }

    if (properties.getProperty("help", "false").toLowerCase()
        .equals("true")) {
      System.out.println("Usage: java -jar http.jar -verbose");
      return;
    }

    new Daemon(properties).start();
  }
}
TOP

Related Classes of se.rupy.http.Daemon

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.