From f54a9d8949803261a87f5bb5d5a1adcb528d1d41 Mon Sep 17 00:00:00 2001 From: Flyge Date: Sun, 1 Oct 2017 23:55:33 +0800 Subject: [PATCH] :sparkles: Support for redirection --- .../sketch/decode/ImageDecodeUtils.java | 10 +++--- .../xiaopan/sketch/decode/ImageDecoder.java | 5 +++ .../me/xiaopan/sketch/http/HttpStack.java | 6 ++++ .../me/xiaopan/sketch/http/HurlStack.java | 7 ++++ .../xiaopan/sketch/http/ImageDownloader.java | 36 +++++++++++++++---- .../sketch/http/RedirectsException.java | 32 +++++++++++++++++ 6 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 sketch/src/main/java/me/xiaopan/sketch/http/RedirectsException.java diff --git a/sketch/src/main/java/me/xiaopan/sketch/decode/ImageDecodeUtils.java b/sketch/src/main/java/me/xiaopan/sketch/decode/ImageDecodeUtils.java index 60f5d85fad..339c38a3bd 100644 --- a/sketch/src/main/java/me/xiaopan/sketch/decode/ImageDecodeUtils.java +++ b/sketch/src/main/java/me/xiaopan/sketch/decode/ImageDecodeUtils.java @@ -103,7 +103,7 @@ static void decodeSuccess(@NonNull Bitmap bitmap, int outWidth, int outHeight, i } } - static void decodeError(LoadRequest loadRequest, DataSource dataSource, String logName, String cause, Throwable tr) { + static void decodeError(LoadRequest request, DataSource dataSource, String logName, String cause, Throwable tr) { if (tr != null) { SLog.e(logName, Log.getStackTraceString(tr)); } @@ -112,17 +112,17 @@ static void decodeError(LoadRequest loadRequest, DataSource dataSource, String l DiskCache.Entry diskCacheEntry = ((DiskCacheDataSource) dataSource).getDiskCacheEntry(); File cacheFile = diskCacheEntry.getFile(); if (diskCacheEntry.delete()) { - SLog.e(logName, "Decode failed. %s. Disk cache deleted. fileLength=%d. %s", cause, cacheFile.length(), loadRequest.getKey(), tr); + SLog.e(logName, "Decode failed. %s. Disk cache deleted. fileLength=%d. %s", cause, cacheFile.length(), request.getKey(), tr); } else { - SLog.e(logName, "Decode failed. %s. Disk cache can not be deleted. fileLength=%d. %s", cause, cacheFile.length(), loadRequest.getKey()); + SLog.e(logName, "Decode failed. %s. Disk cache can not be deleted. fileLength=%d. %s", cause, cacheFile.length(), request.getKey()); } } else if (dataSource instanceof FileDataSource) { File file = ((FileDataSource) dataSource).getFile(null, null); //noinspection ConstantConditions SLog.e(logName, "Decode failed. %s. filePath=%s, fileLength=%d. %s", - cause, file.getPath(), file.exists() ? file.length() : -1); + cause, file.getPath(), file.exists() ? file.length() : -1, request.getKey()); } else { - SLog.e(logName, "Decode failed. %s. %s", cause, loadRequest.getUri()); + SLog.e(logName, "Decode failed. %s. %s", cause, request.getUri()); } } diff --git a/sketch/src/main/java/me/xiaopan/sketch/decode/ImageDecoder.java b/sketch/src/main/java/me/xiaopan/sketch/decode/ImageDecoder.java index a8ab59a33d..cf1adf2ebe 100644 --- a/sketch/src/main/java/me/xiaopan/sketch/decode/ImageDecoder.java +++ b/sketch/src/main/java/me/xiaopan/sketch/decode/ImageDecoder.java @@ -83,6 +83,11 @@ public DecodeResult decode(@NonNull LoadRequest request) throws DecodeException } return result; + } catch (DecodeException e) { + if (result != null) { + result.recycle(request.getConfiguration().getBitmapPool()); + } + throw e; } catch (Throwable tr) { if (result != null) { result.recycle(request.getConfiguration().getBitmapPool()); diff --git a/sketch/src/main/java/me/xiaopan/sketch/http/HttpStack.java b/sketch/src/main/java/me/xiaopan/sketch/http/HttpStack.java index 64a971e98f..a3fb453bf3 100644 --- a/sketch/src/main/java/me/xiaopan/sketch/http/HttpStack.java +++ b/sketch/src/main/java/me/xiaopan/sketch/http/HttpStack.java @@ -148,6 +148,12 @@ interface ImageHttpResponse { */ boolean isContentChunked(); + /** + * 获取响应头 + */ + @Nullable + String getResponseHeader(@NonNull String name); + /** * 获取所有的响应头 */ diff --git a/sketch/src/main/java/me/xiaopan/sketch/http/HurlStack.java b/sketch/src/main/java/me/xiaopan/sketch/http/HurlStack.java index 215a3aff7e..29dbd843e7 100644 --- a/sketch/src/main/java/me/xiaopan/sketch/http/HurlStack.java +++ b/sketch/src/main/java/me/xiaopan/sketch/http/HurlStack.java @@ -18,6 +18,7 @@ import android.os.Build; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import java.io.IOException; import java.io.InputStream; @@ -243,5 +244,11 @@ public boolean isContentChunked() { } return transferEncodingValue != null && "chunked".equalsIgnoreCase(transferEncodingValue); } + + @Nullable + @Override + public String getResponseHeader(@NonNull String name) { + return connection.getHeaderField(name); + } } } diff --git a/sketch/src/main/java/me/xiaopan/sketch/http/ImageDownloader.java b/sketch/src/main/java/me/xiaopan/sketch/http/ImageDownloader.java index 8473e72793..645ce51d56 100644 --- a/sketch/src/main/java/me/xiaopan/sketch/http/ImageDownloader.java +++ b/sketch/src/main/java/me/xiaopan/sketch/http/ImageDownloader.java @@ -17,6 +17,7 @@ package me.xiaopan.sketch.http; import android.support.annotation.NonNull; +import android.text.TextUtils; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; @@ -102,9 +103,12 @@ private DownloadResult loopRetryDownload(@NonNull DownloadRequest request, @NonN HttpStack httpStack = request.getConfiguration().getHttpStack(); int retryCount = 0; final int maxRetryCount = httpStack.getMaxRetryCount(); + String uri = request.getUri(); while (true) { try { - return doDownload(request, httpStack, diskCache, diskCacheKey); + return doDownload(request, uri, httpStack, diskCache, diskCacheKey); + } catch (RedirectsException e) { + uri = e.getNewUrl(); } catch (Throwable tr) { request.getConfiguration().getErrorTracker().onDownloadError(request, tr); @@ -136,6 +140,7 @@ private DownloadResult loopRetryDownload(@NonNull DownloadRequest request, @NonN * Real execute download * * @param request DownloadRequest + * @param uri image uri * @param httpStack HttpStack * @param diskCache DiskCache * @param diskCacheKey disk cache key @@ -145,15 +150,15 @@ private DownloadResult loopRetryDownload(@NonNull DownloadRequest request, @NonN * @throws DownloadException download failed */ @NonNull - private DownloadResult doDownload(@NonNull DownloadRequest request, @NonNull HttpStack httpStack, + private DownloadResult doDownload(@NonNull DownloadRequest request, @NonNull String uri, @NonNull HttpStack httpStack, @NonNull DiskCache diskCache, @NonNull String diskCacheKey) - throws IOException, CanceledException, DownloadException { + throws IOException, CanceledException, DownloadException, RedirectsException { // Opening http connection request.setStatus(BaseRequest.Status.CONNECTING); HttpStack.ImageHttpResponse httpResponse; //noinspection CaughtExceptionImmediatelyRethrown try { - httpResponse = httpStack.getHttpResponse(request.getUri()); + httpResponse = httpStack.getHttpResponse(uri); } catch (IOException e) { throw e; } @@ -180,6 +185,25 @@ private DownloadResult doDownload(@NonNull DownloadRequest request, @NonNull Htt } if (responseCode != 200) { httpResponse.releaseConnection(); + + // redirects + if (responseCode == 301 || responseCode == 302) { + String newUri = httpResponse.getResponseHeader("Location"); + if (!TextUtils.isEmpty(newUri)) { + // To prevent infinite redirection + if (uri.equals(request.getUri())) { + if (SLog.isLoggable(SLog.LEVEL_DEBUG | SLog.TYPE_FLOW)) { + SLog.d(NAME, "Uri redirects. originUri: %s, newUri: %s. %s", request.getUri(), newUri, request.getKey()); + } + throw new RedirectsException(newUri); + } else { + SLog.e(NAME, "Disable unlimited redirects, originUri: %s, redirectsUri=%s, newUri=%s. %s", request.getUri(), uri, newUri, request.getKey()); + } + } else { + SLog.w(NAME, "Uri redirects failed. newUri is empty, originUri: %s. %s", request.getUri(), request.getKey()); + } + } + String message = String.format("Response code exception. responseHeaders: %s. %s. %s", httpResponse.getResponseHeadersString(), request.getThreadName(), request.getKey()); SLog.e(NAME, message); @@ -236,13 +260,12 @@ private DownloadResult doDownload(@NonNull DownloadRequest request, @NonNull Htt // Read data request.setStatus(BaseRequest.Status.READ_DATA); - int completedLength = 0; + int completedLength; try { completedLength = readData(request, inputStream, outputStream, (int) contentLength); } catch (IOException e) { if (diskCacheEditor != null) { diskCacheEditor.abort(); - diskCacheEditor = null; } String message = String.format("Read data exception. %s. %s", request.getThreadName(), request.getKey()); SLog.e(NAME, e, message); @@ -250,7 +273,6 @@ private DownloadResult doDownload(@NonNull DownloadRequest request, @NonNull Htt } catch (CanceledException e) { if (diskCacheEditor != null) { diskCacheEditor.abort(); - diskCacheEditor = null; } throw e; } finally { diff --git a/sketch/src/main/java/me/xiaopan/sketch/http/RedirectsException.java b/sketch/src/main/java/me/xiaopan/sketch/http/RedirectsException.java new file mode 100644 index 0000000000..ca9e49d64e --- /dev/null +++ b/sketch/src/main/java/me/xiaopan/sketch/http/RedirectsException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 Peng fei Pan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.xiaopan.sketch.http; + +import android.support.annotation.NonNull; + +public class RedirectsException extends Exception { + private String newUrl; + + public RedirectsException(@NonNull String newUrl) { + this.newUrl = newUrl; + } + + @NonNull + public String getNewUrl() { + return newUrl; + } +}