diff --git a/make/modules/jdk.httpserver/Launcher.gmk b/make/modules/jdk.httpserver/Launcher.gmk new file mode 100644 index 00000000000..ead28ba1ca5 --- /dev/null +++ b/make/modules/jdk.httpserver/Launcher.gmk @@ -0,0 +1,30 @@ +# +# Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +include LauncherCommon.gmk + +$(eval $(call SetupBuildLauncher, jwebserver, \ + MAIN_CLASS := sun.net.httpserver.simpleserver.JWebServer, \ +)) diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/SimpleFileServer.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/SimpleFileServer.java index bd81e8e5eac..3d5c448c687 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/SimpleFileServer.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/SimpleFileServer.java @@ -104,12 +104,12 @@ import sun.net.httpserver.simpleserver.OutputFilter; * server.start(); * } * - *
A simple HTTP file server implementation is - * provided via the - * main entry point - * of the {@code jdk.httpserver} module. + *
A simple HTTP file server implementation is provided via the + * {@code jwebserver} tool. + * + * @toolGuide jwebserver * * @since 18 */ diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/package-info.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/package-info.java index ed3978e0eae..3b9de3520fb 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/package-info.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/package-info.java @@ -31,6 +31,24 @@ Any HTTP functionality not provided by this API can be implemented by application code using the API.
+ * The main components are: + *
+ * The {@link com.sun.net.httpserver.SimpleFileServer} class offers a simple + * HTTP-only file server (intended for testing, development and debugging purposes + * only). A default implementation is provided via the {@code jwebserver} tool. +
Programmers must implement the {@link com.sun.net.httpserver.HttpHandler} interface. This interface provides a callback which is invoked to handle incoming requests from clients. A HTTP request and its response is known as an exchange. HTTP exchanges are @@ -120,13 +138,6 @@ } }); -
- The {@link com.sun.net.httpserver.SimpleFileServer} class offers a simple - HTTP file server (intended for testing, development and debugging purposes - only). A default implementation is provided via the - main entry point - of the {@code jdk.httpserver} module. - @since 1.6 */ package com.sun.net.httpserver; diff --git a/src/jdk.httpserver/share/classes/module-info.java b/src/jdk.httpserver/share/classes/module-info.java index 417854d2ee8..d7ddf8eb303 100644 --- a/src/jdk.httpserver/share/classes/module-info.java +++ b/src/jdk.httpserver/share/classes/module-info.java @@ -24,41 +24,21 @@ */ /** - * Defines the JDK-specific HTTP server API. - *
- * A basic high-level API for building embedded servers. Both HTTP and - * HTTPS are supported. - *
- * The main components are: - *
- * The {@link com.sun.net.httpserver.SimpleFileServer} class offers a simple - * HTTP file server (intended for testing, development and debugging purposes - * only). A default implementation is provided via the - * main entry point of the {@code jdk.httpserver} module, which can be used on - * the command line as such: - *
{@code - * Usage: java -m jdk.httpserver [-b bind address] [-p port] [-d directory] - * [-o none|info|verbose] [-h to show options] - * Options: - * -b, --bind-address - Address to bind to. Default: 127.0.0.1 or ::1 (loopback). - * For all interfaces use "-b 0.0.0.0" or "-b ::". - * -d, --directory - Directory to serve. Default: current directory. - * -o, --output - Output format. none|info|verbose. Default: info. - * -p, --port - Port to listen on. Default: 8000. - * -h, -?, --help - Print this help message. - * }+ * Defines the JDK-specific HTTP server API, and provides the jwebserver tool + * for running a minimal HTTP server. + * + *
The {@link com.sun.net.httpserver} package defines a high-level API for + * building servers that support HTTP and HTTPS. The SimpleFileServer class + * implements a simple HTTP-only file server intended for testing, development + * and debugging purposes. A default implementation is provided via the + * {@code jwebserver} tool and the main entry point of the module, which can + * also be invoked with {@code java -m jdk.httpserver}. + * + *
The {@link com.sun.net.httpserver.spi} package specifies a Service Provider + * Interface (SPI) for locating HTTP server implementations based on the + * {@code com.sun.net.httpserver} API. + * + * @toolGuide jwebserver * * @uses com.sun.net.httpserver.spi.HttpServerProvider * diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/JWebServer.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/JWebServer.java new file mode 100644 index 00000000000..c338e4844e6 --- /dev/null +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/JWebServer.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.net.httpserver.simpleserver; + +import java.io.PrintWriter; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Programmatic entry point to start the jwebserver tool. + * + *
This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interface are subject to change or deletion + * without notice. + */ +public class JWebServer { + + /** + * This constructor should never be called. + */ + private JWebServer() { throw new AssertionError(); } + + /** + * The main entry point. + * + *
The command line arguments are parsed and the server is started. If + * started successfully, the server will run on a new non-daemon thread, + * and this method will return. Otherwise, if the server is not started + * successfully, e.g. an error is encountered while parsing the arguments + * or an I/O error occurs, the server is not started and this method invokes + * System::exit with an appropriate exit code. + * + * @param args the command-line options + * @throws NullPointerException if {@code args} is {@code null}, or if there + * are any {@code null} values in the {@code args} array + */ + public static void main(String... args) { + int ec = SimpleFileServerImpl.start(new PrintWriter(System.out, true, UTF_8), "jwebserver", args); + if (ec != 0) { + System.exit(ec); + } // otherwise, the server has either been started successfully and + // runs in another non-daemon thread, or -h or -version have been + // passed and the main thread has exited normally. + } +} diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/Main.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/Main.java index c51140bf5e9..9f76be48abd 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/Main.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/Main.java @@ -27,7 +27,7 @@ import java.io.PrintWriter; import static java.nio.charset.StandardCharsets.UTF_8; /** - * Programmatic entry point to start the simpleserver tool. + * Programmatic entry point to start "java -m jdk.httpserver". * *
This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. @@ -56,10 +56,11 @@ public class Main { * are any {@code null} values in the {@code args} array */ public static void main(String... args) { - int ec = SimpleFileServerImpl.start(new PrintWriter(System.out, true, UTF_8), args); - if (ec != 0) + int ec = SimpleFileServerImpl.start(new PrintWriter(System.out, true, UTF_8), "java", args); + if (ec != 0) { System.exit(ec); - // otherwise the server has been started successfully and runs in - // another non-daemon thread. + } // otherwise, the server has either been started successfully and + // runs in another non-daemon thread, or -h or -version have been + // passed and the main thread has exited normally. } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/SimpleFileServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/SimpleFileServerImpl.java index 57138018bb9..852aae5a838 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/SimpleFileServerImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/SimpleFileServerImpl.java @@ -71,11 +71,12 @@ final class SimpleFileServerImpl { * * @param writer the writer to which output should be written * @param args the command line options + * @param launcher the launcher the server is started from * @throws NullPointerException if any of the arguments are {@code null}, * or if there are any {@code null} values in the {@code args} array * @return startup status code */ - static int start(PrintWriter writer, String[] args) { + static int start(PrintWriter writer, String launcher, String[] args) { Objects.requireNonNull(args); for (var arg : args) { Objects.requireNonNull(arg); @@ -96,7 +97,11 @@ final class SimpleFileServerImpl { option = options.next(); switch (option) { case "-h", "-?", "--help" -> { - out.showHelp(); + out.showHelp(launcher); + return Startup.OK.statusCode; + } + case "-version", "--version" -> { + out.showVersion(launcher); return Startup.OK.statusCode; } case "-b", "--bind-address" -> { @@ -115,7 +120,7 @@ final class SimpleFileServerImpl { } } catch (AssertionError ae) { out.reportError(ResourceBundleHelper.getMessage("err.unknown.option", option)); - out.showUsage(); + out.showUsage(launcher); return Startup.CMDERR.statusCode; } catch (NoSuchElementException nsee) { out.reportError(ResourceBundleHelper.getMessage("err.missing.arg", option)); @@ -169,12 +174,16 @@ final class SimpleFileServerImpl { } } - void showUsage() { - writer.println(ResourceBundleHelper.getMessage("usage")); + void showUsage(String launcher) { + writer.println(ResourceBundleHelper.getMessage("usage." + launcher)); } - void showHelp() { - writer.println(ResourceBundleHelper.getMessage("usage")); + void showVersion(String launcher) { + writer.println(ResourceBundleHelper.getMessage("version", launcher, System.getProperty("java.version"))); + } + + void showHelp(String launcher) { + writer.println(ResourceBundleHelper.getMessage("usage." + launcher)); writer.println(ResourceBundleHelper.getMessage("options", LOOPBACK_ADDR.getHostAddress())); } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/resources/simpleserver.properties b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/resources/simpleserver.properties index bf7572b02d6..fc5a9ec6029 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/resources/simpleserver.properties +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/resources/simpleserver.properties @@ -23,9 +23,18 @@ # questions. # -usage=\ +usage.java=\ Usage: java -m jdk.httpserver [-b bind address] [-p port] [-d directory]\n\ -\ [-o none|info|verbose] [-h to show options] +\ [-o none|info|verbose] [-h to show options]\n\ +\ [-version to show version information] + +usage.jwebserver=\ +Usage: jwebserver [-b bind address] [-p port] [-d directory]\n\ +\ [-o none|info|verbose] [-h to show options]\n\ +\ [-version to show version information] + +version=\ +{0} {1} options=\ Options:\n\ @@ -34,7 +43,8 @@ Options:\n\ -d, --directory - Directory to serve. Default: current directory.\n\ -o, --output - Output format. none|info|verbose. Default: info.\n\ -p, --port - Port to listen on. Default: 8000.\n\ --h, -?, --help - Print this help message.\n\ +-h, -?, --help - Prints this help message and exits.\n\ +-version, --version - Prints version information and exits.\n\ To stop the server, press Ctrl + C. opt.bindaddress=\ diff --git a/src/jdk.httpserver/share/man/jwebserver.1 b/src/jdk.httpserver/share/man/jwebserver.1 new file mode 100644 index 00000000000..c0c1341a0f8 --- /dev/null +++ b/src/jdk.httpserver/share/man/jwebserver.1 @@ -0,0 +1,175 @@ +.\" Automatically generated by Pandoc 2.3.1 +.\" +.TH "JWEBSERVER" "1" "2021" "JDK 18\-internal" "JDK Commands" +.hy +.SH NAME +.PP +jwebserver \- launch the Java Simple Web Server +.SH SYNOPSIS +.PP +\f[CB]jwebserver\f[R] [\f[I]options\f[R]] +.TP +.B \f[I]options\f[R] +Command\-line options. +For a detailed description of the options, see \f[B]Options\f[R]. +.RS +.RE +.SH DESCRIPTION +.PP +The \f[CB]jwebserver\f[R] tool provides a minimal HTTP server, designed to +be used for prototyping, testing, and debugging. +It serves a single directory hierarchy, and only serves static files. +Only HTTP/1.1 is supported; HTTP/2 and HTTPS are not supported. +.PP +Only idempotent HEAD and GET requests are served. +Any other requests receive a \f[CB]501\ \-\ Not\ Implemented\f[R] or a +\f[CB]405\ \-\ Not\ Allowed\f[R] response. +GET requests are mapped to the directory being served, as follows: +.IP \[bu] 2 +If the requested resource is a file, its content is served. +.IP \[bu] 2 +If the requested resource is a directory that contains an index file, +the content of the index file is served. +.IP \[bu] 2 +Otherwise, the names of all files and subdirectories of the directory +are listed. +Symbolic links and hidden files are not listed or served. +.PP +MIME types are configured automatically, using the built\-in table. +For example, \f[CB]\&.html\f[R] files are served as \f[CB]text/html\f[R] and +\f[CB]\&.java\f[R] files are served as \f[CB]text/plain\f[R]. +.PP +\f[CB]jwebserver\f[R] is located in the jdk.httpserver module, and can +alternatively be started with \f[CB]java\ \-m\ jdk.httpserver\f[R]. +It is based on the web server implementation in the +\f[CB]com.sun.net.httpserver\f[R] package. +The \f[CB]com.sun.net.httpserver.SimpleFileServer\f[R] class provides a +programmatic way to retrieve the server and its components for reuse and +extension. +.SH USAGE +.IP +.nf +\f[CB] +jwebserver\ [\-b\ bind\ address]\ [\-p\ port]\ [\-d\ directory] +\ \ \ \ \ \ \ \ \ \ \ [\-o\ none|info|verbose]\ [\-h\ to\ show\ options] +\ \ \ \ \ \ \ \ \ \ \ [\-version\ to\ show\ version\ information] +\f[R] +.fi +.SH OPTIONS +.TP +.B \f[CB]\-h\f[R] or \f[CB]\-?\f[R] or \f[CB]\-\-help\f[R] +Prints the help message and exits. +.RS +.RE +.TP +.B \f[CB]\-b\f[R] \f[I]addr\f[R] or \f[CB]\-\-bind\-address\f[R] \f[I]addr\f[R] +Specifies the address to bind to. +Default: 127.0.0.1 or ::1 (loopback). +For all interfaces use \f[CB]\-b\ 0.0.0.0\f[R] or \f[CB]\-b\ ::\f[R]. +.RS +.RE +.TP +.B \f[CB]\-d\f[R] \f[I]dir\f[R] or \f[CB]\-\-directory\f[R] \f[I]dir\f[R] +Specifies the directory to serve. +Default: current directory. +.RS +.RE +.TP +.B \f[CB]\-o\f[R] \f[I]level\f[R] or \f[CB]\-\-output\f[R] \f[I]level\f[R] +Specifies the output format. +\f[CB]none\f[R] | \f[CB]info\f[R] | \f[CB]verbose\f[R]. +Default: \f[CB]info\f[R]. +.RS +.RE +.TP +.B \f[CB]\-p\f[R] \f[I]port\f[R] or \f[CB]\-\-port\f[R] \f[I]port\f[R] +Specifies the port to listen on. +Default: 8000. +.RS +.RE +.TP +.B \f[CB]\-version\f[R] or \f[CB]\-\-version\f[R] +Prints the version information and exits. +.RS +.RE +.PP +To stop the server, press \f[CB]Ctrl\ +\ C\f[R]. +.SH STARTING THE SERVER +.PP +The following command starts the Simple Web Server: +.IP +.nf +\f[CB] +$\ jwebserver +\f[R] +.fi +.PP +If startup is successful, the server prints a message to +\f[CB]System.out\f[R] listing the local address and the absolute path of +the directory being served. +For example: +.IP +.nf +\f[CB] +$\ jwebserver +Binding\ to\ loopback\ by\ default.\ For\ all\ interfaces\ use\ "\-b\ 0.0.0.0"\ or\ "\-b\ ::". +Serving\ /cwd\ and\ subdirectories\ on\ 127.0.0.1\ port\ 8000 +URL\ http://127.0.0.1:8000/ +\f[R] +.fi +.SH CONFIGURATION +.PP +By default, the server runs in the foreground and binds to the loopback +address and port 8000. +This can be changed with the \f[CB]\-b\f[R] and \f[CB]\-p\f[R] options. +.PD 0 +.P +.PD +For example, to bind the Simple Web Server to all interfaces, use: +.IP +.nf +\f[CB] +$\ jwebserver\ \-b\ 0.0.0.0 +Serving\ /cwd\ and\ subdirectories\ on\ 0.0.0.0\ (all\ interfaces)\ port\ 8000 +URL\ http://123.456.7.891:8000/ +\f[R] +.fi +.PP +Note that this makes the web server accessible to all hosts on the +network. +\f[I]Do not do this unless you are sure the server cannot leak any +sensitive information.\f[R] +.PP +As another example, use the following command to run on port 9000: +.IP +.nf +\f[CB] +$\ jwebserver\ \-p\ 9000 +\f[R] +.fi +.PP +By default, the files of the current directory are served. +A different directory can be specified with the \f[CB]\-d\f[R] option. +.PP +By default, every request is logged on the console. +The output looks like this: +.IP +.nf +\f[CB] +127.0.0.1\ \-\ \-\ [10/Feb/2021:14:34:11\ +0000]\ "GET\ /some/subdirectory/\ HTTP/1.1"\ 200\ \- +\f[R] +.fi +.PP +Logging output can be changed with the \f[CB]\-o\f[R] option. +The default setting is \f[CB]info\f[R]. +The \f[CB]verbose\f[R] setting additionally includes the request and +response headers as well as the absolute path of the requested resource. +.SH STOPPING THE SERVER +.PP +Once started successfully, the server runs until it is stopped. +On Unix platforms, the server can be stopped by sending it a +\f[CB]SIGINT\f[R] signal (\f[CB]Ctrl+C\f[R] in a terminal window). +.SH HELP OPTION +.PP +The \f[CB]\-h\f[R] option displays a help message describing the usage and +the options of the \f[CB]jwebserver\f[R]. diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java index a494d5961a3..3a22d54f36d 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java @@ -23,7 +23,7 @@ /* * @test - * @summary Negative tests for simpleserver command-line tool + * @summary Negative tests for java -m jdk.httpserver command * @library /test/lib * @modules jdk.httpserver * @run testng/othervm CommandLineNegativeTest diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePortNotSpecifiedTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePortNotSpecifiedTest.java index 9e53d1008e4..ba414a9d0c1 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePortNotSpecifiedTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePortNotSpecifiedTest.java @@ -24,7 +24,7 @@ /* * @test * @bug 8276848 - * @summary Tests the command-line tool with port not specified + * @summary Tests the java -m jdk.httpserver command with port not specified * @modules jdk.httpserver * @library /test/lib * @run testng/othervm/manual CommandLinePortNotSpecifiedTest @@ -118,7 +118,8 @@ public class CommandLinePortNotSpecifiedTest { -d, --directory - Directory to serve. Default: current directory. -o, --output - Output format. none|info|verbose. Default: info. -p, --port - Port to listen on. Default: 8000. - -h, -?, --help - Print this help message. + -h, -?, --help - Prints this help message and exits. + -version, --version - Prints version information and exits. To stop the server, press Ctrl + C.""".formatted(LOOPBACK_ADDR); // The stdout/stderr output line to wait for when starting the simpleserver diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePositiveTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePositiveTest.java index c17b9e1b973..fce013460c1 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePositiveTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePositiveTest.java @@ -23,7 +23,7 @@ /* * @test - * @summary Positive tests for simpleserver command-line tool + * @summary Positive tests for java -m jdk.httpserver command * @library /test/lib * @modules jdk.httpserver * @run testng/othervm CommandLinePositiveTest @@ -46,6 +46,7 @@ import static java.lang.System.out; public class CommandLinePositiveTest { + static final String JAVA_VERSION = System.getProperty("java.version"); static final Path JAVA_HOME = Path.of(System.getProperty("java.home")); static final String JAVA = getJava(JAVA_HOME); static final Path CWD = Path.of(".").toAbsolutePath().normalize(); @@ -106,7 +107,8 @@ public class CommandLinePositiveTest { static final String USAGE_TEXT = """ Usage: java -m jdk.httpserver [-b bind address] [-p port] [-d directory] - [-o none|info|verbose] [-h to show options]"""; + [-o none|info|verbose] [-h to show options] + [-version to show version information]"""; static final String OPTIONS_TEXT = """ Options: @@ -115,7 +117,8 @@ public class CommandLinePositiveTest { -d, --directory - Directory to serve. Default: current directory. -o, --output - Output format. none|info|verbose. Default: info. -p, --port - Port to listen on. Default: 8000. - -h, -?, --help - Print this help message. + -h, -?, --help - Prints this help message and exits. + -version, --version - Prints version information and exits. To stop the server, press Ctrl + C.""".formatted(LOOPBACK_ADDR); @Test(dataProvider = "helpOptions") @@ -129,6 +132,18 @@ public class CommandLinePositiveTest { .shouldContain(OPTIONS_TEXT); } + @DataProvider + public Object[][] versionOptions() { return new Object[][] {{"-version"}, {"--version"}}; } + + @Test(dataProvider = "versionOptions") + public void testVersion(String opt) throws Throwable { + out.println("\n--- testVersion, opt=\"%s\" ".formatted(opt)); + simpleserver(WaitForLine.VERSION_STARTUP_LINE, + false, // do not explicitly destroy the process + JAVA, "-m", "jdk.httpserver", opt) + .shouldHaveExitValue(0); + } + @DataProvider public Object[][] bindOptions() { return new Object[][] {{"-b"}, {"--bind-address"}}; } @@ -207,11 +222,13 @@ public class CommandLinePositiveTest { static final String REGULAR_STARTUP_LINE1_STRING = "Serving"; static final String REGULAR_STARTUP_LINE2_STRING = "URL http://"; + static final String VERSION_STARTUP_LINE_STRING = "java " + JAVA_VERSION; // The stdout/stderr output line to wait for when starting the simpleserver enum WaitForLine { REGULAR_STARTUP_LINE (REGULAR_STARTUP_LINE2_STRING) , - HELP_STARTUP_LINE (OPTIONS_TEXT.lines().reduce((first, second) -> second).orElseThrow()); + HELP_STARTUP_LINE (OPTIONS_TEXT.lines().reduce((first, second) -> second).orElseThrow()), + VERSION_STARTUP_LINE (VERSION_STARTUP_LINE_STRING); final String value; WaitForLine(String value) { this.value = value; } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLineNegativeTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLineNegativeTest.java new file mode 100644 index 00000000000..77671d1e82d --- /dev/null +++ b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLineNegativeTest.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Negative tests for the jwebserver command-line tool + * @library /test/lib + * @modules jdk.httpserver + * @run testng/othervm CommandLineNegativeTest + */ + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import jdk.test.lib.Platform; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.util.FileUtils; +import org.testng.SkipException; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static java.lang.System.out; +import static org.testng.Assert.assertFalse; + +public class CommandLineNegativeTest { + + static final Path JAVA_HOME = Path.of(System.getProperty("java.home")); + static final String JWEBSERVER = getJwebserver(JAVA_HOME); + static final Path CWD = Path.of(".").toAbsolutePath().normalize(); + static final Path TEST_DIR = CWD.resolve("CommandLineNegativeTest"); + static final Path TEST_FILE = TEST_DIR.resolve("file.txt"); + static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress(); + + @BeforeTest + public void setup() throws IOException { + if (Files.exists(TEST_DIR)) { + FileUtils.deleteFileTreeWithRetry(TEST_DIR); + } + Files.createDirectories(TEST_DIR); + Files.createFile(TEST_FILE); + } + + @DataProvider + public Object[][] unknownOption() { + return new Object[][] { + {"--unknownOption"}, + {"null"} + }; + } + + @Test(dataProvider = "unknownOption") + public void testBadOption(String opt) throws Throwable { + out.println("\n--- testUnknownOption, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, opt) + .shouldNotHaveExitValue(0) + .shouldContain("Error: unknown option: " + opt); + } + + @DataProvider + public Object[][] tooManyOptionArgs() { + return new Object[][] { + {"-b", "localhost"}, + {"-d", "/some/path"}, + {"-o", "none"}, + {"-p", "0"}, + {"--bind-address", "localhost"}, + {"--directory", "/some/path"}, + {"--output", "none"}, + {"--port", "0"} + // doesn't fail for -h option + }; + } + + @Test(dataProvider = "tooManyOptionArgs") + public void testTooManyOptionArgs(String opt, String arg) throws Throwable { + out.println("\n--- testTooManyOptionArgs, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, opt, arg, arg) + .shouldNotHaveExitValue(0) + .shouldContain("Error: unknown option: " + arg); + } + + @DataProvider + public Object[][] noArg() { + return new Object[][] { + {"-b", """ + -b, --bind-address - Address to bind to. Default: %s (loopback). + For all interfaces use "-b 0.0.0.0" or "-b ::".""".formatted(LOOPBACK_ADDR)}, + {"-d", "-d, --directory - Directory to serve. Default: current directory."}, + {"-o", "-o, --output - Output format. none|info|verbose. Default: info."}, + {"-p", "-p, --port - Port to listen on. Default: 8000."}, + {"--bind-address", """ + -b, --bind-address - Address to bind to. Default: %s (loopback). + For all interfaces use "-b 0.0.0.0" or "-b ::".""".formatted(LOOPBACK_ADDR)}, + {"--directory", "-d, --directory - Directory to serve. Default: current directory."}, + {"--output", "-o, --output - Output format. none|info|verbose. Default: info."}, + {"--port", "-p, --port - Port to listen on. Default: 8000."} + // doesn't fail for -h option + }; + } + + @Test(dataProvider = "noArg") + public void testNoArg(String opt, String msg) throws Throwable { + out.println("\n--- testNoArg, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, opt) + .shouldNotHaveExitValue(0) + .shouldContain("Error: no value given for " + opt) + .shouldContain(msg); + } + + @DataProvider + public Object[][] invalidValue() { + return new Object[][] { + {"-b", "[127.0.0.1]"}, + {"-b", "badhost"}, + {"--bind-address", "192.168.1.220..."}, + + {"-o", "bad-output-level"}, + {"--output", "bad-output-level"}, + + {"-p", "+-"}, + {"--port", "+-"} + }; + } + + @Test(dataProvider = "invalidValue") + public void testInvalidValue(String opt, String val) throws Throwable { + out.println("\n--- testInvalidValue, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, opt, val) + .shouldNotHaveExitValue(0) + .shouldContain("Error: invalid value given for " + opt + ": " + val); + } + + @DataProvider + public Object[][] portOptions() { return new Object[][] {{"-p"}, {"--port"}}; } + + @Test(dataProvider = "portOptions") + public void testPortOutOfRange(String opt) throws Throwable { + out.println("\n--- testPortOutOfRange, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, opt, "65536") // range 0 to 65535 + .shouldNotHaveExitValue(0) + .shouldContain("Error: server config failed: " + "port out of range:65536"); + } + + @DataProvider + public Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; } + + @Test(dataProvider = "directoryOptions") + public void testRootNotAbsolute(String opt) throws Throwable { + out.println("\n--- testRootNotAbsolute, opt=\"%s\" ".formatted(opt)); + var root = Path.of("."); + assertFalse(root.isAbsolute()); + simpleserver(JWEBSERVER, opt, root.toString()) + .shouldNotHaveExitValue(0) + .shouldContain("Error: server config failed: " + "Path is not absolute:"); + } + + @Test(dataProvider = "directoryOptions") + public void testRootNotADirectory(String opt) throws Throwable { + out.println("\n--- testRootNotADirectory, opt=\"%s\" ".formatted(opt)); + var file = TEST_FILE.toString(); + assertFalse(Files.isDirectory(TEST_FILE)); + simpleserver(JWEBSERVER, opt, file) + .shouldNotHaveExitValue(0) + .shouldContain("Error: server config failed: " + "Path is not a directory: " + file); + } + + @Test(dataProvider = "directoryOptions") + public void testRootDoesNotExist(String opt) throws Throwable { + out.println("\n--- testRootDoesNotExist, opt=\"%s\" ".formatted(opt)); + Path root = TEST_DIR.resolve("not/existent/dir"); + assertFalse(Files.exists(root)); + simpleserver(JWEBSERVER, opt, root.toString()) + .shouldNotHaveExitValue(0) + .shouldContain("Error: server config failed: " + "Path does not exist: " + root.toString()); + } + + @Test(dataProvider = "directoryOptions") + public void testRootNotReadable(String opt) throws Throwable { + out.println("\n--- testRootNotReadable, opt=\"%s\" ".formatted(opt)); + if (Platform.isWindows()) { + // Not applicable to Windows. Reason: cannot revoke an owner's read + // access to a directory that was created by that owner + throw new SkipException("cannot run on Windows"); + } + Path root = Files.createDirectories(TEST_DIR.resolve("not/readable/dir")); + try { + root.toFile().setReadable(false, false); + assertFalse(Files.isReadable(root)); + simpleserver(JWEBSERVER, opt, root.toString()) + .shouldNotHaveExitValue(0) + .shouldContain("Error: server config failed: " + "Path is not readable: " + root.toString()); + } finally { + root.toFile().setReadable(true, false); + } + } + + @AfterTest + public void teardown() throws IOException { + if (Files.exists(TEST_DIR)) { + FileUtils.deleteFileTreeWithRetry(TEST_DIR); + } + } + + // --- infra --- + + static String getJwebserver(Path image) { + boolean isWindows = System.getProperty("os.name").startsWith("Windows"); + Path jwebserver = image.resolve("bin").resolve(isWindows ? "jwebserver.exe" : "jwebserver"); + if (Files.notExists(jwebserver)) + throw new RuntimeException(jwebserver + " not found"); + return jwebserver.toAbsolutePath().toString(); + } + + static OutputAnalyzer simpleserver(String... args) throws Throwable { + var pb = new ProcessBuilder(args) + .directory(TEST_DIR.toFile()); + var outputAnalyser = ProcessTools.executeCommand(pb) + .outputTo(System.out) + .errorTo(System.out); + return outputAnalyser; + } +} diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePortNotSpecifiedTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePortNotSpecifiedTest.java new file mode 100644 index 00000000000..4d850442b25 --- /dev/null +++ b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePortNotSpecifiedTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8276848 + * @summary Tests the jwebserver tool with port not specified + * @modules jdk.httpserver + * @library /test/lib + * @run testng/othervm/manual CommandLinePortNotSpecifiedTest + */ + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; +import jdk.test.lib.Platform; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.util.FileUtils; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; +import static java.lang.System.out; + +public class CommandLinePortNotSpecifiedTest { + + static final Path JAVA_HOME = Path.of(System.getProperty("java.home")); + static final String JWEBSERVER = getJwebserver(JAVA_HOME); + static final Path CWD = Path.of(".").toAbsolutePath().normalize(); + static final Path TEST_DIR = CWD.resolve("CommandLinePortNotSpecifiedTest"); + static final Path TEST_FILE = TEST_DIR.resolve("file.txt"); + static final String TEST_DIR_STR = TEST_DIR.toString(); + static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress(); + + @BeforeTest + public void setup() throws IOException { + if (Files.exists(TEST_DIR)) { + FileUtils.deleteFileTreeWithRetry(TEST_DIR); + } + Files.createDirectories(TEST_DIR); + Files.createFile(TEST_FILE); + } + + static final int SIGTERM = 15; + static final int NORMAL_EXIT_CODE = normalExitCode(); + + static int normalExitCode() { + if (Platform.isWindows()) { + return 1; // expected process destroy exit code + } else { + // signal terminated exit code on Unix is 128 + signal value + return 128 + SIGTERM; + } + } + + /** + * This is a manual test to confirm the command-line tool starts successfully + * in the case where the port is not specified. In this case the server uses + * the default port 8000. The test is manual to avoid a BindException in the + * unlikely but not impossible case that the port is already in use. + */ + @Test + public void testPortNotSpecified() throws Throwable { + out.println("\n--- testPortNotSpecified"); + simpleserver(JWEBSERVER) + .shouldHaveExitValue(NORMAL_EXIT_CODE) + .shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".") + .shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port") + .shouldContain("URL http://" + LOOPBACK_ADDR); + } + + @AfterTest + public void teardown() throws IOException { + if (Files.exists(TEST_DIR)) { + FileUtils.deleteFileTreeWithRetry(TEST_DIR); + } + } + + // --- infra --- + + static String getJwebserver(Path image) { + boolean isWindows = System.getProperty("os.name").startsWith("Windows"); + Path jwebserver = image.resolve("bin").resolve(isWindows ? "jwebserver.exe" : "jwebserver"); + if (Files.notExists(jwebserver)) + throw new RuntimeException(jwebserver + " not found"); + return jwebserver.toAbsolutePath().toString(); + } + + static final String REGULAR_STARTUP_LINE1_STRING = "Serving"; + static final String REGULAR_STARTUP_LINE2_STRING = "URL http://"; + + static final String OPTIONS_TEXT = """ + Options: + -b, --bind-address - Address to bind to. Default: %s (loopback). + For all interfaces use "-b 0.0.0.0" or "-b ::". + -d, --directory - Directory to serve. Default: current directory. + -o, --output - Output format. none|info|verbose. Default: info. + -p, --port - Port to listen on. Default: 8000. + -h, -?, --help - Prints this help message and exits. + -version, --version - Prints version information and exits. + To stop the server, press Ctrl + C.""".formatted(LOOPBACK_ADDR); + + // The stdout/stderr output line to wait for when starting the simpleserver + enum WaitForLine { + REGULAR_STARTUP_LINE (REGULAR_STARTUP_LINE2_STRING) , + HELP_STARTUP_LINE (OPTIONS_TEXT.lines().reduce((first, second) -> second).orElseThrow()); + + final String value; + WaitForLine(String value) { this.value = value; } + } + + static OutputAnalyzer simpleserver(String... args) throws Throwable { + return simpleserver(WaitForLine.REGULAR_STARTUP_LINE, true, args); + } + + static OutputAnalyzer simpleserver(WaitForLine waitForLine, boolean destroy, String... args) throws Throwable { + StringBuffer sb = new StringBuffer(); // stdout & stderr + // start the process and await the waitForLine before returning + var p = ProcessTools.startProcess("simpleserver", + new ProcessBuilder(args).directory(TEST_DIR.toFile()), + line -> sb.append(line + "\n"), + line -> line.startsWith(waitForLine.value), + 30, // suitably high default timeout, not expected to timeout + TimeUnit.SECONDS); + if (destroy) { + p.destroy(); // SIGTERM on Unix + } + int ec = p.waitFor(); + var outputAnalyser = new OutputAnalyzer(sb.toString(), "", ec); + return outputAnalyser; + } +} diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePositiveTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePositiveTest.java new file mode 100644 index 00000000000..c3a63371828 --- /dev/null +++ b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePositiveTest.java @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Positive tests for the jwebserver command-line tool + * @library /test/lib + * @modules jdk.httpserver + * @run testng/othervm CommandLinePositiveTest + */ + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; +import jdk.test.lib.Platform; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.util.FileUtils; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static java.lang.System.out; + +public class CommandLinePositiveTest { + + static final String JAVA_VERSION = System.getProperty("java.version"); + static final Path JAVA_HOME = Path.of(System.getProperty("java.home")); + static final String JWEBSERVER = getJwebserver(JAVA_HOME); + static final Path CWD = Path.of(".").toAbsolutePath().normalize(); + static final Path TEST_DIR = CWD.resolve("CommandLinePositiveTest"); + static final Path TEST_FILE = TEST_DIR.resolve("file.txt"); + static final String TEST_DIR_STR = TEST_DIR.toString(); + static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress(); + + @BeforeTest + public void setup() throws IOException { + if (Files.exists(TEST_DIR)) { + FileUtils.deleteFileTreeWithRetry(TEST_DIR); + } + Files.createDirectories(TEST_DIR); + Files.createFile(TEST_FILE); + } + + static final int SIGTERM = 15; + static final int NORMAL_EXIT_CODE = normalExitCode(); + + static int normalExitCode() { + if (Platform.isWindows()) { + return 1; // expected process destroy exit code + } else { + // signal terminated exit code on Unix is 128 + signal value + return 128 + SIGTERM; + } + } + + @DataProvider + public Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; } + + @Test(dataProvider = "directoryOptions") + public void testDirectory(String opt) throws Throwable { + out.println("\n--- testDirectory, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, "-p", "0", opt, TEST_DIR_STR) + .shouldHaveExitValue(NORMAL_EXIT_CODE) + .shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".") + .shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port") + .shouldContain("URL http://" + LOOPBACK_ADDR); + } + + @DataProvider + public Object[][] portOptions() { return new Object[][] {{"-p"}, {"--port"}}; } + + @Test(dataProvider = "portOptions") + public void testPort(String opt) throws Throwable { + out.println("\n--- testPort, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, opt, "0") + .shouldHaveExitValue(NORMAL_EXIT_CODE) + .shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".") + .shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port") + .shouldContain("URL http://" + LOOPBACK_ADDR); + } + + @DataProvider + public Object[][] helpOptions() { return new Object[][] {{"-h"}, {"-?"}, {"--help"}}; } + + static final String USAGE_TEXT = """ + Usage: jwebserver [-b bind address] [-p port] [-d directory] + [-o none|info|verbose] [-h to show options] + [-version to show version information]"""; + + static final String OPTIONS_TEXT = """ + Options: + -b, --bind-address - Address to bind to. Default: %s (loopback). + For all interfaces use "-b 0.0.0.0" or "-b ::". + -d, --directory - Directory to serve. Default: current directory. + -o, --output - Output format. none|info|verbose. Default: info. + -p, --port - Port to listen on. Default: 8000. + -h, -?, --help - Prints this help message and exits. + -version, --version - Prints version information and exits. + To stop the server, press Ctrl + C.""".formatted(LOOPBACK_ADDR); + + @Test(dataProvider = "helpOptions") + public void testHelp(String opt) throws Throwable { + out.println("\n--- testHelp, opt=\"%s\" ".formatted(opt)); + simpleserver(WaitForLine.HELP_STARTUP_LINE, + false, // do not explicitly destroy the process + JWEBSERVER, opt) + .shouldHaveExitValue(0) + .shouldContain(USAGE_TEXT) + .shouldContain(OPTIONS_TEXT); + } + + @DataProvider + public Object[][] versionOptions() { return new Object[][] {{"-version"}, {"--version"}}; } + + @Test(dataProvider = "versionOptions") + public void testVersion(String opt) throws Throwable { + out.println("\n--- testVersion, opt=\"%s\" ".formatted(opt)); + simpleserver(WaitForLine.VERSION_STARTUP_LINE, + false, // do not explicitly destroy the process + JWEBSERVER, opt) + .shouldHaveExitValue(0); + } + + @DataProvider + public Object[][] bindOptions() { return new Object[][] {{"-b"}, {"--bind-address"}}; } + + @Test(dataProvider = "bindOptions") + public void testBindAllInterfaces(String opt) throws Throwable { + out.println("\n--- testBindAllInterfaces, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, "-p", "0", opt, "0.0.0.0") + .shouldHaveExitValue(NORMAL_EXIT_CODE) + .shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on 0.0.0.0 (all interfaces) port") + .shouldContain("URL http://" + InetAddress.getLocalHost().getHostAddress()); + simpleserver(JWEBSERVER, opt, "::0") + .shouldHaveExitValue(NORMAL_EXIT_CODE) + .shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on 0.0.0.0 (all interfaces) port") + .shouldContain("URL http://" + InetAddress.getLocalHost().getHostAddress()); + } + + @Test(dataProvider = "bindOptions") + public void testLastOneWinsBindAddress(String opt) throws Throwable { + out.println("\n--- testLastOneWinsBindAddress, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, "-p", "0", opt, "123.4.5.6", opt, LOOPBACK_ADDR) + .shouldHaveExitValue(NORMAL_EXIT_CODE) + .shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port") + .shouldContain("URL http://" + LOOPBACK_ADDR); + + } + + @Test(dataProvider = "directoryOptions") + public void testLastOneWinsDirectory(String opt) throws Throwable { + out.println("\n--- testLastOneWinsDirectory, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, "-p", "0", opt, TEST_DIR_STR, opt, TEST_DIR_STR) + .shouldHaveExitValue(NORMAL_EXIT_CODE) + .shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".") + .shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port") + .shouldContain("URL http://" + LOOPBACK_ADDR); + } + + @DataProvider + public Object[][] outputOptions() { return new Object[][] {{"-o"}, {"--output"}}; } + + @Test(dataProvider = "outputOptions") + public void testLastOneWinsOutput(String opt) throws Throwable { + out.println("\n--- testLastOneWinsOutput, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, "-p", "0", opt, "none", opt, "verbose") + .shouldHaveExitValue(NORMAL_EXIT_CODE) + .shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".") + .shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port") + .shouldContain("URL http://" + LOOPBACK_ADDR); + } + + @Test(dataProvider = "portOptions") + public void testLastOneWinsPort(String opt) throws Throwable { + out.println("\n--- testLastOneWinsPort, opt=\"%s\" ".formatted(opt)); + simpleserver(JWEBSERVER, opt, "-999", opt, "0") + .shouldHaveExitValue(NORMAL_EXIT_CODE) + .shouldContain("Binding to loopback by default. For all interfaces use \"-b 0.0.0.0\" or \"-b ::\".") + .shouldContain("Serving " + TEST_DIR_STR + " and subdirectories on " + LOOPBACK_ADDR + " port") + .shouldContain("URL http://" + LOOPBACK_ADDR); + } + + @AfterTest + public void teardown() throws IOException { + if (Files.exists(TEST_DIR)) { + FileUtils.deleteFileTreeWithRetry(TEST_DIR); + } + } + + // --- infra --- + + static String getJwebserver(Path image) { + boolean isWindows = System.getProperty("os.name").startsWith("Windows"); + Path jwebserver = image.resolve("bin").resolve(isWindows ? "jwebserver.exe" : "jwebserver"); + if (Files.notExists(jwebserver)) + throw new RuntimeException(jwebserver + " not found"); + return jwebserver.toAbsolutePath().toString(); + } + + static final String REGULAR_STARTUP_LINE1_STRING = "Serving"; + static final String REGULAR_STARTUP_LINE2_STRING = "URL http://"; + static final String VERSION_STARTUP_LINE_STRING = "jwebserver " + JAVA_VERSION; + + // The stdout/stderr output line to wait for when starting the simpleserver + enum WaitForLine { + REGULAR_STARTUP_LINE (REGULAR_STARTUP_LINE2_STRING) , + HELP_STARTUP_LINE (OPTIONS_TEXT.lines().reduce((first, second) -> second).orElseThrow()), + VERSION_STARTUP_LINE (VERSION_STARTUP_LINE_STRING); + + final String value; + WaitForLine(String value) { this.value = value; } + } + + static OutputAnalyzer simpleserver(String... args) throws Throwable { + return simpleserver(WaitForLine.REGULAR_STARTUP_LINE, true, args); + } + + static OutputAnalyzer simpleserver(WaitForLine waitForLine, boolean destroy, String... args) throws Throwable { + StringBuffer sb = new StringBuffer(); // stdout & stderr + // start the process and await the waitForLine before returning + var p = ProcessTools.startProcess("simpleserver", + new ProcessBuilder(args).directory(TEST_DIR.toFile()), + line -> sb.append(line + "\n"), + line -> line.startsWith(waitForLine.value), + 30, // suitably high default timeout, not expected to timeout + TimeUnit.SECONDS); + if (destroy) { + p.destroy(); // SIGTERM on Unix + } + int ec = p.waitFor(); + var outputAnalyser = new OutputAnalyzer(sb.toString(), "", ec); + return outputAnalyser; + } +} diff --git a/test/jdk/tools/launcher/HelpFlagsTest.java b/test/jdk/tools/launcher/HelpFlagsTest.java index 173074ab905..dda649b9f41 100644 --- a/test/jdk/tools/launcher/HelpFlagsTest.java +++ b/test/jdk/tools/launcher/HelpFlagsTest.java @@ -151,6 +151,7 @@ public class HelpFlagsTest extends TestHelper { new ToolHelpSpec("rmiregistry", 0, 0, 0, 0, 0, 0, 1), // none, prints help message anyways. new ToolHelpSpec("serialver", 0, 0, 0, 0, 0, 0, 1), // none, prints help message anyways. new ToolHelpSpec("jpackage", 0, 1, 1, 0, 0, 1, 1), // -h, --help, + new ToolHelpSpec("jwebserver", 1, 1, 1, 0, 0, 1, 1), // -?, -h, --help }; // Returns corresponding object from jdkTools array. diff --git a/test/jdk/tools/launcher/VersionCheck.java b/test/jdk/tools/launcher/VersionCheck.java index 38a61b54198..32d8db799ea 100644 --- a/test/jdk/tools/launcher/VersionCheck.java +++ b/test/jdk/tools/launcher/VersionCheck.java @@ -61,7 +61,8 @@ public class VersionCheck extends TestHelper { "jmc.ini", "jweblauncher", "jpackage", - "ssvagent" + "ssvagent", + "jwebserver" }; // tools that do not accept -version