| WebResource.java |
1 /**
2 * Distribution License:
3 * BibleDesktop is free software; you can redistribute it and/or modify it under
4 * the terms of the GNU Lesser General Public License, version 2.1 as published by
5 * the Free Software Foundation. This program is distributed in the hope
6 * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8 * See the GNU Lesser General Public License for more details.
9 *
10 * The License is available on the internet at:
11 * http://www.gnu.org/copyleft/lgpl.html
12 * or by writing to:
13 * Free Software Foundation, Inc.
14 * 59 Temple Place - Suite 330
15 * Boston, MA 02111-1307, USA
16 *
17 * Copyright: 2005
18 * The copyright to this program is held by it's authors.
19 *
20 * ID: $Id: WebResource.java 2099 2011-03-07 17:13:00Z dmsmith $
21 */
22 package org.crosswire.common.util;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.net.ProxySelector;
28 import java.net.URI;
29 import java.util.Date;
30
31 import org.apache.http.Header;
32 import org.apache.http.HttpEntity;
33 import org.apache.http.HttpHost;
34 import org.apache.http.HttpResponse;
35 import org.apache.http.HttpStatus;
36 import org.apache.http.StatusLine;
37 import org.apache.http.client.HttpClient;
38 import org.apache.http.client.methods.HttpGet;
39 import org.apache.http.client.methods.HttpHead;
40 import org.apache.http.client.methods.HttpRequestBase;
41 import org.apache.http.conn.params.ConnRouteParams;
42 import org.apache.http.impl.client.AbstractHttpClient;
43 import org.apache.http.impl.client.DefaultHttpClient;
44 import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
45 import org.apache.http.params.HttpConnectionParams;
46 import org.apache.http.params.HttpParams;
47 import org.crosswire.common.progress.Progress;
48 import org.crosswire.jsword.JSMsg;
49
50 /**
51 * A WebResource is backed by an URL and potentially the proxy through which it
52 * need go. It can get basic information about the resource and it can get the
53 * resource. The requests are subject to a timeout, which can be set via the
54 * constructor or previously by a call to set the default timeout. The initial
55 * default timeout is 750 milliseconds.
56 *
57 *
58 * @see gnu.lgpl.License for license details.<br>
59 * The copyright to this program is held by it's authors.
60 * @author DM Smith [dmsmith555 at yahoo dot com]
61 */
62 public class WebResource {
63 /**
64 * Construct a WebResource for the given URL, while timing out if too much
65 * time has passed.
66 *
67 * @param theURI
68 * the Resource to get via HTTP
69 */
70 public WebResource(URI theURI) {
71 this(theURI, null, null, timeout);
72 }
73
74 /**
75 * Construct a WebResource for the given URL, while timing out if too much
76 * time has passed.
77 *
78 * @param theURI
79 * the Resource to get via HTTP
80 * @param theTimeout
81 * the length of time in milliseconds to allow a connection to
82 * respond before timing out
83 */
84 public WebResource(URI theURI, int theTimeout) {
85 this(theURI, null, null, theTimeout);
86 }
87
88 /**
89 * Construct a WebResource for the given URL, going through the optional
90 * proxy and default port, while timing out if too much time has passed.
91 *
92 * @param theURI
93 * the Resource to get via HTTP
94 * @param theProxyHost
95 * the proxy host or null
96 */
97 public WebResource(URI theURI, String theProxyHost) {
98 this(theURI, theProxyHost, null, timeout);
99 }
100
101 /**
102 * Construct a WebResource for the given URL, going through the optional
103 * proxy and default port, while timing out if too much time has passed.
104 *
105 * @param theURI
106 * the Resource to get via HTTP
107 * @param theProxyHost
108 * the proxy host or null
109 * @param theTimeout
110 * the length of time in milliseconds to allow a connection to
111 * respond before timing out
112 */
113 public WebResource(URI theURI, String theProxyHost, int theTimeout) {
114 this(theURI, theProxyHost, null, theTimeout);
115 }
116
117 /**
118 * Construct a WebResource for the given URL, going through the optional
119 * proxy and port, while timing out if too much time has passed.
120 *
121 * @param theURI
122 * the Resource to get via HTTP
123 * @param theProxyHost
124 * the proxy host or null
125 * @param theProxyPort
126 * the proxy port or null, where null means use the standard port
127 */
128 public WebResource(URI theURI, String theProxyHost, Integer theProxyPort) {
129 this(theURI, theProxyHost, theProxyPort, timeout);
130 }
131
132 /**
133 * Construct a WebResource for the given URL, going through the optional
134 * proxy and port, while timing out if too much time has passed.
135 *
136 * @param theURI
137 * the Resource to get via HTTP
138 * @param theProxyHost
139 * the proxy host or null
140 * @param theProxyPort
141 * the proxy port or null, where null means use the standard port
142 * @param theTimeout
143 * the length of time in milliseconds to allow a connection to
144 * respond before timing out
145 */
146 public WebResource(URI theURI, String theProxyHost, Integer theProxyPort, int theTimeout) {
147 uri = theURI;
148 client = new DefaultHttpClient();
149 HttpParams params = client.getParams();
150
151 // Allowable time between packets
152 HttpConnectionParams.setSoTimeout(params, theTimeout);
153 // Allowable time to get a connection
154 HttpConnectionParams.setConnectionTimeout(params, theTimeout);
155
156 // Configure proxy info if necessary and defined
157 if (theProxyHost != null && theProxyHost.length() > 0) {
158 // Configure the host and port
159 HttpHost proxy = new HttpHost(theProxyHost, theProxyPort == null ? -1 : theProxyPort.intValue());
160 ConnRouteParams.setDefaultProxy(params, proxy);
161 }
162 ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(
163 client.getConnectionManager().getSchemeRegistry(),
164 ProxySelector.getDefault());
165 ((AbstractHttpClient) client).setRoutePlanner(routePlanner);
166 }
167
168 /**
169 * When this WebResource is no longer needed it should be shutdown to return
170 * underlying resources back to the OS.
171 */
172 public void shutdown() {
173 client.getConnectionManager().shutdown();
174 }
175
176 /**
177 * @return the timeout in milliseconds
178 */
179 public static int getTimeout() {
180 return timeout;
181 }
182
183 /**
184 * @param timeout
185 * the timeout to set in milliseconds
186 */
187 public static void setTimeout(int timeout) {
188 WebResource.timeout = timeout;
189 }
190
191 /**
192 * Determine the size of this WebResource.
193 * <p>
194 * Note that the http client may read the entire file to determine this.
195 * </p>
196 *
197 * @return the size of the file
198 */
199 public int getSize() {
200 HttpRequestBase method = new HttpHead(uri);
201 HttpResponse response = null;
202 try {
203 // Execute the method.
204 response = client.execute(method);
205 StatusLine statusLine = response.getStatusLine();
206 if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
207 return getHeaderAsInt(response, "Content-Length");
208 }
209 String reason = response.getStatusLine().getReasonPhrase();
210 // TRANSLATOR: Common error condition: {0} is a placeholder for the
211 // URL of what could not be found.
212 Reporter.informUser(this, JSMsg.gettext("Unable to find: {0}", reason + ':' + uri.getPath()));
213 } catch (IOException e) {
214 return 0;
215 }
216 return 0;
217 }
218
219 /**
220 * Determine the last modified date of this WebResource.
221 * <p>
222 * Note that the http client may read the entire file.
223 * </p>
224 *
225 * @return the last mod date of the file
226 */
227 public long getLastModified() {
228 HttpRequestBase method = new HttpHead(uri);
229 HttpResponse response = null;
230 try {
231 // Execute the method.
232 response = client.execute(method);
233 StatusLine statusLine = response.getStatusLine();
234 if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
235 return getHeaderAsDate(response, "Last-Modified");
236 }
237 String reason = response.getStatusLine().getReasonPhrase();
238 // TRANSLATOR: Common error condition: {0} is a placeholder for the
239 // URL of what could not be found.
240 Reporter.informUser(this, JSMsg.gettext("Unable to find: {0}", reason + ':' + uri.getPath()));
241 } catch (IOException e) {
242 return new Date().getTime();
243 }
244 return new Date().getTime();
245 }
246
247 /**
248 * Copy this WebResource to the destination and report progress.
249 *
250 * @param dest
251 * the URI of the destination, typically a file:///.
252 * @param meter
253 * the job on which to report progress
254 * @throws LucidException
255 */
256 public void copy(URI dest, Progress meter) throws LucidException {
257 InputStream in = null;
258 OutputStream out = null;
259 HttpRequestBase method = new HttpGet(uri);
260 HttpResponse response = null;
261 HttpEntity entity = null;
262 try {
263 // Execute the method.
264 response = client.execute(method);
265 // Initialize the meter, if present
266 if (meter != null) {
267 // Find out how big it is
268 int size = getHeaderAsInt(response, "Content-Length");
269 // Sometimes the Content-Length is not given and we have to grab it via HEAD method
270 if (size == 0) {
271 size = getSize();
272 }
273 meter.setTotalWork(size);
274 }
275
276 entity = response.getEntity();
277 if (entity != null) {
278 in = entity.getContent();
279
280 // Download the index file
281 out = NetUtil.getOutputStream(dest);
282
283 byte[] buf = new byte[4096];
284 int count = in.read(buf);
285 while (-1 != count) {
286 if (meter != null) {
287 meter.incrementWorkDone(count);
288 }
289 out.write(buf, 0, count);
290 count = in.read(buf);
291 }
292 } else {
293 String reason = response.getStatusLine().getReasonPhrase();
294 // TRANSLATOR: Common error condition: {0} is a placeholder for
295 // the URL of what could not be found.
296 Reporter.informUser(this, JSMsg.gettext("Unable to find: {0}", reason + ':' + uri.getPath()));
297 }
298 } catch (IOException e) {
299 // TRANSLATOR: Common error condition: {0} is a placeholder for the
300 // URL of what could not be found.
301 throw new LucidException(JSMsg.gettext("Unable to find: {0}", uri.toString()), e);
302 } finally {
303 // Close the streams
304 IOUtil.close(in);
305 IOUtil.close(out);
306 }
307 }
308
309 /**
310 * Copy this WebResource to the destination.
311 *
312 * @param dest
313 * @throws LucidException
314 */
315 public void copy(URI dest) throws LucidException {
316 copy(dest, null);
317 }
318
319 /**
320 * Get the field as a long.
321 *
322 * @param response The response from the request
323 * @param field the header field to check
324 * @return the int value for the field
325 */
326 private int getHeaderAsInt(HttpResponse response, String field) {
327 Header header = response.getFirstHeader(field);
328 String value = header.getValue();
329 try {
330 return Integer.parseInt(value);
331 } catch (NumberFormatException ex) {
332 return 0;
333 }
334 }
335
336 /**
337 * Get the number of seconds since start of epoch for the field in the response headers as a Date.
338 *
339 * @param response The response from the request
340 * @param field the header field to check
341 * @return number of seconds since start of epoch
342 */
343 @SuppressWarnings("deprecation")
344 private long getHeaderAsDate(HttpResponse response, String field) {
345 Header header = response.getFirstHeader(field);
346 String value = header.getValue();
347 try {
348 // This date cannot be readily parsed with DateFormatter
349 return Date.parse(value);
350 } catch (IllegalArgumentException ex) {
351 return 0;
352 }
353 }
354 /**
355 * Define a 750 ms timeout to get a connection
356 */
357 private static int timeout = 750;
358
359 private URI uri;
360 private HttpClient client;
361 }
362