8286918: Better HttpServer service

Reviewed-by: dfuchs, michaelm, ahgross, rhalade
This commit is contained in:
Jaikiran Pai 2022-07-21 08:58:04 +00:00 committed by Henry Jen
parent 400aa2fb2c
commit 1553551d82
8 changed files with 328 additions and 149 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
* 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
@ -30,8 +30,6 @@ import javax.net.ssl.*;
import java.nio.channels.*;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;
/**
* encapsulates all the connection specific state for a HTTP/S connection
@ -55,14 +53,14 @@ class HttpConnection {
SocketChannel chan;
SelectionKey selectionKey;
String protocol;
long time;
volatile long creationTime; // time this connection was created
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};
public enum State {IDLE, REQUEST, RESPONSE, NEWLY_ACCEPTED};
volatile State state;
public String toString() {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
* 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
@ -44,7 +44,6 @@ class SSLStreams {
SSLContext sslctx;
SocketChannel chan;
TimeSource time;
ServerImpl server;
SSLEngine engine;
EngineWrapper wrapper;
@ -56,7 +55,6 @@ class SSLStreams {
SSLStreams (ServerImpl server, SSLContext sslctx, SocketChannel chan) throws IOException {
this.server = server;
this.time= (TimeSource)server;
this.sslctx= sslctx;
this.chan= chan;
InetSocketAddress addr =

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
* 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
@ -37,29 +37,35 @@ import java.security.PrivilegedAction;
@SuppressWarnings("removal")
class ServerConfig {
private static final int DEFAULT_CLOCK_TICK = 10000 ; // 10 sec.
private static final int DEFAULT_IDLE_TIMER_SCHEDULE_MILLIS = 10000 ; // 10 sec.
/* These values must be a reasonable multiple of clockTick */
private static final long DEFAULT_IDLE_INTERVAL = 30 ; // 5 min
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
private static final long DEFAULT_TIMER_MILLIS = 1000;
// 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 long DEFAULT_DRAIN_AMOUNT = 64 * 1024;
private static int clockTick;
private static long idleInterval;
private static long idleTimerScheduleMillis;
private static long idleIntervalMillis;
// The maximum number of bytes to drain from an inputstream
private static 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 int maxConnections;
private static int maxIdleConnections;
// The maximum number of request headers allowable
private static int maxReqHeaders;
// max time a request or response is allowed to take
private static long maxReqTime;
private static long maxRspTime;
private static long timerMillis;
private static long reqRspTimerScheduleMillis;
private static boolean debug;
// the value of the TCP_NODELAY socket-level option
@ -70,11 +76,22 @@ class ServerConfig {
new PrivilegedAction<Void>() {
@Override
public Void run () {
idleInterval = Long.getLong("sun.net.httpserver.idleInterval",
DEFAULT_IDLE_INTERVAL) * 1000;
idleIntervalMillis = Long.getLong("sun.net.httpserver.idleInterval",
DEFAULT_IDLE_INTERVAL_IN_SECS) * 1000;
if (idleIntervalMillis <= 0) {
idleIntervalMillis = DEFAULT_IDLE_INTERVAL_IN_SECS * 1000;
}
clockTick = Integer.getInteger("sun.net.httpserver.clockTick",
DEFAULT_CLOCK_TICK);
idleTimerScheduleMillis = Long.getLong("sun.net.httpserver.clockTick",
DEFAULT_IDLE_TIMER_SCHEDULE_MILLIS);
if (idleTimerScheduleMillis <= 0) {
// ignore zero or negative value and use the default schedule
idleTimerScheduleMillis = DEFAULT_IDLE_TIMER_SCHEDULE_MILLIS;
}
maxConnections = Integer.getInteger(
"jdk.httpserver.maxConnections",
DEFAULT_MAX_CONNECTIONS);
maxIdleConnections = Integer.getInteger(
"sun.net.httpserver.maxIdleConnections",
@ -93,8 +110,13 @@ class ServerConfig {
maxRspTime = Long.getLong("sun.net.httpserver.maxRspTime",
DEFAULT_MAX_RSP_TIME);
timerMillis = Long.getLong("sun.net.httpserver.timerMillis",
DEFAULT_TIMER_MILLIS);
reqRspTimerScheduleMillis = Long.getLong("sun.net.httpserver.timerMillis",
DEFAULT_REQ_RSP_TIMER_TASK_SCHEDULE_MILLIS);
if (reqRspTimerScheduleMillis <= 0) {
// ignore any negative or zero value for this configuration and reset
// to default schedule
reqRspTimerScheduleMillis = DEFAULT_REQ_RSP_TIMER_TASK_SCHEDULE_MILLIS;
}
debug = Boolean.getBoolean("sun.net.httpserver.debug");
@ -150,14 +172,34 @@ class ServerConfig {
return debug;
}
static long getIdleInterval() {
return idleInterval;
/**
* {@return Returns the maximum duration, in milli seconds, a connection can be idle}
*/
static long getIdleIntervalMillis() {
return idleIntervalMillis;
}
static int getClockTick() {
return clockTick;
/**
* {@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;
}
@ -170,16 +212,30 @@ class ServerConfig {
return maxReqHeaders;
}
/**
* @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;
}
static long getTimerMillis() {
return timerMillis;
/**
* {@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() {

View File

@ -25,26 +25,52 @@
package sun.net.httpserver;
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.*;
import java.util.concurrent.*;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import javax.net.ssl.*;
import com.sun.net.httpserver.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsConfigurator;
import sun.net.httpserver.HttpConnection.State;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static sun.net.httpserver.Utils.*;
import static sun.net.httpserver.Utils.isValidName;
/**
* Provides implementation for both HTTP and HTTPS
*/
class ServerImpl implements TimeSource {
class ServerImpl {
private String protocol;
private boolean https;
@ -56,32 +82,47 @@ class ServerImpl implements TimeSource {
private ServerSocketChannel schan;
private Selector selector;
private SelectionKey listenerKey;
private Set<HttpConnection> idleConnections;
private Set<HttpConnection> allConnections;
private final Set<HttpConnection> idleConnections;
// connections which have been accepted() by the server but which haven't
// yet sent any byte on the connection yet
private final Set<HttpConnection> newlyAcceptedConnections;
private final Set<HttpConnection> allConnections;
/* following two are used to keep track of the times
* when a connection/request is first received
* and when we start to send the response
*/
private Set<HttpConnection> reqConnections;
private Set<HttpConnection> rspConnections;
private final Set<HttpConnection> reqConnections;
private final Set<HttpConnection> rspConnections;
private List<Event> events;
private Object lolock = new Object();
private final Object lolock = new Object();
private volatile boolean finished = false;
private volatile boolean terminating = false;
private boolean bound = false;
private boolean started = false;
private volatile long time; /* current time */
private volatile long subticks = 0;
private volatile long ticks; /* number of clock ticks since server started */
private HttpServer wrapper;
static final int CLOCK_TICK = ServerConfig.getClockTick();
static final long IDLE_INTERVAL = ServerConfig.getIdleInterval();
// schedule for the timer task that's responsible for idle connection management
static final long IDLE_TIMER_TASK_SCHEDULE = ServerConfig.getIdleTimerScheduleMillis();
static final int MAX_CONNECTIONS = ServerConfig.getMaxConnections();
static final int MAX_IDLE_CONNECTIONS = ServerConfig.getMaxIdleConnections();
static final long TIMER_MILLIS = ServerConfig.getTimerMillis ();
static final long MAX_REQ_TIME=getTimeMillis(ServerConfig.getMaxReqTime());
static final long MAX_RSP_TIME=getTimeMillis(ServerConfig.getMaxRspTime());
static final boolean timer1Enabled = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1;
// schedule for the timer task that's responsible for request/response timeout management
static final long REQ_RSP_TIMER_SCHEDULE = ServerConfig.getReqRspTimerScheduleMillis();
static final long MAX_REQ_TIME = getTimeMillis(ServerConfig.getMaxReqTime());
static final long MAX_RSP_TIME = getTimeMillis(ServerConfig.getMaxRspTime());
static final boolean reqRspTimeoutEnabled = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1;
// the maximum idle duration for a connection which is currently idle but has served
// some request in the past
static final long IDLE_INTERVAL = ServerConfig.getIdleIntervalMillis();
// the maximum idle duration for a newly accepted connection which hasn't yet received
// the first byte of data on that connection
static final long NEWLY_ACCEPTED_CONN_IDLE_INTERVAL;
static {
// the idle duration of a newly accepted connection is considered to be the least of the
// configured idle interval and the configured max request time (if any).
NEWLY_ACCEPTED_CONN_IDLE_INTERVAL = MAX_REQ_TIME > 0
? Math.min(IDLE_INTERVAL, MAX_REQ_TIME)
: IDLE_INTERVAL;
}
private Timer timer, timer1;
private final Logger logger;
@ -112,13 +153,14 @@ class ServerImpl implements TimeSource {
allConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());
reqConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());
rspConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());
time = System.currentTimeMillis();
timer = new Timer ("server-timer", true);
timer.schedule (new ServerTimerTask(), CLOCK_TICK, CLOCK_TICK);
if (timer1Enabled) {
timer1 = new Timer ("server-timer1", true);
timer1.schedule (new ServerTimerTask1(),TIMER_MILLIS,TIMER_MILLIS);
logger.log (Level.DEBUG, "HttpServer timer1 enabled period in ms: ", TIMER_MILLIS);
newlyAcceptedConnections = Collections.synchronizedSet(new HashSet<>());
timer = new Timer ("idle-timeout-task", true);
timer.schedule (new IdleTimeoutTask(), IDLE_TIMER_TASK_SCHEDULE, IDLE_TIMER_TASK_SCHEDULE);
if (reqRspTimeoutEnabled) {
timer1 = new Timer ("req-rsp-timeout-task", true);
timer1.schedule (new ReqRspTimeoutTask(), REQ_RSP_TIMER_SCHEDULE, REQ_RSP_TIMER_SCHEDULE);
logger.log(Level.DEBUG, "HttpServer request/response timeout task schedule ms: ",
REQ_RSP_TIMER_SCHEDULE);
logger.log (Level.DEBUG, "MAX_REQ_TIME: "+MAX_REQ_TIME);
logger.log (Level.DEBUG, "MAX_RSP_TIME: "+MAX_RSP_TIME);
}
@ -209,8 +251,9 @@ class ServerImpl implements TimeSource {
}
allConnections.clear();
idleConnections.clear();
newlyAcceptedConnections.clear();
timer.cancel();
if (timer1Enabled) {
if (reqRspTimeoutEnabled) {
timer1.cancel();
}
if (dispatcherThread != null && dispatcherThread != Thread.currentThread()) {
@ -273,10 +316,6 @@ class ServerImpl implements TimeSource {
});
}
Selector getSelector () {
return selector;
}
void addEvent (Event r) {
synchronized (lolock) {
events.add (r);
@ -286,6 +325,70 @@ class ServerImpl implements TimeSource {
/* main server listener task */
/**
* The Dispatcher is responsible for accepting any connections and then using those connections
* to processing any incoming requests. A connection is represented as an instance of
* sun.net.httpserver.HttpConnection.
*
* Connection states:
* An instance of HttpConnection goes through the following states:
*
* - NEWLY_ACCEPTED: A connection is marked as newly accepted as soon as the Dispatcher
* accept()s a connection. A newly accepted connection is added to a newlyAcceptedConnections
* collection. A newly accepted connection also gets added to the allConnections collection.
* The newlyAcceptedConnections isn't checked for any size limits, however, if the server is
* configured with a maximum connection limit, then the elements in the
* newlyAcceptedConnections will never exceed that configured limit (no explicit size checks
* are done on the newlyAcceptedConnections collection, since the maximum connection limit
* applies to connections across different connection states). A connection in NEWLY_ACCEPTED
* state is considered idle and is eligible for idle connection management.
*
* - REQUEST: A connection is marked to be in REQUEST state when the request processing starts
* on that connection. This typically happens when the first byte of data is received on a
* NEWLY_ACCEPTED connection or when new data arrives on a connection which was previously
* in IDLE state. When a connection is in REQUEST state, it implies that the connection is
* active and thus isn't eligible for idle connection management. If the server is configured
* with a maximum request timeout, then connections in REQUEST state are eligible
* for Request/Response timeout management.
*
* - RESPONSE: A connection is marked to be in RESPONSE state when the server has finished
* reading the request. A connection is RESPONSE state is considered active and isn't eligible
* for idle connection management. If the server is configured with a maximum response timeout,
* then connections in RESPONSE state are eligible for Request/Response timeout management.
*
* - IDLE: A connection is marked as IDLE when a request/response cycle (successfully) completes
* on that particular connection. Idle connections are held in a idleConnections collection.
* The idleConnections collection is limited in size and the size is decided by a server
* configuration. Connections in IDLE state get added to the idleConnections collection only
* if that collection hasn't reached the configured limit. If a connection has reached IDLE
* state and there's no more room in the idleConnections collection, then such a connection
* gets closed. Connections in idleConnections collection are eligible for idle connection
* management.
*
* Idle connection management:
* A timer task is responsible for closing idle connections. Each connection that is in a state
* which is eligible for idle timeout management (see above section on connection states)
* will have a corresponding idle expiration time associated with it. The idle timeout management
* task will check the expiration time of each such connection against the current time and will
* close the connection if the current time is either equal to or past the expiration time.
*
* Request/Response timeout management:
* The server can be optionally configured with a maximum request timeout and/or maximum response
* timeout. If either of these timeouts have been configured, then an additional timer task is
* run by the server. This timer task is then responsible for closing connections which have
* been in REQUEST or RESPONSE state for a period of time that exceeds the respective configured
* timeouts.
*
* Maximum connection limit management:
* The server can be optionally configured with a maximum connection limit. A value of 0 or
* negative integer is ignored and considered to represent no connection limit. In case of a
* positive integer value, any newly accepted connections will be first checked against the
* current count of established connections (held by the allConnections collection) and if the
* configured limit has reached, then the newly accepted connection will be closed immediately
* (even before setting its state to NEWLY_ACCEPTED or adding it to the newlyAcceptedConnections
* collection).
*
*/
class Dispatcher implements Runnable {
private void handleEvent (Event r) {
@ -339,8 +442,7 @@ class ServerImpl implements TimeSource {
SelectionKey key = chan.register (selector, SelectionKey.OP_READ);
key.attach (c);
c.selectionKey = key;
c.time = getTime() + IDLE_INTERVAL;
idleConnections.add (c);
markIdle(c);
} catch (IOException e) {
dprint(e);
logger.log (Level.TRACE, "Dispatcher(8)", e);
@ -387,9 +489,18 @@ class ServerImpl implements TimeSource {
continue;
}
SocketChannel chan = schan.accept();
// optimist there's a channel
if (chan != null) {
if (MAX_CONNECTIONS > 0 && allConnections.size() >= MAX_CONNECTIONS) {
// we've hit max limit of current open connections, so we go
// ahead and close this connection without processing it
try {
chan.close();
} catch (IOException ignore) {
}
// move on to next selected key
continue;
}
// Set TCP_NODELAY, if appropriate
if (ServerConfig.noDelay()) {
chan.socket().setTcpNoDelay(true);
@ -401,7 +512,7 @@ class ServerImpl implements TimeSource {
c.selectionKey = newkey;
c.setChannel (chan);
newkey.attach (c);
requestStarted (c);
markNewlyAccepted(c);
allConnections.add (c);
}
} else {
@ -412,10 +523,12 @@ class ServerImpl implements TimeSource {
key.cancel();
chan.configureBlocking (true);
if (idleConnections.remove(conn)) {
// was an idle connection so add it
// to reqConnections set.
requestStarted (conn);
if (newlyAcceptedConnections.remove(conn)
|| idleConnections.remove(conn)) {
// was either a newly accepted connection or an idle
// connection. In either case, we mark that the request
// has now started on this connection.
requestStarted(conn);
}
handle (chan, conn);
} else {
@ -497,10 +610,14 @@ class ServerImpl implements TimeSource {
case IDLE:
idleConnections.remove(conn);
break;
case NEWLY_ACCEPTED:
newlyAcceptedConnections.remove(conn);
break;
}
assert !reqConnections.remove(conn);
assert !rspConnections.remove(conn);
assert !idleConnections.remove(conn);
assert !newlyAcceptedConnections.remove(conn);
}
/* per exchange task */
@ -672,8 +789,8 @@ class ServerImpl implements TimeSource {
rheaders.set ("Connection", "close");
} else if (chdr.equalsIgnoreCase("keep-alive")) {
rheaders.set("Connection", "keep-alive");
int timeoutSeconds = (int) (ServerConfig.getIdleInterval() / 1000);
String val = "timeout=" + timeoutSeconds;
int idleSeconds = (int) (ServerConfig.getIdleIntervalMillis() / 1000);
String val = "timeout=" + idleSeconds;
rheaders.set("Keep-Alive", val);
}
}
@ -814,14 +931,6 @@ class ServerImpl implements TimeSource {
logger.log (Level.DEBUG, message);
}
long getTicks() {
return ticks;
}
public long getTime() {
return time;
}
void delay () {
Thread.yield();
try {
@ -846,11 +955,23 @@ class ServerImpl implements TimeSource {
}
void requestStarted (HttpConnection c) {
c.creationTime = getTime();
c.reqStartedTime = System.currentTimeMillis();
c.setState (State.REQUEST);
reqConnections.add (c);
}
void markIdle(HttpConnection c) {
c.idleStartTime = System.currentTimeMillis();
c.setState(State.IDLE);
idleConnections.add(c);
}
void markNewlyAccepted(HttpConnection c) {
c.idleStartTime = System.currentTimeMillis();
c.setState(State.NEWLY_ACCEPTED);
newlyAcceptedConnections.add(c);
}
// called after a request has been completely read
// by the server. This stops the timer which would
// close the connection if the request doesn't arrive
@ -862,7 +983,7 @@ class ServerImpl implements TimeSource {
State s = c.getState();
assert s == State.REQUEST : "State is not REQUEST ("+s+")";
reqConnections.remove (c);
c.rspStartedTime = getTime();
c.rspStartedTime = System.currentTimeMillis();
rspConnections.add (c);
c.setState (State.RESPONSE);
}
@ -876,38 +997,58 @@ class ServerImpl implements TimeSource {
}
/**
* Responsible for closing connections that have been idle.
* TimerTask run every CLOCK_TICK ms
*/
class ServerTimerTask extends TimerTask {
class IdleTimeoutTask extends TimerTask {
public void run () {
LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();
time = System.currentTimeMillis();
ticks ++;
final long currentTime = System.currentTimeMillis();
synchronized (idleConnections) {
for (HttpConnection c : idleConnections) {
if (c.time <= time) {
toClose.add (c);
final Iterator<HttpConnection> it = idleConnections.iterator();
while (it.hasNext()) {
final HttpConnection c = it.next();
if (currentTime - c.idleStartTime >= IDLE_INTERVAL) {
toClose.add(c);
it.remove();
}
}
for (HttpConnection c : toClose) {
idleConnections.remove (c);
allConnections.remove (c);
c.close();
}
// if any newly accepted connection has been idle (i.e. no byte has been sent on that
// connection during the configured idle timeout period) then close it as well
synchronized (newlyAcceptedConnections) {
final Iterator<HttpConnection> it = newlyAcceptedConnections.iterator();
while (it.hasNext()) {
final HttpConnection c = it.next();
if (currentTime - c.idleStartTime >= NEWLY_ACCEPTED_CONN_IDLE_INTERVAL) {
toClose.add(c);
it.remove();
}
}
}
for (HttpConnection c : toClose) {
allConnections.remove(c);
c.close();
if (logger.isLoggable(Level.TRACE)) {
logger.log(Level.TRACE, "Closed idle connection " + c);
}
}
}
}
class ServerTimerTask1 extends TimerTask {
/**
* Responsible for closing connections which have timed out while in REQUEST or RESPONSE state
*/
class ReqRspTimeoutTask extends TimerTask {
// runs every TIMER_MILLIS
public void run () {
LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();
time = System.currentTimeMillis();
final long currentTime = System.currentTimeMillis();
synchronized (reqConnections) {
if (MAX_REQ_TIME != -1) {
for (HttpConnection c : reqConnections) {
if (c.creationTime + TIMER_MILLIS + MAX_REQ_TIME <= time) {
if (currentTime - c.reqStartedTime >= MAX_REQ_TIME) {
toClose.add (c);
}
}
@ -923,7 +1064,7 @@ class ServerImpl implements TimeSource {
synchronized (rspConnections) {
if (MAX_RSP_TIME != -1) {
for (HttpConnection c : rspConnections) {
if (c.rspStartedTime + TIMER_MILLIS +MAX_RSP_TIME <= time) {
if (currentTime - c.rspStartedTime >= MAX_RSP_TIME) {
toClose.add (c);
}
}
@ -938,21 +1079,17 @@ class ServerImpl implements TimeSource {
}
}
void logStackTrace (String s) {
logger.log (Level.TRACE, s);
StringBuilder b = new StringBuilder ();
StackTraceElement[] e = Thread.currentThread().getStackTrace();
for (int i=0; i<e.length; i++) {
b.append (e[i].toString()).append("\n");
}
logger.log (Level.TRACE, b.toString());
}
static long getTimeMillis(long secs) {
if (secs == -1) {
/**
* Converts and returns the passed {@code secs} as milli seconds. If the passed {@code secs}
* is negative or zero or if the conversion from seconds to milli seconds results in a negative
* number, then this method returns -1.
*/
private static long getTimeMillis(long secs) {
if (secs <= 0) {
return -1;
} else {
return secs * 1000;
}
final long milli = secs * 1000;
// this handles potential numeric overflow that may have happened during conversion
return milli > 0 ? milli : -1;
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright (c) 2005, 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;
interface TimeSource {
public long getTime();
}

View File

@ -26,6 +26,9 @@
package sun.net.httpserver.simpleserver;
import java.io.PrintWriter;
import java.security.AccessController;
import java.security.PrivilegedAction;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
@ -33,6 +36,9 @@ import static java.nio.charset.StandardCharsets.UTF_8;
*/
public class JWebServer {
private static final String SYS_PROP_MAX_CONNECTIONS = "jdk.httpserver.maxConnections";
private static final String DEFAULT_JWEBSERVER_MAX_CONNECTIONS = "200";
/**
* This constructor should never be called.
*/
@ -59,6 +65,7 @@ public class JWebServer {
*/
public static void main(String... args) {
setMaxReqTime();
setMaxConnectionsIfNotSet();
int ec = SimpleFileServerImpl.start(new PrintWriter(System.out, true, UTF_8), "jwebserver", args);
if (ec != 0) {
@ -76,4 +83,16 @@ public class JWebServer {
System.setProperty(MAXREQTIME_KEY, MAXREQTIME_VAL);
}
}
@SuppressWarnings("removal")
static void setMaxConnectionsIfNotSet() {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
if (System.getProperty(SYS_PROP_MAX_CONNECTIONS) != null) {
// an explicit value has already been set, so we don't override it
return null;
}
System.setProperty(SYS_PROP_MAX_CONNECTIONS, DEFAULT_JWEBSERVER_MAX_CONNECTIONS);
return null;
});
}
}

View File

@ -59,6 +59,7 @@ public class Main {
*/
public static void main(String... args) {
setMaxReqTime();
JWebServer.setMaxConnectionsIfNotSet();
int ec = SimpleFileServerImpl.start(new PrintWriter(System.out, true, UTF_8), "java", args);
if (ec != 0) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
* 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
@ -25,8 +25,8 @@
* @test
* @bug 6725892
* @library /test/lib
* @run main/othervm -Dsun.net.httpserver.maxReqTime=2 Test
* @run main/othervm -Djava.net.preferIPv6Addresses=true -Dsun.net.httpserver.maxReqTime=2 Test
* @run main/othervm -Dsun.net.httpserver.maxReqTime=2 -Dsun.net.httpserver.clockTick=2000 Test
* @run main/othervm -Djava.net.preferIPv6Addresses=true -Dsun.net.httpserver.maxReqTime=2 -Dsun.net.httpserver.clockTick=2000 Test
* @summary
*/