undefect. CWE-407 — 63 sites patched across 27 ecosystems

Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
This commit is contained in:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,162 @@
/*
* Copyright (c) 2006, 2025, 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.
*/
package com.sun.net.httpserver;
/**
* Authenticator represents an implementation of an HTTP authentication
* mechanism. Sub-classes provide implementations of specific mechanisms
* such as Digest or Basic auth. Instances are invoked to provide verification
* of the authentication information provided in all incoming requests.
* Note. This implies that any caching of credentials or other authentication
* information must be done outside of this class.
*
* @since 1.6
*/
public abstract class Authenticator {
/**
* Constructor for subclasses to call.
*/
protected Authenticator() { }
/**
* Base class for return type from {@link #authenticate(HttpExchange)} method.
*/
public abstract static class Result {
/**
* Constructor for subclasses to call.
*/
protected Result() {}
}
/**
* Indicates an authentication failure. The authentication
* attempt has completed.
*/
public static class Failure extends Result {
private int responseCode;
/**
* Creates a {@code Failure} instance with given response code.
*
* @param responseCode the response code to associate with this
* {@code Failure} instance
*/
public Failure(int responseCode) {
this.responseCode = responseCode;
}
/**
* Returns the response code to send to the client.
*
* @return the response code associated with this {@code Failure} instance
*/
public int getResponseCode() {
return responseCode;
}
}
/**
* Indicates an authentication has succeeded and the
* authenticated user {@linkplain HttpPrincipal principal} can be acquired by calling
* {@link #getPrincipal()}.
*/
public static class Success extends Result {
private HttpPrincipal principal;
/**
* Creates a {@code Success} instance with given {@code Principal}.
*
* @param p the authenticated user you wish to set as {@code Principal}
*/
public Success(HttpPrincipal p) {
principal = p;
}
/**
* Returns the authenticated user {@code Principal}.
*
* @return the {@code Principal} instance associated with the authenticated user
*
*/
public HttpPrincipal getPrincipal() {
return principal;
}
}
/**
* Indicates an authentication must be retried. The
* response code to be sent back is as returned from
* {@link #getResponseCode()}. The {@code Authenticator} must also have
* set any necessary response headers in the given {@link HttpExchange}
* before returning this {@code Retry} object.
*/
public static class Retry extends Result {
private int responseCode;
/**
* Creates a {@code Retry} instance with given response code.
*
* @param responseCode the response code to associate with this
* {@code Retry} instance
*/
public Retry(int responseCode) {
this.responseCode = responseCode;
}
/**
* Returns the response code to send to the client.
*
* @return the response code associated with this {@code Retry} instance
*/
public int getResponseCode() {
return responseCode;
}
}
/**
* Called to authenticate each incoming request. The implementation
* must return a {@link Failure}, {@link Success} or {@link Retry} object as appropriate:
* <ul>
* <li> {@code Failure} means the authentication has completed, but has
* failed due to invalid credentials.
* <li> {@code Success} means that the authentication has succeeded,
* and a {@code Principal} object representing the user can be retrieved
* by calling {@link Success#getPrincipal()}.
* <li> {@code Retry} means that another HTTP {@linkplain HttpExchange exchange}
* is required. Any response headers needing to be sent back to the client are set
* in the given {@code HttpExchange}. The response code to be returned must be
* provided in the {@code Retry} object. {@code Retry} may occur multiple times.
* </ul>
*
* @param exch the {@code HttpExchange} upon which authenticate is called
* @return the result
*/
public abstract Result authenticate(HttpExchange exch);
}

View file

@ -0,0 +1,164 @@
/*
* Copyright (c) 2006, 2025, 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.
*/
package com.sun.net.httpserver;
import java.nio.charset.Charset;
import java.util.Base64;
import java.util.Objects;
import static sun.net.httpserver.Utils.isQuotedStringContent;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* BasicAuthenticator provides an implementation of HTTP Basic
* authentication. It is an abstract class and must be extended
* to provide an implementation of {@link #checkCredentials(String, String)}
* which is called to verify each incoming request.
*
* @since 1.6
*/
public abstract class BasicAuthenticator extends Authenticator {
/** The HTTP Basic authentication realm. */
protected final String realm;
private final Charset charset;
private final boolean isUTF8;
/**
* Creates a {@code BasicAuthenticator} for the given HTTP realm.
* The Basic authentication credentials (username and password) are decoded
* using the platform's {@link Charset#defaultCharset() default character set}.
*
* @apiNote The value of the {@code realm} parameter will be embedded in a
* quoted string.
*
* @param realm the HTTP Basic authentication realm
* @throws NullPointerException if realm is {@code null}
* @throws IllegalArgumentException if realm is an empty string or is not
* correctly quoted, as specified in <a href="https://tools.ietf.org/html/rfc7230#section-3.2">
* RFC 7230 section-3.2</a>. Note, any {@code \} character used for
* quoting must itself be quoted in source code.
*/
public BasicAuthenticator(String realm) {
this(realm, Charset.defaultCharset());
}
/**
* Creates a {@code BasicAuthenticator} for the given HTTP realm and using the
* given {@link Charset} to decode the Basic authentication credentials
* (username and password).
*
* @apiNote {@code UTF-8} is the recommended charset because its usage is
* communicated to the client, and therefore more likely to be used also
* by the client.
* <p>The value of the {@code realm} parameter will be embedded in a quoted
* string.
*
* @param realm the HTTP Basic authentication realm
* @param charset the {@code Charset} to decode incoming credentials from the client
* @throws NullPointerException if realm or charset are {@code null}
* @throws IllegalArgumentException if realm is an empty string or is not
* correctly quoted, as specified in <a href="https://tools.ietf.org/html/rfc7230#section-3.2">
* RFC 7230 section-3.2</a>. Note, any {@code \} character used for
* quoting must itself be quoted in source code.
*
* @since 14
*/
public BasicAuthenticator(String realm, Charset charset) {
Objects.requireNonNull(charset);
if (realm.isEmpty()) // implicit NPE check
throw new IllegalArgumentException("realm must not be empty");
if (!isQuotedStringContent(realm))
throw new IllegalArgumentException("realm invalid: " + realm);
this.realm = realm;
this.charset = charset;
this.isUTF8 = charset.equals(UTF_8);
}
/**
* Returns the realm this {@code BasicAuthenticator} was created with.
*
* @return the authenticator's realm string
*/
public String getRealm() {
return realm;
}
public Result authenticate(HttpExchange t)
{
Headers rmap = t.getRequestHeaders();
/*
* look for auth token
*/
String auth = rmap.getFirst("Authorization");
if (auth == null) {
setAuthHeader(t);
return new Authenticator.Retry(401);
}
int sp = auth.indexOf(' ');
if (sp == -1 || !auth.substring(0, sp).equalsIgnoreCase("Basic")) {
return new Authenticator.Failure(401);
}
byte[] b = Base64.getDecoder().decode(auth.substring(sp+1));
String userpass = new String(b, charset);
int colon = userpass.indexOf(':');
String uname = userpass.substring(0, colon);
String pass = userpass.substring(colon+1);
if (checkCredentials(uname, pass)) {
return new Authenticator.Success(
new HttpPrincipal(
uname, realm
)
);
} else {
/* reject the request again with 401 */
setAuthHeader(t);
return new Authenticator.Failure(401);
}
}
private void setAuthHeader(HttpExchange t) {
Headers map = t.getResponseHeaders();
var authString = "Basic realm=" + "\"" + realm + "\"" +
(isUTF8 ? ", charset=\"UTF-8\"" : "");
map.set("WWW-Authenticate", authString);
}
/**
* Called for each incoming request to verify the
* given name and password in the context of this
* authenticator's realm. Any caching of credentials
* must be done by the implementation of this method.
*
* @param username the username from the request
* @param password the password from the request
* @return {@code true} if the credentials are valid, {@code false} otherwise
*/
public abstract boolean checkCredentials(String username, String password);
}

View file

@ -0,0 +1,317 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package com.sun.net.httpserver;
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
* before the application's exchange handler is invoked, and post-processing
* occurs after the exchange handler returns. Filters are organised in chains,
* and are associated with {@link HttpContext} instances.
*
* <p> Each {@code Filter} in the chain, invokes the next filter within its own
* {@link #doFilter(HttpExchange, Chain)} implementation. The final {@code Filter}
* in the chain invokes the applications exchange handler.
*
* @since 1.6
*/
public abstract class Filter {
/**
* Constructor for subclasses to call.
*/
protected Filter() {}
/**
* A chain of filters associated with a {@link HttpServer}.
* Each filter in the chain is given one of these so it can invoke the
* next filter in the chain.
*/
public static class Chain {
/**
* The last element in the chain must invoke the user's
* handler.
*/
private ListIterator<Filter> iter;
private HttpHandler handler;
/**
* Creates a {@code Chain} instance with given filters and handler.
*
* @param filters the filters that make up the {@code Chain}
* @param handler the {@link HttpHandler} that will be invoked after
* the final {@code Filter} has finished
*/
public Chain(List<Filter> filters, HttpHandler handler) {
iter = filters.listIterator();
this.handler = handler;
}
/**
* Calls the next filter in the chain, or else the users exchange
* handler, if this is the final filter in the chain. The {@code Filter}
* may decide to terminate the chain, by not calling this method.
* In this case, the filter <b>must</b> send the response to the
* request, because the application's {@linkplain HttpExchange exchange}
* handler will not be invoked.
*
* @param exchange the {@code HttpExchange}
* @throws IOException if an I/O error occurs
* @throws NullPointerException if exchange is {@code null}
*/
public void doFilter(HttpExchange exchange) throws IOException {
if (!iter.hasNext()) {
handler.handle(exchange);
} else {
Filter f = iter.next();
f.doFilter(exchange, this);
}
}
}
/**
* Asks this filter to pre/post-process the given exchange. The filter
* can:
*
* <ul>
* <li> Examine or modify the request headers.
* <li> Filter the request body or the response body, by creating suitable
* filter streams and calling {@link HttpExchange#setStreams(InputStream, OutputStream)}.
* <li> Set attribute objects in the exchange, which other filters or
* the exchange handler can access.
* <li> Decide to either:
*
* <ol>
* <li> Invoke the next filter in the chain, by calling
* {@link Filter.Chain#doFilter(HttpExchange)}.
* <li> Terminate the chain of invocation, by <b>not</b> calling
* {@link Filter.Chain#doFilter(HttpExchange)}.
* </ol>
*
* <li> If option 1. above is taken, then when doFilter() returns all subsequent
* filters in the Chain have been called, and the response headers can be
* examined or modified.
* <li> If option 2. above is taken, then this Filter must use the HttpExchange
* to send back an appropriate response.
* </ul>
*
* @param exchange the {@code HttpExchange} to be filtered
* @param chain the {@code Chain} which allows the next filter to be invoked
* @throws IOException may be thrown by any filter module, and if caught,
* must be rethrown again
* @throws NullPointerException if either exchange or chain are {@code null}
*/
public abstract void doFilter(HttpExchange exchange, Chain chain)
throws IOException;
/**
* Returns a short description of this {@code Filter}.
*
* @return a {@code String} describing the {@code Filter}
*/
public abstract String description();
/**
* Returns a pre-processing {@code Filter} with the given description and
* operation.
*
* <p>The {@link Consumer operation} is the effective implementation of the
* filter. 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 operation} are not handled by the filter.
*
* @apiNote
* A beforeHandler filter is typically used to examine or modify the
* exchange state before it is handled. The filter {@code operation} is
* executed before {@link Filter.Chain#doFilter(HttpExchange)} is invoked,
* so before any subsequent filters in the chain and the exchange handler
* are executed. The filter {@code operation} is not expected to handle the
* request or {@linkplain HttpExchange#sendResponseHeaders(int, long) send response headers},
* since this is commonly done by the exchange handler.
*
* <p> Example of adding the {@code "Foo"} response header to all responses:
* <pre>{@code
* var filter = Filter.beforeHandler("Add response header Foo",
* e -> e.getResponseHeaders().set("Foo", "Bar"));
* httpContext.getFilters().add(filter);
* }</pre>
*
* @param description the string to be returned from {@link #description()}
* @param operation the operation of the returned filter
* @return a filter whose operation is invoked before the exchange is handled
* @throws NullPointerException if any argument is null
* @since 17
*/
public static Filter beforeHandler(String description,
Consumer<HttpExchange> operation) {
Objects.requireNonNull(description);
Objects.requireNonNull(operation);
return new Filter() {
@Override
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
operation.accept(exchange);
chain.doFilter(exchange);
}
@Override
public String description() {
return description;
}
};
}
/**
* Returns a post-processing {@code Filter} with the given description and
* operation.
*
* <p>The {@link Consumer operation} is the effective implementation of the
* filter. It is executed for each {@code HttpExchange} after invoking
* either the next filter in the chain or the exchange handler (if this
* filter is the final filter in the chain). Exceptions thrown by the
* {@code operation} are not handled by the filter.
*
* @apiNote
* An afterHandler filter is typically used to examine the exchange state
* rather than modifying it. The filter {@code operation} is executed after
* {@link Filter.Chain#doFilter(HttpExchange)} is invoked, this means any
* subsequent filters in the chain and the exchange handler have been
* executed. The filter {@code operation} is not expected to handle the
* exchange or {@linkplain HttpExchange#sendResponseHeaders(int, long) send the response headers}.
* Doing so is likely to fail, since the exchange has commonly been handled
* before the {@code operation} is invoked. More specifically, the response
* may be sent before the filter {@code operation} is executed.
*
* <p> Example of adding a filter that logs the response code of all exchanges:
* <pre>{@code
* var filter = Filter.afterHandler("Log response code", e -> log(e.getResponseCode());
* httpContext.getFilters().add(filter);
* }</pre>
*
* <p> Example of adding a sequence of afterHandler filters to a context:<br>
* The order in which the filter operations are invoked is reverse to the
* order in which the filters are added to the context's filter-list.
*
* <pre>{@code
* var a1Set = Filter.afterHandler("Set a1", e -> e.setAttribute("a1", "some value"));
* var a1Get = Filter.afterHandler("Get a1", e -> doSomething(e.getAttribute("a1")));
* httpContext.getFilters().addAll(List.of(a1Get, a1Set));
* }</pre>
* <p>The operation of {@code a1Get} will be invoked after the operation of
* {@code a1Set} because {@code a1Get} was added before {@code a1Set}.
*
* @param description the string to be returned from {@link #description()}
* @param operation the operation of the returned filter
* @return a filter whose operation is invoked after the exchange is handled
* @throws NullPointerException if any argument is null
* @since 17
*/
public static Filter afterHandler(String description,
Consumer<HttpExchange> operation) {
Objects.requireNonNull(description);
Objects.requireNonNull(operation);
return new Filter() {
@Override
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
chain.doFilter(exchange);
operation.accept(exchange);
}
@Override
public String description() {
return 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;
}
};
}
}

View file

@ -0,0 +1,361 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package com.sun.net.httpserver;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import sun.net.httpserver.UnmodifiableHeaders;
import sun.net.httpserver.Utils;
/**
* HTTP request and response headers are represented by this class which
* implements the interface
* {@link java.util.Map}{@literal <}{@link java.lang.String},
* {@link java.util.List} {@literal <}{@link java.lang.String}{@literal >>}.
* The keys are case-insensitive Strings representing the header names and
* the value associated with each key is
* a {@link List}{@literal <}{@link String}{@literal >} with one
* element for each occurrence of the header name in the request or response.
*
* <p> For example, if a response header instance contains
* one key "HeaderName" with two values "value1 and value2"
* then this object is output as two header lines:
*
* <blockquote><pre>
* HeaderName: value1
* HeaderName: value2
* </pre></blockquote>
*
* All the normal {@link java.util.Map} methods are provided, but the
* following additional convenience methods are most likely to be used:
*
* <ul>
* <li>{@link #getFirst(String)} returns a single valued header or the first
* value of a multi-valued header.
* <li>{@link #add(String, String)} adds the given header value to the list
* for the given key.
* <li>{@link #set(String, String)} sets the given header field to the single
* 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
*/
public class Headers implements Map<String, List<String>> {
HashMap<String, List<String>> map;
/**
* Creates an empty instance of {@code Headers}.
*/
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);
}
/**
* {@return the normalized header name of the following form: the first
* character in upper-case, the rest in lower-case}
* The input header name is assumed to be encoded in ASCII.
*
* @implSpec
* This method is performance-sensitive; update with care.
*
* @param key an ASCII-encoded header name
* @throws NullPointerException on null {@code key}
* @throws IllegalArgumentException if {@code key} contains {@code \r} or {@code \n}
*/
private static String normalize(String key) {
// Fast path for the empty key
Objects.requireNonNull(key);
int l = key.length();
if (l == 0) {
return key;
}
// Find the first non-normalized `char`
int i = 0;
char c = key.charAt(i);
if (!(c == '\r' || c == '\n' || (c >= 'a' && c <= 'z'))) {
i++;
for (; i < l; i++) {
c = key.charAt(i);
if (c == '\r' || c == '\n' || (c >= 'A' && c <= 'Z')) {
break;
}
}
}
// Fast path for the already normalized key
if (i == l) {
return key;
}
// Upper-case the first `char`
char[] cs = key.toCharArray();
int o = 'a' - 'A';
if (i == 0) {
if (c == '\r' || c == '\n') {
throw new IllegalArgumentException("illegal character in key at index " + i);
}
if (c >= 'a' && c <= 'z') {
cs[0] = (char) (c - o);
}
i++;
}
// Lower-case the secondary `char`s
for (; i < l; i++) {
c = cs[i];
if (c >= 'A' && c <= 'Z') {
cs[i] = (char) (c + o);
} else if (c == '\r' || c == '\n') {
throw new IllegalArgumentException("illegal character in key at index " + i);
}
}
return new String(cs);
}
@Override
public int size() {return map.size();}
@Override
public boolean isEmpty() {return map.isEmpty();}
@Override
public boolean containsKey(Object key) {
Objects.requireNonNull(key);
return key instanceof String k && map.containsKey(normalize(k));
}
@Override
public boolean containsValue(Object value) {
Objects.requireNonNull(value);
return map.containsValue(value);
}
@Override
public List<String> get(Object key) {
return map.get(normalize((String)key));
}
/**
* Returns the first value from the {@link List} of {@code String} values
* for the given {@code key}, or {@code null} if no mapping for the
* {@code key} exists.
*
* @param key the key to search for
* @return the first {@code String} value associated with the key,
* or {@code null} if no mapping for the key exists
*/
public String getFirst(String key) {
List<String> l = map.get(normalize(key));
if (l == null || l.size() == 0) { // no mapping exists
return null;
}
return l.get(0);
}
@Override
public List<String> put(String key, List<String> value) {
// checkHeader is called in this class to fail fast
// It also must be called in sendResponseHeaders because
// Headers instances internal state can be modified
// external to these methods.
Utils.checkHeader(key, false);
for (String v : value)
Utils.checkHeader(v, true);
return map.put(normalize(key), value);
}
/**
* Adds the given {@code value} to the list of headers for the given
* {@code key}. If the mapping does not already exist, then it is created.
*
* @param key the header name
* @param value the value to add to the header
*/
public void add(String key, String value) {
Utils.checkHeader(key, false);
Utils.checkHeader(value, true);
String k = normalize(key);
List<String> l = map.get(k);
if (l == null) {
l = new LinkedList<>();
map.put(k, l);
}
l.add(value);
}
/**
* Sets the given {@code value} as the sole header value for the given
* {@code key}. If the mapping does not already exist, then it is created.
*
* @param key the header name
* @param value the header value to set
*/
public void set(String key, String value) {
LinkedList<String> l = new LinkedList<>();
l.add(value);
put(key, l);
}
@Override
public List<String> remove(Object key) {
return map.remove(normalize((String)key));
}
@Override
public void putAll(Map<? extends String,? extends List<String>> t) {
t.forEach(this::put);
}
@Override
public void clear() {map.clear();}
@Override
public Set<String> keySet() {return map.keySet();}
@Override
public Collection<List<String>> values() {return map.values();}
@Override
public Set<Map.Entry<String, List<String>>> entrySet() {
return map.entrySet();
}
@Override
public void replaceAll(BiFunction<? super String, ? super List<String>, ? extends List<String>> function) {
var f = function.andThen(values -> {
Objects.requireNonNull(values);
values.forEach(value -> Utils.checkHeader(value, true));
return values;
});
Map.super.replaceAll(f);
}
@Override
public boolean equals(Object o) { return map.equals(o); }
@Override
public int hashCode() {return map.hashCode();}
@Override
public String toString() {
final var sb = new StringBuilder(Headers.class.getSimpleName());
sb.append(" { ");
sb.append(map.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));
}
}

View file

@ -0,0 +1,125 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package com.sun.net.httpserver;
import java.util.List;
import java.util.Map;
/**
* {@code HttpContext} represents a mapping between the root {@link java.net.URI}
* path of an application to a {@link HttpHandler} which is invoked to handle
* requests destined for that path on the associated {@link HttpServer} or
* {@link HttpsServer}.
*
* <p> {@code HttpContext} instances are created by the create methods in
* {@code HttpServer} and {@code HttpsServer}.
*
* <p> A chain of {@link Filter} objects can be added to a {@code HttpContext}.
* All exchanges processed by the context can be pre- and post-processed by each
* {@code Filter} in the chain.
*
* @since 1.6
*/
public abstract class HttpContext {
/**
* Constructor for subclasses to call.
*/
protected HttpContext() {
}
/**
* Returns the handler for this context.
*
* @return the {@code HttpHandler} for this context
*/
public abstract HttpHandler getHandler();
/**
* Sets the handler for this context, if not already set.
*
* @param handler the handler to set for this context
* @throws IllegalArgumentException if the context for this handler is already set.
* @throws NullPointerException if handler is {@code null}
*/
public abstract void setHandler(HttpHandler handler);
/**
* Returns the path this context was created with.
*
* @return the context of this path
*/
public abstract String getPath();
/**
* Returns the server this context was created with.
*
* @return the context of this server
*/
public abstract HttpServer getServer();
/**
* Returns a mutable {@link Map}, which can be used to pass configuration
* and other data to {@link Filter} modules and to the context's exchange
* handler.
*
* <p> Every attribute stored in this {@code Map} will be visible to every
* {@code HttpExchange} processed by this context.
*
* @return a {@code Map} containing the attributes of this context
*/
public abstract Map<String, Object> getAttributes() ;
/**
* Returns this context's {@link List} of {@linkplain Filter filters}. This
* is the actual list used by the server when dispatching requests so
* modifications to this list immediately affect the handling of exchanges.
*
* @return a {@link List} containing the filters of this context
*/
public abstract List<Filter> getFilters();
/**
* Sets the {@link Authenticator} for this {@code HttpContext}. Once an authenticator
* is established on a context, all client requests must be authenticated,
* and the given object will be invoked to validate each request. Each call
* to this method replaces any previous value set.
*
* @param auth the {@code Authenticator} to set. If {@code null} then any previously
* set {@code Authenticator} is removed, and client authentication
* will no longer be required.
* @return the previous {@code Authenticator}, if any set, or {@code null} otherwise.
*/
public abstract Authenticator setAuthenticator(Authenticator auth);
/**
* Returns the currently set {@link Authenticator} for this context
* if one exists.
*
* @return this {@linkplain HttpContext HttpContext's} {@code Authenticator},
* or {@code null} if none is set
*/
public abstract Authenticator getAuthenticator();
}

View file

@ -0,0 +1,322 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package com.sun.net.httpserver;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
/**
* This class encapsulates a HTTP request received and a
* response to be generated in one exchange. It provides methods
* for examining the request from the client, and for building and
* sending the response.
*
* <p> The typical life-cycle of a {@code HttpExchange} is shown in the sequence
* below:
* <ol>
* <li>{@link #getRequestMethod()} to determine the command.
* <li>{@link #getRequestHeaders()} to examine the request headers (if
* needed).
* <li>{@link #getRequestBody()} returns an {@link InputStream} for
* reading the request body. After reading the request body, the stream
* should be closed.
* <li>{@link #getResponseHeaders()} to set any response headers, except
* content-length.
* <li>{@link #sendResponseHeaders(int, long)} to send the response headers.
* Must be called before next step.
* <li>{@link #getResponseBody()} to get a {@link OutputStream} to
* send the response body. When the response body has been written, the
* stream must be closed to terminate the exchange.
* </ol>
*
* <b>Terminating exchanges</b>
* <br>Exchanges are terminated when both the request {@code InputStream} and
* response {@code OutputStream} are closed. Closing the {@code OutputStream},
* implicitly closes the {@code InputStream} (if it is not already closed).
* However, it is recommended to consume all the data from the {@code InputStream}
* before closing it. The convenience method {@link #close()} does all of these
* tasks. Closing an exchange without consuming all of the request body is not
* an error but may make the underlying TCP connection unusable for following
* exchanges. The effect of failing to terminate an exchange is undefined, but
* will typically result in resources failing to be freed/reused.
*
* @since 1.6
*/
public abstract class HttpExchange implements AutoCloseable, Request {
/*
* Symbolic values for the responseLength parameter of
* sendResponseHeaders(int, long)
*/
/**
* No response body is being sent with this response
*
* @see #sendResponseHeaders(int, long)
* @since 26
*/
public static final long RSPBODY_EMPTY = -1l;
/**
* The response body length is unspecified and will be chunk encoded
*
* @see #sendResponseHeaders(int, long)
* @since 26
*/
public static final long RSPBODY_CHUNKED = 0;
/**
* Constructor for subclasses to call.
*/
protected HttpExchange() {
}
/**
* {@inheritDoc}
* @return {@inheritDoc}
*/
public abstract Headers getRequestHeaders();
/**
* Returns a mutable {@link Headers} into which the HTTP response headers
* can be stored and which will be transmitted as part of this response.
*
* <p> The keys in the {@code Headers} are the header names, while the
* values must be a {@link java.util.List} of {@linkplain java.lang.String Strings}
* containing each value that should be included multiple times (in the
* order that they should be included).
*
* <p> The keys in {@code Headers} are case-insensitive.
*
* @return a writable {@code Headers} which can be used to set response
* headers.
*/
public abstract Headers getResponseHeaders();
/**
* {@inheritDoc}
* @return {@inheritDoc}
*/
public abstract URI getRequestURI();
/**
* {@inheritDoc}
* @return {@inheritDoc}
*/
public abstract String getRequestMethod();
/**
* Returns the {@link HttpContext} for this exchange.
*
* @return the {@code HttpContext}
*/
public abstract HttpContext getHttpContext();
/**
* Ends this exchange by doing the following in sequence:
* <ol>
* <li> close the request {@link InputStream}, if not already closed.
* <li> close the response {@link OutputStream}, if not already closed.
* </ol>
*/
public abstract void close();
/**
* Returns a stream from which the request body can be read.
* Multiple calls to this method will return the same stream.
* It is recommended that applications should consume (read) all of the data
* from this stream before closing it. If a stream is closed before all data
* has been read, then the {@link InputStream#close()} call will read
* and discard remaining data (up to an implementation specific number of
* bytes).
*
* @return the stream from which the request body can be read
*/
public abstract InputStream getRequestBody();
/**
* Returns a stream to which the response body must be
* written. {@link #sendResponseHeaders(int, long)}) must be called prior to
* calling this method. Multiple calls to this method (for the same exchange)
* will return the same stream. In order to correctly terminate each exchange,
* the output stream must be closed, even if no response body is being sent.
*
* <p> Closing this stream implicitly closes the {@link InputStream}
* returned from {@link #getRequestBody()} (if it is not already closed).
*
* <p> If the call to {@link #sendResponseHeaders(int, long)} specified a
* fixed response body length, then the exact number of bytes specified in
* that call must be written to this stream. If too many bytes are written,
* then the write method of {@link OutputStream} will throw an {@code IOException}.
* If too few bytes are written then the stream
* {@link OutputStream#close()} will throw an {@code IOException}.
* In both cases, the exchange is aborted and the underlying TCP connection
* closed.
*
* @return the stream to which the response body is written
*/
public abstract OutputStream getResponseBody();
/**
* Starts sending the response back to the client using the current set of
* response headers and the numeric response code as specified in this
* method. The response body length is also specified as follows. If the
* response length parameter is greater than {@code zero}, this specifies an
* exact number of bytes to send and the application must send that exact
* amount of data. If the response length parameter has the value
* {@link #RSPBODY_CHUNKED} (zero) then the response body uses
* chunked transfer encoding and an arbitrary amount of data may be
* sent. The application terminates the response body by closing the
* {@link OutputStream}.
* If response length has the value {@link #RSPBODY_EMPTY} then no
* response body is being sent.
*
* <p> If the content-length response header has not already been set then
* this is set to the appropriate value depending on the response length
* parameter.
*
* <p> This method must be called prior to calling {@link #getResponseBody()}.
*
* @apiNote If a response body is to be sent from a byte array and the
* length of the array is used as the responseLength parameter, then note
* the behavior in the case when the array is empty. In that case, the
* responseLength will be zero which is the value of {@link #RSPBODY_CHUNKED}
* resulting in a zero length, but chunked encoded response body. While this
* is not incorrect, it may be preferable to check for an empty array and set
* responseLength to {@link #RSPBODY_EMPTY} instead. Also, when sending a
* chunked encoded response body, whatever length, the output stream must
* be explicitly closed by the handler.
*
* @implNote This implementation allows the caller to instruct the
* server to force a connection close after the exchange terminates, by
* supplying a {@code Connection: close} header to the {@linkplain
* #getResponseHeaders() response headers} before {@code sendResponseHeaders}
* is called.
*
* @param rCode the response code to send
* @param responseLength if {@literal > 0}, specifies a fixed response body
* length and that exact number of bytes must be written
* to the stream acquired from {@link #getResponseCode()}
* If equal to {@link #RSPBODY_CHUNKED}, then chunked encoding is used,
* and an arbitrary number of bytes may be written.
* If equal to {@link #RSPBODY_EMPTY}, then no response body length is
* specified and no response body may be written. Any value {@literal <= -1}
* is treated the same as {@link #RSPBODY_EMPTY}.
* @throws IOException if the response headers have already been sent or an I/O error occurs
* @see HttpExchange#getResponseBody()
*/
public abstract void sendResponseHeaders(int rCode, long responseLength) throws IOException;
/**
* Returns the address of the remote entity invoking this request.
*
* @return the {@link InetSocketAddress} of the caller
*/
public abstract InetSocketAddress getRemoteAddress();
/**
* Returns the response code, if it has already been set.
*
* @return the response code, if available. {@code -1} if not available yet.
*/
public abstract int getResponseCode();
/**
* Returns the local address on which the request was received.
*
* @return the {@link InetSocketAddress} of the local interface
*/
public abstract InetSocketAddress getLocalAddress();
/**
* Returns the protocol string from the request in the form
* <i>protocol/majorVersion.minorVersion</i>. For example,
* "{@code HTTP/1.1}".
*
* @return the protocol string from the request
*/
public abstract String getProtocol();
/**
* {@link Filter} modules may store arbitrary objects with {@code HttpExchange}
* instances as an out-of-band communication mechanism. Other filters
* or the exchange handler may then access these objects.
*
* <p> Each {@code Filter} class will document the attributes which they make
* available.
*
* @param name the name of the attribute to retrieve
* @return the attribute object, or {@code null} if it does not exist
* @throws NullPointerException if name is {@code null}
*/
public abstract Object getAttribute(String name);
/**
* {@link Filter} modules may store arbitrary objects with {@code HttpExchange}
* instances as an out-of-band communication mechanism. Other filters
* or the exchange handler may then access these objects.
*
* <p> Each {@code Filter} class will document the attributes which they make
* available.
*
* @param name the name to associate with the attribute value
* @param value the object to store as the attribute value. {@code null}
* value is permitted.
* @throws NullPointerException if name is {@code null}
*/
public abstract void setAttribute(String name, Object value);
/**
* Used by {@linkplain com.sun.net.httpserver.Filter Filters} to wrap either
* (or both) of this exchange's {@link InputStream} and
* {@link OutputStream}, with the given filtered streams so that
* subsequent calls to {@link #getRequestBody()} will return the given
* {@code InputStream}, and calls to {@link #getResponseBody()} will return
* the given {@code OutputStream}. The streams provided to this call must wrap
* the original streams, and may be (but are not required to be) sub-classes
* of {@link java.io.FilterInputStream} and {@link java.io.FilterOutputStream}.
*
* @param i the filtered input stream to set as this object's
* {@code Inputstream}, or {@code null} if no change
* @param o the filtered output stream to set as this object's
* {@code Outputstream}, or {@code null} if no change
*/
public abstract void setStreams(InputStream i, OutputStream o);
/**
* If an authenticator is set on the {@link HttpContext} that owns this exchange,
* then this method will return the {@link HttpPrincipal} that represents
* the authenticated user for this {@code HttpExchange}.
*
* @return the {@code HttpPrincipal}, or {@code null} if no authenticator is set
*/
public abstract HttpPrincipal getPrincipal();
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package com.sun.net.httpserver;
import java.io.IOException;
/**
* A handler which is invoked to process HTTP exchanges. Each
* HTTP exchange is handled by one of these handlers.
*
* @since 1.6
*/
public interface HttpHandler {
/**
* Handle the given request and generate an appropriate response.
* See {@link HttpExchange} for a description of the steps
* involved in handling an exchange.
*
* @param exchange the exchange containing the request from the
* client and used to send the response
* @throws NullPointerException if exchange is {@code null}
* @throws IOException if an I/O error occurs
*/
public abstract void handle(HttpExchange exchange) throws IOException;
}

View file

@ -0,0 +1,170 @@
/*
* Copyright (c) 2021, 2025, 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.
*/
package com.sun.net.httpserver;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.function.Predicate;
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
/**
* Implementations of {@link com.sun.net.httpserver.HttpHandler HttpHandler}
* that implement various useful handlers, such as a static response handler,
* or a conditional handler that complements one handler with another.
*
* <p> The factory method {@link #of(int, Headers, String)} provides a
* means to create handlers with pre-set static response state. For example, a
* {@code jsonHandler} that always returns <i>200</i> with the same json:
* <pre>{@code
* HttpHandlers.of(200,
* Headers.of("Content-Type", "application/json"),
* Files.readString(Path.of("some.json")));
* }</pre>
* or a {@code notAllowedHandler} that always replies with <i>405</i> -
* Method Not Allowed, and indicates the set of methods that are allowed:
* <pre>{@code
* HttpHandlers.of(405, Headers.of("Allow", "GET"), "");
* }</pre>
*
* <p> The functionality of a handler can be extended or enhanced through the
* use of {@link #handleOrElse(Predicate, HttpHandler, HttpHandler) handleOrElse},
* which allows to complement a given handler. For example, complementing a
* {@code jsonHandler} with <i>notAllowedHandler</i>:
*
* <pre>{@code
* Predicate<Request> IS_GET = r -> r.getRequestMethod().equals("GET");
* var handler = HttpHandlers.handleOrElse(IS_GET, jsonHandler, notAllowedHandler);
* }</pre>
*
* The above <i>handleOrElse</i> {@code handler} offers an if-else like construct;
* if the request method is "GET" then handling of the exchange is delegated to
* the {@code jsonHandler}, otherwise handling of the exchange is delegated to
* the {@code notAllowedHandler}.
*
* @since 18
*/
public final class HttpHandlers {
private HttpHandlers() { }
/**
* Complements a conditional {@code HttpHandler} with another handler.
*
* <p> This method creates a <i>handleOrElse</i> handler; an if-else like
* construct. Exchanges who's request matches the {@code handlerTest}
* predicate are handled by the {@code handler}. All remaining exchanges
* are handled by the {@code fallbackHandler}.
*
* <p> Example of a nested handleOrElse handler:
* <pre>{@code
* Predicate<Request> IS_GET = r -> r.getRequestMethod().equals("GET");
* Predicate<Request> WANTS_DIGEST = r -> r.getRequestHeaders().containsKey("Want-Digest");
*
* var h1 = new SomeHandler();
* var h2 = HttpHandlers.handleOrElse(IS_GET, new SomeGetHandler(), h1);
* var h3 = HttpHandlers.handleOrElse(WANTS_DIGEST.and(IS_GET), new SomeDigestHandler(), h2);
* }</pre>
* The {@code h3} handleOrElse handler delegates handling of the exchange to
* {@code SomeDigestHandler} if the "Want-Digest" request header is present
* and the request method is {@code GET}, otherwise it delegates handling of
* the exchange to the {@code h2} handler. The {@code h2} handleOrElse
* handler, in turn, delegates handling of the exchange to {@code
* SomeGetHandler} if the request method is {@code GET}, otherwise it
* delegates handling of the exchange to the {@code h1} handler. The {@code
* h1} handler handles all exchanges that are not previously delegated to
* either {@code SomeGetHandler} or {@code SomeDigestHandler}.
*
* @param handlerTest a request predicate
* @param handler a conditional handler
* @param fallbackHandler a fallback handler
* @return a handler
* @throws NullPointerException if any argument is null
*/
public static HttpHandler handleOrElse(Predicate<Request> handlerTest,
HttpHandler handler,
HttpHandler fallbackHandler) {
Objects.requireNonNull(handlerTest);
Objects.requireNonNull(handler);
Objects.requireNonNull(fallbackHandler);
return exchange -> {
if (handlerTest.test(exchange))
handler.handle(exchange);
else
fallbackHandler.handle(exchange);
};
}
/**
* Returns an {@code HttpHandler} that sends a response comprising the given
* {@code statusCode}, {@code headers}, and {@code body}.
*
* <p> This method creates a handler that reads and discards the request
* body before it sets the response state and sends the response.
*
* <p> {@code headers} are the effective headers of the response. The
* response <i>body bytes</i> are a {@code UTF-8} encoded byte sequence of
* {@code body}. The response headers
* {@linkplain HttpExchange#sendResponseHeaders(int, long) are sent} with
* the given {@code statusCode} and the body bytes' length (or {@code -1}
* if the body is empty). The body bytes are then sent as response body,
* unless the body is empty, in which case no response body is sent.
*
* @param statusCode a response status code
* @param headers a headers
* @param body a response body string
* @return a handler
* @throws IllegalArgumentException if statusCode is not a positive 3-digit
* integer, as per rfc2616, section 6.1.1
* @throws NullPointerException if headers or body are null
*/
public static HttpHandler of(int statusCode, Headers headers, String body) {
if (statusCode < 100 || statusCode > 999)
throw new IllegalArgumentException("statusCode must be 3-digit: "
+ statusCode);
Objects.requireNonNull(headers);
Objects.requireNonNull(body);
final var headersCopy = Headers.of(headers);
final var bytes = body.getBytes(StandardCharsets.UTF_8);
return exchange -> {
try (exchange) {
exchange.getRequestBody().readAllBytes();
exchange.getResponseHeaders().putAll(headersCopy);
if (exchange.getRequestMethod().equals("HEAD")) {
exchange.getResponseHeaders().set("Content-Length", Integer.toString(bytes.length));
exchange.sendResponseHeaders(statusCode, RSPBODY_EMPTY);
}
else if (bytes.length == 0) {
exchange.sendResponseHeaders(statusCode, RSPBODY_EMPTY);
} else {
exchange.sendResponseHeaders(statusCode, bytes.length);
exchange.getResponseBody().write(bytes);
}
}
};
}
}

View file

@ -0,0 +1,119 @@
/*
* Copyright (c) 2006, 2023, 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.
*/
package com.sun.net.httpserver;
import java.security.Principal;
/**
* Represents a user authenticated by HTTP Basic or Digest
* authentication.
*
* @since 1.6
*/
public class HttpPrincipal implements Principal {
private String username, realm;
/**
* Creates a {@code HttpPrincipal} from the given {@code username} and
* {@code realm}.
*
* @param username the name of the user within the realm
* @param realm the realm for this user
* @throws NullPointerException if either username or realm are {@code null}
*/
public HttpPrincipal(String username, String realm) {
if (username == null || realm == null) {
throw new NullPointerException();
}
this.username = username;
this.realm = realm;
}
/**
* Compare two instances of {@code HttpPrincipal}. Returns {@code true} if
* <i>another</i> is an instance of {@code HttpPrincipal}, and its username
* and realm are equal to this object's username and realm. Returns {@code false}
* otherwise.
*
* @param another the object to compare this instance of {@code HttpPrincipal} against
* @return {@code true} or {@code false} depending on whether objects are
* equal or not
*/
public boolean equals(Object another) {
if (!(another instanceof HttpPrincipal)) {
return false;
}
HttpPrincipal theother = (HttpPrincipal)another;
return (username.equals(theother.username) &&
realm.equals(theother.realm));
}
/**
* Returns the contents of this principal in the form
* <i>realm:username</i>.
*
* @return the contents of this principal in the form realm:username
*/
public String getName() {
return String.format("%s:%s", realm, username);
}
/**
* Returns the {@code username} this object was created with.
*
* @return the name of the user associated with this object
*/
public String getUsername() {
return username;
}
/**
* Returns the {@code realm} this object was created with.
*
* @return the realm associated with this object
*/
public String getRealm() {
return realm;
}
/**
* Returns a hashcode for this {@code HttpPrincipal}. This is calculated
* as {@code (getUsername()+getRealm()).hashCode()}.
*
* @return the hashcode for this object
*/
public int hashCode() {
return (username+realm).hashCode();
}
/**
* Returns the same string as {@link #getName()}.
*
* @return the name associated with this object
*/
public String toString() {
return getName();
}
}

View file

@ -0,0 +1,376 @@
/*
* Copyright (c) 2005, 2026, 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.
*/
package com.sun.net.httpserver;
import com.sun.net.httpserver.spi.HttpServerProvider;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* This class implements a simple HTTP server. A {@code HttpServer} is bound to an IP address
* and port number and listens for incoming TCP connections from clients on this address.
* The sub-class {@link HttpsServer} implements a server which handles HTTPS requests.
*
* <p>One or more {@link HttpHandler} objects must be associated with a server
* in order to process requests. Each such {@code HttpHandler} is registered with
* a root URI path which represents the location of the application or service
* on this server. The mapping of a handler to a {@code HttpServer} is
* encapsulated by a {@link HttpContext} object. HttpContexts are created by
* calling {@link #createContext(String, HttpHandler)}.
* Any request for which no handler can be found is rejected with a 404 response.
* Management of threads can be done external to this object by providing a
* {@link java.util.concurrent.Executor} object. If none is provided a default
* implementation is used.
*
* <p> <a id="mapping_description"></a> <b>Mapping request URIs to HttpContext paths</b>
*
* <p>When a HTTP request is received, the appropriate {@code HttpContext}
* (and handler) is located by finding the context whose path is the longest
* matching prefix of the request URI's path. Paths are matched literally,
* which means that the strings are compared case sensitively, and with no
* conversion to or from any encoded forms. For example, given a {@code HttpServer}
* with the following HttpContexts configured:
*
* <table class="striped"><caption style="display:none">description</caption>
* <thead>
* <tr>
* <th scope="col"><i>Context</i></th>
* <th scope="col"><i>Context path</i></th>
* </tr>
* </thead>
* <tbody>
* <tr><th scope="row">ctx1</th><td>"/"</td></tr>
* <tr><th scope="row">ctx2</th><td>"/apps/"</td></tr>
* <tr><th scope="row">ctx3</th><td>"/apps/foo/"</td></tr>
* </tbody>
* </table>
*
* <p>The following table shows some request URIs and which, if any context they would
* match with:
* <table class="striped" style="text-align:left"><caption style="display:none">description</caption>
* <thead>
* <tr>
* <th scope="col"><i>Request URI</i></th>
* <th scope="col"><i>Matches context</i></th>
* </tr>
* </thead>
* <tbody>
* <tr><th scope="row">"http://foo.com/apps/foo/bar"</th><td>ctx3</td></tr>
* <tr><th scope="row">"http://foo.com/apps/Foo/bar"</th><td>no match, wrong case</td></tr>
* <tr><th scope="row">"http://foo.com/apps/app1"</th><td>ctx2</td></tr>
* <tr><th scope="row">"http://foo.com/foo"</th><td>ctx1</td></tr>
* </tbody>
* </table>
*
* <p><b>Note about socket backlogs</b>
*
* <p>When binding to an address and port number, the application can also
* specify an integer <i>backlog</i> parameter. This represents the maximum
* number of incoming TCP connections which the system will queue internally.
* Connections are queued while they are waiting to be accepted by the
* {@code HttpServer}. When the limit is reached, further connections may be
* rejected (or possibly ignored) by the underlying TCP implementation. Setting
* the right backlog value is a compromise between efficient resource usage in
* the TCP layer (not setting it too high) and allowing adequate throughput of
* incoming requests (not setting it too low).
*
* @since 1.6
*/
public abstract class HttpServer {
/**
* Constructor for subclasses to call.
*/
protected HttpServer() {
}
/**
* Creates a {@code HttpServer} instance which is initially not bound to any
* local address/port. The {@code HttpServer} is acquired from the currently
* installed {@link HttpServerProvider}. The server must be bound using
* {@link #bind(InetSocketAddress, int)} before it can be used.
*
* @throws IOException if an I/O error occurs
* @return an instance of {@code HttpServer}
*/
public static HttpServer create() throws IOException {
return create(null, 0);
}
/**
* Create a {@code HttpServer} instance which will bind to the
* specified {@link java.net.InetSocketAddress} (IP address and port number).
*
* A maximum backlog can also be specified. This is the maximum number of
* queued incoming connections to allow on the listening socket.
* Queued TCP connections exceeding this limit may be rejected by the TCP
* implementation. The {@code HttpServer} is acquired from the currently
* installed {@link HttpServerProvider}
*
* @param addr the address to listen on, if {@code null} then
* {@link #bind(InetSocketAddress, int)} must be called to set
* the address
* @param backlog the socket backlog. If this value is less than or equal to zero,
* then a system default value is used
* @throws IOException if an I/O error occurs
* @throws BindException if the server cannot bind to the requested address,
* or if the server is already bound
* @return an instance of {@code HttpServer}
*/
public static HttpServer create(InetSocketAddress addr, int backlog) throws IOException {
HttpServerProvider provider = HttpServerProvider.provider();
return provider.createHttpServer(addr, backlog);
}
/**
* Creates an {@code HttpServer} instance with an initial context.
*
* <p> The server is created with an <i>initial context</i> that maps the
* URI {@code path} to the exchange {@code handler}. The initial context is
* created as if by an invocation of
* {@link HttpServer#createContext(String) createContext(path)}. The
* {@code filters}, if any, are added to the initial context, in the order
* they are given. The returned server is not started so can be configured
* further if required.
*
* <p> The server instance will bind to the given
* {@link java.net.InetSocketAddress}.
*
* <p> A maximum backlog can also be specified. This is the maximum number
* of queued incoming connections to allow on the listening socket.
* Queued TCP connections exceeding this limit may be rejected by
* the TCP implementation. The HttpServer is acquired from the currently
* installed {@link HttpServerProvider}.
*
* @param addr the address to listen on, if {@code null} then
* {@link #bind bind} must be called to set the address
* @param backlog the socket backlog. If this value is less than or
* equal to zero, then a system default value is used
* @param path the root URI path of the context, must be absolute
* @param handler the HttpHandler for the context
* @param filters the Filters for the context, optional
* @return the HttpServer
* @throws BindException if the server cannot bind to the address
* @throws IOException if an I/O error occurs
* @throws IllegalArgumentException if path is invalid
* @throws NullPointerException if any of: {@code path}, {@code handler},
* {@code filters}, or any element of {@code filters}, are {@code null}
* @since 18
*/
public static HttpServer create(InetSocketAddress addr,
int backlog,
String path,
HttpHandler handler,
Filter... filters) throws IOException {
Objects.requireNonNull(path);
Objects.requireNonNull(handler);
Objects.requireNonNull(filters);
Arrays.stream(filters).forEach(Objects::requireNonNull);
HttpServer server = HttpServer.create(addr, backlog);
HttpContext context = server.createContext(path);
context.setHandler(handler);
Arrays.stream(filters).forEach(f -> context.getFilters().add(f));
return server;
}
/**
* Binds a currently unbound {@code HttpServer} to the given address and
* port number. A maximum backlog can also be specified. This is the maximum
* number of queued incoming connections to allow on the listening socket.
* Queued TCP connections exceeding this limit may be rejected by the TCP
* implementation.
*
* @param addr the address to listen on
* @param backlog the socket backlog. If this value is less than or equal to
* zero, then a system default value is used
* @throws BindException if the server cannot bind to the requested address
* or if the server is already bound
* @throws NullPointerException if addr is {@code null}
*/
public abstract void bind(InetSocketAddress addr, int backlog) throws IOException;
/**
* Starts this server in a new background thread. The background thread
* inherits the priority, thread group and context class loader
* of the caller.
*/
public abstract void start();
/**
* Sets this server's {@link java.util.concurrent.Executor} object. An
* {@code Executor} must be established before {@link #start()} is called.
* All HTTP requests are handled in tasks given to the executor.
* If this method is not called (before {@link #start()}) or if it is called
* with a {@code null Executor}, then a default implementation is used,
* which uses the thread which was created by the {@link #start()} method.
*
* @param executor the {@code Executor} to set, or {@code null} for default
* implementation
* @throws IllegalStateException if the server is already started
*/
public abstract void setExecutor(Executor executor);
/**
* Returns this server's {@code Executor} object if one was specified with
* {@link #setExecutor(Executor)}, or {@code null} if none was specified.
*
* @return the {@code Executor} established for this server or {@code null} if not set.
*/
public abstract Executor getExecutor() ;
/**
* Stops this server by closing the listening socket and disallowing
* any new exchanges from being processed. The method will then block
* until all current exchange handlers have completed or else when
* approximately <i>delay</i> seconds have elapsed (whichever happens
* sooner). Then, all open TCP connections are closed, the background
* thread created by {@link #start()} exits, and the method returns.
* Once stopped, a {@code HttpServer} cannot be re-used.
*
* @param delay the maximum time in seconds to wait until exchanges have finished
* @throws IllegalArgumentException if delay is less than zero
*/
public abstract void stop(int delay);
/**
* Creates a {@code HttpContext}. A {@code HttpContext} represents a mapping
* from a URI path to a exchange handler on this {@code HttpServer}. Once
* created, all requests received by the server for the path will be handled
* by calling the given handler object. The context is identified by the
* path, and can later be removed from the server using this with the
* {@link #removeContext(String)} method.
*
* <p> The path specifies the root URI path for this context. The first
* character of path must be '/'.
*
* <p>The class overview describes how incoming request URIs are
* <a href="#mapping_description">mapped</a> to HttpContext instances.
*
* @apiNote
* The path should generally, but is not required to, end with {@code /}.
* If the path does not end with {@code /}, e.g., such as with {@code /foo},
* then some implementations may use <em>string prefix matching</em> where
* this context path matches request paths {@code /foo},
* {@code /foo/bar}, or {@code /foobar}. Others may use <em>path prefix
* matching</em> where {@code /foo} matches request paths {@code /foo} and
* {@code /foo/bar}, but not {@code /foobar}.
*
* @implNote
* By default, the JDK built-in implementation uses path prefix matching.
* String prefix matching can be enabled using the
* {@link jdk.httpserver/##sun.net.httpserver.pathMatcher sun.net.httpserver.pathMatcher}
* system property.
*
* @param path the root URI path to associate the context with
* @param handler the handler to invoke for incoming requests
* @throws IllegalArgumentException if path is invalid, or if a context
* already exists for this path
* @throws NullPointerException if either path, or handler are {@code null}
* @return an instance of {@code HttpContext}
*
* @see jdk.httpserver/##sun.net.httpserver.pathMatcher sun.net.httpserver.pathMatcher
*/
public abstract HttpContext createContext(String path, HttpHandler handler);
/**
* Creates a HttpContext without initially specifying a handler. The handler
* must later be specified using {@link HttpContext#setHandler(HttpHandler)}.
* A {@code HttpContext} represents a mapping from a URI path to an exchange
* handler on this {@code HttpServer}. Once created, and when the handler has
* been set, all requests received by the server for the path will be handled
* by calling the handler object. The context is identified by the path, and
* can later be removed from the server using this with the
* {@link #removeContext(String)} method.
*
* <p>The path specifies the root URI path for this context. The first character of path must be
* '/'.
*
* <p>The class overview describes how incoming request URIs are
* <a href="#mapping_description">mapped</a> to {@code HttpContext} instances.
*
* @apiNote
* The path should generally, but is not required to, end with {@code /}.
* If the path does not end with {@code /}, e.g., such as with {@code /foo},
* then some implementations may use <em>string prefix matching</em> where
* this context path matches request paths {@code /foo},
* {@code /foo/bar}, or {@code /foobar}. Others may use <em>path prefix
* matching</em> where {@code /foo} matches request paths
* {@code /foo} and {@code /foo/bar}, but not {@code /foobar}.
*
* @implNote
* By default, the JDK built-in implementation uses path prefix matching.
* String prefix matching can be enabled using the
* {@link jdk.httpserver/##sun.net.httpserver.pathMatcher sun.net.httpserver.pathMatcher}
* system property.
*
* @param path the root URI path to associate the context with
* @throws IllegalArgumentException if path is invalid, or if a context
* already exists for this path
* @throws NullPointerException if path is {@code null}
* @return an instance of {@code HttpContext}
*
* @see jdk.httpserver/##sun.net.httpserver.pathMatcher sun.net.httpserver.pathMatcher
*/
public abstract HttpContext createContext(String path);
/**
* Removes the context identified by the given path from the server.
* Removing a context does not affect exchanges currently being processed
* but prevents new ones from being accepted.
*
* @param path the path of the handler to remove
* @throws IllegalArgumentException if no handler corresponding to this
* path exists.
* @throws NullPointerException if path is {@code null}
*/
public abstract void removeContext(String path) throws IllegalArgumentException;
/**
* Removes the given context from the server.
* Removing a context does not affect exchanges currently being processed
* but prevents new ones from being accepted.
*
* @param context the context to remove
* @throws NullPointerException if context is {@code null}
*/
public abstract void removeContext(HttpContext context);
/**
* Returns the address this server is listening on
*
* @return the {@code InetSocketAddress} the server is listening on
*/
public abstract InetSocketAddress getAddress();
}

View file

@ -0,0 +1,112 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package com.sun.net.httpserver;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
/**
* This class is used to configure the https parameters for each incoming
* https connection on a {@link HttpsServer}. Applications need to override
* the {@link #configure(HttpsParameters)} method in order to change
* the default configuration.
*
* <p> The following <a id="example">example</a> shows how this may be done:
*
* <blockquote><pre>
* SSLContext sslContext = SSLContext.getInstance(....);
* HttpsServer server = HttpsServer.create();
*
* server.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
* public void configure(HttpsParameters params) {
*
* // get the remote address if needed
* InetSocketAddress remote = params.getClientAddress();
*
* SSLContext c = getSSLContext();
*
* // get the default parameters
* SSLParameters sslparams = c.getDefaultSSLParameters();
* if (remote.equals(...)) {
* // modify the default set for client x
* }
*
* params.setSSLParameters(sslparams);
* }
* });
* </pre></blockquote>
*
* @since 1.6
*/
public class HttpsConfigurator {
private SSLContext context;
/**
* Creates a Https configuration, with the given {@link SSLContext}.
*
* @param context the {@code SSLContext} to use for this configurator
* @throws NullPointerException if no {@code SSLContext} supplied
*/
public HttpsConfigurator(SSLContext context) {
if (context == null) {
throw new NullPointerException("null SSLContext");
}
this.context = context;
}
/**
* Returns the {@link SSLContext} for this {@code HttpsConfigurator}.
*
* @return the {@code SSLContext}
*/
public SSLContext getSSLContext() {
return context;
}
/**
* Called by the {@link HttpsServer} to configure the parameters for a https
* connection currently being established. The implementation of configure()
* must call {@link HttpsParameters#setSSLParameters(SSLParameters)} in order
* to set the SSL parameters for the connection.
*
* <p> The default implementation of this method uses the
* SSLParameters returned from:
*
* <p> {@code getSSLContext().getDefaultSSLParameters()}
*
* <p> configure() may be overridden in order to modify this behavior. See
* example <a href="#example">above</a>.
*
* @param params the {@code HttpsParameters} to be configured
*
* @since 1.6
*/
public void configure(HttpsParameters params) {
params.setSSLParameters(getSSLContext().getDefaultSSLParameters());
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2005, 2013, 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.
*/
package com.sun.net.httpserver;
import javax.net.ssl.SSLSession;
/**
* This class encapsulates a HTTPS request received and a response to be
* generated in one exchange and defines the extensions to {@link HttpExchange}
* that are specific to the HTTPS protocol.
*
* @since 1.6
*/
public abstract class HttpsExchange extends HttpExchange {
/**
* Constructor for subclasses to call.
*/
protected HttpsExchange() {}
/**
* Get the {@link SSLSession} for this exchange.
*
* @return the {@code SSLSession}
*/
public abstract SSLSession getSSLSession();
}

View file

@ -0,0 +1,204 @@
/*
* Copyright (c) 2005, 2024, 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.
*/
package com.sun.net.httpserver;
import java.net.InetSocketAddress;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
/**
* Represents the set of parameters for each https connection negotiated with
* clients. One of these is created and passed to
* {@link HttpsConfigurator#configure(HttpsParameters)} for every incoming https
* connection, in order to determine the parameters to use.
*
* <p> The underlying SSL parameters may be established either via the set/get
* methods of this class, or else via a {@link javax.net.ssl.SSLParameters}
* object. {@code SSLParameters} is the preferred method, because in the future,
* additional configuration capabilities may be added to that class, and it is
* easier to determine the set of supported parameters and their default values
* with SSLParameters. Also, if an {@code SSLParameters} object is provided via
* {@link #setSSLParameters(SSLParameters)} then those parameter settings are
* used, and any settings made in this object are ignored.
*
* @since 1.6
*/
public abstract class HttpsParameters {
private String[] cipherSuites;
private String[] protocols;
private boolean wantClientAuth;
private boolean needClientAuth;
/**
* Constructor for subclasses to call.
*/
protected HttpsParameters() {}
/**
* Returns the {@link HttpsConfigurator} for this {@code HttpsParameters}.
*
* @return {@code HttpsConfigurator} for this instance of {@code HttpsParameters}
*/
public abstract HttpsConfigurator getHttpsConfigurator();
/**
* Returns the address of the remote client initiating the connection.
*
* @return address of the remote client initiating the connection
*/
public abstract InetSocketAddress getClientAddress();
/**
* Sets the {@link SSLParameters} to use for this {@code HttpsParameters}.
* The parameters must be supported by the {@link SSLContext} contained
* by the {@link HttpsConfigurator} associated with this {@code HttpsParameters}.
* If no parameters are set, then the default behavior is to use
* the default parameters from the associated {@link SSLContext}.
*
* @param params the {@code SSLParameters} to set. If {@code null} then the
* existing parameters (if any) remain unchanged
* @throws IllegalArgumentException if any of the parameters are invalid or
* unsupported
*/
public abstract void setSSLParameters(SSLParameters params);
/**
* Returns a copy of the array of ciphersuites or {@code null} if none
* have been set.
*
* @deprecated It is recommended that the SSL parameters be configured and
* read through the use of {@link #setSSLParameters(SSLParameters) SSLParameters}.
*
* @return a copy of the array of ciphersuites or {@code null} if none have
* been set
*/
@Deprecated(since = "23")
public String[] getCipherSuites() {
return cipherSuites != null ? cipherSuites.clone() : null;
}
/**
* Sets the array of ciphersuites.
*
* @deprecated It is recommended that the SSL parameters be configured and
* read through the use of {@link #setSSLParameters(SSLParameters) SSLParameters}. Use
* {@link SSLParameters#setCipherSuites(String[])} instead.
*
* @param cipherSuites the array of ciphersuites (or {@code null})
*/
@Deprecated(since = "23")
public void setCipherSuites(String[] cipherSuites) {
this.cipherSuites = cipherSuites != null ? cipherSuites.clone() : null;
}
/**
* Returns a copy of the array of protocols or {@code null} if none have been
* set.
*
* @deprecated It is recommended that the SSL parameters be configured and
* read through the use of {@link #setSSLParameters(SSLParameters) SSLParameters}.
*
* @return a copy of the array of protocols or {@code null} if none have been
* set
*/
@Deprecated(since = "23")
public String[] getProtocols() {
return protocols != null ? protocols.clone() : null;
}
/**
* Sets the array of protocols.
*
* @deprecated It is recommended that the SSL parameters be configured and
* read through the use of {@link #setSSLParameters(SSLParameters) SSLParameters}. Use
* {@link SSLParameters#setProtocols(String[])} instead.
*
* @param protocols the array of protocols (or {@code null})
*/
@Deprecated(since = "23")
public void setProtocols(String[] protocols) {
this.protocols = protocols != null ? protocols.clone() : null;
}
/**
* Returns whether client authentication should be requested.
*
* @deprecated It is recommended that the SSL parameters be configured and
* read through the use of {@link #setSSLParameters(SSLParameters) SSLParameters}.
*
* @return whether client authentication should be requested
*/
@Deprecated(since = "23")
public boolean getWantClientAuth() {
return wantClientAuth;
}
/**
* Sets whether client authentication should be requested. Calling this
* method clears the {@code needClientAuth} flag.
*
* @deprecated It is recommended that the SSL parameters be configured and
* read through the use of {@link #setSSLParameters(SSLParameters) SSLParameters}. Use
* {@link SSLParameters#setWantClientAuth(boolean)} instead.
*
* @param wantClientAuth whether client authentication should be requested
*/
@Deprecated(since = "23")
public void setWantClientAuth(boolean wantClientAuth) {
this.wantClientAuth = wantClientAuth;
this.needClientAuth = false;
}
/**
* Returns whether client authentication should be required.
*
* @deprecated It is recommended that the SSL parameters be configured and
* read through the use of {@link #setSSLParameters(SSLParameters) SSLParameters}.
*
* @return whether client authentication should be required
*/
@Deprecated(since = "23")
public boolean getNeedClientAuth() {
return needClientAuth;
}
/**
* Sets whether client authentication should be required. Calling this method
* clears the {@code wantClientAuth} flag.
*
* @deprecated It is recommended that the SSL parameters be configured and
* read through the use of {@link #setSSLParameters(SSLParameters) SSLParameters}. Use
* {@link SSLParameters#setNeedClientAuth(boolean)} instead.
*
* @param needClientAuth whether client authentication should be required
*/
@Deprecated(since = "23")
public void setNeedClientAuth(boolean needClientAuth) {
this.needClientAuth = needClientAuth;
this.wantClientAuth = false;
}
}

View file

@ -0,0 +1,168 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package com.sun.net.httpserver;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Objects;
import com.sun.net.httpserver.spi.HttpServerProvider;
/**
* This class is an extension of {@link HttpServer} which provides support for
* HTTPS.
*
* <p>A {@code HttpsServer} must have an associated {@link HttpsConfigurator} object
* which is used to establish the SSL configuration for the SSL connections.
*
* <p>All other configuration is the same as for {@code HttpServer}.
*
* @since 1.6
*/
public abstract class HttpsServer extends HttpServer {
/**
* Constructor for subclasses to call.
*/
protected HttpsServer() {
}
/**
* Creates a {@code HttpsServer} instance which is initially not bound to any
* local address/port. The {@code HttpsServer} is acquired from the currently
* installed {@link HttpServerProvider}. The server must be bound using
* {@link #bind(InetSocketAddress, int)} before it can be used. The server
* must also have a {@code HttpsConfigurator} established with
* {@link #setHttpsConfigurator(HttpsConfigurator)}.
*
* @return an instance of {@code HttpsServer}
* @throws IOException if an I/O error occurs
*/
public static HttpsServer create() throws IOException {
return create(null, 0);
}
/**
* Create a {@code HttpsServer} instance which will bind to the specified
* {@link java.net.InetSocketAddress} (IP address and port number).
*
* A maximum backlog can also be specified. This is the maximum number of
* queued incoming connections to allow on the listening socket. Queued TCP
* connections exceeding this limit may be rejected by the TCP implementation.
* The {@code HttpsServer} is acquired from the currently installed
* {@link HttpServerProvider}. The server must have a {@code HttpsConfigurator}
* established with {@link #setHttpsConfigurator(HttpsConfigurator)}.
*
* @param addr the address to listen on, if {@code null} then
* {@link #bind(InetSocketAddress, int)} must be called to set
* the address
* @param backlog the socket backlog. If this value is less than or equal to
* zero, then a system default value is used.
* @return an instance of {@code HttpsServer}
* @throws BindException if the server cannot bind to the requested address,
* or if the server is already bound
* @throws IOException if an I/O error occurs
*/
public static HttpsServer create(InetSocketAddress addr, int backlog) throws IOException {
HttpServerProvider provider = HttpServerProvider.provider();
return provider.createHttpsServer(addr, backlog);
}
/**
* Creates an {@code HttpsServer} instance with an initial context.
*
* <p> The server is created with an <i>initial context</i> that maps the
* URI {@code path} to the exchange {@code handler}. The initial context is
* created as if by an invocation of
* {@link HttpsServer#createContext(String) createContext(path)}. The
* {@code filters}, if any, are added to the initial context, in the order
* they are given. The returned server is not started so can be configured
* further if required.
*
* <p> The server instance will bind to the given
* {@link java.net.InetSocketAddress}.
*
* <p> A maximum backlog can also be specified. This is the maximum number
* of queued incoming connections to allow on the listening socket.
* Queued TCP connections exceeding this limit may be rejected by
* the TCP implementation. The HttpsServer is acquired from the currently
* installed {@link HttpServerProvider}.
*
* <p> The server must have an HttpsConfigurator established with
* {@link #setHttpsConfigurator(HttpsConfigurator)}.
*
* @param addr the address to listen on, if {@code null} then
* {@link #bind bind} must be called to set the address
* @param backlog the socket backlog. If this value is less than or
* equal to zero, then a system default value is used
* @param path the root URI path of the context, must be absolute
* @param handler the HttpHandler for the context
* @param filters the Filters for the context, optional
* @return the HttpsServer
* @throws BindException if the server cannot bind to the address
* @throws IOException if an I/O error occurs
* @throws IllegalArgumentException if path is invalid
* @throws NullPointerException if any of: {@code path}, {@code handler},
* {@code filters}, or any element of {@code filters}, are {@code null}
* @since 18
*/
public static HttpsServer create(InetSocketAddress addr,
int backlog,
String path,
HttpHandler handler,
Filter... filters) throws IOException {
Objects.requireNonNull(path);
Objects.requireNonNull(handler);
Objects.requireNonNull(filters);
Arrays.stream(filters).forEach(Objects::requireNonNull);
HttpsServer server = HttpsServer.create(addr, backlog);
HttpContext context = server.createContext(path);
context.setHandler(handler);
Arrays.stream(filters).forEach(f -> context.getFilters().add(f));
return server;
}
/**
* Sets this server's {@link HttpsConfigurator} object.
*
* @param config the {@code HttpsConfigurator} to set
* @throws NullPointerException if config is {@code null}
*/
public abstract void setHttpsConfigurator(HttpsConfigurator config);
/**
* Gets this server's {@link HttpsConfigurator} object, if it has been set.
*
* @return the {@code HttpsConfigurator} for this server, or {@code null} if
* not set
*/
public abstract HttpsConfigurator getHttpsConfigurator();
}

View file

@ -0,0 +1,120 @@
/*
* 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.
*/
package com.sun.net.httpserver;
import java.net.URI;
import java.util.List;
import java.util.Objects;
/**
* A view of the immutable request state of an HTTP exchange.
*
* @since 18
*/
public interface Request {
/**
* Returns the request {@link URI}.
*
* @return the request {@code URI}
*/
URI getRequestURI();
/**
* Returns the request method.
*
* @return the request method string
*/
String getRequestMethod();
/**
* 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.
*/
Headers getRequestHeaders();
/**
* Returns an identical {@code Request} with an additional header.
*
* <p> The returned {@code Request} has the same set of
* {@link #getRequestHeaders() headers} as {@code this} request, but with
* the addition of the given header. All other request state remains
* unchanged.
*
* <p> If {@code this} request already contains a header with the same name
* as the given {@code headerName}, then its value is not replaced.
*
* @implSpec
* The default implementation first creates a new {@code Headers}, {@code h},
* then adds all the request headers from {@code this} request to {@code h},
* then adds the given name-values mapping if {@code headerName} is
* not present in {@code h}. Then an unmodifiable view, {@code h'}, of
* {@code h} and a new {@code Request}, {@code r}, are created.
* The {@code getRequestMethod} and {@code getRequestURI} methods of
* {@code r} simply invoke the equivalently named method of {@code this}
* request. The {@code getRequestHeaders} method returns {@code h'}. Lastly,
* {@code r} is returned.
*
* @param headerName the header name
* @param headerValues the list of header values
* @return a request
* @throws NullPointerException if any argument is null, or if any element
* of headerValues is null.
*/
default Request with(String headerName, List<String> headerValues) {
Objects.requireNonNull(headerName);
Objects.requireNonNull(headerValues);
final Request r = this;
var h = new Headers();
h.putAll(r.getRequestHeaders());
if (!h.containsKey(headerName)) {
h.put(headerName, headerValues);
}
var unmodifiableHeaders = Headers.of(h);
return new Request() {
@Override
public URI getRequestURI() { return r.getRequestURI(); }
@Override
public String getRequestMethod() { return r.getRequestMethod(); }
@Override
public Headers getRequestHeaders() { return unmodifiableHeaders; }
};
}
}

View file

@ -0,0 +1,261 @@
/*
* Copyright (c) 2021, 2025, 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.
*/
package com.sun.net.httpserver;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress;
import java.net.URLConnection;
import java.nio.file.Path;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import sun.net.httpserver.simpleserver.FileServerHandler;
import sun.net.httpserver.simpleserver.OutputFilter;
/**
* A simple HTTP file server and its components (intended for testing,
* development and debugging purposes only).
*
* <p> A simple file server is composed of three components:
* <ul>
* <li> an {@link HttpServer HttpServer} that is bound to a given address, </li>
* <li> an {@link HttpHandler HttpHandler} that serves files from a given
* directory path, and </li>
* <li> an optional {@link Filter Filter} that prints log messages relating to
* the exchanges handled by the server. </li>
* </ul>
* The individual server components can be retrieved for reuse and extension via
* the static methods provided.
*
* <h2>Simple file server</h2>
*
* <p> The {@link #createFileServer(InetSocketAddress, Path, OutputLevel) createFileServer}
* static factory method returns an {@link HttpServer HttpServer} that is a
* simple out-of-the-box file server. The server comes with an initial handler
* that serves files from a given directory path (and its subdirectories).
* The output level determines what log messages are printed to
* {@code System.out}, if any.
*
* <p> Example of a simple file server:
* <pre>{@code
* var addr = new InetSocketAddress(8080);
* var server = SimpleFileServer.createFileServer(addr, Path.of("/some/path"), OutputLevel.INFO);
* server.start();
* }</pre>
*
* <h2>File handler</h2>
*
* <p> The {@link #createFileHandler(Path) createFileHandler} static factory
* method returns an {@code HttpHandler} that serves files and directory
* listings. The handler supports only the <i>HEAD</i> and <i>GET</i> request
* methods; to handle other request methods, one can either add additional
* handlers to the server, or complement the file handler by composing a single
* handler via
* {@link HttpHandlers#handleOrElse(Predicate, HttpHandler, HttpHandler)}.
*
* <p>Example of composing a single handler:
* <pre>{@code
* var handler = HttpHandlers.handleOrElse(
* (req) -> req.getRequestMethod().equals("PUT"),
* (exchange) -> {
* // validate and handle PUT request
* },
* SimpleFileServer.createFileHandler(Path.of("/some/path")))
* );
* }</pre>
*
* <h2>Output filter</h2>
*
* <p> The {@link #createOutputFilter(OutputStream, OutputLevel) createOutputFilter}
* static factory method returns a
* {@link Filter#afterHandler(String, Consumer) post-processing filter} that
* prints log messages relating to the exchanges handled by the server. The
* output format is specified by the {@link OutputLevel outputLevel}.
*
* <p> Example of an output filter:
* <pre>{@code
* var filter = SimpleFileServer.createOutputFilter(System.out, OutputLevel.VERBOSE);
* var server = HttpServer.create(new InetSocketAddress(8080), 10, "/some/path/", new SomeHandler(), filter);
* server.start();
* }</pre>
*
* <h2>jwebserver Tool</h2>
*
* <p>A simple HTTP file server implementation is provided via the
* {@code jwebserver} tool.
*
* @toolGuide jwebserver
*
* @since 18
*/
public final class SimpleFileServer {
private static final UnaryOperator<String> MIME_TABLE =
URLConnection.getFileNameMap()::getContentTypeFor;
private SimpleFileServer() { }
/**
* Describes the log message output level produced by the server when
* processing exchanges.
*
* @since 18
*/
public enum OutputLevel {
/**
* Used to specify no log message output level.
*/
NONE,
/**
* Used to specify the informative log message output level.
*
* <p> The log message format is based on the
* <a href='https://www.w3.org/Daemon/User/Config/Logging.html#common-logfile-format'>Common Logfile Format</a>,
* that includes the following information about an {@code HttpExchange}:
*
* <p> {@code remotehost rfc931 authuser [date] "request" status bytes}
*
* <p> Example:
* <pre>{@code
* 127.0.0.1 - - [22/Jun/2000:13:55:36 -0700] "GET /example.txt HTTP/1.1" 200 -
* }</pre>
*
* @implNote The fields {@code rfc931}, {@code authuser} and {@code bytes}
* are not captured in the implementation, so are always represented as
* {@code '-'}.
*/
INFO,
/**
* Used to specify the verbose log message output level.
*
* <p> Additional to the information provided by the
* {@linkplain OutputLevel#INFO info} level, the verbose level
* includes the request and response headers of the {@code HttpExchange}
* and the absolute path of the resource served up.
*/
VERBOSE
}
/**
* Creates a <i>file server</i> that serves files from a given path.
*
* <p> The server is configured with an initial context that maps the
* URI {@code path} to a <i>file handler</i>. The <i>file handler</i> is
* created as if by an invocation of
* {@link #createFileHandler(Path) createFileHandler(rootDirectory)}, and is
* associated to a context created as if by an invocation of
* {@link HttpServer#createContext(String) createContext("/")}. The returned
* server is not started.
*
* <p> An output level can be given to print log messages relating to the
* exchanges handled by the server. The log messages, if any, are printed to
* {@code System.out}. If {@link OutputLevel#NONE OutputLevel.NONE} is
* given, no log messages are printed.
*
* @param addr the address to listen on
* @param rootDirectory the root directory to be served, must be an absolute path
* @param outputLevel the log message output level
* @return an HttpServer
* @throws IllegalArgumentException if root does not exist, is not absolute,
* is not a directory, or is not readable
* @throws UncheckedIOException if an I/O error occurs
* @throws NullPointerException if any argument is null
*/
public static HttpServer createFileServer(InetSocketAddress addr,
Path rootDirectory,
OutputLevel outputLevel) {
Objects.requireNonNull(addr);
Objects.requireNonNull(rootDirectory);
Objects.requireNonNull(outputLevel);
try {
var handler = FileServerHandler.create(rootDirectory, MIME_TABLE);
if (outputLevel.equals(OutputLevel.NONE))
return HttpServer.create(addr, 0, "/", handler);
else
return HttpServer.create(addr, 0, "/", handler, OutputFilter.create(System.out, outputLevel));
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
/**
* Creates a <i>file handler</i> that serves files from a given directory
* path (and its subdirectories).
*
* <p> The file handler resolves the request URI against the given
* {@code rootDirectory} path to determine the path {@code p} on the
* associated file system to serve the response. If the path {@code p} is
* a directory, then the response contains a directory listing, formatted in
* HTML, as the response body. If the path {@code p} is a file, then the
* response contains a "Content-Type" header based on the best-guess
* content type, as determined by an invocation of
* {@linkplain java.net.FileNameMap#getContentTypeFor(String) getContentTypeFor},
* on the system-wide {@link URLConnection#getFileNameMap() mimeTable}, as
* well as the contents of the file as the response body.
*
* <p> The handler supports only requests with the <i>HEAD</i> or <i>GET</i>
* method, and will reply with a {@code 405} response code for requests with
* any other method.
*
* @param rootDirectory the root directory to be served, must be an absolute path
* @return a file handler
* @throws IllegalArgumentException if rootDirectory does not exist,
* is not absolute, is not a directory, or is not readable
* @throws NullPointerException if the argument is null
*/
public static HttpHandler createFileHandler(Path rootDirectory) {
Objects.requireNonNull(rootDirectory);
return FileServerHandler.create(rootDirectory, MIME_TABLE);
}
/**
* Creates a {@linkplain Filter#afterHandler(String, Consumer)
* post-processing Filter} that prints log messages about
* {@linkplain HttpExchange exchanges}. The log messages are printed to
* the given {@code OutputStream} in {@code UTF-8} encoding.
*
* @apiNote
* To not output any log messages it is recommended to not use a filter.
*
* @param out the stream to print to
* @param outputLevel the output level
* @return a post-processing filter
* @throws IllegalArgumentException if {@link OutputLevel#NONE OutputLevel.NONE}
* is given
* @throws NullPointerException if any argument is null
*/
public static Filter createOutputFilter(OutputStream out,
OutputLevel outputLevel) {
Objects.requireNonNull(out);
Objects.requireNonNull(outputLevel);
return OutputFilter.create(out, outputLevel);
}
}

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
/**
Provides a simple high-level Http server API, which can be used to build
embedded HTTP servers. Both "http" and "https" are supported. The API provides
a partial implementation of RFC <a href="https://www.ietf.org/rfc/rfc2616.txt">2616</a> (HTTP 1.1)
and RFC <a href="https://www.ietf.org/rfc/rfc2818.txt">2818</a> (HTTP over TLS).
Any HTTP functionality not provided by this API can be implemented by application code
using the API.
<p>
* The main components are:
* <ul>
* <li>the {@link com.sun.net.httpserver.HttpExchange} class that describes a
* request and response pair,</li>
* <li>the {@link com.sun.net.httpserver.HttpHandler} interface to handle
* incoming requests, plus the {@link com.sun.net.httpserver.HttpHandlers} class
* that provides useful handler implementations,</li>
* <li>the {@link com.sun.net.httpserver.HttpContext} class that maps a URI path
* to a {@code HttpHandler},</li>
* <li>the {@link com.sun.net.httpserver.HttpServer} class to listen for
* connections and dispatch requests to handlers,</li>
* <li>the {@link com.sun.net.httpserver.Filter} class that allows pre- and post-
* processing of requests.</li></ul>
* <p>
* 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.
<p>
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
represented by the {@link com.sun.net.httpserver.HttpExchange} class.
The {@link com.sun.net.httpserver.HttpServer} class is used to listen for incoming TCP connections
and it dispatches requests on these connections to handlers which have been
registered with the server.
<p>
A minimal Http server example is shown below:
<blockquote><pre>
class MyHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
InputStream is = t.getRequestBody();
read(is); // .. read the request body
String response = "This is the response";
t.sendResponseHeaders(200, response.length());
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
...
HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
server.createContext("/applications/myapp", new MyHandler());
server.setExecutor(null); // creates a default executor
server.start();
</pre></blockquote>
<p>The example above creates a simple HttpServer which uses the calling
application thread to invoke the handle() method for incoming http
requests directed to port 8000, and to the path /applications/myapp/.
<p>
The {@link com.sun.net.httpserver.HttpExchange} class encapsulates everything an application needs to
process incoming requests and to generate appropriate responses.
<p>
Registering a handler with a HttpServer creates a {@link com.sun.net.httpserver.HttpContext} object and
{@link com.sun.net.httpserver.Filter}
objects can be added to the returned context. Filters are used to perform automatic pre- and
post-processing of exchanges before they are passed to the exchange handler.
<p>
For sensitive information, a {@link com.sun.net.httpserver.HttpsServer} can
be used to process "https" requests secured by the SSL or TLS protocols.
A HttpsServer must be provided with a
{@link com.sun.net.httpserver.HttpsConfigurator} object, which contains an
initialized {@link javax.net.ssl.SSLContext}.
HttpsConfigurator can be used to configure the
cipher suites and other SSL operating parameters.
A simple example SSLContext could be created as follows:
<blockquote><pre>
char[] passphrase = "passphrase".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("testkeys"), passphrase);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passphrase);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
SSLContext ssl = SSLContext.getInstance("TLS");
ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
</pre></blockquote>
<p>
In the example above, a keystore file called "testkeys", created with the keytool utility
is used as a certificate store for client and server certificates.
The following code shows how the SSLContext is then used in a HttpsConfigurator
and how the SSLContext and HttpsConfigurator are linked to the HttpsServer.
<blockquote><pre>
server.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
public void configure(HttpsParameters params) {
// get the remote address if needed
InetSocketAddress remote = params.getClientAddress();
SSLContext c = getSSLContext();
// get the default parameters
SSLParameters sslparams = c.getDefaultSSLParameters();
if (remote.equals(...)) {
// modify the default set for client x
}
params.setSSLParameters(sslparams);
// statement above could throw IAE if any params invalid.
// eg. if app has a UI and parameters supplied by a user.
}
});
</pre></blockquote>
@since 1.6
*/
package com.sun.net.httpserver;

View file

@ -0,0 +1,168 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package com.sun.net.httpserver.spi;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsServer;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
/**
* Service provider class for HttpServer.
* Sub-classes of HttpServerProvider provide an implementation of
* {@link HttpServer} and associated classes. Applications do not normally use
* this class. See {@link #provider()} for how providers are found and loaded.
*
* @since 1.6
*/
public abstract class HttpServerProvider {
/**
* creates a HttpServer from this provider
*
* @param addr
* the address to bind to. May be {@code null}
*
* @param backlog
* the socket backlog. A value of {@code zero} means the systems default
* @throws IOException if an I/O error occurs
* @return An instance of HttpServer
*/
public abstract HttpServer createHttpServer(InetSocketAddress addr,
int backlog)
throws IOException;
/**
* creates a HttpsServer from this provider
*
* @param addr
* the address to bind to. May be {@code null}
*
* @param backlog
* the socket backlog. A value of {@code zero} means the systems default
* @throws IOException if an I/O error occurs
* @return An instance of HttpServer
*/
public abstract HttpsServer createHttpsServer(InetSocketAddress addr,
int backlog)
throws IOException;
private static final Object lock = new Object();
private static HttpServerProvider provider = null;
/**
* Initializes a new instance of this class.
*/
protected HttpServerProvider() {}
private static boolean loadProviderFromProperty() {
String cn = System.getProperty("com.sun.net.httpserver.HttpServerProvider");
if (cn == null)
return false;
try {
var cls = Class.forName(cn, false, ClassLoader.getSystemClassLoader());
if (HttpServerProvider.class.isAssignableFrom(cls)) {
provider = (HttpServerProvider) cls.getDeclaredConstructor().newInstance();
return true;
} else {
throw new ServiceConfigurationError("not assignable to HttpServerProvider: "
+ cls.getName());
}
} catch (InvocationTargetException |
NoSuchMethodException |
ClassNotFoundException |
IllegalAccessException |
InstantiationException x) {
throw new ServiceConfigurationError(null, x);
}
}
private static boolean loadProviderAsService() {
Iterator<HttpServerProvider> i =
ServiceLoader.load(HttpServerProvider.class,
ClassLoader.getSystemClassLoader())
.iterator();
if (!i.hasNext())
return false;
provider = i.next();
return true;
}
/**
* Returns the system wide default HttpServerProvider for this invocation of
* the Java virtual machine.
*
* <p> The first invocation of this method locates the default provider
* object as follows: </p>
*
* <ol>
*
* <li><p> If the system property
* {@systemProperty com.sun.net.httpserver.HttpServerProvider}
* is defined then it is taken to be the fully-qualified name
* of a concrete provider class.
* The class is loaded and instantiated; if this process fails then an
* unspecified unchecked error or exception is thrown. </p></li>
*
* <li><p> If a provider class has been installed in a jar file that is
* visible to the system class loader, and that jar file contains a
* provider-configuration file named
* {@code com.sun.net.httpserver.HttpServerProvider} in the resource
* directory {@code META-INF/services}, then the first class name
* specified in that file is taken. The class is loaded and
* instantiated; if this process fails then an unspecified unchecked error
* or exception is thrown. </p></li>
*
* <li><p> Finally, if no provider has been specified by any of the above
* means then the system-default provider class is instantiated and the
* result is returned. </p></li>
*
* </ol>
*
* <p> Subsequent invocations of this method return the provider that was
* returned by the first invocation. </p>
*
* @return The system-wide default HttpServerProvider
*/
public static HttpServerProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = new sun.net.httpserver.DefaultHttpServerProvider();
return provider;
}
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2005, 2024, 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.
*/
/**
* Provides a pluggable service provider interface, which allows the HTTP server
* implementation to be replaced with other implementations.
* @since 1.6
*/
package com.sun.net.httpserver.spi;

View file

@ -0,0 +1,163 @@
/*
* Copyright (c) 2014, 2026, 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.
*/
import com.sun.net.httpserver.*;
/**
* Defines the JDK-specific HTTP server API, and provides the jwebserver tool
* for running a minimal HTTP server.
*
* <p>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}.
*
* <p>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.
* <p>
* <b id="httpserverprops">System properties used by the HTTP server API</b>
* <p>
* The following is a list of JDK specific system properties used by the default HTTP
* server implementation in the JDK. Any properties below that take a numeric value
* assume the default value if given a string that does not parse as a number.
* <ul>
* <li><p><b>{@systemProperty sun.net.httpserver.idleInterval}</b> (default: 30 sec)<br>
* Maximum duration in seconds which an idle connection is kept open. This timer
* has an implementation specific granularity that may mean that idle connections are
* closed later than the specified interval. Values less than or equal to zero are mapped
* to the default setting.
* </li>
* <li><p><b>{@systemProperty jdk.httpserver.maxConnections}</b> (default: -1)<br>
* The maximum number of open connections at a time. This includes active and idle connections.
* If zero or negative, then no limit is enforced.
* </li>
* <li><p><b>{@systemProperty sun.net.httpserver.maxIdleConnections}</b> (default: 200)<br>
* The maximum number of idle connections at a time. If set to zero or a negative value
* then connections are closed after use.
* </li>
* <li><p><b>{@systemProperty sun.net.httpserver.drainAmount}</b> (default: 65536)<br>
* The maximum number of bytes that will be automatically read and discarded from a
* request body that has not been completely consumed by its
* {@link com.sun.net.httpserver.HttpHandler HttpHandler}. If the number of remaining
* unread bytes are less than this limit then the connection will be put in the idle connection
* cache. If not, then it will be closed.
* </li>
* <li><p><b>{@systemProperty sun.net.httpserver.maxReqHeaders}</b> (default: 200)<br>
* The maxiumum number of header fields accepted in a request. If this limit is exceeded
* while the headers are being read, then the connection is terminated and the request ignored.
* If the value is less than or equal to zero, then the default value is used.
* </li>
* <li><p><b>{@systemProperty sun.net.httpserver.maxReqHeaderSize}</b> (default: 393216 or 384kB)<br>
* The maximum header field section size that the server is prepared to accept.
* This is computed as the sum of the size of the header name, plus
* the size of the header value, plus an overhead of 32 bytes for
* each field section line. The request line counts as a first field section line,
* where the name is empty and the value is the whole line.
* If this limit is exceeded while the headers are being read, then the connection
* is terminated and the request ignored.
* If the value is less than or equal to zero, there is no limit.
* </li>
* <li><p><b>{@systemProperty sun.net.httpserver.maxReqTime}</b> (default: -1)<br>
* The maximum time in milliseconds allowed to receive a request headers and body.
* In practice, the actual time is a function of request size, network speed, and handler
* processing delays. A value less than or equal to zero means the time is not limited.
* If the limit is exceeded then the connection is terminated and the handler will receive a
* {@link java.io.IOException}. This timer has an implementation specific granularity
* that may mean requests are aborted later than the specified interval.
* </li>
* <li><p><b>{@systemProperty sun.net.httpserver.maxRspTime}</b> (default: -1)<br>
* The maximum time in milliseconds allowed to receive a response headers and body.
* In practice, the actual time is a function of response size, network speed, and handler
* processing delays. A value less than or equal to zero means the time is not limited.
* If the limit is exceeded then the connection is terminated and the handler will receive a
* {@link java.io.IOException}. This timer has an implementation specific granularity
* that may mean responses are aborted later than the specified interval.
* </li>
* <li><p><b>{@systemProperty sun.net.httpserver.nodelay}</b> (default: false)<br>
* Boolean value, which if true, sets the {@link java.net.StandardSocketOptions#TCP_NODELAY TCP_NODELAY}
* socket option on all incoming connections.
* </li>
* <li>
* <p><b>{@systemProperty sun.net.httpserver.pathMatcher}</b> (default:
* {@code pathPrefix})<br/>
*
* The path matching scheme used to route requests to context handlers.
* The property can be configured with one of the following values:</p>
*
* <blockquote>
* <dl>
* <dt>{@code pathPrefix} (default)</dt>
* <dd>The request path must begin with the context path and all matching path
* segments must be identical. For instance, the context path {@code /foo}
* would match request paths {@code /foo}, {@code /foo/}, and {@code /foo/bar},
* but not {@code /foobar}.</dd>
* <dt>{@code stringPrefix}</dt>
* <dd>The request path string must begin with the context path string. For
* instance, the context path {@code /foo} would match request paths
* {@code /foo}, {@code /foo/}, {@code /foo/bar}, and {@code /foobar}.
* </dd>
* </dl>
* </blockquote>
*
* <p>In case of a blank or invalid value, the default will be used.</p>
*
* <p>This property and the ability to restore the string prefix matching
* behavior may be removed in a future release.</p>
* </li>
* </ul>
*
* @apiNote The API and SPI in this module are designed and implemented to support a minimal
* HTTP server and simple HTTP semantics primarily.
*
* @implNote The default implementation of the HTTP server provided in this module is intended
* for simple usages like local testing, development, and debugging. Accordingly, the design
* and implementation of the server does not intend to be a full-featured, high performance
* HTTP server.
*
* @implNote
* Prior to JDK 26, in the JDK default implementation, the {@link HttpExchange} attribute map was
* shared with the enclosing {@link HttpContext}.
* Since JDK 26, by default, exchange attributes are per-exchange and the context attributes must
* be accessed by calling {@link HttpExchange#getHttpContext() getHttpContext()}{@link
* HttpContext#getAttributes() .getAttributes()}. <br>
* A new system property, <b>{@systemProperty jdk.httpserver.attributes}</b> (default value: {@code ""})
* allows to revert this new behavior. Set this property to "context" to restore the pre JDK 26 behavior.
* @toolGuide jwebserver
*
* @uses com.sun.net.httpserver.spi.HttpServerProvider
*
* @moduleGraph
* @since 9
*/
module jdk.httpserver {
exports com.sun.net.httpserver;
exports com.sun.net.httpserver.spi;
uses com.sun.net.httpserver.spi.HttpServerProvider;
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2006, 2025, 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.
*/
package sun.net.httpserver;
import com.sun.net.httpserver.*;
import java.io.*;
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
public class AuthFilter extends Filter {
private Authenticator authenticator;
public AuthFilter(Authenticator authenticator) {
this.authenticator = authenticator;
}
public String description() {
return "Authentication filter";
}
public void setAuthenticator(Authenticator a) {
authenticator = a;
}
public void consumeInput(HttpExchange t) throws IOException {
InputStream i = t.getRequestBody();
byte[] b = new byte [4096];
while (i.read(b) != -1);
i.close();
}
/**
* The filter's implementation, which is invoked by the server
*/
public void doFilter(HttpExchange t, Filter.Chain chain) throws IOException
{
if (authenticator != null) {
Authenticator.Result r = authenticator.authenticate(t);
if (r instanceof Authenticator.Success) {
Authenticator.Success s = (Authenticator.Success)r;
ExchangeImpl e = ExchangeImpl.get(t);
e.setPrincipal(s.getPrincipal());
chain.doFilter(t);
} else if (r instanceof Authenticator.Retry) {
Authenticator.Retry ry = (Authenticator.Retry)r;
consumeInput(t);
t.sendResponseHeaders(ry.getResponseCode(), RSPBODY_EMPTY);
} else if (r instanceof Authenticator.Failure) {
Authenticator.Failure f = (Authenticator.Failure)r;
consumeInput(t);
t.sendResponseHeaders(f.getResponseCode(), RSPBODY_EMPTY);
}
} else {
chain.doFilter(t);
}
}
}

View file

@ -0,0 +1,186 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.io.*;
import java.net.*;
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;
class ChunkedInputStream extends LeftOverInputStream {
ChunkedInputStream(ExchangeImpl t, InputStream src) {
super (t, src);
}
private int remaining;
/* true when a chunk header needs to be read */
private boolean needToReadHeader = true;
static final char CR = '\r';
static final char LF = '\n';
/*
* Maximum chunk header size of 2KB + 2 bytes for CRLF
*/
private static final int MAX_CHUNK_HEADER_SIZE = 2050;
private int numeric(char[] arr, int nchars) throws IOException {
assert arr.length >= nchars;
int len = 0;
for (int i=0; i<nchars; i++) {
char c = arr[i];
int val=0;
if (c>='0' && c <='9') {
val = c - '0';
} else if (c>='a' && c<= 'f') {
val = c - 'a' + 10;
} else if (c>='A' && c<= 'F') {
val = c - 'A' + 10;
} else {
throw new IOException("invalid chunk length");
}
len = len * 16 + val;
}
return len;
}
/* read the chunk header line and return the chunk length
* any chunk extensions are ignored
*/
private int readChunkHeader() throws IOException {
boolean gotCR = false;
int c;
char[] len_arr = new char[16];
int len_size = 0;
boolean end_of_len = false;
int read = 0;
while ((c=in.read())!= -1) {
char ch = (char) c;
read++;
if ((len_size == len_arr.length -1) ||
(read > MAX_CHUNK_HEADER_SIZE))
{
throw new IOException("invalid chunk header");
}
if (gotCR) {
if (ch == LF) {
int l = numeric(len_arr, len_size);
return l;
} else {
gotCR = false;
}
if (!end_of_len) {
len_arr[len_size++] = ch;
}
} else {
if (ch == CR) {
gotCR = true;
} else if (ch == ';') {
end_of_len = true;
} else if (!end_of_len) {
len_arr[len_size++] = ch;
}
}
}
throw new IOException("end of stream reading chunk header");
}
protected int readImpl(byte[] b, int off, int len) throws IOException {
if (eof) {
return -1;
}
if (needToReadHeader) {
remaining = readChunkHeader();
if (remaining == 0) {
eof = true;
consumeCRLF();
t.getServerImpl().requestCompleted(t.getConnection());
return -1;
}
needToReadHeader = false;
}
if (len > remaining) {
len = remaining;
}
int n = in.read(b, off, len);
if (n > -1) {
remaining -= n;
}
if (remaining == 0) {
needToReadHeader = true;
consumeCRLF();
}
if (n < 0 && !eof)
throw new IOException("connection closed before all data received");
return n;
}
private void consumeCRLF() throws IOException {
char c;
c = (char)in.read(); /* CR */
if (c != CR) {
throw new IOException("invalid chunk end");
}
c = (char)in.read(); /* LF */
if (c != LF) {
throw new IOException("invalid chunk end");
}
}
/**
* returns the number of bytes available to read in the current chunk
* which may be less than the real amount, but we'll live with that
* limitation for the moment. It only affects potential efficiency
* rather than correctness.
*/
public int available() throws IOException {
if (eof || closed) {
return 0;
}
int n = in.available();
return n > remaining? remaining: n;
}
/* called after the stream is closed to see if bytes
* have been read from the underlying channel
* and buffered internally
*/
public boolean isDataBuffered() throws IOException {
assert eof;
return in.available() > 0;
}
public boolean markSupported() {return false;}
public void mark(int l) {
}
public void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
}

View file

@ -0,0 +1,169 @@
/*
* Copyright (c) 2005, 2026, 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.
*/
package sun.net.httpserver;
import java.io.*;
import java.util.Objects;
/**
* a class which allows the caller to write an arbitrary
* number of bytes to an underlying stream.
* normal close() does not close the underlying stream
*
* This class is buffered.
*
* Each chunk is written in one go as :-
* abcd\r\nxxxxxxxxxxxxxx\r\n
*
* abcd is the chunk-size, and xxx is the chunk data
* If the length is less than 4 chars (in size) then the buffer
* is written with an offset.
* Final chunk is:
* 0\r\n\r\n
*/
class ChunkedOutputStream extends FilterOutputStream
{
private boolean closed = false;
/* max. amount of user data per chunk */
static final int CHUNK_SIZE = 4096;
/* allow 4 bytes for chunk-size plus 4 for CRLFs */
static final int OFFSET = 6; /* initial <=4 bytes for len + CRLF */
private int pos = OFFSET;
private int count = 0;
private byte[] buf = new byte [CHUNK_SIZE+OFFSET+2];
ExchangeImpl t;
ChunkedOutputStream(ExchangeImpl t, OutputStream src) {
super(src);
this.t = t;
}
public void write(int b) throws IOException {
if (closed) {
throw new StreamClosedException();
}
buf [pos++] = (byte)b;
count ++;
if (count == CHUNK_SIZE) {
writeChunk();
}
assert count < CHUNK_SIZE;
}
public void write(byte[] b, int off, int len) throws IOException {
Objects.checkFromIndexSize(off, len, b.length);
if (len == 0) {
return;
}
if (closed) {
throw new StreamClosedException();
}
int remain = CHUNK_SIZE - count;
if (len > remain) {
System.arraycopy(b, off, buf, pos, remain);
count = CHUNK_SIZE;
writeChunk();
len -= remain;
off += remain;
while (len >= CHUNK_SIZE) {
System.arraycopy(b, off, buf, OFFSET, CHUNK_SIZE);
len -= CHUNK_SIZE;
off += CHUNK_SIZE;
count = CHUNK_SIZE;
writeChunk();
}
}
if (len > 0) {
System.arraycopy(b, off, buf, pos, len);
count += len;
pos += len;
}
if (count == CHUNK_SIZE) {
writeChunk();
}
}
/**
* write out a chunk , and reset the pointers
* chunk does not have to be CHUNK_SIZE bytes
* count must == number of user bytes (<= CHUNK_SIZE)
*/
private void writeChunk() throws IOException {
char[] c = Integer.toHexString(count).toCharArray();
int clen = c.length;
int startByte = 4 - clen;
int i;
for (i=0; i<clen; i++) {
buf[startByte+i] = (byte)c[i];
}
buf[startByte + (i++)] = '\r';
buf[startByte + (i++)] = '\n';
buf[startByte + (i++) + count] = '\r';
buf[startByte + (i++) + count] = '\n';
out.write(buf, startByte, i+count);
count = 0;
pos = OFFSET;
}
public void close() throws IOException {
if (closed) {
return;
}
try {
/*
* write any pending chunk data. manually write chunk rather than
* calling flush to avoid sending small packets
*/
if (count > 0) {
writeChunk();
}
/* write an empty chunk */
writeChunk();
out.flush();
LeftOverInputStream is = t.getOriginalInputStream();
if (!is.isClosed()) {
is.close();
}
/* some clients close the connection before empty chunk is sent */
} catch (IOException e) {
} finally {
closed = true;
}
t.postExchangeFinished(true);
}
public void flush() throws IOException {
if (closed) {
throw new StreamClosedException();
}
if (count > 0) {
writeChunk();
}
out.flush();
}
}

View file

@ -0,0 +1,109 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
class Code {
public static final int HTTP_CONTINUE = 100;
public static final int HTTP_OK = 200;
public static final int HTTP_CREATED = 201;
public static final int HTTP_ACCEPTED = 202;
public static final int HTTP_NOT_AUTHORITATIVE = 203;
public static final int HTTP_NO_CONTENT = 204;
public static final int HTTP_RESET = 205;
public static final int HTTP_PARTIAL = 206;
public static final int HTTP_MULT_CHOICE = 300;
public static final int HTTP_MOVED_PERM = 301;
public static final int HTTP_MOVED_TEMP = 302;
public static final int HTTP_SEE_OTHER = 303;
public static final int HTTP_NOT_MODIFIED = 304;
public static final int HTTP_USE_PROXY = 305;
public static final int HTTP_BAD_REQUEST = 400;
public static final int HTTP_UNAUTHORIZED = 401;
public static final int HTTP_PAYMENT_REQUIRED = 402;
public static final int HTTP_FORBIDDEN = 403;
public static final int HTTP_NOT_FOUND = 404;
public static final int HTTP_BAD_METHOD = 405;
public static final int HTTP_NOT_ACCEPTABLE = 406;
public static final int HTTP_PROXY_AUTH = 407;
public static final int HTTP_CLIENT_TIMEOUT = 408;
public static final int HTTP_CONFLICT = 409;
public static final int HTTP_GONE = 410;
public static final int HTTP_LENGTH_REQUIRED = 411;
public static final int HTTP_PRECON_FAILED = 412;
public static final int HTTP_ENTITY_TOO_LARGE = 413;
public static final int HTTP_REQ_TOO_LONG = 414;
public static final int HTTP_UNSUPPORTED_TYPE = 415;
public static final int HTTP_INTERNAL_ERROR = 500;
public static final int HTTP_NOT_IMPLEMENTED = 501;
public static final int HTTP_BAD_GATEWAY = 502;
public static final int HTTP_UNAVAILABLE = 503;
public static final int HTTP_GATEWAY_TIMEOUT = 504;
public static final int HTTP_VERSION = 505;
static String msg(int code) {
switch (code) {
case HTTP_OK: return " OK";
case HTTP_CONTINUE: return " Continue";
case HTTP_CREATED: return " Created";
case HTTP_ACCEPTED: return " Accepted";
case HTTP_NOT_AUTHORITATIVE: return " Non-Authoritative Information";
case HTTP_NO_CONTENT: return " No Content";
case HTTP_RESET: return " Reset Content";
case HTTP_PARTIAL: return " Partial Content";
case HTTP_MULT_CHOICE: return " Multiple Choices";
case HTTP_MOVED_PERM: return " Moved Permanently";
case HTTP_MOVED_TEMP: return " Temporary Redirect";
case HTTP_SEE_OTHER: return " See Other";
case HTTP_NOT_MODIFIED: return " Not Modified";
case HTTP_USE_PROXY: return " Use Proxy";
case HTTP_BAD_REQUEST: return " Bad Request";
case HTTP_UNAUTHORIZED: return " Unauthorized" ;
case HTTP_PAYMENT_REQUIRED: return " Payment Required";
case HTTP_FORBIDDEN: return " Forbidden";
case HTTP_NOT_FOUND: return " Not Found";
case HTTP_BAD_METHOD: return " Method Not Allowed";
case HTTP_NOT_ACCEPTABLE: return " Not Acceptable";
case HTTP_PROXY_AUTH: return " Proxy Authentication Required";
case HTTP_CLIENT_TIMEOUT: return " Request Time-Out";
case HTTP_CONFLICT: return " Conflict";
case HTTP_GONE: return " Gone";
case HTTP_LENGTH_REQUIRED: return " Length Required";
case HTTP_PRECON_FAILED: return " Precondition Failed";
case HTTP_ENTITY_TOO_LARGE: return " Request Entity Too Large";
case HTTP_REQ_TOO_LONG: return " Request-URI Too Large";
case HTTP_UNSUPPORTED_TYPE: return " Unsupported Media Type";
case HTTP_INTERNAL_ERROR: return " Internal Server Error";
case HTTP_NOT_IMPLEMENTED: return " Not Implemented";
case HTTP_BAD_GATEWAY: return " Bad Gateway";
case HTTP_UNAVAILABLE: return " Service Unavailable";
case HTTP_GATEWAY_TIMEOUT: return " Gateway Timeout";
case HTTP_VERSION: return " HTTP Version Not Supported";
default: return " ";
}
}
}

View file

@ -0,0 +1,268 @@
/*
* Copyright (c) 2005, 2026, 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.
*/
package sun.net.httpserver;
import java.util.*;
import java.util.function.BiPredicate;
class ContextList {
private static final System.Logger LOGGER = System.getLogger(ContextList.class.getName());
private final LinkedList<HttpContextImpl> list = new LinkedList<>();
public synchronized void add(HttpContextImpl ctx) {
assert ctx != null;
// `findContext(String protocol, String path, ContextPathMatcher matcher)`
// expects the protocol to be lower-cased using ROOT locale, hence:
assert ctx.getProtocol().equals(ctx.getProtocol().toLowerCase(Locale.ROOT));
assert ctx.getPath() != null;
// `ContextPathMatcher` expects context paths to be non-empty:
assert !ctx.getPath().isEmpty();
if (contains(ctx)) {
throw new IllegalArgumentException("cannot add context to list");
}
list.add(ctx);
}
boolean contains(HttpContextImpl ctx) {
return findContext(ctx.getProtocol(), ctx.getPath(), ContextPathMatcher.EXACT) != null;
}
public synchronized int size() {
return list.size();
}
/**
* {@return the context with the longest case-sensitive prefix match}
*
* @param protocol the request protocol
* @param path the request path
*/
HttpContextImpl findContext(String protocol, String path) {
var matcher = ContextPathMatcher.ofConfiguredPrefixPathMatcher();
return findContext(protocol, path, matcher);
}
private synchronized HttpContextImpl findContext(String protocol, String path, ContextPathMatcher matcher) {
protocol = protocol.toLowerCase(Locale.ROOT);
String longest = "";
HttpContextImpl lc = null;
for (HttpContextImpl ctx: list) {
if (!ctx.getProtocol().equals(protocol)) {
continue;
}
String cpath = ctx.getPath();
if (!matcher.test(cpath, path)) {
continue;
}
if (cpath.length() > longest.length()) {
longest = cpath;
lc = ctx;
}
}
return lc;
}
private enum ContextPathMatcher implements BiPredicate<String, String> {
/**
* Tests if both the request path and the context path are identical.
*/
EXACT(String::equals),
/**
* Tests <em>string prefix matches</em> where the request path string
* starts with the context path string.
*
* <h3>Examples</h3>
*
* <table>
* <thead>
* <tr>
* <th rowspan="2">Context path</th>
* <th colspan="4">Request path</th>
* </tr>
* <tr>
* <th>/foo</th>
* <th>/foo/</th>
* <th>/foo/bar</th>
* <th>/foobar</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>/</td>
* <td>Y</td>
* <td>Y</td>
* <td>Y</td>
* <td>Y</td>
* </tr>
* <tr>
* <td>/foo</td>
* <td>Y</td>
* <td>Y</td>
* <td>Y</td>
* <td>Y</td>
* </tr>
* <tr>
* <td>/foo/</td>
* <td>N</td>
* <td>Y</td>
* <td>Y</td>
* <td>N</td>
* </tr>
* </tbody>
* </table>
*/
STRING_PREFIX((contextPath, requestPath) -> requestPath.startsWith(contextPath)),
/**
* Tests <em>path prefix matches</em> where path segments must have an
* exact match.
*
* <h3>Examples</h3>
*
* <table>
* <thead>
* <tr>
* <th rowspan="2">Context path</th>
* <th colspan="4">Request path</th>
* </tr>
* <tr>
* <th>/foo</th>
* <th>/foo/</th>
* <th>/foo/bar</th>
* <th>/foobar</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>/</td>
* <td>Y</td>
* <td>Y</td>
* <td>Y</td>
* <td>Y</td>
* </tr>
* <tr>
* <td>/foo</td>
* <td>Y</td>
* <td>Y</td>
* <td>Y</td>
* <td>N</td>
* </tr>
* <tr>
* <td>/foo/</td>
* <td>N</td>
* <td>Y</td>
* <td>Y</td>
* <td>N</td>
* </tr>
* </tbody>
* </table>
*/
PATH_PREFIX((contextPath, requestPath) -> {
// Does the request path prefix match?
if (!requestPath.startsWith(contextPath)) {
return false;
}
// Is it an exact match?
int contextPathLength = contextPath.length();
if (requestPath.length() == contextPathLength) {
return true;
}
// Is it a path-prefix match?
assert contextPathLength > 0;
return
// Case 1: The request path starts with the context
// path, but the context path has an extra path
// separator suffix. For instance, the context path is
// `/foo/` and the request path is `/foo/bar`.
contextPath.charAt(contextPathLength - 1) == '/' ||
// Case 2: The request path starts with the
// context path, but the request path has an
// extra path separator suffix. For instance,
// context path is `/foo` and the request path
// is `/foo/` or `/foo/bar`.
requestPath.charAt(contextPathLength) == '/';
});
private final BiPredicate<String, String> predicate;
ContextPathMatcher(BiPredicate<String, String> predicate) {
this.predicate = predicate;
}
@Override
public boolean test(String contextPath, String requestPath) {
return predicate.test(contextPath, requestPath);
}
private static ContextPathMatcher ofConfiguredPrefixPathMatcher() {
var propertyName = "sun.net.httpserver.pathMatcher";
var propertyValueDefault = "pathPrefix";
var propertyValue = System.getProperty(propertyName, propertyValueDefault);
return switch (propertyValue) {
case "pathPrefix" -> ContextPathMatcher.PATH_PREFIX;
case "stringPrefix" -> ContextPathMatcher.STRING_PREFIX;
default -> {
LOGGER.log(
System.Logger.Level.WARNING,
"System property \"{}\" contains an invalid value: \"{}\". Falling back to the default: \"{}\"",
propertyName, propertyValue, propertyValueDefault);
yield ContextPathMatcher.PATH_PREFIX;
}
};
}
}
public synchronized void remove(String protocol, String path)
throws IllegalArgumentException
{
HttpContextImpl ctx = findContext(protocol, path, ContextPathMatcher.EXACT);
if (ctx == null) {
throw new IllegalArgumentException("cannot remove element from list");
}
list.remove(ctx);
}
public synchronized void remove(HttpContextImpl context)
throws IllegalArgumentException
{
for (HttpContextImpl ctx: list) {
if (ctx.equals(context)) {
list.remove(ctx);
return;
}
}
throw new IllegalArgumentException("no such context in list");
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.net.*;
import java.io.*;
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;
public class DefaultHttpServerProvider extends HttpServerProvider {
public HttpServer createHttpServer(InetSocketAddress addr, int backlog) throws IOException {
return new HttpServerImpl(addr, backlog);
}
public HttpsServer createHttpsServer(InetSocketAddress addr, int backlog) throws IOException {
return new HttpsServerImpl(addr, backlog);
}
}

View file

@ -0,0 +1,108 @@
/*
* 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.
*/
package sun.net.httpserver;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpPrincipal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
public abstract class DelegatingHttpExchange extends HttpExchange {
private final HttpExchange exchange;
public DelegatingHttpExchange(HttpExchange ex) {
this.exchange = ex;
}
public abstract Headers getRequestHeaders();
public abstract String getRequestMethod();
public abstract URI getRequestURI();
public Headers getResponseHeaders() {
return exchange.getResponseHeaders();
}
public HttpContext getHttpContext() {
return exchange.getHttpContext();
}
public void close() {
exchange.close();
}
public InputStream getRequestBody() {
return exchange.getRequestBody();
}
public int getResponseCode() {
return exchange.getResponseCode();
}
public OutputStream getResponseBody() {
return exchange.getResponseBody();
}
public void sendResponseHeaders(int rCode, long contentLen) throws IOException {
exchange.sendResponseHeaders(rCode, contentLen);
}
public InetSocketAddress getRemoteAddress() {
return exchange.getRemoteAddress();
}
public InetSocketAddress getLocalAddress() {
return exchange.getLocalAddress();
}
public String getProtocol() {
return exchange.getProtocol();
}
public Object getAttribute(String name) {
return exchange.getAttribute(name);
}
public void setAttribute(String name, Object value) {
exchange.setAttribute(name, value);
}
public void setStreams(InputStream i, OutputStream o) {
exchange.setStreams(i, o);
}
public HttpPrincipal getPrincipal() {
return exchange.getPrincipal();
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2005, 2026, 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.
*/
package sun.net.httpserver;
import java.util.Objects;
abstract sealed class Event {
final ExchangeImpl exchange;
protected Event(ExchangeImpl t) {
this.exchange = t;
}
/**
* Stopping event for the http server.
* The event applies to the whole server and is not tied to any particular
* exchange.
*/
static final class StopRequested extends Event {
StopRequested() {
super(null);
}
}
/**
* Event indicating that the exchange is finished,
* without having necessarily read the complete
* request or sent the complete response.
* Typically, this event is posted when invoking
* the filter chain throws an exception.
*/
static final class ExchangeFinished extends Event {
ExchangeFinished(ExchangeImpl t) {
super(Objects.requireNonNull(t));
}
}
}

View file

@ -0,0 +1,550 @@
/*
* Copyright (c) 2005, 2026, 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.
*/
package sun.net.httpserver;
import java.io.*;
import java.net.*;
import javax.net.ssl.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
import com.sun.net.httpserver.*;
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
import static com.sun.net.httpserver.HttpExchange.RSPBODY_CHUNKED;
class ExchangeImpl {
Headers reqHdrs, rspHdrs;
Request req;
String method;
private boolean writefinished;
URI uri;
HttpConnection connection;
long reqContentLen;
long rspContentLen;
/* raw streams which access the socket directly */
InputStream ris;
OutputStream ros;
Thread thread;
/* close the underlying connection when this exchange finished */
boolean close;
boolean closed;
boolean http10 = false;
/* for formatting the Date: header */
private static final DateTimeFormatter FORMATTER;
private static final boolean perExchangeAttributes =
!System.getProperty("jdk.httpserver.attributes", "")
.equals("context");
static {
String pattern = "EEE, dd MMM yyyy HH:mm:ss zzz";
FORMATTER = DateTimeFormatter.ofPattern(pattern, Locale.US)
.withZone(ZoneId.of("GMT"));
}
private static final String HEAD = "HEAD";
/* streams which take care of the HTTP protocol framing
* and are passed up to higher layers
*/
InputStream uis;
OutputStream uos;
LeftOverInputStream uis_orig; // uis may have be a user supplied wrapper
PlaceholderOutputStream uos_orig;
boolean sentHeaders; /* true after response headers sent */
final Map<String, Object> attributes;
int rcode = -1;
HttpPrincipal principal;
ServerImpl server;
// Used to control that ServerImpl::endExchange is called
// exactly once for this exchange. ServerImpl::endExchange decrements
// the refcount that was incremented by calling ServerImpl::startExchange
// in this ExchangeImpl constructor.
private final AtomicBoolean ended = new AtomicBoolean();
// Used to ensure that the Event.ExchangeFinished is posted only
// once for this exchange. The Event.ExchangeFinished is what will
// eventually cause the ServerImpl::finishedLatch to be triggered,
// once the number of active exchanges reaches 0 and ServerImpl::stop
// has been requested.
private final AtomicBoolean finished = new AtomicBoolean();
ExchangeImpl(
String m, URI u, Request req, long len, HttpConnection connection
) throws IOException {
this.req = req;
this.reqHdrs = Headers.of(req.headers());
this.rspHdrs = new Headers();
this.method = m;
this.uri = u;
this.connection = connection;
this.reqContentLen = len;
this.attributes = perExchangeAttributes
? new ConcurrentHashMap<>()
: getHttpContext().getAttributes();
/* ros only used for headers, body written directly to stream */
this.ros = req.outputStream();
this.ris = req.inputStream();
server = getServerImpl();
server.startExchange();
}
/**
* When true, writefinished indicates that all bytes expected
* by the client have been written to the response body
* outputstream, and that the response body outputstream has
* been closed. When all bytes have also been pulled from
* the request body input stream, this makes it possible to
* reuse the connection for the next request.
*/
synchronized boolean writefinished() {
return writefinished;
}
/**
* Calls ServerImpl::endExchange if not already called for this
* exchange. ServerImpl::endExchange must be called exactly once
* per exchange, and this method ensures that it is not called
* more than once for this exchange.
* @return the new (or current) value of the exchange count.
*/
int endExchange() {
// only call server.endExchange(); once per exchange
if (ended.compareAndSet(false, true)) {
return server.endExchange();
}
return server.getExchangeCount();
}
/**
* Posts the ExchangeFinished event if not already posted.
* If `writefinished` is true, marks the exchange as {@link
* #writefinished()} so that the connection can be reused.
* @param writefinished whether all bytes expected by the
* client have been writen out to the
* response body output stream.
*/
void postExchangeFinished(boolean writefinished) {
// only post ExchangeFinished once per exchange
if (finished.compareAndSet(false, true)) {
if (writefinished) {
synchronized (this) {
assert this.writefinished == false;
this.writefinished = true;
}
}
Event e = new Event.ExchangeFinished(this);
getHttpContext().getServerImpl().addEvent(e);
}
}
public Headers getRequestHeaders() {
return reqHdrs;
}
public Headers getResponseHeaders() {
return rspHdrs;
}
public URI getRequestURI() {
return uri;
}
public String getRequestMethod() {
return method;
}
public HttpContextImpl getHttpContext() {
return connection.getHttpContext();
}
private boolean isHeadRequest() {
return HEAD.equals(getRequestMethod());
}
public void close() {
if (closed) {
return;
}
closed = true;
/* close the underlying connection if,
* a) the streams not set up yet, no response can be sent, or
* b) if the wrapper output stream is not set up, or
* c) if the close of the input/output stream fails
*/
try {
if (uis_orig == null || uos == null) {
connection.close();
return;
}
if (!uos_orig.isWrapped()) {
connection.close();
return;
}
if (!uis_orig.isClosed()) {
uis_orig.close();
}
uos.close();
} catch (IOException e) {
connection.close();
} finally {
postExchangeFinished(false);
}
}
public InputStream getRequestBody() {
if (uis != null) {
return uis;
}
if (reqContentLen == -1L) {
uis_orig = new ChunkedInputStream(this, ris);
uis = uis_orig;
} else {
uis_orig = new FixedLengthInputStream(this, ris, reqContentLen);
uis = uis_orig;
}
return uis;
}
LeftOverInputStream getOriginalInputStream() {
return uis_orig;
}
public int getResponseCode() {
return rcode;
}
public OutputStream getResponseBody() {
/* TODO. Change spec to remove restriction below. Filters
* cannot work with this restriction
*
* if (!sentHeaders) {
* throw new IllegalStateException("headers not sent");
* }
*/
if (uos == null) {
uos_orig = new PlaceholderOutputStream(null);
uos = uos_orig;
}
return uos;
}
/* returns the place holder stream, which is the stream
* returned from the 1st call to getResponseBody()
* The "real" ouputstream is then placed inside this
*/
PlaceholderOutputStream getPlaceholderResponseBody() {
getResponseBody();
return uos_orig;
}
private static final byte[] CRLF = new byte[] {0x0D, 0x0A};
public void sendResponseHeaders(int rCode, long contentLen)
throws IOException
{
final Logger logger = server.getLogger();
if (sentHeaders) {
throw new IOException("headers already sent");
}
this.rcode = rCode;
String statusLine = "HTTP/1.1 " + rCode + Code.msg(rCode);
ByteArrayOutputStream tmpout = new ByteArrayOutputStream();
PlaceholderOutputStream o = getPlaceholderResponseBody();
tmpout.write(bytes(statusLine, false, 0), 0, statusLine.length());
tmpout.write(CRLF);
boolean noContentToSend = false; // assume there is content
boolean noContentLengthHeader = false; // must not send Content-length is set
rspHdrs.set("Date", FORMATTER.format(Instant.now()));
/* check for response type that is not allowed to send a body */
if ((rCode >= 100 && rCode < 200) /* informational */
||(rCode == 204) /* no content */
||(rCode == 304)) /* not modified */
{
if (contentLen != RSPBODY_EMPTY) {
String msg = "sendResponseHeaders: rCode = " + rCode
+ ": forcing contentLen = RSPBODY_EMPTY";
logger.log(Level.WARNING, msg);
}
contentLen = RSPBODY_EMPTY;
noContentLengthHeader = (rCode != 304);
}
if (isHeadRequest() || rCode == 304) {
/* HEAD requests or 304 responses should not set a content length by passing it
* through this API, but should instead manually set the required
* headers.*/
if (contentLen >= 0) {
String msg =
"sendResponseHeaders: being invoked with a content length for a HEAD request";
logger.log(Level.WARNING, msg);
}
noContentToSend = true;
contentLen = 0;
o.setWrappedStream(new FixedLengthOutputStream(this, ros, contentLen));
} else { /* not a HEAD request or 304 response */
if (contentLen == RSPBODY_CHUNKED) {
if (http10) {
o.setWrappedStream(new UndefLengthOutputStream(this, ros));
close = true;
} else {
rspHdrs.set("Transfer-encoding", "chunked");
o.setWrappedStream(new ChunkedOutputStream(this, ros));
}
} else {
if (contentLen == RSPBODY_EMPTY) {
noContentToSend = true;
contentLen = 0;
}
if (!noContentLengthHeader) {
rspHdrs.set("Content-length", Long.toString(contentLen));
}
o.setWrappedStream(new FixedLengthOutputStream(this, ros, contentLen));
}
}
// A custom handler can request that the connection be
// closed after the exchange by supplying Connection: close
// to the response header. Nothing to do if the exchange is
// already set up to be closed.
if (!close) {
Stream<String> conheader =
Optional.ofNullable(rspHdrs.get("Connection"))
.map(List::stream).orElse(Stream.empty());
if (conheader.anyMatch("close"::equalsIgnoreCase)) {
logger.log(Level.DEBUG, "Connection: close requested by handler");
close = true;
}
}
write(rspHdrs, tmpout);
this.rspContentLen = contentLen;
tmpout.writeTo(ros);
sentHeaders = true;
logger.log(Level.TRACE, "Sent headers: noContentToSend=" + noContentToSend);
if (noContentToSend) {
ros.flush();
close();
}
server.logReply(rCode, req.requestLine(), null);
}
void write(Headers map, OutputStream os) throws IOException {
Set<Map.Entry<String, List<String>>> entries = map.entrySet();
for (Map.Entry<String, List<String>> entry : entries) {
String key = entry.getKey();
byte[] buf;
List<String> values = entry.getValue();
for (String val : values) {
int i = key.length();
buf = bytes(key, true, 2);
buf[i++] = ':';
buf[i++] = ' ';
os.write(buf, 0, i);
buf = bytes(val, false, 2);
i = val.length();
buf[i++] = '\r';
buf[i++] = '\n';
os.write(buf, 0, i);
}
}
os.write('\r');
os.write('\n');
}
private byte[] rspbuf = new byte[128]; // used by bytes()
/**
* convert string to byte[], using rspbuf
* Make sure that at least "extra" bytes are free at end
* of rspbuf. Reallocate rspbuf if not big enough.
* caller must check return value to see if rspbuf moved
*
* Header values are supposed to be limited to 7-bit ASCII
* but 8-bit has to be allowed (for ISO_8859_1). For efficiency
* we just down cast 16 bit Java chars to byte. We don't allow
* any character that can't be encoded in 8 bits.
*/
private byte[] bytes(String s, boolean isKey, int extra) throws IOException {
Utils.checkHeader(s, !isKey);
int slen = s.length();
if (slen+extra > rspbuf.length) {
int diff = slen + extra - rspbuf.length;
rspbuf = new byte [2* (rspbuf.length + diff)];
}
char c[] = s.toCharArray();
for (int i=0; i<c.length; i++) {
rspbuf[i] = (byte)c[i];
}
return rspbuf;
}
public InetSocketAddress getRemoteAddress() {
Socket s = connection.getChannel().socket();
InetAddress ia = s.getInetAddress();
int port = s.getPort();
return new InetSocketAddress(ia, port);
}
public InetSocketAddress getLocalAddress() {
Socket s = connection.getChannel().socket();
InetAddress ia = s.getLocalAddress();
int port = s.getLocalPort();
return new InetSocketAddress(ia, port);
}
public String getProtocol() {
String reqline = req.requestLine();
int index = reqline.lastIndexOf(' ');
return reqline.substring(index+1);
}
public SSLSession getSSLSession() {
SSLEngine e = connection.getSSLEngine();
if (e == null) {
return null;
}
return e.getSession();
}
public Object getAttribute(String name) {
return attributes.get(Objects.requireNonNull(name, "null name parameter"));
}
public void setAttribute(String name, Object value) {
var key = Objects.requireNonNull(name, "null name parameter");
if (value != null) {
attributes.put(key, value);
} else {
attributes.remove(key);
}
}
public void setStreams(InputStream i, OutputStream o) {
assert uis != null;
if (i != null) {
uis = i;
}
if (o != null) {
uos = o;
}
}
/**
* PP
*/
HttpConnection getConnection() {
return connection;
}
ServerImpl getServerImpl() {
return getHttpContext().getServerImpl();
}
public HttpPrincipal getPrincipal() {
return principal;
}
void setPrincipal(HttpPrincipal principal) {
this.principal = principal;
}
static ExchangeImpl get(HttpExchange t) {
if (t instanceof HttpExchangeImpl) {
return ((HttpExchangeImpl)t).getExchangeImpl();
} else {
assert t instanceof HttpsExchangeImpl;
return ((HttpsExchangeImpl)t).getExchangeImpl();
}
}
}
/**
* An OutputStream which wraps another stream
* which is supplied either at creation time, or sometime later.
* If a caller/user tries to write to this stream before
* the wrapped stream has been provided, then an IOException will
* be thrown.
*/
class PlaceholderOutputStream extends java.io.OutputStream {
OutputStream wrapped;
PlaceholderOutputStream(OutputStream os) {
wrapped = os;
}
void setWrappedStream(OutputStream os) {
wrapped = os;
}
boolean isWrapped() {
return wrapped != null;
}
private void checkWrap() throws IOException {
if (wrapped == null) {
throw new IOException("response headers not sent yet");
}
}
public void write(int b) throws IOException {
checkWrap();
wrapped.write(b);
}
public void write(byte b[]) throws IOException {
checkWrap();
wrapped.write(b);
}
public void write(byte b[], int off, int len) throws IOException {
checkWrap();
wrapped.write(b, off, len);
}
public void flush() throws IOException {
checkWrap();
wrapped.flush();
}
public void close() throws IOException {
checkWrap();
wrapped.close();
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.io.*;
import java.net.*;
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;
/**
* a class which allows the caller to read up to a defined
* number of bytes off an underlying stream
* close() does not close the underlying stream
*/
class FixedLengthInputStream extends LeftOverInputStream {
private long remaining;
FixedLengthInputStream(ExchangeImpl t, InputStream src, long len) {
super(t, src);
if (len < 0) {
throw new IllegalArgumentException("Content-Length: " + len);
}
this.remaining = len;
}
protected int readImpl(byte[] b, int off, int len) throws IOException {
eof = (remaining == 0L);
if (eof) {
return -1;
}
if (len > remaining) {
len = (int)remaining;
}
int n = in.read(b, off, len);
if (n > -1) {
remaining -= n;
if (remaining == 0) {
t.getServerImpl().requestCompleted(t.getConnection());
}
}
if (n < 0 && !eof)
throw new IOException("connection closed before all data received");
return n;
}
public int available() throws IOException {
if (eof) {
return 0;
}
int n = in.available();
return n < remaining? n: (int)remaining;
}
public boolean markSupported() {return false;}
public void mark(int l) {
}
public void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
}

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2005, 2026, 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.
*/
package sun.net.httpserver;
import java.io.*;
import java.util.Objects;
/**
* a class which allows the caller to write up to a defined
* number of bytes to an underlying stream. The caller *must*
* write the pre-defined number or else an exception will be thrown
* and the whole request aborted.
* normal close() does not close the underlying stream
*/
class FixedLengthOutputStream extends FilterOutputStream
{
private long remaining;
private boolean closed = false;
ExchangeImpl t;
FixedLengthOutputStream(ExchangeImpl t, OutputStream src, long len) {
super (src);
if (len < 0) {
throw new IllegalArgumentException("Content-Length: " + len);
}
this.t = t;
this.remaining = len;
}
public void write(int b) throws IOException {
if (closed) {
throw new IOException("stream closed");
}
if (remaining == 0) {
throw new StreamClosedException();
}
out.write(b);
remaining --;
}
public void write(byte[] b, int off, int len) throws IOException {
Objects.checkFromIndexSize(off, len, b.length);
if (len == 0) {
return;
}
if (closed) {
throw new IOException("stream closed");
}
if (len > remaining) {
// stream is still open, caller can retry
throw new IOException("too many bytes to write to stream");
}
out.write(b, off, len);
remaining -= len;
}
public void close() throws IOException {
if (closed) {
return;
}
closed = true;
if (remaining > 0) {
t.close();
throw new IOException("insufficient bytes written to stream");
}
flush();
LeftOverInputStream is = t.getOriginalInputStream();
if (!is.isClosed()) {
try {
is.close();
} catch (IOException e) {}
}
t.postExchangeFinished(true);
}
// flush is a pass-through
}

View file

@ -0,0 +1,197 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.io.*;
import javax.net.ssl.*;
import java.nio.channels.*;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
/**
* encapsulates all the connection specific state for a HTTP/S connection
* one of these is hung from the selector attachment and is used to locate
* everything from that.
*/
class HttpConnection {
HttpContextImpl context;
SSLEngine engine;
SSLContext sslContext;
SSLStreams sslStreams;
/* high level streams returned to application */
InputStream i;
/* low level stream that sits directly over channel */
InputStream raw;
OutputStream rawout;
SocketChannel chan;
SelectionKey selectionKey;
String protocol;
long idleStartTime; // absolute time in milli seconds, starting when the connection was marked idle
volatile long reqStartedTime; // time when the request was initiated
volatile long rspStartedTime; // time we started writing the response
int remaining;
boolean closed = false;
Logger logger;
public enum State {IDLE, REQUEST, RESPONSE, NEWLY_ACCEPTED};
volatile State state;
public String toString() {
final var sb = new StringBuilder(HttpConnection.class.getSimpleName());
if (chan != null) {
sb.append(" (");
sb.append(chan);
sb.append(")");
}
return sb.toString();
}
HttpConnection() {
}
void setChannel(SocketChannel c) {
chan = c;
}
void setContext(HttpContextImpl ctx) {
context = ctx;
}
State getState() {
return state;
}
void setState(State s) {
state = s;
}
void setParameters(
InputStream in, OutputStream rawout, SocketChannel chan,
SSLEngine engine, SSLStreams sslStreams, SSLContext sslContext, String protocol,
HttpContextImpl context, InputStream raw
)
{
this.context = context;
this.i = in;
this.rawout = rawout;
this.raw = raw;
this.protocol = protocol;
this.engine = engine;
this.chan = chan;
this.sslContext = sslContext;
this.sslStreams = sslStreams;
this.logger = context.getLogger();
}
SocketChannel getChannel() {
return chan;
}
synchronized void close() {
if (closed) {
return;
}
closed = true;
if (logger != null && chan != null) {
logger.log(Level.TRACE, "Closing connection: " + chan.toString());
}
if (!chan.isOpen()) {
ServerImpl.dprint("Channel already closed");
return;
}
try {
/* need to ensure temporary selectors are closed */
if (raw != null) {
raw.close();
}
} catch (IOException e) {
ServerImpl.dprint(e);
}
try {
if (rawout != null) {
rawout.close();
}
} catch (IOException e) {
ServerImpl.dprint(e);
}
try {
if (sslStreams != null) {
sslStreams.close();
}
} catch (IOException e) {
ServerImpl.dprint(e);
}
try {
chan.close();
} catch (IOException e) {
ServerImpl.dprint(e);
}
}
/* remaining is the number of bytes left on the lowest level inputstream
* after the exchange is finished
*/
void setRemaining(int r) {
remaining = r;
}
int getRemaining() {
return remaining;
}
SelectionKey getSelectionKey() {
return selectionKey;
}
InputStream getInputStream() {
return i;
}
OutputStream getRawOutputStream() {
return rawout;
}
String getProtocol() {
return protocol;
}
SSLEngine getSSLEngine() {
return engine;
}
SSLContext getSSLContext() {
return sslContext;
}
HttpContextImpl getHttpContext() {
return context;
}
}

View file

@ -0,0 +1,152 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.util.*;
import java.lang.System.Logger;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import com.sun.net.httpserver.*;
/**
* HttpContext represents a mapping between a protocol (http or https) together with a root URI path
* to a {@link HttpHandler} which is invoked to handle requests destined
* for the protocol/path on the associated HttpServer.
* <p>
* HttpContext instances are created by {@link HttpServer#createContext(String, String, HttpHandler, Object)}
* <p>
*/
class HttpContextImpl extends HttpContext {
private final String path;
private final String protocol;
private final ServerImpl server;
private final AuthFilter authfilter;
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
/* system filters, not visible to applications */
private final List<Filter> sfilters = new CopyOnWriteArrayList<>();
/* user filters, set by applications */
private final List<Filter> ufilters = new CopyOnWriteArrayList<>();
private Authenticator authenticator;
private HttpHandler handler;
/**
* constructor is package private.
*/
HttpContextImpl(String protocol, String path, HttpHandler cb, ServerImpl server) {
if (path == null || protocol == null || path.length() < 1 || path.charAt(0) != '/') {
throw new IllegalArgumentException("Illegal value for path or protocol");
}
this.protocol = protocol.toLowerCase(Locale.ROOT);
this.path = path;
if (!this.protocol.equals("http") && !this.protocol.equals("https")) {
throw new IllegalArgumentException("Illegal value for protocol");
}
this.handler = cb;
this.server = server;
authfilter = new AuthFilter(null);
sfilters.add(authfilter);
}
/**
* returns the handler for this context
* @return the HttpHandler for this context
*/
public HttpHandler getHandler() {
return handler;
}
public void setHandler(HttpHandler h) {
if (h == null) {
throw new NullPointerException("Null handler parameter");
}
if (handler != null) {
throw new IllegalArgumentException("handler already set");
}
handler = h;
}
/**
* returns the path this context was created with
* @return this context's path
*/
public String getPath() {
return path;
}
/**
* returns the server this context was created with
* @return this context's server
*/
public HttpServer getServer() {
return server.getWrapper();
}
ServerImpl getServerImpl() {
return server;
}
/**
* returns the protocol this context was created with
* @return this context's path
*/
public String getProtocol() {
return protocol;
}
/**
* returns a mutable Map, which can be used to pass
* configuration and other data to Filter modules
* and to the context's exchange handler.
* <p>
* Every attribute stored in this Map will be visible to
* every HttpExchange processed by this context
*/
public Map<String, Object> getAttributes() {
return attributes;
}
public List<Filter> getFilters() {
return ufilters;
}
List<Filter> getSystemFilters() {
return sfilters;
}
public Authenticator setAuthenticator(Authenticator auth) {
Authenticator old = authenticator;
authenticator = auth;
authfilter.setAuthenticator(auth);
return old;
}
public Authenticator getAuthenticator() {
return authenticator;
}
Logger getLogger() {
return server.getLogger();
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
/**
* A Http error
*/
class HttpError extends RuntimeException {
private static final long serialVersionUID = 8769596371344178179L;
public HttpError(String msg) {
super(msg);
}
}

View file

@ -0,0 +1,119 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import javax.net.ssl.*;
import java.util.*;
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;
class HttpExchangeImpl extends HttpExchange {
ExchangeImpl impl;
HttpExchangeImpl(ExchangeImpl impl) {
this.impl = impl;
}
public Headers getRequestHeaders() {
return impl.getRequestHeaders();
}
public Headers getResponseHeaders() {
return impl.getResponseHeaders();
}
public URI getRequestURI() {
return impl.getRequestURI();
}
public String getRequestMethod() {
return impl.getRequestMethod();
}
public HttpContextImpl getHttpContext() {
return impl.getHttpContext();
}
public void close() {
impl.close();
}
public InputStream getRequestBody() {
return impl.getRequestBody();
}
public int getResponseCode() {
return impl.getResponseCode();
}
public OutputStream getResponseBody() {
return impl.getResponseBody();
}
public void sendResponseHeaders(int rCode, long contentLen)
throws IOException
{
impl.sendResponseHeaders(rCode, contentLen);
}
public InetSocketAddress getRemoteAddress() {
return impl.getRemoteAddress();
}
public InetSocketAddress getLocalAddress() {
return impl.getLocalAddress();
}
public String getProtocol() {
return impl.getProtocol();
}
public Object getAttribute(String name) {
return impl.getAttribute(name);
}
public void setAttribute(String name, Object value) {
impl.setAttribute(name, value);
}
public void setStreams(InputStream i, OutputStream o) {
impl.setStreams(i, o);
}
public HttpPrincipal getPrincipal() {
return impl.getPrincipal();
}
ExchangeImpl getExchangeImpl() {
return impl;
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.net.*;
import java.io.*;
import java.util.concurrent.*;
import com.sun.net.httpserver.*;
public class HttpServerImpl extends HttpServer {
ServerImpl server;
HttpServerImpl() throws IOException {
this(new InetSocketAddress(80), 0);
}
HttpServerImpl(
InetSocketAddress addr, int backlog
) throws IOException {
server = new ServerImpl(this, "http", addr, backlog);
}
public void bind(InetSocketAddress addr, int backlog) throws IOException {
server.bind(addr, backlog);
}
public void start() {
server.start();
}
public void setExecutor(Executor executor) {
server.setExecutor(executor);
}
public Executor getExecutor() {
return server.getExecutor();
}
public void stop(int delay) {
server.stop(delay);
}
public HttpContextImpl createContext(String path, HttpHandler handler) {
return server.createContext(path, handler);
}
public HttpContextImpl createContext(String path) {
return server.createContext(path);
}
public void removeContext(String path) throws IllegalArgumentException {
server.removeContext(path);
}
public void removeContext(HttpContext context) throws IllegalArgumentException {
server.removeContext(context);
}
public InetSocketAddress getAddress() {
return server.getAddress();
}
}

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import javax.net.ssl.*;
import java.util.*;
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;
class HttpsExchangeImpl extends HttpsExchange {
ExchangeImpl impl;
HttpsExchangeImpl(ExchangeImpl impl) throws IOException {
this.impl = impl;
}
public Headers getRequestHeaders() {
return impl.getRequestHeaders();
}
public Headers getResponseHeaders() {
return impl.getResponseHeaders();
}
public URI getRequestURI() {
return impl.getRequestURI();
}
public String getRequestMethod() {
return impl.getRequestMethod();
}
public HttpContextImpl getHttpContext() {
return impl.getHttpContext();
}
public void close() {
impl.close();
}
public InputStream getRequestBody() {
return impl.getRequestBody();
}
public int getResponseCode() {
return impl.getResponseCode();
}
public OutputStream getResponseBody() {
return impl.getResponseBody();
}
public void sendResponseHeaders(int rCode, long contentLen)
throws IOException
{
impl.sendResponseHeaders(rCode, contentLen);
}
public InetSocketAddress getRemoteAddress() {
return impl.getRemoteAddress();
}
public InetSocketAddress getLocalAddress() {
return impl.getLocalAddress();
}
public String getProtocol() {
return impl.getProtocol();
}
public SSLSession getSSLSession() {
return impl.getSSLSession();
}
public Object getAttribute(String name) {
return impl.getAttribute(name);
}
public void setAttribute(String name, Object value) {
impl.setAttribute(name, value);
}
public void setStreams(InputStream i, OutputStream o) {
impl.setStreams(i, o);
}
public HttpPrincipal getPrincipal() {
return impl.getPrincipal();
}
ExchangeImpl getExchangeImpl() {
return impl;
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.net.*;
import java.io.*;
import java.util.concurrent.*;
import com.sun.net.httpserver.*;
public class HttpsServerImpl extends HttpsServer {
ServerImpl server;
HttpsServerImpl() throws IOException {
this(new InetSocketAddress(443), 0);
}
HttpsServerImpl(
InetSocketAddress addr, int backlog
) throws IOException {
server = new ServerImpl(this, "https", addr, backlog);
}
public void setHttpsConfigurator(HttpsConfigurator config) {
server.setHttpsConfigurator(config);
}
public HttpsConfigurator getHttpsConfigurator() {
return server.getHttpsConfigurator();
}
public void bind(InetSocketAddress addr, int backlog) throws IOException {
server.bind(addr, backlog);
}
public void start() {
server.start();
}
public void setExecutor(Executor executor) {
server.setExecutor(executor);
}
public Executor getExecutor() {
return server.getExecutor();
}
public void stop(int delay) {
server.stop(delay);
}
public HttpContextImpl createContext(String path, HttpHandler handler) {
return server.createContext(path, handler);
}
public HttpContextImpl createContext(String path) {
return server.createContext(path);
}
public void removeContext(String path) throws IllegalArgumentException {
server.removeContext(path);
}
public void removeContext(HttpContext context) throws IllegalArgumentException {
server.removeContext(context);
}
public InetSocketAddress getAddress() {
return server.getAddress();
}
}

View file

@ -0,0 +1,143 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.io.*;
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;
/**
* a (filter) input stream which can tell us if bytes are "left over"
* on the underlying stream which can be read (without blocking)
* on another instance of this class.
*
* The class can also report if all bytes "expected" to be read
* were read, by the time close() was called. In that case,
* bytes may be drained to consume them (by calling drain() ).
*
* isEOF() returns true, when all expected bytes have been read
*/
abstract class LeftOverInputStream extends FilterInputStream {
final ExchangeImpl t;
final ServerImpl server;
protected boolean closed = false;
protected boolean eof = false;
byte[] one = new byte[1];
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
public LeftOverInputStream(ExchangeImpl t, InputStream src) {
super(src);
this.t = t;
this.server = t.getServerImpl();
}
/**
* if bytes are left over buffered on *the UNDERLYING* stream
*/
public boolean isDataBuffered() throws IOException {
assert eof;
return super.available() > 0;
}
public void close() throws IOException {
if (closed) {
return;
}
closed = true;
if (!eof) {
eof = drain(ServerConfig.getDrainAmount());
}
}
public boolean isClosed() {
return closed;
}
public boolean isEOF() {
return eof;
}
protected abstract int readImpl(byte[] b, int off, int len) throws IOException;
public synchronized int read() throws IOException {
if (closed) {
throw new IOException("Stream is closed");
}
int c = readImpl(one, 0, 1);
if (c == -1 || c == 0) {
return c;
} else {
return one[0] & 0xFF;
}
}
public synchronized int read(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException("Stream is closed");
}
return readImpl(b, off, len);
}
@Override
public synchronized long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
if (server.isFinishing()) {
break;
}
nr = readImpl(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
eof = true;
break;
}
remaining -= nr;
}
return n - remaining;
}
/**
* read and discard up to l bytes or "eof" occurs,
* (whichever is first). Then return true if the stream
* is at eof (ie. all bytes were read) or false if not
* (still bytes to be read)
*/
public boolean drain(long l) throws IOException {
while (l > 0) {
long skip = skip(l);
if (skip <= 0) break; // might return 0 if isFinishing or EOF
l -= skip;
}
return eof;
}
}

View file

@ -0,0 +1,471 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.net.ProtocolException;
import java.nio.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Objects;
import com.sun.net.httpserver.*;
/**
*/
class Request {
static final int BUF_LEN = 2048;
static final byte CR = 13;
static final byte LF = 10;
static final byte FIRST_CHAR = 32;
private String startLine;
private SocketChannel chan;
private InputStream is;
private OutputStream os;
private final int maxReqHeaderSize;
private final boolean firstClearRequest;
Request(InputStream rawInputStream, OutputStream rawout, boolean firstClearRequest) throws IOException {
this.maxReqHeaderSize = ServerConfig.getMaxReqHeaderSize();
this.firstClearRequest = firstClearRequest;
is = rawInputStream;
os = rawout;
do {
startLine = readLine();
/* skip blank lines */
} while ("".equals(startLine));
}
char[] buf = new char[BUF_LEN];
int pos;
StringBuffer lineBuf;
public InputStream inputStream() {
return is;
}
public OutputStream outputStream() {
return os;
}
/**
* read a line from the stream returning as a String.
* Not used for reading headers.
*/
public String readLine() throws IOException {
boolean gotCR = false, gotLF = false;
pos = 0; lineBuf = new StringBuffer();
long lsize = 32;
// For the first request that comes on a clear connection
// we will check that the first non CR/LF char on the
// request line is eligible. This should be the first char
// of a method name, so it should be at least greater or equal
// to 32 (FIRST_CHAR) which is the space character.
// The main goal here is to fail fast if we receive 0x16 (22) which
// happens to be the first byte of a TLS handshake record.
// This is typically what would be received if a TLS client opened
// a TLS connection on a non-TLS server.
// If we receive 0x16 we should close the connection immediately as
// it indicates we're receiving a ClientHello on a clear
// connection, and we will never receive the expected CRLF that
// terminates the first request line.
// Though we could check only for 0x16, any characters < 32
// (excluding CRLF) is not expected at this position in a
// request line, so we can still fail here early if any of
// those are detected.
int offset = 0;
while (!gotLF) {
int c = is.read();
if (c == -1) {
return null;
}
if (gotCR) {
if (c == LF) {
gotLF = true;
} else {
gotCR = false;
consume(CR);
if (firstClearRequest && offset == 0) {
if (c < FIRST_CHAR) {
throw new ProtocolException("Unexpected start of request line");
}
offset++;
}
consume(c);
lsize = lsize + 2;
}
} else {
if (c == CR) {
gotCR = true;
} else {
if (firstClearRequest && offset == 0) {
if (c < FIRST_CHAR) {
throw new ProtocolException("Unexpected start of request line");
}
offset++;
}
consume(c);
lsize = lsize + 1;
}
}
if (maxReqHeaderSize > 0 && lsize > maxReqHeaderSize) {
throw new IOException("Maximum header (" +
"sun.net.httpserver.maxReqHeaderSize) exceeded, " +
ServerConfig.getMaxReqHeaderSize() + ".");
}
}
lineBuf.append(buf, 0, pos);
return new String(lineBuf);
}
private void consume(int c) throws IOException {
if (pos == BUF_LEN) {
lineBuf.append(buf);
pos = 0;
}
buf[pos++] = (char)c;
}
/**
* returns the request line (first line of a request)
*/
public String requestLine() {
return startLine;
}
Headers hdrs = null;
@SuppressWarnings("fallthrough")
Headers headers() throws IOException {
if (hdrs != null) {
return hdrs;
}
hdrs = new Headers();
char s[] = new char[10];
int len = 0;
int firstc = is.read();
// check for empty headers
if (firstc == CR || firstc == LF) {
int c = is.read();
if (c == CR || c == LF) {
return hdrs;
}
s[0] = (char)firstc;
len = 1;
firstc = c;
}
long hsize = startLine.length() + 32L;
while (firstc != LF && firstc != CR && firstc >= 0) {
int keyend = -1;
int c;
boolean inKey = firstc > ' ';
s[len++] = (char) firstc;
hsize = hsize + 1;
parseloop:{
// We start parsing for a new name value pair here.
// The max header size includes an overhead of 32 bytes per
// name value pair.
// See SETTINGS_MAX_HEADER_LIST_SIZE, RFC 9113, section 6.5.2.
long maxRemaining = maxReqHeaderSize > 0
? maxReqHeaderSize - hsize - 32
: Long.MAX_VALUE;
while ((c = is.read()) >= 0) {
switch (c) {
/*fallthrough*/
case ':':
if (inKey && len > 0)
keyend = len;
inKey = false;
break;
case '\t':
c = ' ';
case ' ':
inKey = false;
break;
case CR:
case LF:
firstc = is.read();
if (c == CR && firstc == LF) {
firstc = is.read();
if (firstc == CR)
firstc = is.read();
}
if (firstc == LF || firstc == CR || firstc > ' ')
break parseloop;
/* continuation */
c = ' ';
break;
}
if (len >= s.length) {
char ns[] = new char[s.length * 2];
System.arraycopy(s, 0, ns, 0, len);
s = ns;
}
s[len++] = (char) c;
if (maxReqHeaderSize > 0 && len > maxRemaining) {
throw new IOException("Maximum header (" +
"sun.net.httpserver.maxReqHeaderSize) exceeded, " +
ServerConfig.getMaxReqHeaderSize() + ".");
}
}
firstc = -1;
}
while (len > 0 && s[len - 1] <= ' ')
len--;
String k;
if (keyend <= 0) {
k = null;
keyend = 0;
} else {
k = String.copyValueOf(s, 0, keyend);
if (keyend < len && s[keyend] == ':')
keyend++;
while (keyend < len && s[keyend] <= ' ')
keyend++;
}
String v;
if (keyend >= len)
v = new String();
else
v = String.copyValueOf(s, keyend, len - keyend);
if (hdrs.size() >= ServerConfig.getMaxReqHeaders()) {
throw new IOException("Maximum number of request headers (" +
"sun.net.httpserver.maxReqHeaders) exceeded, " +
ServerConfig.getMaxReqHeaders() + ".");
}
hsize = hsize + len + 32;
if (maxReqHeaderSize > 0 && hsize > maxReqHeaderSize) {
throw new IOException("Maximum header (" +
"sun.net.httpserver.maxReqHeaderSize) exceeded, " +
ServerConfig.getMaxReqHeaderSize() + ".");
}
if (k == null) { // Headers disallows null keys, use empty string
k = ""; // instead to represent invalid key
}
hdrs.add(k, v);
len = 0;
}
return hdrs;
}
/**
* Implements blocking reading semantics on top of a non-blocking channel
*/
static class ReadStream extends InputStream {
SocketChannel channel;
ByteBuffer chanbuf;
byte[] one;
private boolean closed = false, eof = false;
ByteBuffer markBuf; /* reads may be satisfied from this buffer */
boolean marked;
boolean reset;
int readlimit;
static long readTimeout;
ServerImpl server;
static final int BUFSIZE = 8 * 1024;
public ReadStream(ServerImpl server, SocketChannel chan) throws IOException {
this.channel = chan;
this.server = server;
chanbuf = ByteBuffer.allocate(BUFSIZE);
chanbuf.clear();
one = new byte[1];
closed = marked = reset = false;
}
public synchronized int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public synchronized int read() throws IOException {
int result = read(one, 0, 1);
if (result == 1) {
return one[0] & 0xFF;
} else {
return -1;
}
}
public synchronized int read(byte[] b, int off, int srclen) throws IOException {
int canreturn, willreturn;
if (closed)
throw new IOException("Stream closed");
if (eof) {
return -1;
}
assert channel.isBlocking();
Objects.checkFromIndexSize(off, srclen, b.length);
if (reset) { /* satisfy from markBuf */
canreturn = markBuf.remaining();
willreturn = canreturn>srclen ? srclen : canreturn;
markBuf.get(b, off, willreturn);
if (canreturn == willreturn) {
reset = false;
}
} else { /* satisfy from channel */
chanbuf.clear();
if (srclen < BUFSIZE) {
chanbuf.limit(srclen);
}
do {
willreturn = channel.read(chanbuf);
} while (willreturn == 0);
if (willreturn == -1) {
eof = true;
return -1;
}
chanbuf.flip();
chanbuf.get(b, off, willreturn);
if (marked) { /* copy into markBuf */
try {
markBuf.put(b, off, willreturn);
} catch (BufferOverflowException e) {
marked = false;
}
}
}
return willreturn;
}
public boolean markSupported() {
return true;
}
/* Does not query the OS socket */
public synchronized int available() throws IOException {
if (closed)
throw new IOException("Stream is closed");
if (eof)
return -1;
if (reset)
return markBuf.remaining();
return chanbuf.remaining();
}
public void close() throws IOException {
if (closed) {
return;
}
channel.close();
closed = true;
}
public synchronized void mark(int readlimit) {
if (closed)
return;
this.readlimit = readlimit;
markBuf = ByteBuffer.allocate(readlimit);
marked = true;
reset = false;
}
public synchronized void reset() throws IOException {
if (closed )
return;
if (!marked)
throw new IOException("Stream not marked");
marked = false;
reset = true;
markBuf.flip();
}
}
static class WriteStream extends java.io.OutputStream {
SocketChannel channel;
ByteBuffer buf;
SelectionKey key;
boolean closed;
byte[] one;
ServerImpl server;
public WriteStream(ServerImpl server, SocketChannel channel) throws IOException {
this.channel = channel;
this.server = server;
assert channel.isBlocking();
closed = false;
one = new byte [1];
buf = ByteBuffer.allocate(4096);
}
public synchronized void write(int b) throws IOException {
one[0] = (byte)b;
write (one, 0, 1);
}
public synchronized void write(byte[] b) throws IOException {
write (b, 0, b.length);
}
public synchronized void write(byte[] b, int off, int len) throws IOException {
int l = len;
if (closed)
throw new IOException("stream is closed");
int cap = buf.capacity();
if (cap < len) {
int diff = len - cap;
buf = ByteBuffer.allocate(2*(cap+diff));
}
buf.clear();
buf.put(b, off, len);
buf.flip();
int n;
while ((n = channel.write(buf)) < l) {
l -= n;
if (l == 0)
return;
}
}
public void close() throws IOException {
if (closed)
return;
//server.logStackTrace("Request.OS.close: isOpen="+channel.isOpen());
channel.close();
closed = true;
}
}
}

View file

@ -0,0 +1,671 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.net.*;
import java.nio.*;
import java.io.*;
import java.nio.channels.*;
import java.util.concurrent.locks.*;
import javax.net.ssl.*;
import javax.net.ssl.SSLEngineResult.*;
import com.sun.net.httpserver.*;
/**
* given a non-blocking SocketChannel, it produces
* (blocking) streams which encrypt/decrypt the SSL content
* and handle the SSL handshaking automatically.
*/
class SSLStreams {
SSLContext sslctx;
SocketChannel chan;
ServerImpl server;
SSLEngine engine;
EngineWrapper wrapper;
OutputStream os;
InputStream is;
/* held by thread doing the hand-shake on this connection */
Lock handshaking = new ReentrantLock();
SSLStreams(ServerImpl server, SSLContext sslctx, SocketChannel chan) throws IOException {
this.server = server;
this.sslctx= sslctx;
this.chan= chan;
InetSocketAddress addr =
(InetSocketAddress)chan.socket().getRemoteSocketAddress();
engine = sslctx.createSSLEngine(addr.getHostName(), addr.getPort());
engine.setUseClientMode(false);
HttpsConfigurator cfg = server.getHttpsConfigurator();
configureEngine(cfg, addr);
wrapper = new EngineWrapper(chan, engine);
}
@SuppressWarnings("deprecation")
private void configureEngine(HttpsConfigurator cfg, InetSocketAddress addr) {
if (cfg != null) {
Parameters params = new Parameters(cfg, addr);
//BEGIN_TIGER_EXCLUDE
cfg.configure(params);
SSLParameters sslParams = params.getSSLParameters();
if (sslParams != null) {
engine.setSSLParameters(sslParams);
} else
//END_TIGER_EXCLUDE
{
/* tiger compatibility */
if (params.getCipherSuites() != null) {
try {
engine.setEnabledCipherSuites(
params.getCipherSuites()
);
} catch (IllegalArgumentException e) { /* LOG */}
}
if (params.getNeedClientAuth()) {
engine.setNeedClientAuth(true);
} else if (params.getWantClientAuth()) {
engine.setWantClientAuth(true);
}
if (params.getProtocols() != null) {
try {
engine.setEnabledProtocols(
params.getProtocols()
);
} catch (IllegalArgumentException e) { /* LOG */}
}
}
}
}
class Parameters extends HttpsParameters {
InetSocketAddress addr;
HttpsConfigurator cfg;
Parameters(HttpsConfigurator cfg, InetSocketAddress addr) {
this.addr = addr;
this.cfg = cfg;
}
public InetSocketAddress getClientAddress() {
return addr;
}
public HttpsConfigurator getHttpsConfigurator() {
return cfg;
}
//BEGIN_TIGER_EXCLUDE
SSLParameters params;
public void setSSLParameters(SSLParameters p) {
params = p;
}
SSLParameters getSSLParameters() {
return params;
}
//END_TIGER_EXCLUDE
}
/**
* cleanup resources allocated inside this object
*/
void close() throws IOException {
wrapper.close();
}
/**
* return the SSL InputStream
*/
InputStream getInputStream() throws IOException {
if (is == null) {
is = new InputStream();
}
return is;
}
/**
* return the SSL OutputStream
*/
OutputStream getOutputStream() throws IOException {
if (os == null) {
os = new OutputStream();
}
return os;
}
SSLEngine getSSLEngine() {
return engine;
}
/**
* request the engine to repeat the handshake on this session
* the handshake must be driven by reads/writes on the streams
* Normally, not necessary to call this.
*/
void beginHandshake() throws SSLException {
engine.beginHandshake();
}
class WrapperResult {
SSLEngineResult result;
/* if passed in buffer was not big enough then the
* a reallocated buffer is returned here
*/
ByteBuffer buf;
}
int app_buf_size;
int packet_buf_size;
enum BufType {
PACKET, APPLICATION
};
private ByteBuffer allocate(BufType type) {
return allocate(type, -1);
}
private ByteBuffer allocate(BufType type, int len) {
assert engine != null;
synchronized (this) {
int size;
if (type == BufType.PACKET) {
if (packet_buf_size == 0) {
SSLSession sess = engine.getSession();
packet_buf_size = sess.getPacketBufferSize();
}
if (len > packet_buf_size) {
packet_buf_size = len;
}
size = packet_buf_size;
} else {
if (app_buf_size == 0) {
SSLSession sess = engine.getSession();
app_buf_size = sess.getApplicationBufferSize();
}
if (len > app_buf_size) {
app_buf_size = len;
}
size = app_buf_size;
}
return ByteBuffer.allocate(size);
}
}
/* reallocates the buffer by :-
* 1. creating a new buffer double the size of the old one
* 2. putting the contents of the old buffer into the new one
* 3. set xx_buf_size to the new size if it was smaller than new size
*
* flip is set to true if the old buffer needs to be flipped
* before it is copied.
*/
private ByteBuffer realloc(ByteBuffer b, boolean flip, BufType type) {
synchronized (this) {
int nsize = 2 * b.capacity();
ByteBuffer n = allocate(type, nsize);
if (flip) {
b.flip();
}
n.put(b);
b = n;
}
return b;
}
/**
* This is a thin wrapper over SSLEngine and the SocketChannel,
* which guarantees the ordering of wraps/unwraps with respect to the underlying
* channel read/writes. It handles the UNDER/OVERFLOW status codes
* It does not handle the handshaking status codes, or the CLOSED status code
* though once the engine is closed, any attempt to read/write to it
* will get an exception. The overall result is returned.
* It functions synchronously/blocking
*/
class EngineWrapper {
SocketChannel chan;
SSLEngine engine;
Object wrapLock, unwrapLock;
ByteBuffer unwrap_src, wrap_dst;
boolean closed = false;
int u_remaining; // the number of bytes left in unwrap_src after an unwrap()
EngineWrapper(SocketChannel chan, SSLEngine engine) throws IOException {
this.chan = chan;
this.engine = engine;
wrapLock = new Object();
unwrapLock = new Object();
unwrap_src = allocate(BufType.PACKET);
wrap_dst = allocate(BufType.PACKET);
}
void close() throws IOException {
}
/* try to wrap and send the data in src. Handles OVERFLOW.
* Might block if there is an outbound blockage or if another
* thread is calling wrap(). Also, might not send any data
* if an unwrap is needed.
*/
WrapperResult wrapAndSend(ByteBuffer src) throws IOException {
return wrapAndSendX(src, false);
}
WrapperResult wrapAndSendX(ByteBuffer src, boolean ignoreClose) throws IOException {
if (closed && !ignoreClose) {
throw new IOException("Engine is closed");
}
Status status;
WrapperResult r = new WrapperResult();
synchronized (wrapLock) {
wrap_dst.clear();
do {
r.result = engine.wrap(src, wrap_dst);
status = r.result.getStatus();
if (status == Status.BUFFER_OVERFLOW) {
wrap_dst = realloc(wrap_dst, true, BufType.PACKET);
}
} while (status == Status.BUFFER_OVERFLOW);
if (status == Status.CLOSED && !ignoreClose) {
closed = true;
}
if (r.result.bytesProduced() > 0) {
wrap_dst.flip();
int l = wrap_dst.remaining();
assert l == r.result.bytesProduced();
while (l>0) {
l -= chan.write(wrap_dst);
}
}
}
return r;
}
/* block until a complete message is available and return it
* in dst, together with the Result. dst may have been re-allocated
* so caller should check the returned value in Result
* If handshaking is in progress then, possibly no data is returned
*/
WrapperResult recvAndUnwrap(ByteBuffer dst) throws IOException {
Status status = Status.OK;
WrapperResult r = new WrapperResult();
r.buf = dst;
if (closed) {
throw new IOException("Engine is closed");
}
boolean needData;
if (u_remaining > 0) {
unwrap_src.compact();
unwrap_src.flip();
needData = false;
} else {
unwrap_src.clear();
needData = true;
}
synchronized (unwrapLock) {
int x;
do {
if (needData) {
do {
x = chan.read(unwrap_src);
} while (x == 0);
if (x == -1) {
throw new IOException("connection closed for reading");
}
unwrap_src.flip();
}
r.result = engine.unwrap(unwrap_src, r.buf);
status = r.result.getStatus();
if (status == Status.BUFFER_UNDERFLOW) {
if (unwrap_src.limit() == unwrap_src.capacity()) {
/* buffer not big enough */
unwrap_src = realloc(
unwrap_src, false, BufType.PACKET
);
} else {
/* Buffer not full, just need to read more
* data off the channel. Reset pointers
* for reading off SocketChannel
*/
unwrap_src.position(unwrap_src.limit());
unwrap_src.limit(unwrap_src.capacity());
}
needData = true;
} else if (status == Status.BUFFER_OVERFLOW) {
r.buf = realloc(r.buf, true, BufType.APPLICATION);
needData = false;
} else if (status == Status.CLOSED) {
closed = true;
r.buf.flip();
return r;
}
} while (status != Status.OK);
}
u_remaining = unwrap_src.remaining();
return r;
}
}
/**
* send the data in the given ByteBuffer. If a handshake is needed
* then this is handled within this method. When this call returns,
* all of the given user data has been sent and any handshake has been
* completed. Caller should check if engine has been closed.
*/
public WrapperResult sendData(ByteBuffer src) throws IOException {
WrapperResult r=null;
while (src.remaining() > 0) {
r = wrapper.wrapAndSend(src);
Status status = r.result.getStatus();
if (status == Status.CLOSED) {
doClosure();
return r;
}
HandshakeStatus hs_status = r.result.getHandshakeStatus();
if (hs_status != HandshakeStatus.FINISHED &&
hs_status != HandshakeStatus.NOT_HANDSHAKING)
{
doHandshake(hs_status);
}
}
return r;
}
/**
* read data thru the engine into the given ByteBuffer. If the
* given buffer was not large enough, a new one is allocated
* and returned. This call handles handshaking automatically.
* Caller should check if engine has been closed.
*/
public WrapperResult recvData(ByteBuffer dst) throws IOException {
/* we wait until some user data arrives */
WrapperResult r = null;
assert dst.position() == 0;
while (dst.position() == 0) {
r = wrapper.recvAndUnwrap(dst);
dst = (r.buf != dst) ? r.buf: dst;
Status status = r.result.getStatus();
if (status == Status.CLOSED) {
doClosure();
return r;
}
HandshakeStatus hs_status = r.result.getHandshakeStatus();
if (hs_status != HandshakeStatus.FINISHED &&
hs_status != HandshakeStatus.NOT_HANDSHAKING)
{
doHandshake(hs_status);
}
}
dst.flip();
return r;
}
/* we've received a close notify. Need to call wrap to send
* the response
*/
void doClosure() throws IOException {
try {
handshaking.lock();
ByteBuffer tmp = allocate(BufType.APPLICATION);
WrapperResult r;
Status st;
HandshakeStatus hs;
do {
tmp.clear();
tmp.flip();
r = wrapper.wrapAndSendX(tmp, true);
hs = r.result.getHandshakeStatus();
st = r.result.getStatus();
} while (st != Status.CLOSED &&
!(st == Status.OK && hs == HandshakeStatus.NOT_HANDSHAKING));
} finally {
handshaking.unlock();
}
}
/* do the (complete) handshake after acquiring the handshake lock.
* If two threads call this at the same time, then we depend
* on the wrapper methods being idempotent. eg. if wrapAndSend()
* is called with no data to send then there must be no problem
*/
@SuppressWarnings("fallthrough")
void doHandshake(HandshakeStatus hs_status) throws IOException {
try {
handshaking.lock();
ByteBuffer tmp = allocate(BufType.APPLICATION);
while (hs_status != HandshakeStatus.FINISHED &&
hs_status != HandshakeStatus.NOT_HANDSHAKING)
{
WrapperResult r = null;
switch (hs_status) {
case NEED_TASK:
Runnable task;
while ((task = engine.getDelegatedTask()) != null) {
/* run in current thread, because we are already
* running an external Executor
*/
task.run();
}
/* fall thru - call wrap again */
case NEED_WRAP:
tmp.clear();
tmp.flip();
r = wrapper.wrapAndSend(tmp);
break;
case NEED_UNWRAP:
tmp.clear();
r = wrapper.recvAndUnwrap(tmp);
if (r.buf != tmp) {
tmp = r.buf;
}
assert tmp.position() == 0;
break;
}
hs_status = r.result.getHandshakeStatus();
}
} finally {
handshaking.unlock();
}
}
/**
* represents an SSL input stream. Multiple https requests can
* be sent over one stream. closing this stream causes an SSL close
* input.
*/
class InputStream extends java.io.InputStream {
ByteBuffer bbuf;
boolean closed = false;
/* this stream eof */
boolean eof = false;
boolean needData = true;
InputStream() {
bbuf = allocate(BufType.APPLICATION);
}
public int read(byte[] buf, int off, int len) throws IOException {
if (closed) {
throw new IOException("SSL stream is closed");
}
if (eof) {
return -1;
}
int available = 0;
if (!needData) {
available = bbuf.remaining();
needData = (available == 0);
}
if (needData) {
bbuf.clear();
WrapperResult r = recvData(bbuf);
bbuf = r.buf == bbuf ? bbuf: r.buf;
if ((available = bbuf.remaining()) == 0) {
eof = true;
return -1;
} else {
needData = false;
}
}
/* copy as much as possible from buf into users buf */
if (len > available) {
len = available;
}
bbuf.get(buf, off, len);
return len;
}
public int available() throws IOException {
return bbuf.remaining();
}
public boolean markSupported() {
return false; /* not possible with SSLEngine */
}
public void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
public long skip(long s) throws IOException {
int n = (int)s;
if (closed) {
throw new IOException("SSL stream is closed");
}
if (eof) {
return 0;
}
int ret = n;
while (n > 0) {
if (bbuf.remaining() >= n) {
bbuf.position(bbuf.position()+n);
return ret;
} else {
n -= bbuf.remaining();
bbuf.clear();
WrapperResult r = recvData(bbuf);
bbuf = r.buf == bbuf ? bbuf: r.buf;
}
}
return ret; /* not reached */
}
/**
* close the SSL connection. All data must have been consumed
* before this is called. Otherwise an exception will be thrown.
* [Note. May need to revisit this. not quite the normal close() semantics
*/
public void close() throws IOException {
eof = true;
engine.closeInbound();
}
public int read(byte[] buf) throws IOException {
return read(buf, 0, buf.length);
}
byte single[] = new byte [1];
public int read() throws IOException {
if (eof) {
return -1;
}
int n = read(single, 0, 1);
if (n <= 0) {
return -1;
} else {
return single[0] & 0xFF;
}
}
}
/**
* represents an SSL output stream. plain text data written to this stream
* is encrypted by the stream. Multiple HTTPS responses can be sent on
* one stream. closing this stream initiates an SSL closure
*/
class OutputStream extends java.io.OutputStream {
ByteBuffer buf;
boolean closed = false;
byte single[] = new byte[1];
OutputStream() {
buf = allocate(BufType.APPLICATION);
}
public void write(int b) throws IOException {
single[0] = (byte)b;
write(single, 0, 1);
}
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
public void write(byte b[], int off, int len) throws IOException {
if (closed) {
throw new IOException("output stream is closed");
}
while (len > 0) {
int l = len > buf.capacity() ? buf.capacity() : len;
buf.clear();
buf.put(b, off, l);
len -= l;
off += l;
buf.flip();
WrapperResult r = sendData(buf);
if (r.result.getStatus() == Status.CLOSED) {
closed = true;
if (len > 0) {
throw new IOException("output stream is closed");
}
}
}
}
public void flush() throws IOException {
/* no-op */
}
public void close() throws IOException {
WrapperResult r = null;
engine.closeOutbound();
closed = true;
HandshakeStatus stat = HandshakeStatus.NEED_WRAP;
buf.clear();
while (stat == HandshakeStatus.NEED_WRAP) {
r = wrapper.wrapAndSend(buf);
stat = r.result.getHandshakeStatus();
}
assert r.result.getStatus() == Status.CLOSED
: "status is: " + r.result.getStatus()
+ ", handshakeStatus is: " + stat;
}
}
}

View file

@ -0,0 +1,222 @@
/*
* Copyright (c) 2005, 2024, 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.
*/
package sun.net.httpserver;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
/**
* Parameters that users will not likely need to set
* but are useful for debugging
*/
class ServerConfig {
private static final int DEFAULT_IDLE_TIMER_SCHEDULE_MILLIS = 10000 ; // 10 sec.
private static final long DEFAULT_IDLE_INTERVAL_IN_SECS = 30;
private static final int DEFAULT_MAX_CONNECTIONS = -1 ; // no limit on maximum connections
private static final int DEFAULT_MAX_IDLE_CONNECTIONS = 200 ;
private static final long DEFAULT_MAX_REQ_TIME = -1; // default: forever
private static final long DEFAULT_MAX_RSP_TIME = -1; // default: forever
// default timer schedule, in milli seconds, for the timer task that's responsible for
// timing out request/response if max request/response time is configured
private static final long DEFAULT_REQ_RSP_TIMER_TASK_SCHEDULE_MILLIS = 1000;
private static final int DEFAULT_MAX_REQ_HEADERS = 200;
private static final int DEFAULT_MAX_REQ_HEADER_SIZE = 380 * 1024;
private static final long DEFAULT_DRAIN_AMOUNT = 64 * 1024;
private static final long idleTimerScheduleMillis;
private static final long idleIntervalMillis;
// The maximum number of bytes to drain from an inputstream
private static final long drainAmount;
// the maximum number of connections that the server will allow to be open
// after which it will no longer "accept()" any new connections, till the
// current connection count goes down due to completion of processing the requests
private static final int maxConnections;
private static final int maxIdleConnections;
// The maximum number of request headers allowable
private static final int maxReqHeaders;
// a maximum value for the header list size. This is the
// names size + values size + 32 bytes per field line
private static final int maxReqHeadersSize;
// max time a request or response is allowed to take
private static final long maxReqTime;
private static final long maxRspTime;
private static final long reqRspTimerScheduleMillis;
private static final boolean debug;
// the value of the TCP_NODELAY socket-level option
private static final boolean noDelay;
static {
long providedIdleIntervalMillis =
Long.getLong("sun.net.httpserver.idleInterval", DEFAULT_IDLE_INTERVAL_IN_SECS) * 1000;
idleIntervalMillis = providedIdleIntervalMillis > 0
? providedIdleIntervalMillis
: Math.multiplyExact(DEFAULT_IDLE_INTERVAL_IN_SECS, 1000);
long providedIdleTimerScheduleMillis =
Long.getLong("sun.net.httpserver.clockTick", DEFAULT_IDLE_TIMER_SCHEDULE_MILLIS);
// Ignore zero or negative value and use the default schedule
idleTimerScheduleMillis = providedIdleTimerScheduleMillis > 0
? providedIdleTimerScheduleMillis
: DEFAULT_IDLE_TIMER_SCHEDULE_MILLIS;
maxConnections = Integer.getInteger("jdk.httpserver.maxConnections", DEFAULT_MAX_CONNECTIONS);
maxIdleConnections = Integer.getInteger("sun.net.httpserver.maxIdleConnections", DEFAULT_MAX_IDLE_CONNECTIONS);
drainAmount = Long.getLong("sun.net.httpserver.drainAmount", DEFAULT_DRAIN_AMOUNT);
int providedMaxReqHeaders = Integer.getInteger("sun.net.httpserver.maxReqHeaders", DEFAULT_MAX_REQ_HEADERS);
maxReqHeaders = providedMaxReqHeaders > 0 ? providedMaxReqHeaders : DEFAULT_MAX_REQ_HEADERS;
// A value <= 0 means unlimited
maxReqHeadersSize = Math.max(
Integer.getInteger("sun.net.httpserver.maxReqHeaderSize", DEFAULT_MAX_REQ_HEADER_SIZE),
0);
maxReqTime = Long.getLong("sun.net.httpserver.maxReqTime", DEFAULT_MAX_REQ_TIME);
maxRspTime = Long.getLong("sun.net.httpserver.maxRspTime", DEFAULT_MAX_RSP_TIME);
long providedReqRspTimerScheduleMillis = Long.getLong(
"sun.net.httpserver.timerMillis",
DEFAULT_REQ_RSP_TIMER_TASK_SCHEDULE_MILLIS);
// Ignore any negative or zero value for this configuration and reset to default schedule
reqRspTimerScheduleMillis = providedReqRspTimerScheduleMillis > 0
? providedReqRspTimerScheduleMillis
: DEFAULT_REQ_RSP_TIMER_TASK_SCHEDULE_MILLIS;
debug = Boolean.getBoolean("sun.net.httpserver.debug");
noDelay = Boolean.getBoolean("sun.net.httpserver.nodelay");
}
static void checkLegacyProperties(final Logger logger) {
// legacy properties that are no longer used
// print a warning to logger if they are set.
if (System.getProperty("sun.net.httpserver.readTimeout") != null) {
logger.log(
Level.WARNING,
"sun.net.httpserver.readTimeout property is no longer used. " +
"Use sun.net.httpserver.maxReqTime instead.");
}
if (System.getProperty("sun.net.httpserver.writeTimeout") != null) {
logger.log(
Level.WARNING,
"sun.net.httpserver.writeTimeout property is no longer used. " +
"Use sun.net.httpserver.maxRspTime instead.");
}
if (System.getProperty("sun.net.httpserver.selCacheTimeout") != null) {
logger.log(Level.WARNING, "sun.net.httpserver.selCacheTimeout property is no longer used.");
}
}
static boolean debugEnabled() {
return debug;
}
/**
* {@return Returns the maximum duration, in milli seconds, a connection can be idle}
*/
static long getIdleIntervalMillis() {
return idleIntervalMillis;
}
/**
* {@return Returns the schedule, in milli seconds, for the timer task that is responsible
* for managing the idle connections}
*/
static long getIdleTimerScheduleMillis() {
return idleTimerScheduleMillis;
}
/**
* @return Returns the maximum number of connections that can be open at any given time.
* This method can return a value of 0 or negative to represent that the limit hasn't
* been configured.
*/
static int getMaxConnections() {
return maxConnections;
}
/**
* @return Returns the maximum number of connections that can be idle. This method
* can return a value of 0 or negative.
*/
static int getMaxIdleConnections() {
return maxIdleConnections;
}
static long getDrainAmount() {
return drainAmount;
}
static int getMaxReqHeaders() {
return maxReqHeaders;
}
static int getMaxReqHeaderSize() {
return maxReqHeadersSize;
}
/**
* @return Returns the maximum amount of time the server will wait for the request to be read
* completely. This method can return a value of 0 or negative to imply no maximum limit has
* been configured.
*/
static long getMaxReqTime() {
return maxReqTime;
}
/**
* @return Returns the maximum amount of time the server will wait for the response to be generated
* for a request that is being processed. This method can return a value of 0 or negative to
* imply no maximum limit has been configured.
*/
static long getMaxRspTime() {
return maxRspTime;
}
/**
* {@return Returns the timer schedule of the task that's responsible for timing out
* request/response that have been running longer than any configured timeout}
*/
static long getReqRspTimerScheduleMillis() {
return reqRspTimerScheduleMillis;
}
static boolean noDelay() {
return noDelay;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2005, 2008, 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.
*/
package sun.net.httpserver;
import java.io.*;
class StreamClosedException extends IOException {
private static final long serialVersionUID = -4485921499356327937L;
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2007, 2026, 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.
*/
package sun.net.httpserver;
import java.io.*;
import java.util.Objects;
/**
* a class which allows the caller to write an indefinite
* number of bytes to an underlying stream , but without using
* chunked encoding. Used for http/1.0 clients only
* The underlying connection needs to be closed afterwards.
*/
class UndefLengthOutputStream extends FilterOutputStream
{
private boolean closed = false;
ExchangeImpl t;
UndefLengthOutputStream(ExchangeImpl t, OutputStream src) {
super(src);
this.t = t;
}
public void write(int b) throws IOException {
if (closed) {
throw new IOException("stream closed");
}
out.write(b);
}
public void write(byte[] b, int off, int len) throws IOException {
Objects.checkFromIndexSize(off, len, b.length);
if (len == 0) {
return;
}
if (closed) {
throw new IOException("stream closed");
}
out.write(b, off, len);
}
public void close() throws IOException {
if (closed) {
return;
}
closed = true;
flush();
LeftOverInputStream is = t.getOriginalInputStream();
if (!is.isClosed()) {
try {
is.close();
} catch (IOException e) {}
}
t.postExchangeFinished(true);
}
// flush is a pass-through
}

View file

@ -0,0 +1,127 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver;
import java.util.*;
import java.util.function.BiFunction;
import com.sun.net.httpserver.*;
public class UnmodifiableHeaders extends Headers {
private final Headers headers; // modifiable, but no reference to it escapes
private final Map<String, List<String>> unmodifiableView; // unmodifiable
public UnmodifiableHeaders(Headers headers) {
var h = headers;
var unmodHeaders = new Headers();
h.forEach((k, v) -> unmodHeaders.put(k, Collections.unmodifiableList(v)));
this.unmodifiableView = Collections.unmodifiableMap(unmodHeaders);
this.headers = unmodHeaders;
}
@Override
public int size() {return headers.size();}
@Override
public boolean isEmpty() {return headers.isEmpty();}
@Override
public boolean containsKey(Object key) { return headers.containsKey(key); }
@Override
public boolean containsValue(Object value) { return headers.containsValue(value); }
@Override
public List<String> get(Object key) { return headers.get(key); }
@Override
public String getFirst(String key) { return headers.getFirst(key); }
@Override
public List<String> put(String key, List<String> value) {
throw new UnsupportedOperationException("unsupported operation");
}
@Override
public void add(String key, String value) {
throw new UnsupportedOperationException("unsupported operation");
}
@Override
public void set(String key, String value) {
throw new UnsupportedOperationException("unsupported operation");
}
@Override
public List<String> remove(Object key) {
throw new UnsupportedOperationException("unsupported operation");
}
@Override
public void putAll(Map<? extends String,? extends List<String>> t) {
throw new UnsupportedOperationException("unsupported operation");
}
@Override
public void clear() {
throw new UnsupportedOperationException("unsupported operation");
}
@Override
public Set<String> keySet() { return unmodifiableView.keySet(); }
@Override
public Collection<List<String>> values() { return unmodifiableView.values(); }
@Override
public Set<Map.Entry<String, List<String>>> entrySet() { return unmodifiableView.entrySet(); }
@Override
public List<String> replace(String key, List<String> value) {
throw new UnsupportedOperationException("unsupported operation");
}
@Override
public boolean replace(String key, List<String> oldValue, List<String> newValue) {
throw new UnsupportedOperationException("unsupported operation");
}
@Override
public void replaceAll(BiFunction<? super String, ? super List<String>, ? extends List<String>> function) {
throw new UnsupportedOperationException("unsupported operation");
}
@Override
public boolean equals(Object o) { return headers.equals(o); }
@Override
public int hashCode() { return headers.hashCode(); }
@Override
public String toString() {
return headers.toString();
}
}

View file

@ -0,0 +1,124 @@
/*
* 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.
*/
package sun.net.httpserver;
/**
* Provides utility methods for checking header field names and quoted strings.
*/
public class Utils {
// ABNF primitives defined in RFC 7230
private static final boolean[] TCHAR = new boolean[256];
private static final boolean[] QDTEXT = new boolean[256];
private static final boolean[] QUOTED_PAIR = new boolean[256];
static {
char[] allowedTokenChars =
("!#$%&'*+-.^_`|~0123456789" +
"abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
for (char c : allowedTokenChars) {
TCHAR[c] = true;
}
for (char c = 0x20; c <= 0xFF; c++) {
QDTEXT[c] = true;
}
QDTEXT[0x22] = false; // (") illegal
QDTEXT[0x5c] = false; // (\) illegal
QDTEXT[0x7F] = false; // (DEL) illegal
for (char c = 0x20; c <= 0xFF; c++) {
QUOTED_PAIR[c] = true;
}
QUOTED_PAIR[0x09] = true; // (\t) legal
QUOTED_PAIR[0x7F] = false; // (DEL) illegal
}
/*
* Validates an RFC 7230 field-name.
*/
public static boolean isValidName(String token) {
for (int i = 0; i < token.length(); i++) {
char c = token.charAt(i);
if (c > 255 || !TCHAR[c]) {
return false;
}
}
return !token.isEmpty();
}
/*
* Validates an RFC 7230 quoted-string.
*/
public static boolean isQuotedStringContent(String token) {
for (int i = 0; i < token.length(); i++) {
char c = token.charAt(i);
if (c > 255) {
return false;
} else if (c == 0x5c) { // check if valid quoted-pair
if (i == token.length() - 1 || !QUOTED_PAIR[token.charAt(i++)]) {
return false;
}
} else if (!QDTEXT[c]) {
return false; // illegal char
}
}
return true;
}
/* Throw IAE if illegal character found. isValue is true if String is
* a value. Otherwise it is header name
*/
public static void checkHeader(String str, boolean isValue) {
int len = str.length();
for (int i=0; i<len; i++) {
char c = str.charAt(i);
if (c == '\r') {
if (!isValue) {
throw new IllegalArgumentException("Illegal CR found in header");
}
// is allowed if it is followed by \n and a whitespace char
if (i >= len - 2) {
throw new IllegalArgumentException("Illegal CR found in header");
}
char c1 = str.charAt(i+1);
char c2 = str.charAt(i+2);
if (c1 != '\n') {
throw new IllegalArgumentException("Illegal char found after CR in header");
}
if (c2 != ' ' && c2 != '\t') {
throw new IllegalArgumentException("No whitespace found after CRLF in header");
}
i+=2;
} else if (c == '\n') {
throw new IllegalArgumentException("Illegal LF found in header");
} else if (c > 255) {
throw new IllegalArgumentException("Illegal character found in header");
}
}
}
}

View file

@ -0,0 +1,416 @@
/*
* Copyright (c) 2005, 2025, 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.
*/
package sun.net.httpserver.simpleserver;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.lang.System.Logger;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpHandlers;
import static java.nio.charset.StandardCharsets.UTF_8;
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
/**
* A basic HTTP file server handler for static content.
*
* <p> Must be given an absolute pathname to the directory to be served.
* Supports only HEAD and GET requests. Directory listings and files can be
* served, content types are supported on a best-guess basis.
*/
public final class FileServerHandler implements HttpHandler {
private static final List<String> SUPPORTED_METHODS = List.of("HEAD", "GET");
private static final List<String> UNSUPPORTED_METHODS =
List.of("CONNECT", "DELETE", "OPTIONS", "PATCH", "POST", "PUT", "TRACE");
private static final String FAVICON_RESOURCE_PATH =
"/sun/net/httpserver/simpleserver/resources/favicon.ico";
private static final String FAVICON_LAST_MODIFIED = "Mon, 23 May 1995 11:11:11 GMT";
private final Path root;
private final UnaryOperator<String> mimeTable;
private final Logger logger;
private FileServerHandler(Path root, UnaryOperator<String> mimeTable) {
root = root.normalize();
if (!Files.exists(root))
throw new IllegalArgumentException("Path does not exist: " + root);
if (!root.isAbsolute())
throw new IllegalArgumentException("Path is not absolute: " + root);
if (!Files.isDirectory(root))
throw new IllegalArgumentException("Path is not a directory: " + root);
if (!Files.isReadable(root))
throw new IllegalArgumentException("Path is not readable: " + root);
this.root = root;
this.mimeTable = mimeTable;
this.logger = System.getLogger("com.sun.net.httpserver");
}
private static final HttpHandler NOT_IMPLEMENTED_HANDLER =
HttpHandlers.of(501, Headers.of(), "");
private static final HttpHandler METHOD_NOT_ALLOWED_HANDLER =
HttpHandlers.of(405, Headers.of("Allow", "HEAD, GET"), "");
public static HttpHandler create(Path root, UnaryOperator<String> mimeTable) {
var fallbackHandler = HttpHandlers.handleOrElse(
r -> UNSUPPORTED_METHODS.contains(r.getRequestMethod()),
METHOD_NOT_ALLOWED_HANDLER,
NOT_IMPLEMENTED_HANDLER);
return HttpHandlers.handleOrElse(
r -> SUPPORTED_METHODS.contains(r.getRequestMethod()),
new FileServerHandler(root, mimeTable), fallbackHandler);
}
private void handleHEAD(HttpExchange exchange, Path path) throws IOException {
handleSupportedMethod(exchange, path, false);
}
private void handleGET(HttpExchange exchange, Path path) throws IOException {
handleSupportedMethod(exchange, path, true);
}
private void handleSupportedMethod(HttpExchange exchange, Path path, boolean writeBody)
throws IOException {
if (Files.isDirectory(path)) {
if (missingSlash(exchange)) {
handleMovedPermanently(exchange);
return;
}
if (indexFile(path) != null) {
serveFile(exchange, indexFile(path), writeBody);
} else {
listFiles(exchange, path, writeBody);
}
} else {
serveFile(exchange, path, writeBody);
}
}
private void handleMovedPermanently(HttpExchange exchange) throws IOException {
exchange.getResponseHeaders().set("Location", getRedirectURI(exchange.getRequestURI()));
exchange.sendResponseHeaders(301, RSPBODY_EMPTY);
}
private void handleForbidden(HttpExchange exchange) throws IOException {
exchange.sendResponseHeaders(403, RSPBODY_EMPTY);
}
private void handleNotFound(HttpExchange exchange) throws IOException {
String fileNotFound = ResourceBundleHelper.getMessage("html.not.found");
var bytes = (openHTML
+ "<h1>" + fileNotFound + "</h1>\n"
+ "<p>" + sanitize.apply(exchange.getRequestURI().getPath()) + "</p>\n"
+ closeHTML).getBytes(UTF_8);
exchange.getResponseHeaders().set("Content-Type", "text/html; charset=UTF-8");
if (exchange.getRequestMethod().equals("HEAD")) {
exchange.getResponseHeaders().set("Content-Length", Integer.toString(bytes.length));
exchange.sendResponseHeaders(404, RSPBODY_EMPTY);
} else {
exchange.sendResponseHeaders(404, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
}
}
private static void discardRequestBody(HttpExchange exchange) throws IOException {
try (InputStream is = exchange.getRequestBody()) {
is.skip(Integer.MAX_VALUE);
}
}
private String getRedirectURI(URI uri) {
String query = uri.getRawQuery();
String redirectPath = uri.getRawPath() + "/";
return query == null ? redirectPath : redirectPath + "?" + query;
}
private static boolean missingSlash(HttpExchange exchange) {
return !exchange.getRequestURI().getPath().endsWith("/");
}
private static String contextPath(HttpExchange exchange) {
String context = exchange.getHttpContext().getPath();
if (!context.startsWith("/")) {
throw new IllegalArgumentException("Context path invalid: " + context);
}
return context;
}
private static String requestPath(HttpExchange exchange) {
String request = exchange.getRequestURI().getPath();
if (!request.startsWith("/")) {
throw new IllegalArgumentException("Request path invalid: " + request);
}
return request;
}
// Checks that the request does not escape context.
private static void checkRequestWithinContext(String requestPath,
String contextPath) {
if (requestPath.equals(contextPath)) {
return; // context path requested, e.g. context /foo, request /foo
}
String contextPathWithTrailingSlash = contextPath.endsWith("/")
? contextPath : contextPath + "/";
if (!requestPath.startsWith(contextPathWithTrailingSlash)) {
throw new IllegalArgumentException("Request not in context: " + contextPath);
}
}
// Checks that path is, or is within, the root.
private static Path checkPathWithinRoot(Path path, Path root) {
if (!path.startsWith(root)) {
throw new IllegalArgumentException("Request not in root");
}
return path;
}
// Returns the request URI path relative to the context.
private static String relativeRequestPath(HttpExchange exchange) {
String context = contextPath(exchange);
String request = requestPath(exchange);
checkRequestWithinContext(request, context);
return request.substring(context.length());
}
private Path mapToPath(HttpExchange exchange, Path root) {
try {
assert root.isAbsolute() && Files.isDirectory(root); // checked during creation
String uriPath = relativeRequestPath(exchange);
String[] pathSegment = uriPath.split("/");
// resolve each path segment against the root
Path path = root;
for (var segment : pathSegment) {
if (!URIPathSegment.isSupported(segment)) {
return null; // stop resolution, null results in 404 response
}
path = path.resolve(segment);
if (!Files.isReadable(path) || isHiddenOrSymLink(path)) {
return null; // stop resolution
}
}
path = path.normalize();
return checkPathWithinRoot(path, root);
} catch (Exception e) {
logger.log(System.Logger.Level.TRACE,
"FileServerHandler: request URI path resolution failed", e);
return null; // could not resolve request URI path
}
}
private static Path indexFile(Path path) {
Path html = path.resolve("index.html");
Path htm = path.resolve("index.htm");
return Files.exists(html) ? html : Files.exists(htm) ? htm : null;
}
private static boolean isFavIconRequest(HttpExchange exchange) {
return "/favicon.ico".equals(exchange.getRequestURI().getPath());
}
private void serveDefaultFavIcon(HttpExchange exchange, boolean writeBody)
throws IOException
{
var respHdrs = exchange.getResponseHeaders();
try (var stream = getClass().getModule().getResourceAsStream(FAVICON_RESOURCE_PATH)) {
var bytes = stream.readAllBytes();
respHdrs.set("Content-Type", "image/x-icon");
respHdrs.set("Last-Modified", FAVICON_LAST_MODIFIED);
if (writeBody) {
exchange.sendResponseHeaders(200, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
} else {
respHdrs.set("Content-Length", Integer.toString(bytes.length));
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
}
}
}
private void serveFile(HttpExchange exchange, Path path, boolean writeBody)
throws IOException
{
var respHdrs = exchange.getResponseHeaders();
respHdrs.set("Content-Type", mediaType(path.toString()));
respHdrs.set("Last-Modified", getLastModified(path));
if (writeBody) {
exchange.sendResponseHeaders(200, Files.size(path));
try (InputStream fis = Files.newInputStream(path);
OutputStream os = exchange.getResponseBody()) {
fis.transferTo(os);
}
} else {
respHdrs.set("Content-Length", Long.toString(Files.size(path)));
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
}
}
private void listFiles(HttpExchange exchange, Path path, boolean writeBody)
throws IOException
{
var respHdrs = exchange.getResponseHeaders();
respHdrs.set("Content-Type", "text/html; charset=UTF-8");
respHdrs.set("Last-Modified", getLastModified(path));
var bodyBytes = dirListing(exchange, path).getBytes(UTF_8);
if (writeBody) {
exchange.sendResponseHeaders(200, bodyBytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bodyBytes);
}
} else {
respHdrs.set("Content-Length", Integer.toString(bodyBytes.length));
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
}
}
private static final String openHTML = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
""";
private static final String closeHTML = """
</body>
</html>
""";
private static final String hrefListItemTemplate = """
<li><a href="%s">%s</a></li>
""";
private static String hrefListItemFor(URI uri) {
return hrefListItemTemplate.formatted(uri.toASCIIString(), sanitize.apply(uri.getPath()));
}
private static String dirListing(HttpExchange exchange, Path path) throws IOException {
String dirListing = ResourceBundleHelper.getMessage("html.dir.list");
var sb = new StringBuilder(openHTML
+ "<h1>" + dirListing + " "
+ sanitize.apply(exchange.getRequestURI().getPath())
+ "</h1>\n"
+ "<ul>\n");
try (var paths = Files.list(path)) {
paths.filter(p -> Files.isReadable(p) && !isHiddenOrSymLink(p))
.map(p -> path.toUri().relativize(p.toUri()))
.forEach(uri -> sb.append(hrefListItemFor(uri)));
}
sb.append("</ul>\n");
sb.append(closeHTML);
return sb.toString();
}
private static String getLastModified(Path path) throws IOException {
var fileTime = Files.getLastModifiedTime(path);
return fileTime.toInstant().atZone(ZoneId.of("GMT"))
.format(DateTimeFormatter.RFC_1123_DATE_TIME);
}
private static boolean isHiddenOrSymLink(Path path) {
try {
return Files.isHidden(path) || Files.isSymbolicLink(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
// Default for unknown content types, as per RFC 2046
private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
private String mediaType(String file) {
String type = mimeTable.apply(file);
return type != null ? type : DEFAULT_CONTENT_TYPE;
}
// A non-exhaustive map of reserved-HTML and special characters to their
// equivalent entity.
private static final Map<Integer, String> RESERVED_CHARS = Map.of(
(int) '&' , "&amp;" ,
(int) '<' , "&lt;" ,
(int) '>' , "&gt;" ,
(int) '"' , "&quot;" ,
(int) '\'' , "&#x27;" ,
(int) '/' , "&#x2F;" );
// A function that takes a string and returns a sanitized version of that
// string with the reserved-HTML and special characters replaced with their
// equivalent entity.
private static final UnaryOperator<String> sanitize =
file -> file.chars().collect(StringBuilder::new,
(sb, c) -> sb.append(RESERVED_CHARS.getOrDefault(c, Character.toString(c))),
StringBuilder::append).toString();
@Override
public void handle(HttpExchange exchange) throws IOException {
assert List.of("GET", "HEAD").contains(exchange.getRequestMethod());
try (exchange) {
discardRequestBody(exchange);
boolean isHeadRequest = exchange.getRequestMethod().equals("HEAD");
Path path = mapToPath(exchange, root);
if (path != null) {
exchange.setAttribute("request-path", path.toString()); // store for OutputFilter
if (!Files.exists(path) || !Files.isReadable(path) || isHiddenOrSymLink(path)) {
handleNotFound(exchange);
} else if (isHeadRequest) {
handleHEAD(exchange, path);
} else {
handleGET(exchange, path);
}
} else {
if (isFavIconRequest(exchange)) {
try {
serveDefaultFavIcon(exchange, !isHeadRequest);
return;
} catch (IOException ignore) {
// fall through to send the not-found response
}
}
exchange.setAttribute("request-path", "could not resolve request URI path");
handleNotFound(exchange);
}
}
}
}

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2021, 2025, 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.
*/
package sun.net.httpserver.simpleserver;
import java.io.PrintWriter;
/**
* Programmatic entry point to start the jwebserver tool.
*/
public class JWebServer {
private static final String SYS_PROP_MAX_CONNECTIONS = "jdk.httpserver.maxConnections";
private static final String DEFAULT_JWEBSERVER_MAX_CONNECTIONS = "200";
private static final String SYS_PROP_ENHANCED_EXCEP = "jdk.includeInExceptions";
private static final String DEFAULT_ENHANCED_EXCEP = "net";
/**
* This constructor should never be called.
*/
private JWebServer() { throw new AssertionError(); }
/**
* The main entry point.
*
* <p> 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.
*
* <p> If the system property "sun.net.httpserver.maxReqTime" has not been
* set by the user, it is set to a value of 5 seconds. This is to prevent
* the server from hanging indefinitely, for example in the case of an HTTPS
* request.
*
* @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) {
setMaxReqTime();
setEnhancedExceptions();
setMaxConnectionsIfNotSet();
int ec = SimpleFileServerImpl.start(new PrintWriter(System.out, true), "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.
}
public static final String MAXREQTIME_KEY = "sun.net.httpserver.maxReqTime";
public static final String MAXREQTIME_VAL = "5";
private static void setMaxReqTime() {
if (System.getProperty(MAXREQTIME_KEY) == null) {
System.setProperty(MAXREQTIME_KEY, MAXREQTIME_VAL);
}
}
static void setEnhancedExceptions() {
if (System.getProperty(SYS_PROP_ENHANCED_EXCEP) != null) {
// an explicit value has already been set, so we don't override it
return;
}
System.setProperty(SYS_PROP_ENHANCED_EXCEP, DEFAULT_ENHANCED_EXCEP);
}
static void setMaxConnectionsIfNotSet() {
if (System.getProperty(SYS_PROP_MAX_CONNECTIONS) == null) {
System.setProperty(SYS_PROP_MAX_CONNECTIONS, DEFAULT_JWEBSERVER_MAX_CONNECTIONS);
}
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2021, 2025, 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.
*/
package sun.net.httpserver.simpleserver;
import java.io.PrintWriter;
/**
* Programmatic entry point to start "java -m jdk.httpserver".
*/
public class Main {
/**
* This constructor should never be called.
*/
private Main() { throw new AssertionError(); }
/**
* The main entry point.
*
* <p> 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.
*
* <p> If the system property "sun.net.httpserver.maxReqTime" has not been
* set by the user, it is set to a value of 5 seconds. This is to prevent
* the server from hanging indefinitely, for example in the case of an HTTPS
* request.
*
* @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) {
setMaxReqTime();
JWebServer.setMaxConnectionsIfNotSet();
int ec = SimpleFileServerImpl.start(new PrintWriter(System.out, true), "java", 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.
}
public static final String MAXREQTIME_KEY = "sun.net.httpserver.maxReqTime";
public static final String MAXREQTIME_VAL = "5";
private static void setMaxReqTime() {
if (System.getProperty(MAXREQTIME_KEY) == null) {
System.setProperty(MAXREQTIME_KEY, MAXREQTIME_VAL);
}
}
}

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2005, 2022, 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.
*/
package sun.net.httpserver.simpleserver;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Consumer;
import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.SimpleFileServer.OutputLevel;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* A Filter that outputs log messages about an HttpExchange. The implementation
* uses a {@link Filter#afterHandler(String, Consumer) post-processing filter}.
*
* <p> If the outputLevel is INFO, the format is based on the
* <a href='https://www.w3.org/Daemon/User/Config/Logging.html#common-logfile-format'>Common Logfile Format</a>.
* In this case the output includes the following information about an exchange:
*
* <p> remotehost rfc931 authuser [date] "request line" status bytes
*
* <p> Example:
* 127.0.0.1 - - [22/Jun/2000:13:55:36 -0700] "GET /example.txt HTTP/1.1" 200 -
*
* <p> The fields rfc931, authuser and bytes are not captured in the implementation
* and are always represented as '-'.
*
* <p> If the outputLevel is VERBOSE, the output additionally includes the
* absolute path of the resource requested, if it has been
* {@linkplain HttpExchange#setAttribute(String, Object) provided} via the
* attribute {@code "request-path"}, as well as the request and response headers
* of the exchange.
*/
public final class OutputFilter extends Filter {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MMM/yyyy:HH:mm:ss Z");
private final PrintStream printStream;
private final OutputLevel outputLevel;
private final Filter filter;
private OutputFilter(OutputStream os, OutputLevel outputLevel) {
printStream = new PrintStream(os, true, UTF_8);
this.outputLevel = outputLevel;
var description = "HttpExchange OutputFilter (outputLevel: " + outputLevel + ")";
this.filter = Filter.afterHandler(description, operation());
}
public static OutputFilter create(OutputStream os, OutputLevel outputLevel) {
if (outputLevel.equals(OutputLevel.NONE)) {
throw new IllegalArgumentException("Not a valid outputLevel: " + outputLevel);
}
return new OutputFilter(os, outputLevel);
}
private Consumer<HttpExchange> operation() {
return e -> {
String s = e.getRemoteAddress().getHostString() + " "
+ "- - " // rfc931 and authuser
+ "[" + OffsetDateTime.now().format(FORMATTER) + "] "
+ "\"" + e.getRequestMethod() + " " + e.getRequestURI() + " " + e.getProtocol() + "\" "
+ e.getResponseCode() + " -"; // bytes
printStream.println(s);
if (outputLevel.equals(OutputLevel.VERBOSE)) {
if (e.getAttribute("request-path") instanceof String requestPath) {
printStream.println("Resource requested: " + requestPath);
}
logHeaders(">", e.getRequestHeaders());
logHeaders("<", e.getResponseHeaders());
}
};
}
private void logHeaders(String sign, Headers headers) {
headers.forEach((name, values) -> {
var sb = new StringBuilder();
var it = values.iterator();
while (it.hasNext()) {
sb.append(it.next());
if (it.hasNext()) {
sb.append(", ");
}
}
printStream.println(sign + " " + name + ": " + sb);
});
printStream.println(sign);
}
@Override
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
try {
filter.doFilter(exchange, chain);
} catch (Throwable t) {
if (!outputLevel.equals(OutputLevel.NONE)) {
reportError(ResourceBundleHelper.getMessage("err.server.handle.failed",
t.getMessage()));
}
throw t;
}
}
@Override
public String description() { return filter.description(); }
private void reportError(String message) {
printStream.println(ResourceBundleHelper.getMessage("error.prefix") + " " + message);
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2021, 2022, 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.
*/
package sun.net.httpserver.simpleserver;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
class ResourceBundleHelper {
static final ResourceBundle bundle;
static {
try {
bundle = ResourceBundle.getBundle("sun.net.httpserver.simpleserver.resources.simpleserver");
} catch (MissingResourceException e) {
throw new InternalError("Cannot find simpleserver resource bundle for locale " + Locale.getDefault());
}
}
static String getMessage(String key, Object... args) {
try {
return MessageFormat.format(bundle.getString(key), args);
} catch (MissingResourceException e) {
throw new InternalError("Missing message: " + key);
}
}
}

View file

@ -0,0 +1,257 @@
/*
* Copyright (c) 2021, 2025, 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.
*/
package sun.net.httpserver.simpleserver;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.SimpleFileServer;
import com.sun.net.httpserver.SimpleFileServer.OutputLevel;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Objects;
/**
* A class that provides a simple HTTP file server to serve the content of
* a given directory.
*
* <p> The server is an HttpServer bound to a given address. It comes with an
* HttpHandler that serves files from a given directory path
* (and its subdirectories) on the default file system, and an optional Filter
* that prints log messages related to the exchanges handled by the server to
* a given output stream.
*
* <p> Unless specified as arguments, the default values are:<ul>
* <li>bind address: 127.0.0.1 or ::1 (loopback)</li>
* <li>directory: current working directory</li>
* <li>outputLevel: info</li></ul>
* <li>port: 8000</li>
* <p>
* The implementation is provided via the main entry point of the jdk.httpserver
* module.
*/
final class SimpleFileServerImpl {
private static final InetAddress LOOPBACK_ADDR = InetAddress.getLoopbackAddress();
private static final int DEFAULT_PORT = 8000;
private static final Path DEFAULT_ROOT = Path.of("").toAbsolutePath();
private static final OutputLevel DEFAULT_OUTPUT_LEVEL = OutputLevel.INFO;
private static boolean addrSpecified = false;
private SimpleFileServerImpl() { throw new AssertionError(); }
/**
* Starts a simple HTTP file server created on a directory.
*
* @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 launcher, String[] args) {
Objects.requireNonNull(args);
for (var arg : args) {
Objects.requireNonNull(arg);
}
Out out = new Out(writer);
InetAddress addr = LOOPBACK_ADDR;
int port = DEFAULT_PORT;
Path root = DEFAULT_ROOT;
OutputLevel outputLevel = DEFAULT_OUTPUT_LEVEL;
// parse options
Iterator<String> options = Arrays.asList(args).iterator();
String option = null;
String optionArg = null;
try {
while (options.hasNext()) {
option = options.next();
switch (option) {
case "-h", "-?", "--help" -> {
out.showHelp(launcher);
return Startup.OK.statusCode;
}
case "-version", "--version" -> {
out.showVersion(launcher);
return Startup.OK.statusCode;
}
case "-b", "--bind-address" -> {
addr = InetAddress.getByName(optionArg = options.next());
addrSpecified = true;
}
case "-d", "--directory" ->
root = Path.of(optionArg = options.next());
case "-o", "--output" ->
outputLevel = Enum.valueOf(OutputLevel.class,
(optionArg = options.next()).toUpperCase(Locale.ROOT));
case "-p", "--port" ->
port = Integer.parseInt(optionArg = options.next());
default -> throw new AssertionError();
}
}
} catch (AssertionError ae) {
out.reportError(ResourceBundleHelper.getMessage("err.unknown.option", option));
out.showUsage(launcher);
return Startup.CMDERR.statusCode;
} catch (NoSuchElementException nsee) {
out.reportError(ResourceBundleHelper.getMessage("err.missing.arg", option));
out.showOption(option);
return Startup.CMDERR.statusCode;
} catch (Exception e) {
out.reportError(ResourceBundleHelper.getMessage("err.invalid.arg", option, optionArg));
e.printStackTrace(out.writer);
return Startup.CMDERR.statusCode;
} finally {
out.flush();
}
// configure and start server
try {
root = realPath(root);
var socketAddr = new InetSocketAddress(addr, port);
var server = SimpleFileServer.createFileServer(socketAddr, root, outputLevel);
server.start();
out.printStartMessage(root, server);
} catch (Throwable t) {
out.reportError(ResourceBundleHelper.getMessage("err.server.config.failed", t.getMessage()));
return Startup.SYSERR.statusCode;
} finally {
out.flush();
}
return Startup.OK.statusCode;
}
private static Path realPath(Path root) {
// `toRealPath()` invocation below already checks if file exists, though
// there is no way to figure out if it fails due to a non-existent file.
// Hence, checking the existence here first to deliver the user a more
// descriptive message.
if (!Files.exists(root)) {
throw new IllegalArgumentException("Path does not exist: " + root);
}
// Obtain the real path
try {
return root.toRealPath();
} catch (IOException exception) {
throw new IllegalArgumentException("Path is invalid: " + root, exception);
}
}
private static final class Out {
private final PrintWriter writer;
private Out() { throw new AssertionError(); }
Out(PrintWriter writer) {
this.writer = Objects.requireNonNull(writer);
}
void printStartMessage(Path root, HttpServer server)
throws UnknownHostException
{
String port = Integer.toString(server.getAddress().getPort());
var inetAddr = server.getAddress().getAddress();
var isAnyLocal = inetAddr.isAnyLocalAddress();
var addr = isAnyLocal ? InetAddress.getLocalHost().getHostAddress() : inetAddr.getHostAddress();
if (!addrSpecified) {
writer.println(ResourceBundleHelper.getMessage("loopback.info"));
}
if (inetAddr instanceof Inet6Address && addr.contains(":") && !addr.startsWith("[")) {
// we use the "addr" when printing the URL, so make sure it
// conforms to RFC-2732, section 2:
// To use a literal IPv6 address in a URL, the literal
// address should be enclosed in "[" and "]" characters.
addr = "[" + addr + "]";
}
if (isAnyLocal) {
writer.println(ResourceBundleHelper.getMessage("msg.start.anylocal", root, addr, port));
} else {
writer.println(ResourceBundleHelper.getMessage("msg.start.other", root, addr, port));
}
}
void showUsage(String launcher) {
writer.println(ResourceBundleHelper.getMessage("usage." + launcher));
}
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()));
}
void showOption(String option) {
switch (option) {
case "-b", "--bind-address" ->
writer.println(ResourceBundleHelper.getMessage("opt.bindaddress", LOOPBACK_ADDR.getHostAddress()));
case "-d", "--directory" ->
writer.println(ResourceBundleHelper.getMessage("opt.directory"));
case "-o", "--output" ->
writer.println(ResourceBundleHelper.getMessage("opt.output"));
case "-p", "--port" ->
writer.println(ResourceBundleHelper.getMessage("opt.port"));
}
}
void reportError(String message) {
writer.println(ResourceBundleHelper.getMessage("error.prefix") + " " + message);
}
void flush() {
writer.flush();
}
}
private enum Startup {
/** Started with no errors */
OK(0),
/** Not started, bad command-line arguments */
CMDERR(1),
/** Not started, system error or resource exhaustion */
SYSERR(2);
Startup(int statusCode) {
this.statusCode = statusCode;
}
public final int statusCode;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,80 @@
#
# 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.
#
usage.java=\
Usage: java -m jdk.httpserver [-b bind address] [-p port] [-d directory]\n\
\ [-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\
-b, --bind-address - Address to bind to. Default: {0} (loopback).\n\
\ For all interfaces use "-b 0.0.0.0" or "-b ::".\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 - Prints this help message and exits.\n\
-version, --version - Prints version information and exits.\n\
To stop the server, press Ctrl + C.
opt.bindaddress=\
-b, --bind-address - Address to bind to. Default: {0} (loopback).\n\
\ For all interfaces use "-b 0.0.0.0" or "-b ::".
opt.directory=\
-d, --directory - Directory to serve. Default: current directory.
opt.output=\
-o, --output - Output format. none|info|verbose. Default: info.
opt.port=\
-p, --port - Port to listen on. Default: 8000.
loopback.info=\
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
msg.start.anylocal=\
Serving {0} and subdirectories on 0.0.0.0 (all interfaces) port {2}\n\
URL http://{1}:{2}/
msg.start.other=\
Serving {0} and subdirectories on {1} port {2}\n\
URL http://{1}:{2}/
error.prefix=Error:
err.unknown.option=unknown option: {0}
err.missing.arg=no value given for {0}
err.invalid.arg=invalid value given for {0}: {1}
err.server.config.failed=server config failed: {0}
err.server.handle.failed=server exchange handling failed: {0}
html.dir.list=Directory listing for
html.not.found=File not found

View file

@ -0,0 +1,54 @@
#
# 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.
#
usage.java=Verwendung: java -m jdk.httpserver [-b bind address] [-p port] [-d directory]\n [-o none|info|verbose] [-h zum Anzeigen von Optionen]\n [-version zum Anzeigen der Versionsinformationen]
usage.jwebserver=Verwendung: jwebserver [-b bind address] [-p port] [-d directory]\n [-o none|info|verbose] [-h zum Anzeigen von Optionen]\n [-version zum Anzeigen von Versionsinformationen]
version={0} {1}
options=Optionen:\n-b, --bind-address - Adresse, an die das Binding erfolgt. Standard: {0} (Loopback).\n Verwenden Sie für alle Schnittstellen "-b 0.0.0.0" oder "-b ::".\n-d, --directory - Zu bedienendes Verzeichnis. Standard: Aktuelles Verzeichnis.\n-o, --output - Ausgabeformat. none|info|verbose. Standard: info.\n-p, --port - Port, auf dem gehorcht wird. Standard: 8000.\n-h, -?, --help - Gibt diese Hilfemeldung aus und beendet.\n-version, --version - Gibt Versionsinformationen aus und beendet.\nDrücken Sie zum Stoppen des Servers Strg+C.
opt.bindaddress=-b, --bind-address - Adresse, an die das Binding erfolgt. Standard: {0} (Loopback).\n Verwenden Sie für alle Schnittstellen "-b 0.0.0.0" oder "-b ::".
opt.directory=-d, --directory - Zu bedienendes Verzeichnis. Standard: Aktuelles Verzeichnis.
opt.output=-o, --output - Ausgabeformat. none|info|verbose. Standard: info.
opt.port=-p, --port - Port, auf dem gehorcht wird. Standard: 8000.
loopback.info=Binding an Loopback als Standard. Verwenden Sie für alle Schnittstellen "-b 0.0.0.0" oder "-b ::".
msg.start.anylocal=Bedient {0} und Unterverzeichnisse auf 0.0.0.0 (alle Schnittstellen) Port {2}\nURL http://{1}:{2}/
msg.start.other=Bedient {0} und Unterverzeichnisse auf {1} Port {2}\nURL http://{1}:{2}/
error.prefix=Fehler:
err.unknown.option=unbekannte Option: {0}
err.missing.arg=kein Wert angegeben für {0}
err.invalid.arg=ungültiger Wert angegeben für {0}: {1}
err.server.config.failed=Serverkonfiguration nicht erfolgreich: {0}
err.server.handle.failed=Handling des Serveraustauschs nicht erfolgreich: {0}
html.dir.list=Verzeichnisliste für
html.not.found=Datei wurde nicht gefunden

View file

@ -0,0 +1,54 @@
#
# 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.
#
usage.java=使用方法: java -m jdk.httpserver [-b bind address] [-p port] [-d directory]\n [-o none|info|verbose] [-h to show options]\n [-version to show version information]
usage.jwebserver=使用方法: 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=オプション:\n-b, --bind-address - バインド先アドレス。デフォルト: {0} (ループバック)。\n すべてのインタフェースで"-b 0.0.0.0"または"-b ::"を使用します。\n-d, --directory - 使用するディレクトリ。デフォルト: 現在のディレクトリ。\n-o, --output - 出力形式。none|info|verbose。デフォルト: info。\n-p, --port - リスニングするポート。デフォルト: 8000。\n-h, -?, --help - ヘルプ・メッセージを出力して終了します。\n-version, --version - バージョン情報を出力して終了します。\nサーバーを停止するには、[Ctrl]+[C]を押します。
opt.bindaddress=-b, --bind-address - バインド先アドレス。デフォルト: {0} (ループバック).\n すべてのインタフェースで"-b 0.0.0.0"または"-b ::"を使用します。
opt.directory=-d, --directory - 使用するディレクトリ。デフォルト: 現在のディレクトリ。
opt.output=-o, --output - 出力形式。none|info|verbose. デフォルト: info。
opt.port=-p, --port - リスニングするポート。デフォルト: 8000。
loopback.info=デフォルトでループバックにバインドします。すべてのインタフェースで"-b 0.0.0.0"または"-b ::"を使用します。
msg.start.anylocal={0}およびサブディレクトリを0.0.0.0 (すべてのインタフェース)ポート{2}で使用します\nURL http://{1}:{2}/
msg.start.other={0}およびサブディレクトリを{1}ポート{2}で使用します\nURL http://{1}:{2}/
error.prefix=エラー:
err.unknown.option=不明なオプション: {0}
err.missing.arg={0}に値が指定されていません
err.invalid.arg={0}に無効な値が指定されました: {1}
err.server.config.failed=サーバーの構成に失敗しました: {0}
err.server.handle.failed=サーバー交換処理が失敗しました: {0}
html.dir.list=次のディレクトリ・リスト
html.not.found=ファイルが見つかりませんでした

View file

@ -0,0 +1,54 @@
#
# 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.
#
usage.java=用法java -m jdk.httpserver [-b 绑定地址] [-p 端口] [-d 目录]\n [-o none|info|verbose] [-h 显示选项]\n [-version 显示版本信息]
usage.jwebserver=用法jwebserver [-b 绑定地址] [-p 端口] [-d 目录]\n [-o none|info|verbose] [-h 显示选项]\n [-version 显示版本信息]
version={0} {1}
options=选项:\n-b, --bind-address - 要绑定到的地址。默认值:{0}(环回)。\n 如果要表示所有接口,请使用 "-b 0.0.0.0" 或 "-b ::"。\n-d, --directory - 要为其提供服务的目录。默认值:当前目录。\n-o, --output - 输出格式。none|info|verbose。默认值info。\n-p, --port - 要监听的端口。默认值8000。\n-h, -?, --help - 输出此帮助消息并退出。\n-version, --version - 输出版本信息并退出。\n要停止服务器请按 Ctrl + C。
opt.bindaddress=-b, --bind-address - 要绑定到的地址。默认值:{0}(环回)。\n 如果要表示所有接口,请使用 "-b 0.0.0.0" 或 "-b ::"。
opt.directory=-d, --directory - 要为其提供服务的目录。默认值:当前目录。
opt.output=-o, --output - 输出格式。none|info|verbose。默认值info。
opt.port=-p, --port - 要监听的端口。默认值8000。
loopback.info=默认情况下绑定到环回。如果要表示所有接口,请使用 "-b 0.0.0.0" 或 "-b ::"。
msg.start.anylocal=为 0.0.0.0(所有接口)端口 {2} 上的 {0} 及子目录提供服务\nURL http://{1}:{2}/
msg.start.other=为 {1} 端口 {2} 上的 {0} 及子目录提供服务\nURL http://{1}:{2}/
error.prefix=错误:
err.unknown.option=未知选项: {0}
err.missing.arg=没有为{0}指定值
err.invalid.arg=为 {0} 提供的值无效:{1}
err.server.config.failed=服务器配置失败:{0}
err.server.handle.failed=服务器交换处理失败:{0}
html.dir.list=以下项的目录列表
html.not.found=找不到文件

View file

@ -0,0 +1,152 @@
---
# Copyright (c) 2021, 2024, 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.
#
title: 'JWEBSERVER(1) JDK @@VERSION_SHORT@@ | JDK Commands'
date: @@COPYRIGHT_YEAR@@
lang: en
---
## Name
jwebserver - launch the Java Simple Web Server
## Synopsis
`jwebserver` \[*options*\]
*options*
: Command-line options. For a detailed description of the options, see [Options].
## Description
The `jwebserver` 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.
Only idempotent HEAD and GET requests are served. Any other requests receive
a `501 - Not Implemented` or a `405 - Not Allowed` response. GET requests are
mapped to the directory being served, as follows:
* If the requested resource is a file, its content is served.
* If the requested resource is a directory that contains an index file,
the content of the index file is served.
* Otherwise, the names of all files and subdirectories of the directory are
listed. Symbolic links and hidden files are not listed or served.
MIME types are configured automatically, using the built-in table. For example,
`.html` files are served as `text/html` and `.java` files are served as
`text/plain`.
`jwebserver` is located in the jdk.httpserver module, and can alternatively
be started with `java -m jdk.httpserver`. It is based on the web server
implementation in the `com.sun.net.httpserver` package.
The `com.sun.net.httpserver.SimpleFileServer` class provides a programmatic
way to retrieve the server and its components for reuse and extension.
## Usage
```
jwebserver [-b bind address] [-p port] [-d directory]
[-o none|info|verbose] [-h to show options]
[-version to show version information]
```
## Options
`-h` or `-?` or `--help`
: Prints the help message and exits.
`-b` *addr* or `--bind-address` *addr*
: Specifies the address to bind to.
Default: 127.0.0.1 or ::1 (loopback).
For all interfaces use `-b 0.0.0.0` or `-b ::`.
`-d` *dir* or `--directory` *dir*
: Specifies the directory to serve.
Default: current directory.
`-o` *level* or `--output` *level*
: Specifies the output format. `none` | `info` | `verbose`.
Default: `info`.
`-p` *port* or `--port` *port*
: Specifies the port to listen on.
Default: 8000.
`-version` or `--version`
: Prints the version information and exits.
To stop the server, press `Ctrl + C`.
## Starting the Server
The following command starts the Simple Web Server:
```
$ jwebserver
```
If startup is successful, the server prints a message to `System.out`
listing the local address and the absolute path of the directory being
served. For example:
```
$ 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/
```
## Configuration
By default, the server runs in the foreground and binds to the loopback
address and port 8000. This can be changed with the `-b` and `-p` options.
For example, to bind the Simple Web Server to all interfaces, use:
```
$ 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/
```
Note that this makes the web server accessible to all hosts on the network.
*Do not do this unless you are sure the server cannot leak any sensitive
information.*
As another example, use the following command to run on port 9000:
```
$ jwebserver -p 9000
```
By default, the files of the current directory are served. A different
directory can be specified with the `-d` option.
By default, every request is logged on the console. The output looks like
this:
```
127.0.0.1 - - [10/Feb/2021:14:34:11 +0000] "GET /some/subdirectory/ HTTP/1.1" 200 -
```
Logging output can be changed with the `-o` option. The default setting is
`info`. The `verbose` setting additionally includes the request and response
headers as well as the absolute path of the requested resource.
## Stopping the Server
Once started successfully, the server runs until it is stopped. On Unix
platforms, the server can be stopped by sending it a `SIGINT` signal
(`Ctrl+C` in a terminal window).
## Help Option
The `-h` option displays a help message describing the usage and the options
of the `jwebserver`.

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2022, 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.
*/
package sun.net.httpserver.simpleserver;
/**
* A class that represents a URI path segment.
*/
final class URIPathSegment {
private URIPathSegment() { throw new AssertionError(); }
/**
* Checks if the segment of a URI path is supported.
*
* @param segment the segment string
* @return true
*/
static boolean isSupported(String segment) {
return true;
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2022, 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.
*/
package sun.net.httpserver.simpleserver;
/**
* A class that represents a URI path segment.
*/
final class URIPathSegment {
private URIPathSegment() { throw new AssertionError(); }
/**
* Checks if the segment of a URI path is supported. For example,
* "C:" is supported as a drive on Windows only.
*
* @param segment the segment string
* @return true if the segment is supported
*/
static boolean isSupported(String segment) {
// apply same logic as WindowsPathParser
if (segment.length() >= 2 && isLetter(segment.charAt(0)) && segment.charAt(1) == ':') {
return false;
}
return true;
}
private static boolean isLetter(char c) {
return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
}
}