Skip to content
Permalink
Browse files
8245095: Implementation of JEP 408: Simple Web Server
Co-authored-by: Julia Boes <jboes@openjdk.org>
Co-authored-by: Chris Hegarty <chegar@openjdk.org>
Co-authored-by: Michael McMahon <michaelm@openjdk.org>
Co-authored-by: Daniel Fuchs <dfuchs@openjdk.org>
Co-authored-by: Jan Lahoda <jlahoda@openjdk.org>
Co-authored-by: Ivan Šipka <isipka@openjdk.org>
Reviewed-by: ihse, jlaskey, michaelm, chegar, dfuchs
  • Loading branch information
6 people committed Oct 19, 2021
1 parent 947d52c commit 9d191fce55fa70d6a2affc724fad57b0e20e4bde
Showing with 7,166 additions and 35 deletions.
  1. +6 −1 make/CreateJmods.gmk
  2. +24 −0 make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java
  3. +41 −0 make/modules/jdk.httpserver/Gensrc.gmk
  4. +63 −1 src/jdk.httpserver/share/classes/com/sun/net/httpserver/Filter.java
  5. +81 −0 src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java
  6. +7 −20 src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpExchange.java
  7. +169 −0 src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpHandlers.java
  8. +55 −1 src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpServer.java
  9. +59 −1 src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpsServer.java
  10. +120 −0 src/jdk.httpserver/share/classes/com/sun/net/httpserver/Request.java
  11. +265 −0 src/jdk.httpserver/share/classes/com/sun/net/httpserver/SimpleFileServer.java
  12. +7 −1 src/jdk.httpserver/share/classes/com/sun/net/httpserver/package-info.java
  13. +35 −1 src/jdk.httpserver/share/classes/module-info.java
  14. +108 −0 src/jdk.httpserver/share/classes/sun/net/httpserver/DelegatingHttpExchange.java
  15. +1 −1 src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java
  16. +385 −0 src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java
  17. +65 −0 src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/Main.java
  18. +133 −0 src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/OutputFilter.java
  19. +49 −0 src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/ResourceBundleHelper.java
  20. +216 −0 src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/SimpleFileServerImpl.java
  21. +70 −0 src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/resources/simpleserver.properties
  22. +3 −2 test/jdk/TEST.ROOT
  23. +103 −1 test/jdk/com/sun/net/httpserver/FilterTest.java
  24. +130 −0 test/jdk/com/sun/net/httpserver/HeadersTest.java
  25. +16 −5 test/jdk/com/sun/net/httpserver/UnmodifiableHeadersTest.java
  26. +245 −0 test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java
  27. +240 −0 test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePositiveTest.java
  28. +970 −0 test/jdk/com/sun/net/httpserver/simpleserver/CustomFileSystemTest.java
  29. +231 −0 test/jdk/com/sun/net/httpserver/simpleserver/FileServerHandlerTest.java
  30. +362 −0 test/jdk/com/sun/net/httpserver/simpleserver/HttpHandlersTest.java
  31. +173 −0 test/jdk/com/sun/net/httpserver/simpleserver/HttpsServerTest.java
  32. +146 −0 test/jdk/com/sun/net/httpserver/simpleserver/IdempotencyAndCommutativityTest.java
  33. +359 −0 test/jdk/com/sun/net/httpserver/simpleserver/MapToPathTest.java
  34. +342 −0 test/jdk/com/sun/net/httpserver/simpleserver/OutputFilterTest.java
  35. +167 −0 test/jdk/com/sun/net/httpserver/simpleserver/RequestTest.java
  36. +201 −0 test/jdk/com/sun/net/httpserver/simpleserver/SecurityManagerTest.java
  37. +39 −0 test/jdk/com/sun/net/httpserver/simpleserver/SecurityManagerTestNoRead.policy
  38. +43 −0 test/jdk/com/sun/net/httpserver/simpleserver/SecurityManagerTestRead.policy
  39. +211 −0 test/jdk/com/sun/net/httpserver/simpleserver/ServerMimeTypesResolutionTest.java
  40. +703 −0 test/jdk/com/sun/net/httpserver/simpleserver/SimpleFileServerTest.java
  41. +110 −0 test/jdk/com/sun/net/httpserver/simpleserver/StressDirListings.java
  42. +413 −0 test/jdk/com/sun/net/httpserver/simpleserver/ZipFileSystemTest.java
@@ -1,5 +1,5 @@
#
# Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2014, 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
@@ -196,6 +196,11 @@ else # not java.base
endif
endif

# Set main class of jdk.httpserver module
ifeq ($(MODULE), jdk.httpserver)
JMOD_FLAGS += --main-class sun.net.httpserver.simpleserver.Main
endif

# Changes to the jmod tool itself should also trigger a rebuild of all jmods.
# The variable JMOD_CMD could contain an environment variable assignment before
# the actual command. Filter that out using wildcard before adding to DEPS.
@@ -123,6 +123,7 @@
import com.sun.tools.classfile.InnerClasses_attribute.Info;
import com.sun.tools.classfile.Method;
import com.sun.tools.classfile.MethodParameters_attribute;
import com.sun.tools.classfile.ModuleMainClass_attribute;
import com.sun.tools.classfile.ModuleResolution_attribute;
import com.sun.tools.classfile.ModuleTarget_attribute;
import com.sun.tools.classfile.Module_attribute;
@@ -928,6 +929,12 @@ private void addAttributes(ModuleDescription md,
attributes.put(Attribute.ModuleTarget,
new ModuleTarget_attribute(attrIdx, targetIdx));
}
if (header.moduleMainClass != null) {
int attrIdx = addString(cp, Attribute.ModuleMainClass);
int targetIdx = addString(cp, header.moduleMainClass);
attributes.put(Attribute.ModuleMainClass,
new ModuleMainClass_attribute(attrIdx, targetIdx));
}
int attrIdx = addString(cp, Attribute.Module);
attributes.put(Attribute.Module,
new Module_attribute(attrIdx,
@@ -2294,6 +2301,13 @@ private boolean readAttribute(ClassFile cf, FeatureDescription feature, Attribut
chd.isSealed = true;
break;
}
case Attribute.ModuleMainClass: {
ModuleMainClass_attribute moduleMainClass = (ModuleMainClass_attribute) attr;
assert feature instanceof ModuleHeaderDescription;
ModuleHeaderDescription mhd = (ModuleHeaderDescription) feature;
mhd.moduleMainClass = moduleMainClass.getMainClassName(cf.constant_pool);
break;
}
default:
throw new IllegalStateException("Unhandled attribute: " +
attrName);
@@ -2731,6 +2745,7 @@ static class ModuleHeaderDescription extends HeaderDescription {
List<ProvidesDescription> provides = new ArrayList<>();
Integer moduleResolution;
String moduleTarget;
String moduleMainClass;

@Override
public int hashCode() {
@@ -2743,6 +2758,7 @@ public int hashCode() {
hash = 83 * hash + Objects.hashCode(this.provides);
hash = 83 * hash + Objects.hashCode(this.moduleResolution);
hash = 83 * hash + Objects.hashCode(this.moduleTarget);
hash = 83 * hash + Objects.hashCode(this.moduleMainClass);
return hash;
}

@@ -2781,6 +2797,10 @@ public boolean equals(Object obj) {
other.moduleResolution)) {
return false;
}
if (!Objects.equals(this.moduleMainClass,
other.moduleMainClass)) {
return false;
}
return true;
}

@@ -2818,6 +2838,8 @@ public void write(Appendable output, String baselineVersion,
output.append(" resolution " +
quote(Integer.toHexString(moduleResolution),
true));
if (moduleMainClass != null)
output.append(" moduleMainClass " + quote(moduleMainClass, true));
writeAttributes(output);
output.append("\n");
writeInnerClasses(output, baselineVersion, version);
@@ -2862,6 +2884,8 @@ public boolean read(LineBasedReader reader) throws IOException {
moduleResolution = Integer.parseInt(resolutionFlags, 16);
}

moduleMainClass = reader.attributes.get("moduleMainClass");

readAttributes(reader);
reader.moveNext();
readInnerClasses(reader);
@@ -0,0 +1,41 @@
#
# 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 GensrcCommonJdk.gmk
include GensrcProperties.gmk
include Modules.gmk

################################################################################

# Use wildcard so as to avoid getting non-existing directories back
SIMPLESERVER_RESOURCES_DIRS := $(wildcard $(addsuffix /sun/net/httpserver/simpleserver/resources, \
$(call FindModuleSrcDirs, jdk.httpserver)))

$(eval $(call SetupCompileProperties, SIMPLESERVER_PROPERTIES, \
SRC_DIRS := $(SIMPLESERVER_RESOURCES_DIRS), \
CLASS := ListResourceBundle, \
))

TARGETS += $(SIMPLESERVER_PROPERTIES)
@@ -28,10 +28,13 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import sun.net.httpserver.DelegatingHttpExchange;

/**
* A filter used to pre- and post-process incoming requests. Pre-processing occurs
@@ -134,7 +137,6 @@ public void doFilter (HttpExchange exchange) throws IOException {
*/
public abstract void doFilter (HttpExchange exchange, Chain chain)
throws IOException;

/**
* Returns a short description of this {@code Filter}.
*
@@ -252,4 +254,64 @@ public String description() {
}
};
}

/**
* Returns a
* {@linkplain Filter#beforeHandler(String, Consumer) pre-processing Filter}
* that inspects and possibly adapts the request state.
*
* The {@code Request} returned by the {@link UnaryOperator requestOperator}
* will be the effective request state of the exchange. It is executed for
* each {@code HttpExchange} before invoking either the next filter in the
* chain or the exchange handler (if this is the final filter in the chain).
* Exceptions thrown by the {@code requestOperator} are not handled by the
* filter.
*
* @apiNote
* When the returned filter is invoked, it first invokes the
* {@code requestOperator} with the given exchange, {@code ex}, in order to
* retrieve the <i>adapted request state</i>. It then invokes the next
* filter in the chain or the exchange handler, passing an exchange
* equivalent to {@code ex} with the <i>adapted request state</i> set as the
* effective request state.
*
* <p> Example of adding the {@code "Foo"} request header to all requests:
* <pre>{@code
* var filter = Filter.adaptRequest("Add Foo header", r -> r.with("Foo", List.of("Bar")));
* httpContext.getFilters().add(filter);
* }</pre>
*
* @param description the string to be returned from {@link #description()}
* @param requestOperator the request operator
* @return a filter that adapts the request state before the exchange is handled
* @throws NullPointerException if any argument is null
* @since 18
*/
public static Filter adaptRequest(String description,
UnaryOperator<Request> requestOperator) {
Objects.requireNonNull(description);
Objects.requireNonNull(requestOperator);

return new Filter() {
@Override
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
var request = requestOperator.apply(exchange);
var newExchange = new DelegatingHttpExchange(exchange) {
@Override
public URI getRequestURI() { return request.getRequestURI(); }

@Override
public String getRequestMethod() { return request.getRequestMethod(); }

@Override
public Headers getRequestHeaders() { return request.getRequestHeaders(); }
};
chain.doFilter(newExchange);
}
@Override
public String description() {
return description;
}
};
}
}
@@ -25,6 +25,7 @@

package com.sun.net.httpserver;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
@@ -33,6 +34,8 @@
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import sun.net.httpserver.UnmodifiableHeaders;

/**
* HTTP request and response headers are represented by this class which
@@ -65,6 +68,14 @@
* value given overwriting any existing values in the value list.
* </ul>
*
* <p> An instance of {@code Headers} is either <i>mutable</i> or <i>immutable</i>.
* A <i>mutable headers</i> allows to add, remove, or modify header names and
* values, e.g. the instance returned by {@link HttpExchange#getResponseHeaders()}.
* An <i>immutable headers</i> disallows any modification to header names or
* values, e.g. the instance returned by {@link HttpExchange#getRequestHeaders()}.
* The mutator methods for an immutable headers instance unconditionally throw
* {@code UnsupportedOperationException}.
*
* <p> All methods in this class reject {@code null} values for keys and values.
* {@code null} keys will never be present in HTTP request or response headers.
* @since 1.6
@@ -78,6 +89,25 @@ public class Headers implements Map<String,List<String>> {
*/
public Headers() {map = new HashMap<>(32);}

/**
* Creates a mutable {@code Headers} from the given {@code headers} with
* the same header names and values.
*
* @param headers a map of header names and values
* @throws NullPointerException if {@code headers} or any of its names or
* values are null, or if any value contains
* null.
* @since 18
*/
public Headers(Map<String,List<String>> headers) {
Objects.requireNonNull(headers);
var h = headers.entrySet().stream()
.collect(Collectors.toUnmodifiableMap(
Entry::getKey, e -> new LinkedList<>(e.getValue())));
map = new HashMap<>(32);
this.putAll(h);
}

/**
* Normalize the key by converting to following form.
* First {@code char} upper case, rest lower case.
@@ -254,4 +284,55 @@ public String toString() {
sb.append(" }");
return sb.toString();
}

/**
* Returns an immutable {@code Headers} with the given name value pairs as
* its set of headers.
*
* <p> The supplied {@code String} instances must alternate as header names
* and header values. To add several values to the same name, the same name
* must be supplied with each new value. If the supplied {@code headers} is
* empty, then an empty {@code Headers} is returned.
*
* @param headers the list of name value pairs
* @return an immutable headers with the given name value pairs
* @throws NullPointerException if {@code headers} or any of its
* elements are null.
* @throws IllegalArgumentException if the number of supplied strings is odd.
* @since 18
*/
public static Headers of(String... headers) {
Objects.requireNonNull(headers);
if (headers.length == 0) {
return new UnmodifiableHeaders(new Headers());
}
if (headers.length % 2 != 0) {
throw new IllegalArgumentException("wrong number, %d, of elements"
.formatted(headers.length));
}
Arrays.stream(headers).forEach(Objects::requireNonNull);

var h = new Headers();
for (int i = 0; i < headers.length; i += 2) {
String name = headers[i];
String value = headers[i + 1];
h.add(name, value);
}
return new UnmodifiableHeaders(h);
}

/**
* Returns an immutable {@code Headers} from the given {@code headers} with
* the same header names and values.
*
* @param headers a map of header names and values
* @return an immutable headers
* @throws NullPointerException if {@code headers} or any of its names or
* values are null, or if any value contains
* null.
* @since 18
*/
public static Headers of(Map<String,List<String>> headers) {
return new UnmodifiableHeaders(new Headers(headers));
}
}
@@ -69,7 +69,7 @@
* @since 1.6
*/

public abstract class HttpExchange implements AutoCloseable {
public abstract class HttpExchange implements AutoCloseable, Request {

/**
* Constructor for subclasses to call.
@@ -78,19 +78,8 @@ protected HttpExchange() {
}

/**
* Returns an immutable {@link Headers} containing the HTTP headers that
* were included with this request.
*
* <p> The keys in this {@code Headers} are the header names, while the
* values are a {@link java.util.List} of
* {@linkplain java.lang.String Strings} containing each value that was
* included in the request, in the order they were included. Header fields
* appearing multiple times are represented as multiple string values.
*
* <p> The keys in {@code Headers} are case-insensitive.
*
* @return a read-only {@code Headers} which can be used to access request
* headers.
* {@inheritDoc}
* @return {@inheritDoc}
*/
public abstract Headers getRequestHeaders();

@@ -111,16 +100,14 @@ protected HttpExchange() {
public abstract Headers getResponseHeaders();

/**
* Returns the request {@link URI}.
*
* @return the request {@code URI}
* {@inheritDoc}
* @return {@inheritDoc}
*/
public abstract URI getRequestURI();

/**
* Returns the request method.
*
* @return the request method
* {@inheritDoc}
* @return {@inheritDoc}
*/
public abstract String getRequestMethod();

1 comment on commit 9d191fc

@openjdk-notifier
Copy link

@openjdk-notifier openjdk-notifier bot commented on 9d191fc Oct 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.