1 /*
   2  * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.net.httpserver;
  27 
  28 import java.net.*;
  29 import java.io.*;
  30 import java.nio.channels.*;
  31 import java.util.*;
  32 import java.util.concurrent.*;
  33 import java.lang.System.Logger;
  34 import java.lang.System.Logger.Level;
  35 import javax.net.ssl.*;
  36 import com.sun.net.httpserver.*;
  37 import java.security.AccessController;
  38 import java.security.PrivilegedAction;
  39 import sun.net.httpserver.HttpConnection.State;
  40 
  41 /**
  42  * Provides implementation for both HTTP and HTTPS
  43  */
  44 class ServerImpl implements TimeSource {
  45 
  46     private String protocol;
  47     private boolean https;
  48     private Executor executor;
  49     private HttpsConfigurator httpsConfig;
  50     private SSLContext sslContext;
  51     private ContextList contexts;
  52     private InetSocketAddress address;
  53     private ServerSocketChannel schan;
  54     private Selector selector;
  55     private SelectionKey listenerKey;
  56     private Set<HttpConnection> idleConnections;
  57     private Set<HttpConnection> allConnections;
  58     /* following two are used to keep track of the times
  59      * when a connection/request is first received
  60      * and when we start to send the response
  61      */
  62     private Set<HttpConnection> reqConnections;
  63     private Set<HttpConnection> rspConnections;
  64     private List<Event> events;
  65     private Object lolock = new Object();
  66     private volatile boolean finished = false;
  67     private volatile boolean terminating = false;
  68     private boolean bound = false;
  69     private boolean started = false;
  70     private volatile long time;  /* current time */
  71     private volatile long subticks = 0;
  72     private volatile long ticks; /* number of clock ticks since server started */
  73     private HttpServer wrapper;
  74 
  75     final static int CLOCK_TICK = ServerConfig.getClockTick();
  76     final static long IDLE_INTERVAL = ServerConfig.getIdleInterval();
  77     final static int MAX_IDLE_CONNECTIONS = ServerConfig.getMaxIdleConnections();
  78     final static long TIMER_MILLIS = ServerConfig.getTimerMillis ();
  79     final static long MAX_REQ_TIME=getTimeMillis(ServerConfig.getMaxReqTime());
  80     final static long MAX_RSP_TIME=getTimeMillis(ServerConfig.getMaxRspTime());
  81     final static boolean timer1Enabled = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1;
  82 
  83     private Timer timer, timer1;
  84     private final Logger logger;
  85     private Thread dispatcherThread;
  86 
  87     ServerImpl (
  88         HttpServer wrapper, String protocol, InetSocketAddress addr, int backlog
  89     ) throws IOException {
  90 
  91         this.protocol = protocol;
  92         this.wrapper = wrapper;
  93         this.logger = System.getLogger ("com.sun.net.httpserver");
  94         ServerConfig.checkLegacyProperties (logger);
  95         https = protocol.equalsIgnoreCase ("https");
  96         this.address = addr;
  97         contexts = new ContextList();
  98         schan = ServerSocketChannel.open();
  99         if (addr != null) {
 100             ServerSocket socket = schan.socket();
 101             socket.bind (addr, backlog);
 102             bound = true;
 103         }
 104         selector = Selector.open ();
 105         schan.configureBlocking (false);
 106         listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT);
 107         dispatcher = new Dispatcher();
 108         idleConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());
 109         allConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());
 110         reqConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());
 111         rspConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());
 112         time = System.currentTimeMillis();
 113         timer = new Timer ("server-timer", true);
 114         timer.schedule (new ServerTimerTask(), CLOCK_TICK, CLOCK_TICK);
 115         if (timer1Enabled) {
 116             timer1 = new Timer ("server-timer1", true);
 117             timer1.schedule (new ServerTimerTask1(),TIMER_MILLIS,TIMER_MILLIS);
 118             logger.log (Level.DEBUG, "HttpServer timer1 enabled period in ms: ", TIMER_MILLIS);
 119             logger.log (Level.DEBUG, "MAX_REQ_TIME:  "+MAX_REQ_TIME);
 120             logger.log (Level.DEBUG, "MAX_RSP_TIME:  "+MAX_RSP_TIME);
 121         }
 122         events = new LinkedList<Event>();
 123         logger.log (Level.DEBUG, "HttpServer created "+protocol+" "+ addr);
 124     }
 125 
 126     public void bind (InetSocketAddress addr, int backlog) throws IOException {
 127         if (bound) {
 128             throw new BindException ("HttpServer already bound");
 129         }
 130         if (addr == null) {
 131             throw new NullPointerException ("null address");
 132         }
 133         ServerSocket socket = schan.socket();
 134         socket.bind (addr, backlog);
 135         bound = true;
 136     }
 137 
 138     public void start () {
 139         if (!bound || started || finished) {
 140             throw new IllegalStateException ("server in wrong state");
 141         }
 142         if (executor == null) {
 143             executor = new DefaultExecutor();
 144         }
 145         dispatcherThread = new Thread(null, dispatcher, "HTTP-Dispatcher", 0, false);
 146         started = true;
 147         dispatcherThread.start();
 148     }
 149 
 150     public void setExecutor (Executor executor) {
 151         if (started) {
 152             throw new IllegalStateException ("server already started");
 153         }
 154         this.executor = executor;
 155     }
 156 
 157     private static class DefaultExecutor implements Executor {
 158         public void execute (Runnable task) {
 159             task.run();
 160         }
 161     }
 162 
 163     public Executor getExecutor () {
 164         return executor;
 165     }
 166 
 167     public void setHttpsConfigurator (HttpsConfigurator config) {
 168         if (config == null) {
 169             throw new NullPointerException ("null HttpsConfigurator");
 170         }
 171         if (started) {
 172             throw new IllegalStateException ("server already started");
 173         }
 174         this.httpsConfig = config;
 175         sslContext = config.getSSLContext();
 176     }
 177 
 178     public HttpsConfigurator getHttpsConfigurator () {
 179         return httpsConfig;
 180     }
 181 
 182     public final boolean isFinishing() {
 183         return finished;
 184     }
 185 
 186     public void stop (int delay) {
 187         if (delay < 0) {
 188             throw new IllegalArgumentException ("negative delay parameter");
 189         }
 190         terminating = true;
 191         try { schan.close(); } catch (IOException e) {}
 192         selector.wakeup();
 193         long latest = System.currentTimeMillis() + delay * 1000;
 194         while (System.currentTimeMillis() < latest) {
 195             delay();
 196             if (finished) {
 197                 break;
 198             }
 199         }
 200         finished = true;
 201         selector.wakeup();
 202         synchronized (allConnections) {
 203             for (HttpConnection c : allConnections) {
 204                 c.close();
 205             }
 206         }
 207         allConnections.clear();
 208         idleConnections.clear();
 209         timer.cancel();
 210         if (timer1Enabled) {
 211             timer1.cancel();
 212         }
 213         if (dispatcherThread != null) {
 214             try {
 215                 dispatcherThread.join();
 216             } catch (InterruptedException e) {
 217                 Thread.currentThread().interrupt();
 218                 logger.log (Level.TRACE, "ServerImpl.stop: ", e);
 219             }
 220         }
 221     }
 222 
 223     Dispatcher dispatcher;
 224 
 225     public synchronized HttpContextImpl createContext (String path, HttpHandler handler) {
 226         if (handler == null || path == null) {
 227             throw new NullPointerException ("null handler, or path parameter");
 228         }
 229         HttpContextImpl context = new HttpContextImpl (protocol, path, handler, this);
 230         contexts.add (context);
 231         logger.log (Level.DEBUG, "context created: " + path);
 232         return context;
 233     }
 234 
 235     public synchronized HttpContextImpl createContext (String path) {
 236         if (path == null) {
 237             throw new NullPointerException ("null path parameter");
 238         }
 239         HttpContextImpl context = new HttpContextImpl (protocol, path, null, this);
 240         contexts.add (context);
 241         logger.log (Level.DEBUG, "context created: " + path);
 242         return context;
 243     }
 244 
 245     public synchronized void removeContext (String path) throws IllegalArgumentException {
 246         if (path == null) {
 247             throw new NullPointerException ("null path parameter");
 248         }
 249         contexts.remove (protocol, path);
 250         logger.log (Level.DEBUG, "context removed: " + path);
 251     }
 252 
 253     public synchronized void removeContext (HttpContext context) throws IllegalArgumentException {
 254         if (!(context instanceof HttpContextImpl)) {
 255             throw new IllegalArgumentException ("wrong HttpContext type");
 256         }
 257         contexts.remove ((HttpContextImpl)context);
 258         logger.log (Level.DEBUG, "context removed: " + context.getPath());
 259     }
 260 
 261     public InetSocketAddress getAddress() {
 262         return AccessController.doPrivileged(
 263                 new PrivilegedAction<InetSocketAddress>() {
 264                     public InetSocketAddress run() {
 265                         return
 266                             (InetSocketAddress)schan.socket()
 267                                 .getLocalSocketAddress();
 268                     }
 269                 });
 270     }
 271 
 272     Selector getSelector () {
 273         return selector;
 274     }
 275 
 276     void addEvent (Event r) {
 277         synchronized (lolock) {
 278             events.add (r);
 279             selector.wakeup();
 280         }
 281     }
 282 
 283     /* main server listener task */
 284 
 285     class Dispatcher implements Runnable {
 286 
 287         private void handleEvent (Event r) {
 288             ExchangeImpl t = r.exchange;
 289             HttpConnection c = t.getConnection();
 290             try {
 291                 if (r instanceof WriteFinishedEvent) {
 292 
 293                     int exchanges = endExchange();
 294                     if (terminating && exchanges == 0) {
 295                         finished = true;
 296                     }
 297                     responseCompleted (c);
 298                     LeftOverInputStream is = t.getOriginalInputStream();
 299                     if (!is.isEOF()) {
 300                         t.close = true;
 301                     }
 302                     if (t.close || idleConnections.size() >= MAX_IDLE_CONNECTIONS) {
 303                         c.close();
 304                         allConnections.remove (c);
 305                     } else {
 306                         if (is.isDataBuffered()) {
 307                             /* don't re-enable the interestops, just handle it */
 308                             requestStarted (c);
 309                             handle (c.getChannel(), c);
 310                         } else {
 311                             connsToRegister.add (c);
 312                         }
 313                     }
 314                 }
 315             } catch (IOException e) {
 316                 logger.log (
 317                     Level.TRACE, "Dispatcher (1)", e
 318                 );
 319                 c.close();
 320             }
 321         }
 322 
 323         final LinkedList<HttpConnection> connsToRegister =
 324                 new LinkedList<HttpConnection>();
 325 
 326         void reRegister (HttpConnection c) {
 327             /* re-register with selector */
 328             try {
 329                 SocketChannel chan = c.getChannel();
 330                 chan.configureBlocking (false);
 331                 SelectionKey key = chan.register (selector, SelectionKey.OP_READ);
 332                 key.attach (c);
 333                 c.selectionKey = key;
 334                 c.time = getTime() + IDLE_INTERVAL;
 335                 idleConnections.add (c);
 336             } catch (IOException e) {
 337                 dprint(e);
 338                 logger.log (Level.TRACE, "Dispatcher(8)", e);
 339                 c.close();
 340             }
 341         }
 342 
 343         public void run() {
 344             while (!finished) {
 345                 try {
 346                     List<Event> list = null;
 347                     synchronized (lolock) {
 348                         if (events.size() > 0) {
 349                             list = events;
 350                             events = new LinkedList<Event>();
 351                         }
 352                     }
 353 
 354                     if (list != null) {
 355                         for (Event r: list) {
 356                             handleEvent (r);
 357                         }
 358                     }
 359 
 360                     for (HttpConnection c : connsToRegister) {
 361                         reRegister(c);
 362                     }
 363                     connsToRegister.clear();
 364 
 365                     selector.select(1000);
 366 
 367                     /* process the selected list now  */
 368                     Set<SelectionKey> selected = selector.selectedKeys();
 369                     Iterator<SelectionKey> iter = selected.iterator();
 370                     while (iter.hasNext()) {
 371                         SelectionKey key = iter.next();
 372                         iter.remove ();
 373                         if (key.equals (listenerKey)) {
 374                             if (terminating) {
 375                                 continue;
 376                             }
 377                             SocketChannel chan = schan.accept();
 378 
 379                             // optimist there's a channel
 380                             if (chan != null) {
 381                                 // Set TCP_NODELAY, if appropriate
 382                                 if (ServerConfig.noDelay()) {
 383                                     chan.socket().setTcpNoDelay(true);
 384                                 }
 385                                 chan.configureBlocking (false);
 386                                 SelectionKey newkey =
 387                                     chan.register (selector, SelectionKey.OP_READ);
 388                                 HttpConnection c = new HttpConnection ();
 389                                 c.selectionKey = newkey;
 390                                 c.setChannel (chan);
 391                                 newkey.attach (c);
 392                                 requestStarted (c);
 393                                 allConnections.add (c);
 394                             }
 395                         } else {
 396                             try {
 397                                 if (key.isReadable()) {
 398                                     boolean closed;
 399                                     SocketChannel chan = (SocketChannel)key.channel();
 400                                     HttpConnection conn = (HttpConnection)key.attachment();
 401 
 402                                     key.cancel();
 403                                     chan.configureBlocking (true);
 404                                     if (idleConnections.remove(conn)) {
 405                                         // was an idle connection so add it
 406                                         // to reqConnections set.
 407                                         requestStarted (conn);
 408                                     }
 409                                     handle (chan, conn);
 410                                 } else {
 411                                     assert false;
 412                                 }
 413                             } catch (CancelledKeyException e) {
 414                                 handleException(key, null);
 415                             } catch (IOException e) {
 416                                 handleException(key, e);
 417                             }
 418                         }
 419                     }
 420                     // call the selector just to process the cancelled keys
 421                     selector.selectNow();
 422                 } catch (IOException e) {
 423                     logger.log (Level.TRACE, "Dispatcher (4)", e);
 424                 } catch (Exception e) {
 425                     logger.log (Level.TRACE, "Dispatcher (7)", e);
 426                 }
 427             }
 428             try {selector.close(); } catch (Exception e) {}
 429         }
 430 
 431         private void handleException (SelectionKey key, Exception e) {
 432             HttpConnection conn = (HttpConnection)key.attachment();
 433             if (e != null) {
 434                 logger.log (Level.TRACE, "Dispatcher (2)", e);
 435             }
 436             closeConnection(conn);
 437         }
 438 
 439         public void handle (SocketChannel chan, HttpConnection conn)
 440         throws IOException
 441         {
 442             try {
 443                 Exchange t = new Exchange (chan, protocol, conn);
 444                 executor.execute (t);
 445             } catch (HttpError e1) {
 446                 logger.log (Level.TRACE, "Dispatcher (4)", e1);
 447                 closeConnection(conn);
 448             } catch (IOException e) {
 449                 logger.log (Level.TRACE, "Dispatcher (5)", e);
 450                 closeConnection(conn);
 451             }
 452         }
 453     }
 454 
 455     static boolean debug = ServerConfig.debugEnabled ();
 456 
 457     static synchronized void dprint (String s) {
 458         if (debug) {
 459             System.out.println (s);
 460         }
 461     }
 462 
 463     static synchronized void dprint (Exception e) {
 464         if (debug) {
 465             System.out.println (e);
 466             e.printStackTrace();
 467         }
 468     }
 469 
 470     Logger getLogger () {
 471         return logger;
 472     }
 473 
 474     private void closeConnection(HttpConnection conn) {
 475         conn.close();
 476         allConnections.remove(conn);
 477         switch (conn.getState()) {
 478         case REQUEST:
 479             reqConnections.remove(conn);
 480             break;
 481         case RESPONSE:
 482             rspConnections.remove(conn);
 483             break;
 484         case IDLE:
 485             idleConnections.remove(conn);
 486             break;
 487         }
 488         assert !reqConnections.remove(conn);
 489         assert !rspConnections.remove(conn);
 490         assert !idleConnections.remove(conn);
 491     }
 492 
 493         /* per exchange task */
 494 
 495     class Exchange implements Runnable {
 496         SocketChannel chan;
 497         HttpConnection connection;
 498         HttpContextImpl context;
 499         InputStream rawin;
 500         OutputStream rawout;
 501         String protocol;
 502         ExchangeImpl tx;
 503         HttpContextImpl ctx;
 504         boolean rejected = false;
 505 
 506         Exchange (SocketChannel chan, String protocol, HttpConnection conn) throws IOException {
 507             this.chan = chan;
 508             this.connection = conn;
 509             this.protocol = protocol;
 510         }
 511 
 512         public void run () {
 513             /* context will be null for new connections */
 514             context = connection.getHttpContext();
 515             boolean newconnection;
 516             SSLEngine engine = null;
 517             String requestLine = null;
 518             SSLStreams sslStreams = null;
 519             try {
 520                 if (context != null ) {
 521                     this.rawin = connection.getInputStream();
 522                     this.rawout = connection.getRawOutputStream();
 523                     newconnection = false;
 524                 } else {
 525                     /* figure out what kind of connection this is */
 526                     newconnection = true;
 527                     if (https) {
 528                         if (sslContext == null) {
 529                             logger.log (Level.WARNING,
 530                                 "SSL connection received. No https contxt created");
 531                             throw new HttpError ("No SSL context established");
 532                         }
 533                         sslStreams = new SSLStreams (ServerImpl.this, sslContext, chan);
 534                         rawin = sslStreams.getInputStream();
 535                         rawout = sslStreams.getOutputStream();
 536                         engine = sslStreams.getSSLEngine();
 537                         connection.sslStreams = sslStreams;
 538                     } else {
 539                         rawin = new BufferedInputStream(
 540                             new Request.ReadStream (
 541                                 ServerImpl.this, chan
 542                         ));
 543                         rawout = new Request.WriteStream (
 544                             ServerImpl.this, chan
 545                         );
 546                     }
 547                     connection.raw = rawin;
 548                     connection.rawout = rawout;
 549                 }
 550                 Request req = new Request (rawin, rawout);
 551                 requestLine = req.requestLine();
 552                 if (requestLine == null) {
 553                     /* connection closed */
 554                     closeConnection(connection);
 555                     return;
 556                 }
 557                 int space = requestLine.indexOf (' ');
 558                 if (space == -1) {
 559                     reject (Code.HTTP_BAD_REQUEST,
 560                             requestLine, "Bad request line");
 561                     return;
 562                 }
 563                 String method = requestLine.substring (0, space);
 564                 int start = space+1;
 565                 space = requestLine.indexOf(' ', start);
 566                 if (space == -1) {
 567                     reject (Code.HTTP_BAD_REQUEST,
 568                             requestLine, "Bad request line");
 569                     return;
 570                 }
 571                 String uriStr = requestLine.substring (start, space);
 572                 URI uri = new URI (uriStr);
 573                 start = space+1;
 574                 String version = requestLine.substring (start);
 575                 Headers headers = req.headers();
 576                 String s = headers.getFirst ("Transfer-encoding");
 577                 long clen = 0L;
 578                 if (s !=null && s.equalsIgnoreCase ("chunked")) {
 579                     clen = -1L;
 580                 } else {
 581                     s = headers.getFirst ("Content-Length");
 582                     if (s != null) {
 583                         clen = Long.parseLong(s);
 584                     }
 585                     if (clen == 0) {
 586                         requestCompleted (connection);
 587                     }
 588                 }
 589                 ctx = contexts.findContext (protocol, uri.getPath());
 590                 if (ctx == null) {
 591                     reject (Code.HTTP_NOT_FOUND,
 592                             requestLine, "No context found for request");
 593                     return;
 594                 }
 595                 connection.setContext (ctx);
 596                 if (ctx.getHandler() == null) {
 597                     reject (Code.HTTP_INTERNAL_ERROR,
 598                             requestLine, "No handler for context");
 599                     return;
 600                 }
 601                 tx = new ExchangeImpl (
 602                     method, uri, req, clen, connection
 603                 );
 604                 String chdr = headers.getFirst("Connection");
 605                 Headers rheaders = tx.getResponseHeaders();
 606 
 607                 if (chdr != null && chdr.equalsIgnoreCase ("close")) {
 608                     tx.close = true;
 609                 }
 610                 if (version.equalsIgnoreCase ("http/1.0")) {
 611                     tx.http10 = true;
 612                     if (chdr == null) {
 613                         tx.close = true;
 614                         rheaders.set ("Connection", "close");
 615                     } else if (chdr.equalsIgnoreCase ("keep-alive")) {
 616                         rheaders.set ("Connection", "keep-alive");
 617                         int idle=(int)(ServerConfig.getIdleInterval()/1000);
 618                         int max=ServerConfig.getMaxIdleConnections();
 619                         String val = "timeout="+idle+", max="+max;
 620                         rheaders.set ("Keep-Alive", val);
 621                     }
 622                 }
 623 
 624                 if (newconnection) {
 625                     connection.setParameters (
 626                         rawin, rawout, chan, engine, sslStreams,
 627                         sslContext, protocol, ctx, rawin
 628                     );
 629                 }
 630                 /* check if client sent an Expect 100 Continue.
 631                  * In that case, need to send an interim response.
 632                  * In future API may be modified to allow app to
 633                  * be involved in this process.
 634                  */
 635                 String exp = headers.getFirst("Expect");
 636                 if (exp != null && exp.equalsIgnoreCase ("100-continue")) {
 637                     logReply (100, requestLine, null);
 638                     sendReply (
 639                         Code.HTTP_CONTINUE, false, null
 640                     );
 641                 }
 642                 /* uf is the list of filters seen/set by the user.
 643                  * sf is the list of filters established internally
 644                  * and which are not visible to the user. uc and sc
 645                  * are the corresponding Filter.Chains.
 646                  * They are linked together by a LinkHandler
 647                  * so that they can both be invoked in one call.
 648                  */
 649                 List<Filter> sf = ctx.getSystemFilters();
 650                 List<Filter> uf = ctx.getFilters();
 651 
 652                 Filter.Chain sc = new Filter.Chain(sf, ctx.getHandler());
 653                 Filter.Chain uc = new Filter.Chain(uf, new LinkHandler (sc));
 654 
 655                 /* set up the two stream references */
 656                 tx.getRequestBody();
 657                 tx.getResponseBody();
 658                 if (https) {
 659                     uc.doFilter (new HttpsExchangeImpl (tx));
 660                 } else {
 661                     uc.doFilter (new HttpExchangeImpl (tx));
 662                 }
 663 
 664             } catch (IOException e1) {
 665                 logger.log (Level.TRACE, "ServerImpl.Exchange (1)", e1);
 666                 closeConnection(connection);
 667             } catch (NumberFormatException e3) {
 668                 reject (Code.HTTP_BAD_REQUEST,
 669                         requestLine, "NumberFormatException thrown");
 670             } catch (URISyntaxException e) {
 671                 reject (Code.HTTP_BAD_REQUEST,
 672                         requestLine, "URISyntaxException thrown");
 673             } catch (Exception e4) {
 674                 logger.log (Level.TRACE, "ServerImpl.Exchange (2)", e4);
 675                 closeConnection(connection);
 676             }
 677         }
 678 
 679         /* used to link to 2 or more Filter.Chains together */
 680 
 681         class LinkHandler implements HttpHandler {
 682             Filter.Chain nextChain;
 683 
 684             LinkHandler (Filter.Chain nextChain) {
 685                 this.nextChain = nextChain;
 686             }
 687 
 688             public void handle (HttpExchange exchange) throws IOException {
 689                 nextChain.doFilter (exchange);
 690             }
 691         }
 692 
 693         void reject (int code, String requestStr, String message) {
 694             rejected = true;
 695             logReply (code, requestStr, message);
 696             sendReply (
 697                 code, false, "<h1>"+code+Code.msg(code)+"</h1>"+message
 698             );
 699             closeConnection(connection);
 700         }
 701 
 702         void sendReply (
 703             int code, boolean closeNow, String text)
 704         {
 705             try {
 706                 StringBuilder builder = new StringBuilder (512);
 707                 builder.append ("HTTP/1.1 ")
 708                     .append (code).append (Code.msg(code)).append ("\r\n");
 709 
 710                 if (text != null && text.length() != 0) {
 711                     builder.append ("Content-Length: ")
 712                         .append (text.length()).append ("\r\n")
 713                         .append ("Content-Type: text/html\r\n");
 714                 } else {
 715                     builder.append ("Content-Length: 0\r\n");
 716                     text = "";
 717                 }
 718                 if (closeNow) {
 719                     builder.append ("Connection: close\r\n");
 720                 }
 721                 builder.append ("\r\n").append (text);
 722                 String s = builder.toString();
 723                 byte[] b = s.getBytes("ISO8859_1");
 724                 rawout.write (b);
 725                 rawout.flush();
 726                 if (closeNow) {
 727                     closeConnection(connection);
 728                 }
 729             } catch (IOException e) {
 730                 logger.log (Level.TRACE, "ServerImpl.sendReply", e);
 731                 closeConnection(connection);
 732             }
 733         }
 734 
 735     }
 736 
 737     void logReply (int code, String requestStr, String text) {
 738         if (!logger.isLoggable(Level.DEBUG)) {
 739             return;
 740         }
 741         if (text == null) {
 742             text = "";
 743         }
 744         String r;
 745         if (requestStr.length() > 80) {
 746            r = requestStr.substring (0, 80) + "<TRUNCATED>";
 747         } else {
 748            r = requestStr;
 749         }
 750         String message = r + " [" + code + " " +
 751                     Code.msg(code) + "] ("+text+")";
 752         logger.log (Level.DEBUG, message);
 753     }
 754 
 755     long getTicks() {
 756         return ticks;
 757     }
 758 
 759     public long getTime() {
 760         return time;
 761     }
 762 
 763     void delay () {
 764         Thread.yield();
 765         try {
 766             Thread.sleep (200);
 767         } catch (InterruptedException e) {}
 768     }
 769 
 770     private int exchangeCount = 0;
 771 
 772     synchronized void startExchange () {
 773         exchangeCount ++;
 774     }
 775 
 776     synchronized int endExchange () {
 777         exchangeCount --;
 778         assert exchangeCount >= 0;
 779         return exchangeCount;
 780     }
 781 
 782     HttpServer getWrapper () {
 783         return wrapper;
 784     }
 785 
 786     void requestStarted (HttpConnection c) {
 787         c.creationTime = getTime();
 788         c.setState (State.REQUEST);
 789         reqConnections.add (c);
 790     }
 791 
 792     // called after a request has been completely read
 793     // by the server. This stops the timer which would
 794     // close the connection if the request doesn't arrive
 795     // quickly enough. It then starts the timer
 796     // that ensures the client reads the response in a timely
 797     // fashion.
 798 
 799     void requestCompleted (HttpConnection c) {
 800         assert c.getState() == State.REQUEST;
 801         reqConnections.remove (c);
 802         c.rspStartedTime = getTime();
 803         rspConnections.add (c);
 804         c.setState (State.RESPONSE);
 805     }
 806 
 807     // called after response has been sent
 808     void responseCompleted (HttpConnection c) {
 809         assert c.getState() == State.RESPONSE;
 810         rspConnections.remove (c);
 811         c.setState (State.IDLE);
 812     }
 813 
 814     /**
 815      * TimerTask run every CLOCK_TICK ms
 816      */
 817     class ServerTimerTask extends TimerTask {
 818         public void run () {
 819             LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();
 820             time = System.currentTimeMillis();
 821             ticks ++;
 822             synchronized (idleConnections) {
 823                 for (HttpConnection c : idleConnections) {
 824                     if (c.time <= time) {
 825                         toClose.add (c);
 826                     }
 827                 }
 828                 for (HttpConnection c : toClose) {
 829                     idleConnections.remove (c);
 830                     allConnections.remove (c);
 831                     c.close();
 832                 }
 833             }
 834         }
 835     }
 836 
 837     class ServerTimerTask1 extends TimerTask {
 838 
 839         // runs every TIMER_MILLIS
 840         public void run () {
 841             LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();
 842             time = System.currentTimeMillis();
 843             synchronized (reqConnections) {
 844                 if (MAX_REQ_TIME != -1) {
 845                     for (HttpConnection c : reqConnections) {
 846                         if (c.creationTime + TIMER_MILLIS + MAX_REQ_TIME <= time) {
 847                             toClose.add (c);
 848                         }
 849                     }
 850                     for (HttpConnection c : toClose) {
 851                         logger.log (Level.DEBUG, "closing: no request: " + c);
 852                         reqConnections.remove (c);
 853                         allConnections.remove (c);
 854                         c.close();
 855                     }
 856                 }
 857             }
 858             toClose = new LinkedList<HttpConnection>();
 859             synchronized (rspConnections) {
 860                 if (MAX_RSP_TIME != -1) {
 861                     for (HttpConnection c : rspConnections) {
 862                         if (c.rspStartedTime + TIMER_MILLIS +MAX_RSP_TIME <= time) {
 863                             toClose.add (c);
 864                         }
 865                     }
 866                     for (HttpConnection c : toClose) {
 867                         logger.log (Level.DEBUG, "closing: no response: " + c);
 868                         rspConnections.remove (c);
 869                         allConnections.remove (c);
 870                         c.close();
 871                     }
 872                 }
 873             }
 874         }
 875     }
 876 
 877     void logStackTrace (String s) {
 878         logger.log (Level.TRACE, s);
 879         StringBuilder b = new StringBuilder ();
 880         StackTraceElement[] e = Thread.currentThread().getStackTrace();
 881         for (int i=0; i<e.length; i++) {
 882             b.append (e[i].toString()).append("\n");
 883         }
 884         logger.log (Level.TRACE, b.toString());
 885     }
 886 
 887     static long getTimeMillis(long secs) {
 888         if (secs == -1) {
 889             return -1;
 890         } else {
 891             return secs * 1000;
 892         }
 893     }
 894 }