Path: blob/master/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
41159 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();584String s = headers.getFirst ("Transfer-encoding");585long clen = 0L;586if (s !=null && s.equalsIgnoreCase ("chunked")) {587clen = -1L;588} else {589s = headers.getFirst ("Content-Length");590if (s != null) {591clen = Long.parseLong(s);592}593if (clen == 0) {594requestCompleted (connection);595}596}597ctx = contexts.findContext (protocol, uri.getPath());598if (ctx == null) {599reject (Code.HTTP_NOT_FOUND,600requestLine, "No context found for request");601return;602}603connection.setContext (ctx);604if (ctx.getHandler() == null) {605reject (Code.HTTP_INTERNAL_ERROR,606requestLine, "No handler for context");607return;608}609tx = new ExchangeImpl (610method, uri, req, clen, connection611);612String chdr = headers.getFirst("Connection");613Headers rheaders = tx.getResponseHeaders();614615if (chdr != null && chdr.equalsIgnoreCase ("close")) {616tx.close = true;617}618if (version.equalsIgnoreCase ("http/1.0")) {619tx.http10 = true;620if (chdr == null) {621tx.close = true;622rheaders.set ("Connection", "close");623} else if (chdr.equalsIgnoreCase ("keep-alive")) {624rheaders.set ("Connection", "keep-alive");625int idle=(int)(ServerConfig.getIdleInterval()/1000);626int max=ServerConfig.getMaxIdleConnections();627String val = "timeout="+idle+", max="+max;628rheaders.set ("Keep-Alive", val);629}630}631632if (newconnection) {633connection.setParameters (634rawin, rawout, chan, engine, sslStreams,635sslContext, protocol, ctx, rawin636);637}638/* check if client sent an Expect 100 Continue.639* In that case, need to send an interim response.640* In future API may be modified to allow app to641* be involved in this process.642*/643String exp = headers.getFirst("Expect");644if (exp != null && exp.equalsIgnoreCase ("100-continue")) {645logReply (100, requestLine, null);646sendReply (647Code.HTTP_CONTINUE, false, null648);649}650/* uf is the list of filters seen/set by the user.651* sf is the list of filters established internally652* and which are not visible to the user. uc and sc653* are the corresponding Filter.Chains.654* They are linked together by a LinkHandler655* so that they can both be invoked in one call.656*/657final List<Filter> sf = ctx.getSystemFilters();658final List<Filter> uf = ctx.getFilters();659660final Filter.Chain sc = new Filter.Chain(sf, ctx.getHandler());661final Filter.Chain uc = new Filter.Chain(uf, new LinkHandler (sc));662663/* set up the two stream references */664tx.getRequestBody();665tx.getResponseBody();666if (https) {667uc.doFilter (new HttpsExchangeImpl (tx));668} else {669uc.doFilter (new HttpExchangeImpl (tx));670}671672} catch (IOException e1) {673logger.log (Level.TRACE, "ServerImpl.Exchange (1)", e1);674closeConnection(connection);675} catch (NumberFormatException e2) {676logger.log (Level.TRACE, "ServerImpl.Exchange (2)", e2);677reject (Code.HTTP_BAD_REQUEST,678requestLine, "NumberFormatException thrown");679} catch (URISyntaxException e3) {680logger.log (Level.TRACE, "ServerImpl.Exchange (3)", e3);681reject (Code.HTTP_BAD_REQUEST,682requestLine, "URISyntaxException thrown");683} catch (Exception e4) {684logger.log (Level.TRACE, "ServerImpl.Exchange (4)", e4);685closeConnection(connection);686} catch (Throwable t) {687logger.log(Level.TRACE, "ServerImpl.Exchange (5)", t);688throw t;689}690}691692/* used to link to 2 or more Filter.Chains together */693694class LinkHandler implements HttpHandler {695Filter.Chain nextChain;696697LinkHandler (Filter.Chain nextChain) {698this.nextChain = nextChain;699}700701public void handle (HttpExchange exchange) throws IOException {702nextChain.doFilter (exchange);703}704}705706void reject (int code, String requestStr, String message) {707rejected = true;708logReply (code, requestStr, message);709sendReply (710code, false, "<h1>"+code+Code.msg(code)+"</h1>"+message711);712closeConnection(connection);713}714715void sendReply (716int code, boolean closeNow, String text)717{718try {719StringBuilder builder = new StringBuilder (512);720builder.append ("HTTP/1.1 ")721.append (code).append (Code.msg(code)).append ("\r\n");722723if (text != null && text.length() != 0) {724builder.append ("Content-Length: ")725.append (text.length()).append ("\r\n")726.append ("Content-Type: text/html\r\n");727} else {728builder.append ("Content-Length: 0\r\n");729text = "";730}731if (closeNow) {732builder.append ("Connection: close\r\n");733}734builder.append ("\r\n").append (text);735String s = builder.toString();736byte[] b = s.getBytes("ISO8859_1");737rawout.write (b);738rawout.flush();739if (closeNow) {740closeConnection(connection);741}742} catch (IOException e) {743logger.log (Level.TRACE, "ServerImpl.sendReply", e);744closeConnection(connection);745}746}747748}749750void logReply (int code, String requestStr, String text) {751if (!logger.isLoggable(Level.DEBUG)) {752return;753}754if (text == null) {755text = "";756}757String r;758if (requestStr.length() > 80) {759r = requestStr.substring (0, 80) + "<TRUNCATED>";760} else {761r = requestStr;762}763String message = r + " [" + code + " " +764Code.msg(code) + "] ("+text+")";765logger.log (Level.DEBUG, message);766}767768long getTicks() {769return ticks;770}771772public long getTime() {773return time;774}775776void delay () {777Thread.yield();778try {779Thread.sleep (200);780} catch (InterruptedException e) {}781}782783private int exchangeCount = 0;784785synchronized void startExchange () {786exchangeCount ++;787}788789synchronized int endExchange () {790exchangeCount --;791assert exchangeCount >= 0;792return exchangeCount;793}794795HttpServer getWrapper () {796return wrapper;797}798799void requestStarted (HttpConnection c) {800c.creationTime = getTime();801c.setState (State.REQUEST);802reqConnections.add (c);803}804805// called after a request has been completely read806// by the server. This stops the timer which would807// close the connection if the request doesn't arrive808// quickly enough. It then starts the timer809// that ensures the client reads the response in a timely810// fashion.811812void requestCompleted (HttpConnection c) {813State s = c.getState();814assert s == State.REQUEST : "State is not REQUEST ("+s+")";815reqConnections.remove (c);816c.rspStartedTime = getTime();817rspConnections.add (c);818c.setState (State.RESPONSE);819}820821// called after response has been sent822void responseCompleted (HttpConnection c) {823State s = c.getState();824assert s == State.RESPONSE : "State is not RESPONSE ("+s+")";825rspConnections.remove (c);826c.setState (State.IDLE);827}828829/**830* TimerTask run every CLOCK_TICK ms831*/832class ServerTimerTask extends TimerTask {833public void run () {834LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();835time = System.currentTimeMillis();836ticks ++;837synchronized (idleConnections) {838for (HttpConnection c : idleConnections) {839if (c.time <= time) {840toClose.add (c);841}842}843for (HttpConnection c : toClose) {844idleConnections.remove (c);845allConnections.remove (c);846c.close();847}848}849}850}851852class ServerTimerTask1 extends TimerTask {853854// runs every TIMER_MILLIS855public void run () {856LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();857time = System.currentTimeMillis();858synchronized (reqConnections) {859if (MAX_REQ_TIME != -1) {860for (HttpConnection c : reqConnections) {861if (c.creationTime + TIMER_MILLIS + MAX_REQ_TIME <= time) {862toClose.add (c);863}864}865for (HttpConnection c : toClose) {866logger.log (Level.DEBUG, "closing: no request: " + c);867reqConnections.remove (c);868allConnections.remove (c);869c.close();870}871}872}873toClose = new LinkedList<HttpConnection>();874synchronized (rspConnections) {875if (MAX_RSP_TIME != -1) {876for (HttpConnection c : rspConnections) {877if (c.rspStartedTime + TIMER_MILLIS +MAX_RSP_TIME <= time) {878toClose.add (c);879}880}881for (HttpConnection c : toClose) {882logger.log (Level.DEBUG, "closing: no response: " + c);883rspConnections.remove (c);884allConnections.remove (c);885c.close();886}887}888}889}890}891892void logStackTrace (String s) {893logger.log (Level.TRACE, s);894StringBuilder b = new StringBuilder ();895StackTraceElement[] e = Thread.currentThread().getStackTrace();896for (int i=0; i<e.length; i++) {897b.append (e[i].toString()).append("\n");898}899logger.log (Level.TRACE, b.toString());900}901902static long getTimeMillis(long secs) {903if (secs == -1) {904return -1;905} else {906return secs * 1000;907}908}909}910911912