PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/elasticsearch/src/main/java/org/elasticsearch/common/http/client/HttpDownloadHelper.java

https://github.com/avar/elasticsearch
Java | 400 lines | 231 code | 54 blank | 115 comment | 42 complexity | 175d08c8d50e80110d8f05f16dbe1070 MD5 | raw file
  1. /*
  2. * Licensed to Elastic Search and Shay Banon under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. Elastic Search licenses this
  6. * file to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. package org.elasticsearch.common.http.client;
  20. import org.elasticsearch.common.Nullable;
  21. import java.io.File;
  22. import java.io.FileNotFoundException;
  23. import java.io.FileOutputStream;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.io.OutputStream;
  27. import java.io.PrintStream;
  28. import java.net.HttpURLConnection;
  29. import java.net.URL;
  30. import java.net.URLConnection;
  31. /**
  32. * @author kimchy (shay.banon)
  33. */
  34. public class HttpDownloadHelper {
  35. private boolean useTimestamp = false;
  36. private boolean skipExisting = false;
  37. private long maxTime = 0;
  38. public boolean download(URL source, File dest, @Nullable DownloadProgress progress) throws IOException {
  39. if (dest.exists() && skipExisting) {
  40. return true;
  41. }
  42. //don't do any progress, unless asked
  43. if (progress == null) {
  44. progress = new NullProgress();
  45. }
  46. //set the timestamp to the file date.
  47. long timestamp = 0;
  48. boolean hasTimestamp = false;
  49. if (useTimestamp && dest.exists()) {
  50. timestamp = dest.lastModified();
  51. hasTimestamp = true;
  52. }
  53. GetThread getThread = new GetThread(source, dest, hasTimestamp, timestamp, progress);
  54. getThread.setDaemon(true);
  55. getThread.start();
  56. try {
  57. getThread.join(maxTime * 1000);
  58. } catch (InterruptedException ie) {
  59. // ignore
  60. }
  61. if (getThread.isAlive()) {
  62. String msg = "The GET operation took longer than " + maxTime
  63. + " seconds, stopping it.";
  64. getThread.closeStreams();
  65. throw new IOException(msg);
  66. }
  67. return getThread.wasSuccessful();
  68. }
  69. /**
  70. * Interface implemented for reporting
  71. * progress of downloading.
  72. */
  73. public interface DownloadProgress {
  74. /**
  75. * begin a download
  76. */
  77. void beginDownload();
  78. /**
  79. * tick handler
  80. */
  81. void onTick();
  82. /**
  83. * end a download
  84. */
  85. void endDownload();
  86. }
  87. /**
  88. * do nothing with progress info
  89. */
  90. public static class NullProgress implements DownloadProgress {
  91. /**
  92. * begin a download
  93. */
  94. public void beginDownload() {
  95. }
  96. /**
  97. * tick handler
  98. */
  99. public void onTick() {
  100. }
  101. /**
  102. * end a download
  103. */
  104. public void endDownload() {
  105. }
  106. }
  107. /**
  108. * verbose progress system prints to some output stream
  109. */
  110. public static class VerboseProgress implements DownloadProgress {
  111. private int dots = 0;
  112. // CheckStyle:VisibilityModifier OFF - bc
  113. PrintStream out;
  114. // CheckStyle:VisibilityModifier ON
  115. /**
  116. * Construct a verbose progress reporter.
  117. *
  118. * @param out the output stream.
  119. */
  120. public VerboseProgress(PrintStream out) {
  121. this.out = out;
  122. }
  123. /**
  124. * begin a download
  125. */
  126. public void beginDownload() {
  127. out.print("Downloading ");
  128. dots = 0;
  129. }
  130. /**
  131. * tick handler
  132. */
  133. public void onTick() {
  134. out.print(".");
  135. if (dots++ > 50) {
  136. out.flush();
  137. dots = 0;
  138. }
  139. }
  140. /**
  141. * end a download
  142. */
  143. public void endDownload() {
  144. out.println("DONE");
  145. out.flush();
  146. }
  147. }
  148. private class GetThread extends Thread {
  149. private final URL source;
  150. private final File dest;
  151. private final boolean hasTimestamp;
  152. private final long timestamp;
  153. private final DownloadProgress progress;
  154. private boolean success = false;
  155. private IOException ioexception = null;
  156. private InputStream is = null;
  157. private OutputStream os = null;
  158. private URLConnection connection;
  159. private int redirections = 0;
  160. GetThread(URL source, File dest, boolean h, long t, DownloadProgress p) {
  161. this.source = source;
  162. this.dest = dest;
  163. hasTimestamp = h;
  164. timestamp = t;
  165. progress = p;
  166. }
  167. public void run() {
  168. try {
  169. success = get();
  170. } catch (IOException ioex) {
  171. ioexception = ioex;
  172. }
  173. }
  174. private boolean get() throws IOException {
  175. connection = openConnection(source);
  176. if (connection == null) {
  177. return false;
  178. }
  179. boolean downloadSucceeded = downloadFile();
  180. //if (and only if) the use file time option is set, then
  181. //the saved file now has its timestamp set to that of the
  182. //downloaded file
  183. if (downloadSucceeded && useTimestamp) {
  184. updateTimeStamp();
  185. }
  186. return downloadSucceeded;
  187. }
  188. private boolean redirectionAllowed(URL aSource, URL aDest) throws IOException {
  189. // Argh, github does this...
  190. // if (!(aSource.getProtocol().equals(aDest.getProtocol()) || ("http"
  191. // .equals(aSource.getProtocol()) && "https".equals(aDest
  192. // .getProtocol())))) {
  193. // String message = "Redirection detected from "
  194. // + aSource.getProtocol() + " to " + aDest.getProtocol()
  195. // + ". Protocol switch unsafe, not allowed.";
  196. // throw new IOException(message);
  197. // }
  198. redirections++;
  199. if (redirections > 5) {
  200. String message = "More than " + 5 + " times redirected, giving up";
  201. throw new IOException(message);
  202. }
  203. return true;
  204. }
  205. private URLConnection openConnection(URL aSource) throws IOException {
  206. // set up the URL connection
  207. URLConnection connection = aSource.openConnection();
  208. // modify the headers
  209. // NB: things like user authentication could go in here too.
  210. if (hasTimestamp) {
  211. connection.setIfModifiedSince(timestamp);
  212. }
  213. if (connection instanceof HttpURLConnection) {
  214. ((HttpURLConnection) connection).setInstanceFollowRedirects(false);
  215. ((HttpURLConnection) connection).setUseCaches(true);
  216. ((HttpURLConnection) connection).setConnectTimeout(5000);
  217. }
  218. // connect to the remote site (may take some time)
  219. connection.connect();
  220. // First check on a 301 / 302 (moved) response (HTTP only)
  221. if (connection instanceof HttpURLConnection) {
  222. HttpURLConnection httpConnection = (HttpURLConnection) connection;
  223. int responseCode = httpConnection.getResponseCode();
  224. if (responseCode == HttpURLConnection.HTTP_MOVED_PERM ||
  225. responseCode == HttpURLConnection.HTTP_MOVED_TEMP ||
  226. responseCode == HttpURLConnection.HTTP_SEE_OTHER) {
  227. String newLocation = httpConnection.getHeaderField("Location");
  228. String message = aSource
  229. + (responseCode == HttpURLConnection.HTTP_MOVED_PERM ? " permanently"
  230. : "") + " moved to " + newLocation;
  231. URL newURL = new URL(newLocation);
  232. if (!redirectionAllowed(aSource, newURL)) {
  233. return null;
  234. }
  235. return openConnection(newURL);
  236. }
  237. // next test for a 304 result (HTTP only)
  238. long lastModified = httpConnection.getLastModified();
  239. if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED
  240. || (lastModified != 0 && hasTimestamp && timestamp >= lastModified)) {
  241. // not modified so no file download. just return
  242. // instead and trace out something so the user
  243. // doesn't think that the download happened when it
  244. // didn't
  245. return null;
  246. }
  247. // test for 401 result (HTTP only)
  248. if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
  249. String message = "HTTP Authorization failure";
  250. throw new IOException(message);
  251. }
  252. }
  253. //REVISIT: at this point even non HTTP connections may
  254. //support the if-modified-since behaviour -we just check
  255. //the date of the content and skip the write if it is not
  256. //newer. Some protocols (FTP) don't include dates, of
  257. //course.
  258. return connection;
  259. }
  260. private boolean downloadFile() throws FileNotFoundException, IOException {
  261. IOException lastEx = null;
  262. for (int i = 0; i < 3; i++) {
  263. // this three attempt trick is to get round quirks in different
  264. // Java implementations. Some of them take a few goes to bind
  265. // property; we ignore the first couple of such failures.
  266. try {
  267. is = connection.getInputStream();
  268. break;
  269. } catch (IOException ex) {
  270. lastEx = ex;
  271. }
  272. }
  273. if (is == null) {
  274. throw new IOException("Can't get " + source + " to " + dest, lastEx);
  275. }
  276. os = new FileOutputStream(dest);
  277. progress.beginDownload();
  278. boolean finished = false;
  279. try {
  280. byte[] buffer = new byte[1024 * 100];
  281. int length;
  282. while (!isInterrupted() && (length = is.read(buffer)) >= 0) {
  283. os.write(buffer, 0, length);
  284. progress.onTick();
  285. }
  286. finished = !isInterrupted();
  287. } finally {
  288. try {
  289. os.close();
  290. } catch (IOException e) {
  291. // ignore
  292. }
  293. try {
  294. is.close();
  295. } catch (IOException e) {
  296. // ignore
  297. }
  298. // we have started to (over)write dest, but failed.
  299. // Try to delete the garbage we'd otherwise leave
  300. // behind.
  301. if (!finished) {
  302. dest.delete();
  303. }
  304. }
  305. progress.endDownload();
  306. return true;
  307. }
  308. private void updateTimeStamp() {
  309. long remoteTimestamp = connection.getLastModified();
  310. if (remoteTimestamp != 0) {
  311. dest.setLastModified(remoteTimestamp);
  312. }
  313. }
  314. /**
  315. * Has the download completed successfully?
  316. *
  317. * <p>Re-throws any exception caught during executaion.</p>
  318. */
  319. boolean wasSuccessful() throws IOException {
  320. if (ioexception != null) {
  321. throw ioexception;
  322. }
  323. return success;
  324. }
  325. /**
  326. * Closes streams, interrupts the download, may delete the
  327. * output file.
  328. */
  329. void closeStreams() {
  330. interrupt();
  331. try {
  332. os.close();
  333. } catch (IOException e) {
  334. // ignore
  335. }
  336. try {
  337. is.close();
  338. } catch (IOException e) {
  339. // ignore
  340. }
  341. if (!success && dest.exists()) {
  342. dest.delete();
  343. }
  344. }
  345. }
  346. }