View Javadoc
1   /*
2    * Copyright (c) 1996, 2012, 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 = expected - count;
85                  if (nskip <= available()) {
86                      do {} while ((nskip = (expected - count)) > 0L
87                                   && skip(Math.min(nskip, available())) > 0L);
88                  } else if (expected <= KeepAliveStreamCleaner.MAX_DATA_REMAINING && !hurried) {
89                      //put this KeepAliveStream on the queue so that the data remaining
90                      //on the socket can be cleanup asyncronously.
91                      queueForCleanup(new KeepAliveCleanerEntry(this, hc));
92                  } else {
93                      hc.closeServer();
94                  }
95              }
96              if (!closed && !hurried && !queuedForCleanup) {
97                  hc.finished();
98              }
99          } finally {
100             if (pi != null)
101                 pi.finishTracking();
102 
103             if (!queuedForCleanup) {
104                 // nulling out the underlying inputstream as well as
105                 // httpClient to let gc collect the memories faster
106                 in = null;
107                 hc = null;
108                 closed = true;
109             }
110         }
111     }
112 
113     /* we explicitly do not support mark/reset */
114 
115     public boolean markSupported()  {
116         return false;
117     }
118 
119     public void mark(int limit) {}
120 
121     public void reset() throws IOException {
122         throw new IOException("mark/reset not supported");
123     }
124 
125     public synchronized boolean hurry() {
126         try {
127             /* CASE 0: we're actually already done */
128             if (closed || count >= expected) {
129                 return false;
130             } else if (in.available() < (expected - count)) {
131                 /* CASE I: can't meet the demand */
132                 return false;
133             } else {
134                 /* CASE II: fill our internal buffer
135                  * Remind: possibly check memory here
136                  */
137                 int size = (int) (expected - count);
138                 byte[] buf = new byte[size];
139                 DataInputStream dis = new DataInputStream(in);
140                 dis.readFully(buf);
141                 in = new ByteArrayInputStream(buf);
142                 hurried = true;
143                 return true;
144             }
145         } catch (IOException e) {
146             // e.printStackTrace();
147             return false;
148         }
149     }
150 
151     private static void queueForCleanup(KeepAliveCleanerEntry kace) {
152         synchronized(queue) {
153             if(!kace.getQueuedForCleanup()) {
154                 if (!queue.offer(kace)) {
155                     kace.getHttpClient().closeServer();
156                     return;
157                 }
158 
159                 kace.setQueuedForCleanup();
160                 queue.notifyAll();
161             }
162 
163             boolean startCleanupThread = (cleanerThread == null);
164             if (!startCleanupThread) {
165                 if (!cleanerThread.isAlive()) {
166                     startCleanupThread = true;
167                 }
168             }
169 
170             if (startCleanupThread) {
171                 java.security.AccessController.doPrivileged(
172                     new java.security.PrivilegedAction<Void>() {
173                     public Void run() {
174                         // We want to create the Keep-Alive-SocketCleaner in the
175                         // system threadgroup
176                         ThreadGroup grp = Thread.currentThread().getThreadGroup();
177                         ThreadGroup parent = null;
178                         while ((parent = grp.getParent()) != null) {
179                             grp = parent;
180                         }
181 
182                         cleanerThread = new Thread(grp, queue, "Keep-Alive-SocketCleaner");
183                         cleanerThread.setDaemon(true);
184                         cleanerThread.setPriority(Thread.MAX_PRIORITY - 2);
185                         // Set the context class loader to null in order to avoid
186                         // keeping a strong reference to an application classloader.
187                         cleanerThread.setContextClassLoader(null);
188                         cleanerThread.start();
189                         return null;
190                     }
191                 });
192             }
193         } // queue
194     }
195 
196     protected long remainingToRead() {
197         return expected - count;
198     }
199 
200     protected void setClosed() {
201         in = null;
202         hc = null;
203         closed = true;
204     }
205 }