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 }