8286918: Better HttpServer service
Reviewed-by: dfuchs, michaelm, ahgross, rhalade
This commit is contained in:
parent
400aa2fb2c
commit
1553551d82
@ -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() {
|
||||
|
@ -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 =
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
*/
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user