Home | History | Annotate | Download | only in http
      1 /*
      2  * Copyright (c) 1996, 2011, 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.www.http;
     27 
     28 import java.io.*;
     29 import sun.net.ProgressSource;
     30 import sun.net.www.MeteredStream;
     31 
     32 /**
     33  * A stream that has the property of being able to be kept alive for
     34  * multiple downloads from the same server.
     35  *
     36  * @author Stephen R. Pietrowicz (NCSA)
     37  * @author Dave Brown
     38  */
     39 public
     40 class KeepAliveStream extends MeteredStream implements Hurryable {
     41 
     42     // instance variables
     43     HttpClient hc;
     44 
     45     boolean hurried;
     46 
     47     // has this KeepAliveStream been put on the queue for asynchronous cleanup.
     48     protected boolean queuedForCleanup = false;
     49 
     50     private static final KeepAliveStreamCleaner queue = new KeepAliveStreamCleaner();
     51     private static Thread cleanerThread; // null
     52 
     53     /**
     54      * Constructor
     55      */
     56     public KeepAliveStream(InputStream is, ProgressSource pi, long expected, HttpClient hc)  {
     57         super(is, pi, expected);
     58         this.hc = hc;
     59     }
     60 
     61     /**
     62      * Attempt to cache this connection
     63      */
     64     public void close() throws IOException  {
     65         // If the inputstream is closed already, just return.
     66         if (closed) {
     67             return;
     68         }
     69 
     70         // If this stream has already been queued for cleanup.
     71         if (queuedForCleanup) {
     72             return;
     73         }
     74 
     75         // Skip past the data that's left in the Inputstream because
     76         // some sort of error may have occurred.
     77         // Do this ONLY if the skip won't block. The stream may have
     78         // been closed at the beginning of a big file and we don't want
     79         // to hang around for nothing. So if we can't skip without blocking
     80         // we just close the socket and, therefore, terminate the keepAlive
     81         // NOTE: Don't close super class
     82         try {
     83             if (expected > count) {
     84                 long nskip = (long) (expected - count);
     85                 if (nskip <= available()) {
     86                     long n = 0;
     87                     while (n < nskip) {
     88                         nskip = nskip - n;
     89                         n = skip(nskip);
     90                     }
     91                 } else if (expected <= KeepAliveStreamCleaner.MAX_DATA_REMAINING && !hurried) {
     92                     //put this KeepAliveStream on the queue so that the data remaining
     93                     //on the socket can be cleanup asyncronously.
     94                     queueForCleanup(new KeepAliveCleanerEntry(this, hc));
     95                 } else {
     96                     hc.closeServer();
     97                 }
     98             }
     99             if (!closed && !hurried && !queuedForCleanup) {
    100                 hc.finished();
    101             }
    102         } finally {
    103             if (pi != null)
    104                 pi.finishTracking();
    105 
    106             if (!queuedForCleanup) {
    107                 // nulling out the underlying inputstream as well as
    108                 // httpClient to let gc collect the memories faster
    109                 in = null;
    110                 hc = null;
    111                 closed = true;
    112             }
    113         }
    114     }
    115 
    116     /* we explicitly do not support mark/reset */
    117 
    118     public boolean markSupported()  {
    119         return false;
    120     }
    121 
    122     public void mark(int limit) {}
    123 
    124     public void reset() throws IOException {
    125         throw new IOException("mark/reset not supported");
    126     }
    127 
    128     public synchronized boolean hurry() {
    129         try {
    130             /* CASE 0: we're actually already done */
    131             if (closed || count >= expected) {
    132                 return false;
    133             } else if (in.available() < (expected - count)) {
    134                 /* CASE I: can't meet the demand */
    135                 return false;
    136             } else {
    137                 /* CASE II: fill our internal buffer
    138                  * Remind: possibly check memory here
    139                  */
    140                 int size = (int) (expected - count);
    141                 byte[] buf = new byte[size];
    142                 DataInputStream dis = new DataInputStream(in);
    143                 dis.readFully(buf);
    144                 in = new ByteArrayInputStream(buf);
    145                 hurried = true;
    146                 return true;
    147             }
    148         } catch (IOException e) {
    149             // e.printStackTrace();
    150             return false;
    151         }
    152     }
    153 
    154     private static void queueForCleanup(KeepAliveCleanerEntry kace) {
    155         synchronized(queue) {
    156             if(!kace.getQueuedForCleanup()) {
    157                 if (!queue.offer(kace)) {
    158                     kace.getHttpClient().closeServer();
    159                     return;
    160                 }
    161 
    162                 kace.setQueuedForCleanup();
    163                 queue.notifyAll();
    164             }
    165 
    166             boolean startCleanupThread = (cleanerThread == null);
    167             if (!startCleanupThread) {
    168                 if (!cleanerThread.isAlive()) {
    169                     startCleanupThread = true;
    170                 }
    171             }
    172 
    173             if (startCleanupThread) {
    174                 java.security.AccessController.doPrivileged(
    175                     new java.security.PrivilegedAction<Void>() {
    176                     public Void run() {
    177                         // We want to create the Keep-Alive-SocketCleaner in the
    178                         // system threadgroup
    179                         ThreadGroup grp = Thread.currentThread().getThreadGroup();
    180                         ThreadGroup parent = null;
    181                         while ((parent = grp.getParent()) != null) {
    182                             grp = parent;
    183                         }
    184 
    185                         cleanerThread = new Thread(grp, queue, "Keep-Alive-SocketCleaner");
    186                         cleanerThread.setDaemon(true);
    187                         cleanerThread.setPriority(Thread.MAX_PRIORITY - 2);
    188                         // Set the context class loader to null in order to avoid
    189                         // keeping a strong reference to an application classloader.
    190                         cleanerThread.setContextClassLoader(null);
    191                         cleanerThread.start();
    192                         return null;
    193                     }
    194                 });
    195             }
    196         } // queue
    197     }
    198 
    199     protected long remainingToRead() {
    200         return expected - count;
    201     }
    202 
    203     protected void setClosed() {
    204         in = null;
    205         hc = null;
    206         closed = true;
    207     }
    208 }
    209 
    210 
    211 class KeepAliveCleanerEntry
    212 {
    213     KeepAliveStream kas;
    214     HttpClient hc;
    215 
    216     public KeepAliveCleanerEntry(KeepAliveStream kas, HttpClient hc) {
    217         this.kas = kas;
    218         this.hc = hc;
    219     }
    220 
    221     protected KeepAliveStream getKeepAliveStream() {
    222         return kas;
    223     }
    224 
    225     protected HttpClient getHttpClient() {
    226         return hc;
    227     }
    228 
    229     protected void setQueuedForCleanup() {
    230         kas.queuedForCleanup = true;
    231     }
    232 
    233     protected boolean getQueuedForCleanup() {
    234         return kas.queuedForCleanup;
    235     }
    236 
    237 }
    238