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

org.basex.BaseXHTTP Maven / Gradle / Ivy

The newest version!
package org.basex;

import static org.basex.core.Text.*;
import static org.basex.util.http.HTTPText.*;

import java.io.*;
import java.net.*;
import java.util.Map.*;
import java.util.function.*;

import org.basex.core.*;
import org.basex.http.*;
import org.basex.io.*;
import org.basex.util.*;
import org.basex.util.log.*;
import org.eclipse.jetty.http.*;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.gzip.*;
import org.eclipse.jetty.util.resource.*;
import org.eclipse.jetty.webapp.*;
import org.eclipse.jetty.websocket.server.config.*;
import org.eclipse.jetty.xml.*;

/**
 * This is the main class for the starting the database HTTP services.
 *
 * @author BaseX Team 2005-24, BSD License
 * @author Christian Gruen
 * @author Dirk Kirsten
 */
public final class BaseXHTTP extends CLI {
  /** Static options. */
  private final StaticOptions soptions;
  /** HTTP context. */
  private final HTTPContext hc;
  /** HTTP server. */
  private final Server jetty;

  /** Start as daemon. */
  private boolean service;
  /** Quiet flag. */
  private boolean quiet;
  /** Default admin password. */
  private String password;
  /** Stop flag. */
  private boolean stop;
  /** HTTP port. */
  private int port;

  /**
   * Main method, launching the HTTP services.
   * Command-line arguments are listed with the {@code -h} argument.
   * @param args command-line arguments
   */
  public static void main(final String... args) {
    try {
      new BaseXHTTP(args);
    } catch(final Exception ex) {
      Util.errln(ex);
      System.exit(1);
    }
  }

  /**
   * Constructor.
   * @param args command-line arguments
   * @throws Exception exception
   */
  public BaseXHTTP(final String... args) throws Exception {
    super(null, args);

    // context must be initialized after parsing of arguments
    soptions = new StaticOptions(true);

    if(!quiet) Util.println(header());

    hc = HTTPContext.get();
    hc.init(soptions);

    // create jetty instance and set default context to HTTP path
    final String webapp = soptions.get(StaticOptions.WEBPATH);
    final WebAppContext wac = new WebAppContext(webapp, "/");
    locate(WEBCONF, webapp);
    final IOFile url = locate(JETTYCONF, webapp);
    jetty = (Server) new XmlConfiguration(new PathResource(url.file())).configure();

    // enable GZIP support
    if(soptions.get(StaticOptions.GZIP)) {
      final GzipHandler gzip = new GzipHandler();
      gzip.addIncludedMethods(HttpMethod.POST.asString(), HttpMethod.PUT.asString());
      gzip.setInflateBufferSize(1024);
      gzip.setHandler(wac);
      jetty.setHandler(gzip);
    } else {
      jetty.setHandler(wac);
    }
    JettyWebSocketServletContainerInitializer.configure(wac, null);

    ServerConnector sc = null;
    for(final Connector conn : jetty.getConnectors()) {
      if(conn instanceof ServerConnector) sc = (ServerConnector) conn;
    }
    if(sc == null) throw new BaseXException("No Jetty connector defined in " + JETTYCONF + '.');
    if(port != 0) sc.setPort(port);
    else port = sc.getPort();

    // info strings
    final Function msg1 = start -> start ? SRV_STARTED_PORT_X : SRV_STOPPED_PORT_X;
    final Function msg2 = start -> Util.info(HTTP + ' ' + msg1.apply(start), port);
    // output user info, keep message visible for a while
    final Consumer info = start -> {
      Util.println(msg2.apply(start));
      if(!soptions.get(StaticOptions.HTTPLOCAL)) {
        final int serverPort = soptions.get(StaticOptions.SERVERPORT);
        Util.println(msg1.apply(start), serverPort);
      }
      Performance.sleep(1000);
    };

    // stop web server
    if(stop) {
      stop();
      if(!quiet) info.accept(false);
      return;
    }

    // start web server in a new Java process
    if(service) {
      start(args);
      if(!quiet) info.accept(true);
      return;
    }

    // start web server
    try {
      jetty.start();
    } catch(final BindException ex) {
      Util.debug(ex);
      throw new BaseXException(HTTP + ' ' + SRV_RUNNING_X, port);
    }
    // throw cached exception that did not break the servlet architecture
    final IOException ex = hc.exception();
    if(ex != null) throw ex;

    // initialize web.xml settings, assign system properties and run database server.
    // the call of this function may already have been triggered during the start of jetty
    context = hc.init(wac.getServletContext());
    if(password != null) context.user().password(password);

    // start daemon for stopping the HTTP server
    final int stopPort = soptions.get(StaticOptions.STOPPORT);
    if(stopPort > 0) new StopServer(stopPort).start();

    // show info when HTTP server is aborted. needs to be called in constructor:
    // otherwise, it may only be called if the JVM process is already shut down
    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
      final String message = msg2.apply(false);
      if(!quiet) Util.println(message);
      context.log.writeServer(LogType.OK, message);
      context.close();
    }));

    // show start message
    if(!quiet) Util.println(msg2.apply(true));

    // log server start at very end (logging flag could have been updated by web.xml)
    context.log.writeServer(LogType.OK, msg2.apply(true));

    // execute initial command-line arguments
    for(final Entry cmd : commands) {
      if(!execute(cmd)) return;
    }
  }

  /**
   * Stops the server.
   * @throws IOException I/O exception
   */
  public void stop() throws IOException {
    final String host = soptions.get(StaticOptions.SERVERHOST);
    final int stopPort = soptions.get(StaticOptions.STOPPORT);
    if(stopPort > 0) stop(host.isEmpty() ? S_LOCALHOST : host, stopPort);
  }

  /**
   * Locates the specified configuration file.
   * @param file file to be copied
   * @param root target root directory
   * @return reference to created file
   * @throws IOException I/O exception
   */
  private static IOFile locate(final String file, final String root) throws IOException {
    final IOFile target = new IOFile(root, file);
    final boolean create = !target.exists();

    // try to locate file from development branch
    final IO io = new IOFile("src/main/webapp", file);
    final byte[] data;
    if(io.exists()) {
      data = io.read();
      // check if resource path exists
      IOFile dir = new IOFile("src/main/resources");
      if(dir.exists()) {
        dir = new IOFile(dir, file);
        // update file in resource path if it has changed
        if(!dir.exists() || !Token.eq(data, dir.read())) {
          Util.errln("Updating " +  dir);
          dir.parent().md();
          dir.write(data);
        }
      }
    } else if(create) {
      // try to locate file from resource path
      try(InputStream is = BaseXHTTP.class.getResourceAsStream('/' + file)) {
        if(is == null) throw new BaseXException(io + " not found.");
        data = new IOStream(is).read();
      }
    } else {
      return target;
    }

    if(create) {
      // create configuration file
      Util.errln("Creating " +  target);
      target.parent().md();
      target.write(data);
    }
    return target;
  }

  @Override
  protected void parseArgs() throws IOException {
    /* command-line properties will be stored in system properties;
     * this way, they will not be overwritten by the settings specified in web.xml. */
    final MainParser arg = new MainParser(this);
    boolean daemon = true;

    while(arg.more()) {
      if(arg.dash()) {
        switch(arg.next()) {
          case 'c': // database command
            commands.add(commands(arg.string()));
            break;
          case 'C': // command script
            commands.add(script(arg.string()));
            break;
          case 'd': // activate debug mode
            Prop.put(StaticOptions.DEBUG, Boolean.toString(true));
            Prop.debug = true;
            break;
          case 'D': // hidden flag: daemon mode
            daemon = false;
            break;
          case 'g': // enable GZIP compression
            Prop.put(StaticOptions.GZIP, Boolean.toString(true));
            break;
          case 'h': // parse HTTP port
            port = arg.number();
            break;
          case 'l': // use local mode
            Prop.put(StaticOptions.HTTPLOCAL, Boolean.toString(true));
            break;
          case 'n': // parse host name
            final String n = arg.string();
            Prop.put(StaticOptions.HOST, n);
            Prop.put(StaticOptions.SERVERHOST, n);
            break;
          case 'p': // parse server port
            final int p = arg.number();
            Prop.put(StaticOptions.PORT, Integer.toString(p));
            Prop.put(StaticOptions.SERVERPORT, Integer.toString(p));
            break;
          case 'P': // default admin password
            password = arg.string();
            break;
          case 'q': // quiet flag (hidden)
            quiet = true;
            break;
          case 's': // parse stop port
            Prop.put(StaticOptions.STOPPORT, Integer.toString(arg.number()));
            break;
          case 'S': // set service flag
            service = daemon;
            break;
          case 'U': // specify username
            Prop.put(StaticOptions.USER, arg.string());
            break;
          case 'z': // suppress logging
            Prop.put(StaticOptions.LOG, "");
            break;
          default:
            throw arg.usage();
        }
      } else {
        if(!S_STOP.equalsIgnoreCase(arg.string())) throw arg.usage();
        stop = true;
      }
    }
    // do not evaluate command if additional service will be started
    if(service) commands.clear();
  }

  // STATIC METHODS ===============================================================================

  /**
   * Starts the HTTP server in a separate process.
   * @param args command-line arguments
   * @throws BaseXException database exception
   */
  public static void start(final String... args) throws BaseXException {
    // start server and check if it caused an error message
    final String error = Util.error(Util.start(BaseXHTTP.class, args), 2000);
    if(error != null) throw new BaseXException(error.trim());
  }

  /**
   * Stops the server.
   * @param host server host
   * @param port server port
   * @throws IOException I/O exception
   */
  public static void stop(final String host, final int port) throws IOException {
    // create stop file
    final IOFile stopFile = stopFile(BaseXHTTP.class, port);
    stopFile.parent().md();
    stopFile.touch();

    // try to connect the server
    try(Socket s = new Socket(host, port)) {
      // wait until server was stopped
      do Performance.sleep(10); while(stopFile.exists());
    } catch(final IOException ex) {
      Util.debug(ex);
      stopFile.delete();
      throw new IOException(Util.info(CONNECTION_ERROR_X, port));
    }
  }

  @Override
  public String header() {
    return Util.info(S_CONSOLE_X, S_HTTP_SERVER);
  }

  @Override
  public String usage() {
    return S_HTTPINFO;
  }

  /** Monitor for stopping the Jetty server. */
  private final class StopServer extends Thread {
    /** Server socket. */
    private final ServerSocket socket;
    /** Stop file. */
    private final IOFile stopFile;
    /** Port. */
    private final int stopPort;

    /**
     * Constructor.
     * @param port port to stop server
     * @throws IOException I/O exception
     */
    StopServer(final int port) throws IOException {
      stopPort = port;

      final String host = soptions.get(StaticOptions.SERVERHOST);
      final InetAddress addr = host.isEmpty() ? null : InetAddress.getByName(host);
      socket = new ServerSocket();
      socket.setReuseAddress(true);
      socket.bind(new InetSocketAddress(addr, stopPort));
      stopFile = stopFile(BaseXHTTP.class, stopPort);
    }

    @Override
    public void run() {
      try {
        while(true) {
          Util.println(HTTP + " STOP " + SRV_STARTED_PORT_X, stopPort);
          try(Socket s = socket.accept()) { /* no action */ }
          if(stopFile.exists()) {
            socket.close();
            Util.println(HTTP + " STOP " + SRV_STOPPED_PORT_X, stopPort);
            jetty.stop();
            hc.close();
            Prop.clear();
            if(!stopFile.delete()) {
              context.log.writeServer(LogType.ERROR, Util.info(FILE_NOT_DELETED_X, stopFile));
            }
            break;
          }
        }
      } catch(final Exception ex) {
        Util.stack(ex);
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy