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