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

org.h2.tools.Server Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.tools;

import java.net.URI;
import java.sql.Connection;
import java.sql.SQLException;

import org.h2.api.ErrorCode;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.server.Service;
import org.h2.server.ShutdownHandler;
import org.h2.server.TcpServer;
import org.h2.server.pg.PgServer;
import org.h2.server.web.WebServer;
import org.h2.util.StringUtils;
import org.h2.util.Tool;
import org.h2.util.Utils;

/**
 * Starts the H2 Console (web-) server, TCP, and PG server.
 */
public class Server extends Tool implements Runnable, ShutdownHandler {

    private final Service service;
    private Server web, tcp, pg;
    private ShutdownHandler shutdownHandler;
    private boolean fromCommandLine;
    private boolean started;

    public Server() {
        // nothing to do
        this.service = null;
    }

    /**
     * Create a new server for the given service.
     *
     * @param service the service
     * @param args the command line arguments
     * @throws SQLException on failure
     */
    public Server(Service service, String... args) throws SQLException {
        verifyArgs(args);
        this.service = service;
        try {
            service.init(args);
        } catch (Exception e) {
            throw DbException.toSQLException(e);
        }
    }

    /**
     * When running without options, -tcp, -web, -browser and -pg are started.
     *
     * Options are case sensitive.
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
Supported options
[-help] or [-?]Print the list of options
[-web]Start the web server with the H2 Console
[-webAllowOthers]Allow other computers to connect - see below
[-webExternalNames <names>]The comma-separated list of external names and IP addresses of this server, * used together with -webAllowOthers
[-webDaemon]Use a daemon thread
[-webVirtualThreads <true|false>]Use virtual threads (on Java 21+ only)
[-webPort <port>]The port (default: 8082)
[-webSSL]Use encrypted (HTTPS) connections
[-webAdminPassword]Hash of password of DB Console administrator, can be generated with * {@linkplain WebServer#encodeAdminPassword(String)}. Can be passed only to * the {@link #runTool(String...)} method, this method rejects it. It is * also possible to store this setting in configuration file of H2 * Console.
[-browser]Start a browser connecting to the web server
[-tcp]Start the TCP server
[-tcpAllowOthers]Allow other computers to connect - see below
[-tcpDaemon]Use a daemon thread
[-tcpVirtualThreads <true|false>]Use virtual threads (on Java 21+ only)
[-tcpPort <port>]The port (default: 9092)
[-tcpSSL]Use encrypted (SSL) connections
[-tcpPassword <pwd>]The password for shutting down a TCP server
[-tcpShutdown "<url>"]Stop the TCP server; example: tcp://localhost
[-tcpShutdownForce]Do not wait until all connections are closed
[-pg]Start the PG server
[-pgAllowOthers]Allow other computers to connect - see below
[-pgDaemon]Use a daemon thread
[-pgVirtualThreads <true|false>]Use virtual threads (on Java 21+ only)
[-pgPort <port>]The port (default: 5435)
[-properties "<dir>"]Server properties (default: ~, disable: null)
[-baseDir <dir>]The base directory for H2 databases (all servers)
[-ifExists]Only existing databases may be opened (all servers)
[-ifNotExists]Databases are created when accessed
[-trace]Print additional trace information (all servers)
[-key <from> <to>]Allows to map a database name to another (all servers)
* The options -xAllowOthers are potentially risky. * * For details, see Advanced Topics / Protection against Remote Access. * * @param args the command line arguments * @throws SQLException on failure */ public static void main(String... args) throws SQLException { Server server = new Server(); server.fromCommandLine = true; server.runTool(args); } private void verifyArgs(String... args) throws SQLException { for (int i = 0; args != null && i < args.length; i++) { String arg = args[i]; if (arg == null) { } else if ("-?".equals(arg) || "-help".equals(arg)) { // ok } else if (arg.startsWith("-web")) { if ("-web".equals(arg)) { // ok } else if ("-webAllowOthers".equals(arg)) { // no parameters } else if ("-webExternalNames".equals(arg)) { i++; } else if ("-webDaemon".equals(arg)) { // no parameters } else if ("-webVirtualThreads".equals(arg)) { i++; } else if ("-webSSL".equals(arg)) { // no parameters } else if ("-webPort".equals(arg)) { i++; } else if ("-webAdminPassword".equals(arg)) { if (fromCommandLine) { throwUnsupportedOption(arg); } i++; } else { throwUnsupportedOption(arg); } } else if ("-browser".equals(arg)) { // ok } else if (arg.startsWith("-tcp")) { if ("-tcp".equals(arg)) { // ok } else if ("-tcpAllowOthers".equals(arg)) { // no parameters } else if ("-tcpDaemon".equals(arg)) { // no parameters } else if ("-tcpVirtualThreads".equals(arg)) { i++; } else if ("-tcpSSL".equals(arg)) { // no parameters } else if ("-tcpPort".equals(arg)) { i++; } else if ("-tcpPassword".equals(arg)) { i++; } else if ("-tcpShutdown".equals(arg)) { i++; } else if ("-tcpShutdownForce".equals(arg)) { // ok } else { throwUnsupportedOption(arg); } } else if (arg.startsWith("-pg")) { if ("-pg".equals(arg)) { // ok } else if ("-pgAllowOthers".equals(arg)) { // no parameters } else if ("-pgDaemon".equals(arg)) { // no parameters } else if ("-pgVirtualThreads".equals(arg)) { i++; } else if ("-pgPort".equals(arg)) { i++; } else { throwUnsupportedOption(arg); } } else if (arg.startsWith("-ftp")) { if ("-ftpPort".equals(arg)) { i++; } else if ("-ftpDir".equals(arg)) { i++; } else if ("-ftpRead".equals(arg)) { i++; } else if ("-ftpWrite".equals(arg)) { i++; } else if ("-ftpWritePassword".equals(arg)) { i++; } else if ("-ftpTask".equals(arg)) { // no parameters } else { throwUnsupportedOption(arg); } } else if ("-properties".equals(arg)) { i++; } else if ("-trace".equals(arg)) { // no parameters } else if ("-ifExists".equals(arg)) { // no parameters } else if ("-ifNotExists".equals(arg)) { // no parameters } else if ("-baseDir".equals(arg)) { i++; } else if ("-key".equals(arg)) { i += 2; } else if ("-tool".equals(arg)) { // no parameters } else { throwUnsupportedOption(arg); } } } @Override public void runTool(String... args) throws SQLException { boolean tcpStart = false, pgStart = false, webStart = false; boolean browserStart = false; boolean tcpShutdown = false, tcpShutdownForce = false; String tcpPassword = ""; String tcpShutdownServer = ""; boolean startDefaultServers = true; for (int i = 0; args != null && i < args.length; i++) { String arg = args[i]; if (arg == null) { } else if ("-?".equals(arg) || "-help".equals(arg)) { showUsage(); return; } else if (arg.startsWith("-web")) { if ("-web".equals(arg)) { startDefaultServers = false; webStart = true; } else if ("-webAllowOthers".equals(arg)) { // no parameters } else if ("-webExternalNames".equals(arg)) { i++; } else if ("-webDaemon".equals(arg)) { // no parameters } else if ("-webSSL".equals(arg)) { // no parameters } else if ("-webPort".equals(arg)) { i++; } else if ("-webAdminPassword".equals(arg)) { if (fromCommandLine) { throwUnsupportedOption(arg); } i++; } else { showUsageAndThrowUnsupportedOption(arg); } } else if ("-browser".equals(arg)) { startDefaultServers = false; browserStart = true; } else if (arg.startsWith("-tcp")) { if ("-tcp".equals(arg)) { startDefaultServers = false; tcpStart = true; } else if ("-tcpAllowOthers".equals(arg)) { // no parameters } else if ("-tcpDaemon".equals(arg)) { // no parameters } else if ("-tcpSSL".equals(arg)) { // no parameters } else if ("-tcpPort".equals(arg)) { i++; } else if ("-tcpPassword".equals(arg)) { tcpPassword = args[++i]; } else if ("-tcpShutdown".equals(arg)) { startDefaultServers = false; tcpShutdown = true; tcpShutdownServer = args[++i]; } else if ("-tcpShutdownForce".equals(arg)) { tcpShutdownForce = true; } else { showUsageAndThrowUnsupportedOption(arg); } } else if (arg.startsWith("-pg")) { if ("-pg".equals(arg)) { startDefaultServers = false; pgStart = true; } else if ("-pgAllowOthers".equals(arg)) { // no parameters } else if ("-pgDaemon".equals(arg)) { // no parameters } else if ("-pgPort".equals(arg)) { i++; } else { showUsageAndThrowUnsupportedOption(arg); } } else if ("-properties".equals(arg)) { i++; } else if ("-trace".equals(arg)) { // no parameters } else if ("-ifExists".equals(arg)) { // no parameters } else if ("-ifNotExists".equals(arg)) { // no parameters } else if ("-baseDir".equals(arg)) { i++; } else if ("-key".equals(arg)) { i += 2; } else { showUsageAndThrowUnsupportedOption(arg); } } verifyArgs(args); if (startDefaultServers) { tcpStart = true; pgStart = true; webStart = true; browserStart = true; } // TODO server: maybe use one single properties file? if (tcpShutdown) { out.println("Shutting down TCP Server at " + tcpShutdownServer); shutdownTcpServer(tcpShutdownServer, tcpPassword, tcpShutdownForce, false); } try { if (tcpStart) { tcp = createTcpServer(args); tcp.start(); out.println(tcp.getStatus()); tcp.setShutdownHandler(this); } if (pgStart) { pg = createPgServer(args); pg.start(); out.println(pg.getStatus()); } if (webStart) { web = createWebServer(args); web.setShutdownHandler(this); SQLException result = null; try { web.start(); } catch (Exception e) { result = DbException.toSQLException(e); } out.println(web.getStatus()); // start browser in any case (even if the server is already // running) because some people don't look at the output, but // are wondering why nothing happens if (browserStart) { try { openBrowser(web.getURL()); } catch (Exception e) { out.println(e.getMessage()); } } if (result != null) { throw result; } } else if (browserStart) { out.println("The browser can only start if a web server is started (-web)"); } } catch (SQLException e) { stopAll(); throw e; } } /** * Shutdown one or all TCP server. If force is set to false, the server will * not allow new connections, but not kill existing connections, instead it * will stop if the last connection is closed. If force is set to true, * existing connections are killed. After calling the method with * force=false, it is not possible to call it again with force=true because * new connections are not allowed. Example: * *
     * Server.shutdownTcpServer("tcp://localhost:9094",
     *         password, true, false);
     * 
* * @param url example: tcp://localhost:9094 * @param password the password to use ("" for no password) * @param force the shutdown (don't wait) * @param all whether all TCP servers that are running in the JVM should be * stopped * @throws SQLException on failure */ public static void shutdownTcpServer(String url, String password, boolean force, boolean all) throws SQLException { TcpServer.shutdown(url, password, force, all); } /** * Get the status of this server. * * @return the status */ public String getStatus() { StringBuilder buff = new StringBuilder(); if (!started) { buff.append("Not started"); } else if (isRunning(false)) { buff.append(service.getType()). append(" server running at "). append(service.getURL()). append(" ("); if (service.getAllowOthers()) { buff.append("others can connect"); } else { buff.append("only local connections"); } buff.append(')'); } else { buff.append("The "). append(service.getType()). append(" server could not be started. " + "Possible cause: another server is already running at "). append(service.getURL()); } return buff.toString(); } /** * Create a new web server, but does not start it yet. Example: * *
     * Server server = Server.createWebServer("-trace").start();
     * 
* Supported options are: * -webPort, -webSSL, -webAllowOthers, -webDaemon, * -trace, -ifExists, -ifNotExists, -baseDir, -properties. * See the main method for details. * * @param args the argument list * @return the server * @throws SQLException on failure */ public static Server createWebServer(String... args) throws SQLException { return createWebServer(args, null, false); } /** * Create a new web server, but does not start it yet. * * @param args * the argument list * @param key * key, or null * @param allowSecureCreation * whether creation of databases using the key should be allowed * @return the server */ static Server createWebServer(String[] args, String key, boolean allowSecureCreation) throws SQLException { WebServer service = new WebServer(); service.setKey(key); service.setAllowSecureCreation(allowSecureCreation); Server server = new Server(service, args); service.setShutdownHandler(server); return server; } /** * Create a new TCP server, but does not start it yet. Example: * *
     * Server server = Server.createTcpServer(
     *     "-tcpPort", "9123", "-tcpAllowOthers").start();
     * 
* Supported options are: * -tcpPort, -tcpSSL, -tcpPassword, -tcpAllowOthers, -tcpDaemon, * -trace, -ifExists, -ifNotExists, -baseDir, -key. * See the main method for details. *

* If no port is specified, the default port is used if possible, * and if this port is already used, a random port is used. * Use getPort() or getURL() after starting to retrieve the port. *

* * @param args the argument list * @return the server * @throws SQLException on failure */ public static Server createTcpServer(String... args) throws SQLException { TcpServer service = new TcpServer(); Server server = new Server(service, args); service.setShutdownHandler(server); return server; } /** * Create a new PG server, but does not start it yet. * Example: *
     * Server server =
     *     Server.createPgServer("-pgAllowOthers").start();
     * 
* Supported options are: * -pgPort, -pgAllowOthers, -pgDaemon, * -trace, -ifExists, -ifNotExists, -baseDir, -key. * See the main method for details. *

* If no port is specified, the default port is used if possible, * and if this port is already used, a random port is used. * Use getPort() or getURL() after starting to retrieve the port. *

* * @param args the argument list * @return the server * @throws SQLException on failure */ public static Server createPgServer(String... args) throws SQLException { return new Server(new PgServer(), args); } /** * Tries to start the server. * @return the server if successful * @throws SQLException if the server could not be started */ public Server start() throws SQLException { try { started = true; service.start(); String url = service.getURL(); int idx = url.indexOf('?'); if (idx >= 0) { url = url.substring(0, idx); } String name = service.getName() + " (" + url + ')'; Thread t = new Thread(this, name); t.setDaemon(service.isDaemon()); t.start(); for (int i = 1; i < 64; i += i) { wait(i); if (isRunning(false)) { return this; } } if (isRunning(true)) { return this; } throw DbException.get(ErrorCode.EXCEPTION_OPENING_PORT_2, name, "timeout; " + "please check your network configuration, specially the file /etc/hosts"); } catch (DbException e) { throw DbException.toSQLException(e); } } private static void wait(int i) { try { // sleep at most 4096 ms long sleep = (long) i * (long) i; Thread.sleep(sleep); } catch (InterruptedException e) { // ignore } } private void stopAll() { Server s = web; if (s != null && s.isRunning(false)) { s.stop(); web = null; } s = tcp; if (s != null && s.isRunning(false)) { s.stop(); tcp = null; } s = pg; if (s != null && s.isRunning(false)) { s.stop(); pg = null; } } /** * Checks if the server is running. * * @param traceError if errors should be written * @return if the server is running */ public boolean isRunning(boolean traceError) { return service.isRunning(traceError); } /** * Stops the server. */ public void stop() { started = false; if (service != null) { service.stop(); } } /** * Gets the URL of this server. * * @return the url */ public String getURL() { return service.getURL(); } /** * Gets the port this server is listening on. * * @return the port */ public int getPort() { return service.getPort(); } /** * INTERNAL */ @Override public void run() { try { service.listen(); } catch (Exception e) { DbException.traceThrowable(e); } } /** * INTERNAL * @param shutdownHandler to set */ public void setShutdownHandler(ShutdownHandler shutdownHandler) { this.shutdownHandler = shutdownHandler; } /** * INTERNAL */ @Override public void shutdown() { if (shutdownHandler != null) { shutdownHandler.shutdown(); } else { stopAll(); } } /** * Get the service attached to this server. * * @return the service */ public Service getService() { return service; } /** * Open a new browser tab or window with the given URL. * * @param url the URL to open * @throws Exception on failure */ public static void openBrowser(String url) throws Exception { try { String osName = StringUtils.toLowerEnglish( Utils.getProperty("os.name", "linux")); Runtime rt = Runtime.getRuntime(); String browser = Utils.getProperty(SysProperties.H2_BROWSER, null); if (browser == null) { // under Linux, this will point to the default system browser try { browser = System.getenv("BROWSER"); } catch (SecurityException se) { // ignore } } if (browser != null) { if (browser.startsWith("call:")) { browser = browser.substring("call:".length()); Utils.callStaticMethod(browser, url); } else if (browser.contains("%url")) { String[] args = StringUtils.arraySplit(browser, ',', false); for (int i = 0; i < args.length; i++) { args[i] = StringUtils.replaceAll(args[i], "%url", url); } rt.exec(args); } else if (osName.contains("windows")) { rt.exec(new String[] { "cmd.exe", "/C", browser, url }); } else { rt.exec(new String[] { browser, url }); } return; } try { Class desktopClass = Class.forName("java.awt.Desktop"); // Desktop.isDesktopSupported() Boolean supported = (Boolean) desktopClass. getMethod("isDesktopSupported"). invoke(null, new Object[0]); URI uri = new URI(url); if (supported) { // Desktop.getDesktop(); Object desktop = desktopClass.getMethod("getDesktop"). invoke(null); // desktop.browse(uri); desktopClass.getMethod("browse", URI.class). invoke(desktop, uri); return; } } catch (Exception e) { // ignore } if (osName.contains("windows")) { rt.exec(new String[] { "rundll32", "url.dll,FileProtocolHandler", url }); } else if (osName.contains("mac") || osName.contains("darwin")) { // Mac OS: to open a page with Safari, use "open -a Safari" Runtime.getRuntime().exec(new String[] { "open", url }); } else { String[] browsers = { "xdg-open", "chromium", "google-chrome", "firefox", "mozilla-firefox", "mozilla", "konqueror", "netscape", "opera", "midori" }; boolean ok = false; for (String b : browsers) { try { rt.exec(new String[] { b, url }); ok = true; break; } catch (Exception e) { // ignore and try the next } } if (!ok) { // No success in detection. throw new Exception( "Browser detection failed, and java property 'h2.browser' " + "and environment variable BROWSER are not set to a browser executable."); } } } catch (Exception e) { throw new Exception( "Failed to start a browser to open the URL " + url + ": " + e.getMessage()); } } /** * Start a web server and a browser that uses the given connection. The * current transaction is preserved. This is specially useful to manually * inspect the database when debugging. This method return as soon as the * user has disconnected. * * @param conn the database connection (the database must be open) * @throws SQLException on failure */ public static void startWebServer(Connection conn) throws SQLException { startWebServer(conn, false); } /** * Start a web server and a browser that uses the given connection. The * current transaction is preserved. This is specially useful to manually * inspect the database when debugging. This method return as soon as the * user has disconnected. * * @param conn the database connection (the database must be open) * @param ignoreProperties if {@code true} properties from * {@code .h2.server.properties} will be ignored * @throws SQLException on failure */ public static void startWebServer(Connection conn, boolean ignoreProperties) throws SQLException { WebServer webServer = new WebServer(); String[] args; if (ignoreProperties) { args = new String[] { "-webPort", "0", "-properties", "null"}; } else { args = new String[] { "-webPort", "0" }; } Server web = new Server(webServer, args); web.start(); Server server = new Server(); server.web = web; webServer.setShutdownHandler(server); String url = webServer.addSession(conn); try { Server.openBrowser(url); while (!webServer.isStopped()) { Thread.sleep(1000); } } catch (Exception e) { // ignore } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy