Skip to content

Commit 91f4efe

Browse files
committed
Merge pull request #99 from longbai/use_own_multipart_entity
refactor multipart post
2 parents c3af3b5 + c656364 commit 91f4efe

File tree

9 files changed

+229
-16
lines changed

9 files changed

+229
-16
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
#Changelog
22

3+
## 7.0.3 (2015-03-09)
4+
5+
### 修正
6+
* multipart key为\r\n时出错
7+
* 生成的jdoc html 乱码
8+
9+
### 增加
10+
* Estat/Xstat 等性能报告
11+
* post上传进度粒度更细(小影)
12+
313
## 7.0.2 (2015-01-22)
414

515
### 增加

library/src/androidTest/java/com/qiniu/android/EtagTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public void testData() {
1717
Assert.assertEquals("Fto5o-5ea0sNMlW_75VgGJCv2AcJ", m);
1818

1919
try {
20-
String etag = Etag.data("etag".getBytes(Config.CHARSET));
20+
String etag = Etag.data("etag".getBytes(Config.UTF_8));
2121
Assert.assertEquals("FpLiADEaVoALPkdb8tJEJyRTXoe_", etag);
2222
} catch (UnsupportedEncodingException e) {
2323
e.printStackTrace();

library/src/main/java/com/qiniu/android/common/Config.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Created by bailong on 14/10/8.
55
*/
66
public final class Config {
7-
public static final String VERSION = "7.0.2";
7+
public static final String VERSION = "7.0.3";
88

99
/**
1010
* 默认上传服务器
@@ -46,6 +46,6 @@ public final class Config {
4646
*/
4747
public static final int RETRY_MAX = 5;
4848

49-
public static final String CHARSET = "utf-8";
49+
public static final String UTF_8 = "utf-8";
5050

5151
}

library/src/main/java/com/qiniu/android/http/ByteArrayEntity.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ public final class ByteArrayEntity extends AbstractHttpEntity implements Cloneab
1717
private final ProgressHandler progressHandler;
1818
private static final int progressStep = 8 * 1024;
1919

20-
public ByteArrayEntity(final byte[] b) {
21-
this(b, 0, b.length, null);
20+
public ByteArrayEntity(final byte[] b, ProgressHandler h) {
21+
this(b, 0, b.length, h);
2222
}
2323

2424
public ByteArrayEntity(final byte[] b, final int off, final int len, ProgressHandler h) {

library/src/main/java/com/qiniu/android/http/HttpManager.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import java.io.ByteArrayInputStream;
1212
import java.io.FileNotFoundException;
13+
import java.io.IOException;
1314
import java.util.Map;
1415
import java.util.Random;
1516

@@ -108,27 +109,32 @@ public void postData(String url, byte[] data, Header[] headers,
108109
*/
109110
public void multipartPost(String url, PostArgs args, ProgressHandler progressHandler,
110111
final CompletionHandler completionHandler) {
111-
RequestParams requestParams = new RequestParams();
112+
MultipartBuilder mbuilder = new MultipartBuilder();
112113
for (Map.Entry<String, String> entry : args.params.entrySet()) {
113-
requestParams.put(escapeMultipartString(entry.getKey()), entry.getValue());
114+
mbuilder.addPart(entry.getKey(), entry.getValue());
114115
}
115116
if (args.data != null) {
116117
ByteArrayInputStream buff = new ByteArrayInputStream(args.data);
117-
requestParams.put("file", buff, escapeMultipartString(args.fileName), args.mimeType);
118+
try {
119+
mbuilder.addPart("file", args.fileName, buff, args.mimeType);
120+
} catch (IOException e) {
121+
completionHandler.complete(ResponseInfo.fileError(e), null);
122+
return;
123+
}
118124
} else {
119125
try {
120-
requestParams.put("file", args.file, args.mimeType);
121-
} catch (FileNotFoundException e) {
122-
e.printStackTrace();
126+
mbuilder.addPart("file", args.file, args.mimeType, "filename");
127+
} catch (IOException e) {
123128
completionHandler.complete(ResponseInfo.fileError(e), null);
124129
return;
125130
}
126131
}
127132

128133
CompletionHandler wrapper = wrap(completionHandler);
129-
Header[] h = reporter.appendStatHeaders(new Header[0]);
130134
AsyncHttpResponseHandler handler = new ResponseHandler(url, wrapper, progressHandler);
131-
client.post(null, url, h, requestParams, null, handler);
135+
ByteArrayEntity entity = mbuilder.build(progressHandler);
136+
Header[] h = reporter.appendStatHeaders(new Header[0]);
137+
client.post(null, url, h, entity, null, handler);
132138
}
133139

134140
private CompletionHandler wrap(final CompletionHandler completionHandler) {
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
This code is taken from Rafael Sanches' blog.
3+
http://blog.rafaelsanches.com/2011/01/29/upload-using-multipart-post-using-httpclient-in-android/
4+
*/
5+
6+
package com.qiniu.android.http;
7+
8+
import com.qiniu.android.utils.StringUtils;
9+
10+
import org.apache.http.message.BasicHeader;
11+
import org.apache.http.protocol.HTTP;
12+
13+
import java.io.ByteArrayOutputStream;
14+
import java.io.File;
15+
import java.io.FileInputStream;
16+
import java.io.IOException;
17+
import java.io.InputStream;
18+
import java.util.Random;
19+
20+
21+
final class MultipartBuilder {
22+
23+
private static final String STR_CR_LF = "\r\n";
24+
private static final byte[] CR_LF = {'\r', '\n'};
25+
private static final byte[] TRANSFER_ENCODING_BINARY =
26+
("Content-Transfer-Encoding: binary" + STR_CR_LF).getBytes();
27+
28+
private final static char[] MULTIPART_CHARS =
29+
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
30+
31+
private final String boundary;
32+
private final byte[] boundaryLine;
33+
private final byte[] boundaryEnd;
34+
35+
36+
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
37+
38+
public MultipartBuilder() {
39+
final StringBuilder buf = new StringBuilder();
40+
final Random rand = new Random();
41+
for (int i = 0; i < 30; i++) {
42+
buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
43+
}
44+
45+
boundary = buf.toString();
46+
boundaryLine = ("--" + boundary + STR_CR_LF).getBytes();
47+
boundaryEnd = ("--" + boundary + "--" + STR_CR_LF).getBytes();
48+
49+
}
50+
51+
/**
52+
* Appends a quoted-string to a StringBuilder.
53+
* <p/>
54+
* <p>RFC 2388 is rather vague about how one should escape special characters
55+
* in form-data parameters, and as it turns out Firefox and Chrome actually
56+
* do rather different things, and both say in their comments that they're
57+
* not really sure what the right approach is. We go with Chrome's behavior
58+
* (which also experimentally seems to match what IE does), but if you
59+
* actually want to have a good chance of things working, please avoid
60+
* double-quotes, newlines, percent signs, and the like in your field names.
61+
*/
62+
private static StringBuilder appendQuotedString(StringBuilder target, String key) {
63+
target.append('"');
64+
for (int i = 0, len = key.length(); i < len; i++) {
65+
char ch = key.charAt(i);
66+
switch (ch) {
67+
case '\n':
68+
target.append("%0A");
69+
break;
70+
case '\r':
71+
target.append("%0D");
72+
break;
73+
case '"':
74+
target.append("%22");
75+
break;
76+
default:
77+
target.append(ch);
78+
break;
79+
}
80+
}
81+
target.append('"');
82+
return target;
83+
}
84+
85+
public void addPart(String key, String value, String contentType) {
86+
try {
87+
out.write(boundaryLine);
88+
out.write(createContentDisposition(key));
89+
out.write(createContentType(contentType));
90+
out.write(CR_LF);
91+
out.write(value.getBytes());
92+
out.write(CR_LF);
93+
} catch (final IOException e) {
94+
throw new AssertionError(e);
95+
}
96+
}
97+
98+
public void addPartWithCharset(String key, String value, String charset) {
99+
if (charset == null) charset = HTTP.UTF_8;
100+
addPart(key, value, "text/plain; charset=" + charset);
101+
}
102+
103+
public void addPart(String key, String value) {
104+
addPartWithCharset(key, value, null);
105+
}
106+
107+
public void addPart(String key, File file) throws IOException {
108+
addPart(key, file, null, file.getName());
109+
}
110+
111+
112+
public void addPart(String key, File file, String type, String customFileName) throws IOException {
113+
FileInputStream fis = new FileInputStream(file);
114+
IOException e = null;
115+
try {
116+
addPart(key, customFileName, fis, type);
117+
} catch (IOException e1) {
118+
e = e1;
119+
}
120+
fis.close();
121+
if (e != null) {
122+
throw e;
123+
}
124+
}
125+
126+
public void addPart(String key, String streamName, InputStream inputStream, String type)
127+
throws IOException {
128+
129+
out.write(boundaryLine);
130+
131+
// Headers
132+
out.write(createContentDisposition(key, streamName));
133+
out.write(createContentType(type));
134+
out.write(TRANSFER_ENCODING_BINARY);
135+
out.write(CR_LF);
136+
137+
// Stream (file)
138+
final byte[] tmp = new byte[1024 * 8];
139+
int l;
140+
while ((l = inputStream.read(tmp)) != -1) {
141+
out.write(tmp, 0, l);
142+
}
143+
144+
out.write(CR_LF);
145+
}
146+
147+
private String normalizeContentType(String type) {
148+
return type == null ? HTTP.DEFAULT_CONTENT_TYPE : type;
149+
}
150+
151+
private byte[] createContentType(String type) {
152+
String result = HTTP.CONTENT_TYPE + ": " + normalizeContentType(type) + STR_CR_LF;
153+
return result.getBytes();
154+
}
155+
156+
private byte[] createContentDisposition(String key) {
157+
StringBuilder builder = new StringBuilder("Content-Disposition: form-data; name=");
158+
appendQuotedString(builder, key);
159+
builder.append(STR_CR_LF);
160+
return StringUtils.utf8Bytes(builder.toString());
161+
}
162+
163+
private byte[] createContentDisposition(String key, String fileName) {
164+
StringBuilder builder = new StringBuilder("Content-Disposition: form-data; name=");
165+
appendQuotedString(builder, key);
166+
builder.append("; filename=");
167+
appendQuotedString(builder, fileName);
168+
builder.append(STR_CR_LF);
169+
return StringUtils.utf8Bytes(builder.toString());
170+
}
171+
172+
public ByteArrayEntity build(ProgressHandler progressHandler) {
173+
try {
174+
out.write(boundaryEnd);
175+
} catch (IOException e) {
176+
throw new AssertionError(e);
177+
}
178+
byte[] data = out.toByteArray();
179+
ByteArrayEntity b = new ByteArrayEntity(data, progressHandler);
180+
b.setContentType(new BasicHeader(
181+
HTTP.CONTENT_TYPE,
182+
"multipart/form-data; boundary=" + boundary));
183+
return b;
184+
}
185+
}

library/src/main/java/com/qiniu/android/http/ResponseHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ private static ResponseInfo buildResponseInfo(int statusCode, Header[] headers,
8383
if (statusCode != 200) {
8484
if (responseBody != null) {
8585
try {
86-
err = new String(responseBody, Config.CHARSET);
86+
err = new String(responseBody, Config.UTF_8);
8787
JSONObject obj = new JSONObject(err);
8888
err = obj.optString("error", err);
8989
} catch (JSONException e) {
@@ -113,7 +113,7 @@ private static ResponseInfo buildResponseInfo(int statusCode, Header[] headers,
113113
}
114114

115115
private static JSONObject buildJsonResp(byte[] body) throws Exception {
116-
String str = new String(body, Config.CHARSET);
116+
String str = new String(body, Config.UTF_8);
117117
return new JSONObject(str);
118118
}
119119

library/src/main/java/com/qiniu/android/utils/StringUtils.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.qiniu.android.utils;
22

3+
import com.qiniu.android.common.Config;
4+
5+
import java.io.UnsupportedEncodingException;
6+
37
/**
48
* 字符串连接工具类
59
*/
@@ -78,5 +82,13 @@ public static String jsonJoin(String[] array) {
7882
}
7983
return buf.toString();
8084
}
85+
86+
public static byte[] utf8Bytes(String data) {
87+
try {
88+
return data.getBytes(Config.UTF_8);
89+
} catch (UnsupportedEncodingException e) {
90+
throw new AssertionError(e);
91+
}
92+
}
8193
}
8294

library/src/main/java/com/qiniu/android/utils/UrlSafeBase64.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public final class UrlSafeBase64 {
1919
*/
2020
public static String encodeToString(String data) {
2121
try {
22-
return encodeToString(data.getBytes(Config.CHARSET));
22+
return encodeToString(data.getBytes(Config.UTF_8));
2323
} catch (UnsupportedEncodingException e) {
2424
//never in
2525
e.printStackTrace();

0 commit comments

Comments
 (0)