From 6f0af8b6ec077cdf60560c0a3bcd91db62ad968f Mon Sep 17 00:00:00 2001 From: seven332 Date: Thu, 14 Aug 2014 15:19:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=81=E8=AE=B8=20Http=20=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E9=87=8D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- res/layout/preview_list.xml | 8 +- src/com/hippo/ehviewer/ehclient/EhClient.java | 2 +- .../hippo/ehviewer/network/HttpHelper.java | 171 +++++++------ .../util/FastByteArrayOutputStream.java | 227 ++++++++++++++++++ 4 files changed, 324 insertions(+), 84 deletions(-) create mode 100644 src/com/hippo/ehviewer/util/FastByteArrayOutputStream.java diff --git a/res/layout/preview_list.xml b/res/layout/preview_list.xml index 23309adce..8b9c3d41c 100644 --- a/res/layout/preview_list.xml +++ b/res/layout/preview_list.xml @@ -17,8 +17,8 @@ @@ -33,7 +33,7 @@ diff --git a/src/com/hippo/ehviewer/ehclient/EhClient.java b/src/com/hippo/ehviewer/ehclient/EhClient.java index 597a40dcf..08b742ec0 100644 --- a/src/com/hippo/ehviewer/ehclient/EhClient.java +++ b/src/com/hippo/ehviewer/ehclient/EhClient.java @@ -859,7 +859,7 @@ public class EhClient { Object checkFlag, String eMsg) { this.isOk = false; this.listener = listener; - this.checkFlag = null; + this.checkFlag = checkFlag; this.pageList = null; this.isLastPage = false; this.eMesg = eMsg; diff --git a/src/com/hippo/ehviewer/network/HttpHelper.java b/src/com/hippo/ehviewer/network/HttpHelper.java index 56dc12724..08ffd4ea9 100644 --- a/src/com/hippo/ehviewer/network/HttpHelper.java +++ b/src/com/hippo/ehviewer/network/HttpHelper.java @@ -52,6 +52,7 @@ import com.hippo.ehviewer.ehclient.EhClient; import com.hippo.ehviewer.ehclient.EhInfo; import com.hippo.ehviewer.exception.StopRequestException; import com.hippo.ehviewer.util.Constants; +import com.hippo.ehviewer.util.FastByteArrayOutputStream; import com.hippo.ehviewer.util.Log; import com.hippo.ehviewer.util.Ui; import com.hippo.ehviewer.util.Utils; @@ -63,7 +64,10 @@ import com.hippo.ehviewer.util.Utils; * */ public class HttpHelper { - private static final String TAG = "HttpHelper"; + private static final String TAG = HttpHelper.class.getSimpleName(); + + private static final int HTTP_RETRY_TIMES = 3; + public static final String SAD_PANDA_ERROR = "Sad Panda"; public static final String HAPPY_PANDA_BODY = "Happy Panda"; @@ -185,88 +189,92 @@ public class HttpHelper { int redirectionCount = 0; URL url = null; HttpURLConnection conn = null; - boolean firstTime = true; - try { - url = rh.getUrl(); - Log.d(TAG, "Requst " + url.toString()); - boolean isCookiable = isUrlCookiable(url); - while (redirectionCount++ < Constants.MAX_REDIRECTS) { - conn = (HttpURLConnection) url.openConnection(); - conn.setInstanceFollowRedirects(false); - conn.setRequestProperty("User-Agent", Constants.userAgent); - conn.setConnectTimeout(Constants.DEFAULT_TIMEOUT); - conn.setReadTimeout(Constants.DEFAULT_TIMEOUT); - // Set cookie if necessary - if (isCookiable) - EhInfo.getInstance(mContext).setCookie(conn, mPreviewMode); - // Do custom staff - if (firstTime) { - rh.onBeforeConnect(conn); - firstTime = false; - } - - conn.connect(); - final int responseCode = conn.getResponseCode(); - switch (responseCode) { - case HttpURLConnection.HTTP_OK: - case HttpURLConnection.HTTP_PARTIAL: - // Test sad panda - if (url.getHost().equals(EhInfo.EX_HOST)) { - String contentType = conn.getHeaderField("Content-Type"); - if (contentType != null && contentType.equals("image/gif")) - throw new SadPandaException(); - } - // Store cookie if necessary + for (int times = 0; times < HTTP_RETRY_TIMES; times++) { + boolean firstTime = true; + try { + url = rh.getUrl(); + Log.d(TAG, "Requst " + url.toString()); + boolean isCookiable = isUrlCookiable(url); + while (redirectionCount++ < Constants.MAX_REDIRECTS) { + conn = (HttpURLConnection) url.openConnection(); + conn.setInstanceFollowRedirects(false); + conn.setRequestProperty("User-Agent", Constants.userAgent); + conn.setConnectTimeout(Constants.DEFAULT_TIMEOUT); + conn.setReadTimeout(Constants.DEFAULT_TIMEOUT); + // Set cookie if necessary if (isCookiable) - EhInfo.getInstance(mContext).storeCookie(conn); - // Get object connection - Object obj = rh.onAfterConnect(conn); - // Send to UI thread if necessary - if (mListener != null) - AppHandler.getInstance().sendMessage( - Message.obtain(null, AppHandler.HTTP_HELPER_TAG, - Constants.TRUE, 0, new Package(obj, mListener))); - return obj; - // redirect - case HttpURLConnection.HTTP_MOVED_PERM: - case HttpURLConnection.HTTP_MOVED_TEMP: - case HttpURLConnection.HTTP_SEE_OTHER: - case Constants.HTTP_TEMP_REDIRECT: - final String location = conn.getHeaderField("Location"); - Log.d(TAG, "New location " + location); - mLastUrl = location; - conn.disconnect(); - url = new URL(url, location); - continue; + EhInfo.getInstance(mContext).setCookie(conn, mPreviewMode); + // Do custom staff + if (firstTime) { + rh.onBeforeConnect(conn); + firstTime = false; + } - default: - String body; - // Get pure text error - if (rh instanceof GetStringHelper - && (body = (String)rh.onAfterConnect(conn)) != null - && !body.contains("<")) - throw new Exception(body); - else - throw new ResponseCodeException(responseCode); + conn.connect(); + final int responseCode = conn.getResponseCode(); + switch (responseCode) { + case HttpURLConnection.HTTP_OK: + case HttpURLConnection.HTTP_PARTIAL: + // Test sad panda + if (url.getHost().equals(EhInfo.EX_HOST)) { + String contentType = conn.getHeaderField("Content-Type"); + if (contentType != null && contentType.equals("image/gif")) + throw new SadPandaException(); + } + // Store cookie if necessary + if (isCookiable) + EhInfo.getInstance(mContext).storeCookie(conn); + // Get object connection + Object obj = rh.onAfterConnect(conn); + // Send to UI thread if necessary + if (mListener != null) + AppHandler.getInstance().sendMessage( + Message.obtain(null, AppHandler.HTTP_HELPER_TAG, + Constants.TRUE, 0, new Package(obj, mListener))); + return obj; + // redirect + case HttpURLConnection.HTTP_MOVED_PERM: + case HttpURLConnection.HTTP_MOVED_TEMP: + case HttpURLConnection.HTTP_SEE_OTHER: + case Constants.HTTP_TEMP_REDIRECT: + final String location = conn.getHeaderField("Location"); + Log.d(TAG, "New location " + location); + mLastUrl = location; + conn.disconnect(); + url = new URL(url, location); + continue; + + default: + String body; + // Get pure text error + if (rh instanceof GetStringHelper + && (body = (String)rh.onAfterConnect(conn)) != null + && !body.contains("<")) + throw new Exception(body); + else + throw new ResponseCodeException(responseCode); + } } + throw new RedirectionException(); + } catch (Exception e) { + mException = e; + e.printStackTrace(); + } finally { + if (conn != null) + conn.disconnect(); } - throw new RedirectionException(); - } catch (Exception e) { - mException = e; - e.printStackTrace(); - // Send to UI thread if necessary - if (mListener != null) - AppHandler.getInstance().sendMessage( - Message.obtain(null, AppHandler.HTTP_HELPER_TAG, - Constants.FALSE, 0, new Package(getEMsg(), mListener))); - // Send exception to helper - rh.onGetException(e); - return null; - } finally { - if (conn != null) - conn.disconnect(); } + + // Send to UI thread if necessary + if (mListener != null) + AppHandler.getInstance().sendMessage( + Message.obtain(null, AppHandler.HTTP_HELPER_TAG, + Constants.FALSE, 0, new Package(getEMsg(), mListener))); + // Send exception to helper + if (mException != null) + rh.onGetException(mException); + return null; } private interface RequestHelper { @@ -624,8 +632,13 @@ public class HttpHelper { @Override public Object onAfterConnect(HttpURLConnection conn) throws Exception { - // TODO If read out of time, bitmap might be incomplete. - Bitmap bmp = BitmapFactory.decodeStream(conn.getInputStream(), null, Ui.getBitmapOpt()); + // If just + // Bitmap bmp = BitmapFactory.decodeStream(conn.getInputStream(), null, Ui.getBitmapOpt()); + // bitmap might be incomplete. + FastByteArrayOutputStream fbaos = new FastByteArrayOutputStream(24 * 1024); // 24K + Utils.copy(conn.getInputStream(), fbaos); + Bitmap bmp = BitmapFactory.decodeByteArray(fbaos.getBuffer(), 0, fbaos.size(), Ui.getBitmapOpt()); + if (bmp == null) throw new GetBodyException(); return bmp; diff --git a/src/com/hippo/ehviewer/util/FastByteArrayOutputStream.java b/src/com/hippo/ehviewer/util/FastByteArrayOutputStream.java new file mode 100644 index 000000000..d629a11c0 --- /dev/null +++ b/src/com/hippo/ehviewer/util/FastByteArrayOutputStream.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * 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 com.hippo.ehviewer.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +public class FastByteArrayOutputStream extends OutputStream { + /** + * The byte array containing the bytes written. + */ + protected byte[] buf; + + /** + * The number of bytes written. + */ + protected int count; + + /** + * Constructs a new ByteArrayOutputStream with a default size of 32 bytes. + * If more than 32 bytes are written to this instance, the underlying byte + * array will expand. + */ + public FastByteArrayOutputStream() { + buf = new byte[32]; + } + + /** + * Constructs a new {@code ByteArrayOutputStream} with a default size of + * {@code size} bytes. If more than {@code size} bytes are written to this + * instance, the underlying byte array will expand. + * + * @param size + * initial size for the underlying byte array, must be + * non-negative. + * @throws IllegalArgumentException + * if {@code size} < 0. + */ + public FastByteArrayOutputStream(int size) { + if (size >= 0) { + buf = new byte[size]; + } else { + throw new IllegalArgumentException("size < 0"); + } + } + + /** + * Closes this stream. This releases system resources used for this stream. + * + * @throws IOException + * if an error occurs while attempting to close this stream. + */ + @Override + public void close() throws IOException { + /** + * Although the spec claims "A closed stream cannot perform output + * operations and cannot be reopened.", this implementation must do + * nothing. + */ + super.close(); + } + + private void expand(int i) { + /* Can the buffer handle @i more bytes, if not expand it */ + if (count + i <= buf.length) { + return; + } + + byte[] newbuf = new byte[(count + i) * 2]; + System.arraycopy(buf, 0, newbuf, 0, count); + buf = newbuf; + } + + /** + * Resets this stream to the beginning of the underlying byte array. All + * subsequent writes will overwrite any bytes previously stored in this + * stream. + */ + public synchronized void reset() { + count = 0; + } + + /** + * Returns the total number of bytes written to this stream so far. + * + * @return the number of bytes written to this stream. + */ + public int size() { + return count; + } + + /** + * Returns the contents of this ByteArrayOutputStream as a byte array. Any + * changes made to the receiver after returning will not be reflected in the + * byte array returned to the caller. + * + * @return this stream's current contents as a byte array. + */ + public synchronized byte[] getBuffer() { + return buf; + } + + /** + * Returns the contents of this ByteArrayOutputStream as a string. Any + * changes made to the receiver after returning will not be reflected in the + * string returned to the caller. + * + * @return this stream's current contents as a string. + */ + + @Override + public String toString() { + return new String(buf, 0, count); + } + + /** + * Returns the contents of this ByteArrayOutputStream as a string. Each byte + * {@code b} in this stream is converted to a character {@code c} using the + * following function: + * {@code c == (char)(((hibyte & 0xff) << 8) | (b & 0xff))}. This method is + * deprecated and either {@link #toString()} or {@link #toString(String)} + * should be used. + * + * @param hibyte + * the high byte of each resulting Unicode character. + * @return this stream's current contents as a string with the high byte set + * to {@code hibyte}. + * @deprecated Use {@link #toString()} instead. + */ + @Deprecated + public String toString(int hibyte) { + char[] newBuf = new char[size()]; + for (int i = 0; i < newBuf.length; i++) { + newBuf[i] = (char) (((hibyte & 0xff) << 8) | (buf[i] & 0xff)); + } + return new String(newBuf); + } + + /** + * Returns the contents of this ByteArrayOutputStream as a string converted + * according to the encoding declared in {@code charsetName}. + * + * @param charsetName + * a string representing the encoding to use when translating + * this stream to a string. + * @return this stream's current contents as an encoded string. + * @throws UnsupportedEncodingException + * if the provided encoding is not supported. + */ + public String toString(String charsetName) throws UnsupportedEncodingException { + return new String(buf, 0, count, charsetName); + } + + /** + * Writes {@code count} bytes from the byte array {@code buffer} starting at + * offset {@code index} to this stream. + * + * @param buffer + * the buffer to be written. + * @param offset + * the initial position in {@code buffer} to retrieve bytes. + * @param len + * the number of bytes of {@code buffer} to write. + * @throws NullPointerException + * if {@code buffer} is {@code null}. + * @throws IndexOutOfBoundsException + * if {@code offset < 0} or {@code len < 0}, or if + * {@code offset + len} is greater than the length of + * {@code buffer}. + */ + @Override + public synchronized void write(byte[] buffer, int offset, int len) { + if ((offset | count) < 0 || offset > buffer.length || buffer.length - offset < count) { + throw new ArrayIndexOutOfBoundsException("length=" + buffer.length + "; regionStart=" + offset + + "; regionLength=" + count); + } + if (len == 0) { + return; + } + expand(len); + System.arraycopy(buffer, offset, buf, this.count, len); + this.count += len; + } + + /** + * Writes the specified byte {@code oneByte} to the OutputStream. Only the + * low order byte of {@code oneByte} is written. + * + * @param oneByte + * the byte to be written. + */ + @Override + public synchronized void write(int oneByte) { + if (count == buf.length) { + expand(1); + } + buf[count++] = (byte) oneByte; + } + + /** + * Takes the contents of this stream and writes it to the output stream + * {@code out}. + * + * @param out + * an OutputStream on which to write the contents of this stream. + * @throws IOException + * if an error occurs while writing to {@code out}. + */ + public synchronized void writeTo(OutputStream out) throws IOException { + out.write(buf, 0, count); + } +}