1 package com.bumptech.glide.load.data; 2 3 import android.text.TextUtils; 4 5 import com.bumptech.glide.Priority; 6 import com.bumptech.glide.load.model.GlideUrl; 7 8 import java.io.IOException; 9 import java.io.InputStream; 10 import java.net.HttpURLConnection; 11 import java.net.URISyntaxException; 12 import java.net.URL; 13 14 /** 15 * A DataFetcher that retrieves an {@link java.io.InputStream} for a Url. 16 */ 17 public class HttpUrlFetcher implements DataFetcher<InputStream> { 18 private static final int MAXIMUM_REDIRECTS = 5; 19 private static final HttpUrlConnectionFactory DEFAULT_CONNECTION_FACTORY = new DefaultHttpUrlConnectionFactory(); 20 21 private final GlideUrl glideUrl; 22 private final HttpUrlConnectionFactory connectionFactory; 23 24 private HttpURLConnection urlConnection; 25 private InputStream stream; 26 private volatile boolean isCancelled; 27 28 public HttpUrlFetcher(GlideUrl glideUrl) { 29 this(glideUrl, DEFAULT_CONNECTION_FACTORY); 30 } 31 32 // Visible for testing. 33 HttpUrlFetcher(GlideUrl glideUrl, HttpUrlConnectionFactory connectionFactory) { 34 this.glideUrl = glideUrl; 35 this.connectionFactory = connectionFactory; 36 } 37 38 @Override 39 public InputStream loadData(Priority priority) throws Exception { 40 return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/); 41 } 42 43 private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl) throws IOException { 44 if (redirects >= MAXIMUM_REDIRECTS) { 45 throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!"); 46 } else { 47 // Comparing the URLs using .equals performs additional network I/O and is generally broken. 48 // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html. 49 try { 50 if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) { 51 throw new IOException("In re-direct loop"); 52 } 53 } catch (URISyntaxException e) { 54 // Do nothing, this is best effort. 55 } 56 } 57 urlConnection = connectionFactory.build(url); 58 urlConnection.setConnectTimeout(2500); 59 urlConnection.setReadTimeout(2500); 60 urlConnection.setUseCaches(false); 61 urlConnection.setDoInput(true); 62 63 // Connect explicitly to avoid errors in decoders if connection fails. 64 urlConnection.connect(); 65 if (isCancelled) { 66 return null; 67 } 68 final int statusCode = urlConnection.getResponseCode(); 69 if (statusCode / 100 == 2) { 70 stream = urlConnection.getInputStream(); 71 return stream; 72 } else if (statusCode / 100 == 3) { 73 String redirectUrlString = urlConnection.getHeaderField("Location"); 74 if (TextUtils.isEmpty(redirectUrlString)) { 75 throw new IOException("Received empty or null redirect url"); 76 } 77 URL redirectUrl = new URL(url, redirectUrlString); 78 return loadDataWithRedirects(redirectUrl, redirects + 1, url); 79 } else { 80 if (statusCode == -1) { 81 throw new IOException("Unable to retrieve response code from HttpUrlConnection."); 82 } 83 throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage()); 84 } 85 } 86 87 @Override 88 public void cleanup() { 89 if (stream != null) { 90 try { 91 stream.close(); 92 } catch (IOException e) { 93 // Ignore 94 } 95 } 96 if (urlConnection != null) { 97 urlConnection.disconnect(); 98 } 99 } 100 101 @Override 102 public String getId() { 103 return glideUrl.toString(); 104 } 105 106 @Override 107 public void cancel() { 108 // TODO: we should consider disconnecting the url connection here, but we can't do so directly because cancel is 109 // often called on the main thread. 110 isCancelled = true; 111 } 112 113 interface HttpUrlConnectionFactory { 114 HttpURLConnection build(URL url) throws IOException; 115 } 116 117 private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory { 118 @Override 119 public HttpURLConnection build(URL url) throws IOException { 120 return (HttpURLConnection) url.openConnection(); 121 } 122 } 123 } 124