Path: blob/master/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
66646 views
/*1* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.net.httpserver;2627import java.net.*;28import java.io.*;29import java.nio.channels.*;30import java.util.*;31import java.util.concurrent.*;32import java.lang.System.Logger;33import java.lang.System.Logger.Level;34import javax.net.ssl.*;35import com.sun.net.httpserver.*;36import java.security.AccessController;37import java.security.PrivilegedAction;38import sun.net.httpserver.HttpConnection.State;3940/**41* Provides implementation for both HTTP and HTTPS42*/43class ServerImpl implements TimeSource {4445private String protocol;46private boolean https;47private Executor executor;48private HttpsConfigurator httpsConfig;49private SSLContext sslContext;50private ContextList contexts;51private InetSocketAddress address;52private ServerSocketChannel schan;53private Selector selector;54private SelectionKey listenerKey;55private Set<HttpConnection> idleConnections;56private Set<HttpConnection> allConnections;57/* following two are used to keep track of the times58* when a connection/request is first received59* and when we start to send the response60*/61private Set<HttpConnection> reqConnections;62private Set<HttpConnection> rspConnections;63private List<Event> events;64private Object lolock = new Object();65private volatile boolean finished = false;66private volatile boolean terminating = false;67private boolean bound = false;68private boolean started = false;69private volatile long time; /* current time */70private volatile long subticks = 0;71private volatile long ticks; /* number of clock ticks since server started */72private HttpServer wrapper;7374final static int CLOCK_TICK = ServerConfig.getClockTick();75final static long IDLE_INTERVAL = ServerConfig.getIdleInterval();76final static int MAX_IDLE_CONNECTIONS = ServerConfig.getMaxIdleConnections();77final static long TIMER_MILLIS = ServerConfig.getTimerMillis ();78final static long MAX_REQ_TIME=getTimeMillis(ServerConfig.getMaxReqTime());79final static long MAX_RSP_TIME=getTimeMillis(ServerConfig.getMaxRspTime());80final static boolean timer1Enabled = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1;8182private Timer timer, timer1;83private final Logger logger;84private Thread dispatcherThread;8586ServerImpl (87HttpServer wrapper, String protocol, InetSocketAddress addr, int backlog88) throws IOException {8990this.protocol = protocol;91this.wrapper = wrapper;92this.logger = System.getLogger ("com.sun.net.httpserver");93ServerConfig.checkLegacyProperties (logger);94https = protocol.equalsIgnoreCase ("https");95this.address = addr;96contexts = new ContextList();97schan = ServerSocketChannel.open();98if (addr != null) {99ServerSocket socket = schan.socket();100socket.bind (addr, backlog);101bound = true;102}103selector = Selector.open ();104schan.configureBlocking (false);105listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT);106dispatcher = new Dispatcher();107idleConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());108allConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());109reqConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());110rspConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());111time = System.currentTimeMillis();112timer = new Timer ("server-timer", true);113timer.schedule (new ServerTimerTask(), CLOCK_TICK, CLOCK_TICK);114if (timer1Enabled) {115timer1 = new Timer ("server-timer1", true);116timer1.schedule (new ServerTimerTask1(),TIMER_MILLIS,TIMER_MILLIS);117logger.log (Level.DEBUG, "HttpServer timer1 enabled period in ms: ", TIMER_MILLIS);118logger.log (Level.DEBUG, "MAX_REQ_TIME: "+MAX_REQ_TIME);119logger.log (Level.DEBUG, "MAX_RSP_TIME: "+MAX_RSP_TIME);120}121events = new LinkedList<Event>();122logger.log (Level.DEBUG, "HttpServer created "+protocol+" "+ addr);123}124125public void bind (InetSocketAddress addr, int backlog) throws IOException {126if (bound) {127throw new BindException ("HttpServer already bound");128}129if (addr == null) {130throw new NullPointerException ("null address");131}132ServerSocket socket = schan.socket();133socket.bind (addr, backlog);134bound = true;135}136137public void start () {138if (!bound || started || finished) {139throw new IllegalStateException ("server in wrong state");140}141if (executor == null) {142executor = new DefaultExecutor();143}144dispatcherThread = new Thread(null, dispatcher, "HTTP-Dispatcher", 0, false);145started = true;146dispatcherThread.start();147}148149public void setExecutor (Executor executor) {150if (started) {151throw new IllegalStateException ("server already started");152}153this.executor = executor;154}155156private static class DefaultExecutor implements Executor {157public void execute (Runnable task) {158task.run();159}160}161162public Executor getExecutor () {163return executor;164}165166public void setHttpsConfigurator (HttpsConfigurator config) {167if (config == null) {168throw new NullPointerException ("null HttpsConfigurator");169}170if (started) {171throw new IllegalStateException ("server already started");172}173this.httpsConfig = config;174sslContext = config.getSSLContext();175}176177public HttpsConfigurator getHttpsConfigurator () {178return httpsConfig;179}180181public final boolean isFinishing() {182return finished;183}184185public void stop (int delay) {186if (delay < 0) {187throw new IllegalArgumentException ("negative delay parameter");188}189terminating = true;190try { schan.close(); } catch (IOException e) {}191selector.wakeup();192long latest = System.currentTimeMillis() + delay * 1000;193while (System.currentTimeMillis() < latest) {194delay();195if (finished) {196break;197}198}199finished = true;200selector.wakeup();201synchronized (allConnections) {202for (HttpConnection c : allConnections) {203c.close();204}205}206allConnections.clear();207idleConnections.clear();208timer.cancel();209if (timer1Enabled) {210timer1.cancel();211}212if (dispatcherThread != null && dispatcherThread != Thread.currentThread()) {213try {214dispatcherThread.join();215} catch (InterruptedException e) {216Thread.currentThread().interrupt();217logger.log (Level.TRACE, "ServerImpl.stop: ", e);218}219}220}221222Dispatcher dispatcher;223224public synchronized HttpContextImpl createContext (String path, HttpHandler handler) {225if (handler == null || path == null) {226throw new NullPointerException ("null handler, or path parameter");227}228HttpContextImpl context = new HttpContextImpl (protocol, path, handler, this);229contexts.add (context);230logger.log (Level.DEBUG, "context created: " + path);231return context;232}233234public synchronized HttpContextImpl createContext (String path) {235if (path == null) {236throw new NullPointerException ("null path parameter");237}238HttpContextImpl context = new HttpContextImpl (protocol, path, null, this);239contexts.add (context);240logger.log (Level.DEBUG, "context created: " + path);241return context;242}243244public synchronized void removeContext (String path) throws IllegalArgumentException {245if (path == null) {246throw new NullPointerException ("null path parameter");247}248contexts.remove (protocol, path);249logger.log (Level.DEBUG, "context removed: " + path);250}251252public synchronized void removeContext (HttpContext context) throws IllegalArgumentException {253if (!(context instanceof HttpContextImpl)) {254throw new IllegalArgumentException ("wrong HttpContext type");255}256contexts.remove ((HttpContextImpl)context);257logger.log (Level.DEBUG, "context removed: " + context.getPath());258}259260@SuppressWarnings("removal")261public InetSocketAddress getAddress() {262return AccessController.doPrivileged(263new PrivilegedAction<InetSocketAddress>() {264public InetSocketAddress run() {265return266(InetSocketAddress)schan.socket()267.getLocalSocketAddress();268}269});270}271272Selector getSelector () {273return selector;274}275276void addEvent (Event r) {277synchronized (lolock) {278events.add (r);279selector.wakeup();280}281}282283/* main server listener task */284285class Dispatcher implements Runnable {286287private void handleEvent (Event r) {288ExchangeImpl t = r.exchange;289HttpConnection c = t.getConnection();290try {291if (r instanceof WriteFinishedEvent) {292293logger.log(Level.TRACE, "Write Finished");294int exchanges = endExchange();295if (terminating && exchanges == 0) {296finished = true;297}298LeftOverInputStream is = t.getOriginalInputStream();299if (!is.isEOF()) {300t.close = true;301if (c.getState() == State.REQUEST) {302requestCompleted(c);303}304}305responseCompleted (c);306if (t.close || idleConnections.size() >= MAX_IDLE_CONNECTIONS) {307c.close();308allConnections.remove (c);309} else {310if (is.isDataBuffered()) {311/* don't re-enable the interestops, just handle it */312requestStarted (c);313handle (c.getChannel(), c);314} else {315connsToRegister.add (c);316}317}318}319} catch (IOException e) {320logger.log (321Level.TRACE, "Dispatcher (1)", e322);323c.close();324}325}326327final LinkedList<HttpConnection> connsToRegister =328new LinkedList<HttpConnection>();329330void reRegister (HttpConnection c) {331/* re-register with selector */332try {333SocketChannel chan = c.getChannel();334chan.configureBlocking (false);335SelectionKey key = chan.register (selector, SelectionKey.OP_READ);336key.attach (c);337c.selectionKey = key;338c.time = getTime() + IDLE_INTERVAL;339idleConnections.add (c);340} catch (IOException e) {341dprint(e);342logger.log (Level.TRACE, "Dispatcher(8)", e);343c.close();344}345}346347public void run() {348while (!finished) {349try {350List<Event> list = null;351synchronized (lolock) {352if (events.size() > 0) {353list = events;354events = new LinkedList<Event>();355}356}357358if (list != null) {359for (Event r: list) {360handleEvent (r);361}362}363364for (HttpConnection c : connsToRegister) {365reRegister(c);366}367connsToRegister.clear();368369selector.select(1000);370371/* process the selected list now */372Set<SelectionKey> selected = selector.selectedKeys();373Iterator<SelectionKey> iter = selected.iterator();374while (iter.hasNext()) {375SelectionKey key = iter.next();376iter.remove ();377if (key.equals (listenerKey)) {378if (terminating) {379continue;380}381SocketChannel chan = schan.accept();382383// optimist there's a channel384if (chan != null) {385// Set TCP_NODELAY, if appropriate386if (ServerConfig.noDelay()) {387chan.socket().setTcpNoDelay(true);388}389chan.configureBlocking (false);390SelectionKey newkey =391chan.register (selector, SelectionKey.OP_READ);392HttpConnection c = new HttpConnection ();393c.selectionKey = newkey;394c.setChannel (chan);395newkey.attach (c);396requestStarted (c);397allConnections.add (c);398}399} else {400try {401if (key.isReadable()) {402SocketChannel chan = (SocketChannel)key.channel();403HttpConnection conn = (HttpConnection)key.attachment();404405key.cancel();406chan.configureBlocking (true);407if (idleConnections.remove(conn)) {408// was an idle connection so add it409// to reqConnections set.410requestStarted (conn);411}412handle (chan, conn);413} else {414assert false : "Unexpected non-readable key:" + key;415}416} catch (CancelledKeyException e) {417handleException(key, null);418} catch (IOException e) {419handleException(key, e);420}421}422}423// call the selector just to process the cancelled keys424selector.selectNow();425} catch (IOException e) {426logger.log (Level.TRACE, "Dispatcher (4)", e);427} catch (Exception e) {428logger.log (Level.TRACE, "Dispatcher (7)", e);429}430}431try {selector.close(); } catch (Exception e) {}432}433434private void handleException (SelectionKey key, Exception e) {435HttpConnection conn = (HttpConnection)key.attachment();436if (e != null) {437logger.log (Level.TRACE, "Dispatcher (2)", e);438}439closeConnection(conn);440}441442public void handle (SocketChannel chan, HttpConnection conn)443{444try {445Exchange t = new Exchange (chan, protocol, conn);446executor.execute (t);447} catch (HttpError e1) {448logger.log (Level.TRACE, "Dispatcher (4)", e1);449closeConnection(conn);450} catch (IOException e) {451logger.log (Level.TRACE, "Dispatcher (5)", e);452closeConnection(conn);453} catch (Throwable e) {454logger.log (Level.TRACE, "Dispatcher (6)", e);455closeConnection(conn);456}457}458}459460static boolean debug = ServerConfig.debugEnabled ();461462static synchronized void dprint (String s) {463if (debug) {464System.out.println (s);465}466}467468static synchronized void dprint (Exception e) {469if (debug) {470System.out.println (e);471e.printStackTrace();472}473}474475Logger getLogger () {476return logger;477}478479private void closeConnection(HttpConnection conn) {480conn.close();481allConnections.remove(conn);482switch (conn.getState()) {483case REQUEST:484reqConnections.remove(conn);485break;486case RESPONSE:487rspConnections.remove(conn);488break;489case IDLE:490idleConnections.remove(conn);491break;492}493assert !reqConnections.remove(conn);494assert !rspConnections.remove(conn);495assert !idleConnections.remove(conn);496}497498/* per exchange task */499500class Exchange implements Runnable {501SocketChannel chan;502HttpConnection connection;503HttpContextImpl context;504InputStream rawin;505OutputStream rawout;506String protocol;507ExchangeImpl tx;508HttpContextImpl ctx;509boolean rejected = false;510511Exchange (SocketChannel chan, String protocol, HttpConnection conn) throws IOException {512this.chan = chan;513this.connection = conn;514this.protocol = protocol;515}516517public void run () {518/* context will be null for new connections */519logger.log(Level.TRACE, "exchange started");520context = connection.getHttpContext();521boolean newconnection;522SSLEngine engine = null;523String requestLine = null;524SSLStreams sslStreams = null;525try {526if (context != null ) {527this.rawin = connection.getInputStream();528this.rawout = connection.getRawOutputStream();529newconnection = false;530} else {531/* figure out what kind of connection this is */532newconnection = true;533if (https) {534if (sslContext == null) {535logger.log (Level.WARNING,536"SSL connection received. No https context created");537throw new HttpError ("No SSL context established");538}539sslStreams = new SSLStreams (ServerImpl.this, sslContext, chan);540rawin = sslStreams.getInputStream();541rawout = sslStreams.getOutputStream();542engine = sslStreams.getSSLEngine();543connection.sslStreams = sslStreams;544} else {545rawin = new BufferedInputStream(546new Request.ReadStream (547ServerImpl.this, chan548));549rawout = new Request.WriteStream (550ServerImpl.this, chan551);552}553connection.raw = rawin;554connection.rawout = rawout;555}556Request req = new Request (rawin, rawout);557requestLine = req.requestLine();558if (requestLine == null) {559/* connection closed */560logger.log(Level.DEBUG, "no request line: closing");561closeConnection(connection);562return;563}564logger.log(Level.DEBUG, "Exchange request line: {0}", requestLine);565int space = requestLine.indexOf (' ');566if (space == -1) {567reject (Code.HTTP_BAD_REQUEST,568requestLine, "Bad request line");569return;570}571String method = requestLine.substring (0, space);572int start = space+1;573space = requestLine.indexOf(' ', start);574if (space == -1) {575reject (Code.HTTP_BAD_REQUEST,576requestLine, "Bad request line");577return;578}579String uriStr = requestLine.substring (start, space);580URI uri = new URI (uriStr);581start = space+1;582String version = requestLine.substring (start);583Headers headers = req.headers();584/* check key for illegal characters */585for (var k : headers.keySet()) {586if (!isValidHeaderKey(k)) {587reject(Code.HTTP_BAD_REQUEST, requestLine,588"Header key contains illegal characters");589return;590}591}592/* checks for unsupported combinations of lengths and encodings */593if (headers.containsKey("Content-Length") &&594(headers.containsKey("Transfer-encoding") || headers.get("Content-Length").size() > 1)) {595reject(Code.HTTP_BAD_REQUEST, requestLine,596"Conflicting or malformed headers detected");597return;598}599long clen = 0L;600String headerValue = null;601List<String> teValueList = headers.get("Transfer-encoding");602if (teValueList != null && !teValueList.isEmpty()) {603headerValue = teValueList.get(0);604}605if (headerValue != null) {606if (headerValue.equalsIgnoreCase("chunked") && teValueList.size() == 1) {607clen = -1L;608} else {609reject(Code.HTTP_NOT_IMPLEMENTED,610requestLine, "Unsupported Transfer-Encoding value");611return;612}613} else {614headerValue = headers.getFirst("Content-Length");615if (headerValue != null) {616clen = Long.parseLong(headerValue);617if (clen < 0) {618reject(Code.HTTP_BAD_REQUEST, requestLine,619"Illegal Content-Length value");620return;621}622}623if (clen == 0) {624requestCompleted(connection);625}626}627ctx = contexts.findContext (protocol, uri.getPath());628if (ctx == null) {629reject (Code.HTTP_NOT_FOUND,630requestLine, "No context found for request");631return;632}633connection.setContext (ctx);634if (ctx.getHandler() == null) {635reject (Code.HTTP_INTERNAL_ERROR,636requestLine, "No handler for context");637return;638}639tx = new ExchangeImpl (640method, uri, req, clen, connection641);642String chdr = headers.getFirst("Connection");643Headers rheaders = tx.getResponseHeaders();644645if (chdr != null && chdr.equalsIgnoreCase ("close")) {646tx.close = true;647}648if (version.equalsIgnoreCase ("http/1.0")) {649tx.http10 = true;650if (chdr == null) {651tx.close = true;652rheaders.set ("Connection", "close");653} else if (chdr.equalsIgnoreCase ("keep-alive")) {654rheaders.set ("Connection", "keep-alive");655int idle=(int)(ServerConfig.getIdleInterval()/1000);656int max=ServerConfig.getMaxIdleConnections();657String val = "timeout="+idle+", max="+max;658rheaders.set ("Keep-Alive", val);659}660}661662if (newconnection) {663connection.setParameters (664rawin, rawout, chan, engine, sslStreams,665sslContext, protocol, ctx, rawin666);667}668/* check if client sent an Expect 100 Continue.669* In that case, need to send an interim response.670* In future API may be modified to allow app to671* be involved in this process.672*/673String exp = headers.getFirst("Expect");674if (exp != null && exp.equalsIgnoreCase ("100-continue")) {675logReply (100, requestLine, null);676sendReply (677Code.HTTP_CONTINUE, false, null678);679}680/* uf is the list of filters seen/set by the user.681* sf is the list of filters established internally682* and which are not visible to the user. uc and sc683* are the corresponding Filter.Chains.684* They are linked together by a LinkHandler685* so that they can both be invoked in one call.686*/687final List<Filter> sf = ctx.getSystemFilters();688final List<Filter> uf = ctx.getFilters();689690final Filter.Chain sc = new Filter.Chain(sf, ctx.getHandler());691final Filter.Chain uc = new Filter.Chain(uf, new LinkHandler (sc));692693/* set up the two stream references */694tx.getRequestBody();695tx.getResponseBody();696if (https) {697uc.doFilter (new HttpsExchangeImpl (tx));698} else {699uc.doFilter (new HttpExchangeImpl (tx));700}701702} catch (IOException e1) {703logger.log (Level.TRACE, "ServerImpl.Exchange (1)", e1);704closeConnection(connection);705} catch (NumberFormatException e2) {706logger.log (Level.TRACE, "ServerImpl.Exchange (2)", e2);707reject (Code.HTTP_BAD_REQUEST,708requestLine, "NumberFormatException thrown");709} catch (URISyntaxException e3) {710logger.log (Level.TRACE, "ServerImpl.Exchange (3)", e3);711reject (Code.HTTP_BAD_REQUEST,712requestLine, "URISyntaxException thrown");713} catch (Exception e4) {714logger.log (Level.TRACE, "ServerImpl.Exchange (4)", e4);715closeConnection(connection);716} catch (Throwable t) {717logger.log(Level.TRACE, "ServerImpl.Exchange (5)", t);718throw t;719}720}721722/* used to link to 2 or more Filter.Chains together */723724class LinkHandler implements HttpHandler {725Filter.Chain nextChain;726727LinkHandler (Filter.Chain nextChain) {728this.nextChain = nextChain;729}730731public void handle (HttpExchange exchange) throws IOException {732nextChain.doFilter (exchange);733}734}735736void reject (int code, String requestStr, String message) {737rejected = true;738logReply (code, requestStr, message);739sendReply (740code, false, "<h1>"+code+Code.msg(code)+"</h1>"+message741);742closeConnection(connection);743}744745void sendReply (746int code, boolean closeNow, String text)747{748try {749StringBuilder builder = new StringBuilder (512);750builder.append ("HTTP/1.1 ")751.append (code).append (Code.msg(code)).append ("\r\n");752753if (text != null && text.length() != 0) {754builder.append ("Content-Length: ")755.append (text.length()).append ("\r\n")756.append ("Content-Type: text/html\r\n");757} else {758builder.append ("Content-Length: 0\r\n");759text = "";760}761if (closeNow) {762builder.append ("Connection: close\r\n");763}764builder.append ("\r\n").append (text);765String s = builder.toString();766byte[] b = s.getBytes("ISO8859_1");767rawout.write (b);768rawout.flush();769if (closeNow) {770closeConnection(connection);771}772} catch (IOException e) {773logger.log (Level.TRACE, "ServerImpl.sendReply", e);774closeConnection(connection);775}776}777778}779780void logReply (int code, String requestStr, String text) {781if (!logger.isLoggable(Level.DEBUG)) {782return;783}784if (text == null) {785text = "";786}787String r;788if (requestStr.length() > 80) {789r = requestStr.substring (0, 80) + "<TRUNCATED>";790} else {791r = requestStr;792}793String message = r + " [" + code + " " +794Code.msg(code) + "] ("+text+")";795logger.log (Level.DEBUG, message);796}797798long getTicks() {799return ticks;800}801802public long getTime() {803return time;804}805806void delay () {807Thread.yield();808try {809Thread.sleep (200);810} catch (InterruptedException e) {}811}812813private int exchangeCount = 0;814815synchronized void startExchange () {816exchangeCount ++;817}818819synchronized int endExchange () {820exchangeCount --;821assert exchangeCount >= 0;822return exchangeCount;823}824825HttpServer getWrapper () {826return wrapper;827}828829void requestStarted (HttpConnection c) {830c.creationTime = getTime();831c.setState (State.REQUEST);832reqConnections.add (c);833}834835// called after a request has been completely read836// by the server. This stops the timer which would837// close the connection if the request doesn't arrive838// quickly enough. It then starts the timer839// that ensures the client reads the response in a timely840// fashion.841842void requestCompleted (HttpConnection c) {843State s = c.getState();844assert s == State.REQUEST : "State is not REQUEST ("+s+")";845reqConnections.remove (c);846c.rspStartedTime = getTime();847rspConnections.add (c);848c.setState (State.RESPONSE);849}850851// called after response has been sent852void responseCompleted (HttpConnection c) {853State s = c.getState();854assert s == State.RESPONSE : "State is not RESPONSE ("+s+")";855rspConnections.remove (c);856c.setState (State.IDLE);857}858859/**860* TimerTask run every CLOCK_TICK ms861*/862class ServerTimerTask extends TimerTask {863public void run () {864LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();865time = System.currentTimeMillis();866ticks ++;867synchronized (idleConnections) {868for (HttpConnection c : idleConnections) {869if (c.time <= time) {870toClose.add (c);871}872}873for (HttpConnection c : toClose) {874idleConnections.remove (c);875allConnections.remove (c);876c.close();877}878}879}880}881882class ServerTimerTask1 extends TimerTask {883884// runs every TIMER_MILLIS885public void run () {886LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();887time = System.currentTimeMillis();888synchronized (reqConnections) {889if (MAX_REQ_TIME != -1) {890for (HttpConnection c : reqConnections) {891if (c.creationTime + TIMER_MILLIS + MAX_REQ_TIME <= time) {892toClose.add (c);893}894}895for (HttpConnection c : toClose) {896logger.log (Level.DEBUG, "closing: no request: " + c);897reqConnections.remove (c);898allConnections.remove (c);899c.close();900}901}902}903toClose = new LinkedList<HttpConnection>();904synchronized (rspConnections) {905if (MAX_RSP_TIME != -1) {906for (HttpConnection c : rspConnections) {907if (c.rspStartedTime + TIMER_MILLIS +MAX_RSP_TIME <= time) {908toClose.add (c);909}910}911for (HttpConnection c : toClose) {912logger.log (Level.DEBUG, "closing: no response: " + c);913rspConnections.remove (c);914allConnections.remove (c);915c.close();916}917}918}919}920}921922void logStackTrace (String s) {923logger.log (Level.TRACE, s);924StringBuilder b = new StringBuilder ();925StackTraceElement[] e = Thread.currentThread().getStackTrace();926for (int i=0; i<e.length; i++) {927b.append (e[i].toString()).append("\n");928}929logger.log (Level.TRACE, b.toString());930}931932static long getTimeMillis(long secs) {933if (secs == -1) {934return -1;935} else {936return secs * 1000;937}938}939940/*941* Validates a RFC 7230 header-key.942*/943static boolean isValidHeaderKey(String token) {944if (token == null || token.isEmpty()) return false;945946boolean isValidChar;947char[] chars = token.toCharArray();948String validSpecialChars = "!#$%&'*+-.^_`|~";949for (char c : chars) {950isValidChar = ((c >= 'a') && (c <= 'z')) ||951((c >= 'A') && (c <= 'Z')) ||952((c >= '0') && (c <= '9'));953if (!isValidChar && validSpecialChars.indexOf(c) == -1) {954return false;955}956}957return true;958}959}960961962