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:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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; }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
163
src/jdk.httpserver/share/classes/module-info.java
Normal file
163
src/jdk.httpserver/share/classes/module-info.java
Normal 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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
109
src/jdk.httpserver/share/classes/sun/net/httpserver/Code.java
Normal file
109
src/jdk.httpserver/share/classes/sun/net/httpserver/Code.java
Normal 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 " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
471
src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java
Normal file
471
src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
1169
src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
Normal file
1169
src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
124
src/jdk.httpserver/share/classes/sun/net/httpserver/Utils.java
Normal file
124
src/jdk.httpserver/share/classes/sun/net/httpserver/Utils.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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) '&' , "&" ,
|
||||
(int) '<' , "<" ,
|
||||
(int) '>' , ">" ,
|
||||
(int) '"' , """ ,
|
||||
(int) '\'' , "'" ,
|
||||
(int) '/' , "/" );
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 |
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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=ファイルが見つかりませんでした
|
||||
|
|
@ -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=找不到文件
|
||||
152
src/jdk.httpserver/share/man/jwebserver.md
Normal file
152
src/jdk.httpserver/share/man/jwebserver.md
Normal 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`.
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue