Skip to content

Commit d7d7e81

Browse files
author
Noor Dawod
committed
Couple of fixes, one is very important:
1) When uploading many requests using JSON streamer, the reusable StringBuffer would have been emptied; a fix is no w enclosed 2) Added a feature request: Let the user decide whether to embed "_elapsed" field, and even customize its name, or disable it completely.
1 parent 7da8ed5 commit d7d7e81

File tree

2 files changed

+108
-79
lines changed

2 files changed

+108
-79
lines changed

library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java

Lines changed: 86 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package com.loopj.android.http;
2020

21+
import android.text.TextUtils;
2122
import android.util.Log;
2223

2324
import org.apache.http.Header;
@@ -51,11 +52,6 @@ public class JsonStreamerEntity implements HttpEntity {
5152
// Buffer used for reading from input streams.
5253
private final byte[] buffer = new byte[BUFFER_SIZE];
5354

54-
// Reusable StringBuilder used by escape() method.
55-
// Its size is just initial, if more space is needed, the system will
56-
// automatically enlarge the buffer.
57-
private static final StringBuilder BUILDER = new StringBuilder(128);
58-
5955
private static final byte[] JSON_TRUE = "true".getBytes();
6056
private static final byte[] JSON_FALSE = "false".getBytes();
6157
private static final byte[] JSON_NULL = "null".getBytes();
@@ -80,11 +76,14 @@ public class JsonStreamerEntity implements HttpEntity {
8076
// Whether to use gzip compression while uploading
8177
private final Header contentEncoding;
8278

79+
private final String elapsedField;
80+
8381
private final ResponseHandlerInterface progressHandler;
8482

85-
public JsonStreamerEntity(ResponseHandlerInterface progressHandler, boolean useGZipCompression) {
83+
public JsonStreamerEntity(ResponseHandlerInterface progressHandler, boolean useGZipCompression, String elapsedField) {
8684
this.progressHandler = progressHandler;
8785
this.contentEncoding = useGZipCompression ? HEADER_GZIP_ENCODING : null;
86+
this.elapsedField = elapsedField;
8887
}
8988

9089
/**
@@ -157,69 +156,86 @@ public void writeTo(final OutputStream out) throws IOException {
157156
// Keys used by the HashMaps.
158157
Set<String> keys = jsonParams.keySet();
159158

159+
int keysCount = keys.size();
160+
int keysProcessed = -1;
160161
boolean isFileWrapper;
161162

162163
// Go over all keys and handle each's value.
163164
for (String key : keys) {
164-
// Evaluate the value (which cannot be null).
165-
Object value = jsonParams.get(key);
165+
// Indicate that this key has been processed.
166+
keysProcessed++;
166167

167-
// Bail out prematurely if value's null.
168-
if (value == null) {
169-
continue;
170-
}
168+
try {
169+
// Evaluate the value (which cannot be null).
170+
Object value = jsonParams.get(key);
171171

172-
// Write the JSON object's key.
173-
os.write(escape(key));
174-
os.write(':');
172+
// Bail out prematurely if value's null.
173+
if (value == null) {
174+
continue;
175+
}
176+
177+
// Write the JSON object's key.
178+
os.write(escape(key));
179+
os.write(':');
175180

176-
// Check if this is a FileWrapper.
177-
isFileWrapper = value instanceof RequestParams.FileWrapper;
181+
// Check if this is a FileWrapper.
182+
isFileWrapper = value instanceof RequestParams.FileWrapper;
178183

179-
// If a file should be uploaded.
180-
if (isFileWrapper || value instanceof RequestParams.StreamWrapper) {
181-
// All uploads are sent as an object containing the file's details.
182-
os.write('{');
184+
// If a file should be uploaded.
185+
if (isFileWrapper || value instanceof RequestParams.StreamWrapper) {
186+
// All uploads are sent as an object containing the file's details.
187+
os.write('{');
183188

184-
// Determine how to handle this entry.
185-
if (isFileWrapper) {
186-
writeToFromFile(os, (RequestParams.FileWrapper) value);
189+
// Determine how to handle this entry.
190+
if (isFileWrapper) {
191+
writeToFromFile(os, (RequestParams.FileWrapper) value);
192+
} else {
193+
writeToFromStream(os, (RequestParams.StreamWrapper) value);
194+
}
195+
196+
// End the file's object and prepare for next one.
197+
os.write('}');
198+
} else if (value instanceof JsonValueInterface) {
199+
os.write(((JsonValueInterface) value).getEscapedJsonValue());
200+
} else if (value instanceof org.json.JSONObject) {
201+
os.write(((org.json.JSONObject) value).toString().getBytes());
202+
} else if (value instanceof org.json.JSONArray) {
203+
os.write(((org.json.JSONArray) value).toString().getBytes());
204+
} else if (value instanceof Boolean) {
205+
os.write((Boolean) value ? JSON_TRUE : JSON_FALSE);
206+
} else if (value instanceof Long) {
207+
os.write((((Number) value).longValue() + "").getBytes());
208+
} else if (value instanceof Double) {
209+
os.write((((Number) value).doubleValue() + "").getBytes());
210+
} else if (value instanceof Float) {
211+
os.write((((Number) value).floatValue() + "").getBytes());
212+
} else if (value instanceof Integer) {
213+
os.write((((Number) value).intValue() + "").getBytes());
187214
} else {
188-
writeToFromStream(os, (RequestParams.StreamWrapper) value);
215+
os.write(escape(value.toString()));
216+
}
217+
} finally {
218+
// Separate each K:V with a comma, except the last one.
219+
if (!TextUtils.isEmpty(elapsedField) || keysProcessed < keysCount) {
220+
os.write(',');
189221
}
190-
191-
// End the file's object and prepare for next one.
192-
os.write('}');
193-
} else if (value instanceof JsonValueInterface) {
194-
os.write(((JsonValueInterface) value).getEscapedJsonValue());
195-
} else if (value instanceof org.json.JSONObject) {
196-
os.write(((org.json.JSONObject) value).toString().getBytes());
197-
} else if (value instanceof org.json.JSONArray) {
198-
os.write(((org.json.JSONArray) value).toString().getBytes());
199-
} else if (value instanceof Boolean) {
200-
os.write((Boolean) value ? JSON_TRUE : JSON_FALSE);
201-
} else if (value instanceof Long) {
202-
os.write((((Number) value).longValue() + "").getBytes());
203-
} else if (value instanceof Double) {
204-
os.write((((Number) value).doubleValue() + "").getBytes());
205-
} else if (value instanceof Float) {
206-
os.write((((Number) value).floatValue() + "").getBytes());
207-
} else if (value instanceof Integer) {
208-
os.write((((Number) value).intValue() + "").getBytes());
209-
} else {
210-
os.write(escape(value.toString()));
211222
}
212-
213-
os.write(',');
214223
}
215224

225+
// Calculate how many milliseconds it took to upload the contents.
226+
long elapsedTime = System.currentTimeMillis() - now;
227+
216228
// Include the elapsed time taken to upload everything.
217229
// This might be useful for somebody, but it serves us well since
218230
// there will almost always be a ',' as the last sent character.
219-
os.write(STREAM_ELAPSED);
220-
os.write(':');
221-
long elapsedTime = System.currentTimeMillis() - now;
222-
os.write((elapsedTime + "}").getBytes());
231+
if (!TextUtils.isEmpty(elapsedField)) {
232+
os.write(STREAM_ELAPSED);
233+
os.write(':');
234+
os.write((elapsedTime + "").getBytes());
235+
}
236+
237+
// Close the JSON object.
238+
os.write('}');
223239

224240
Log.i(LOG_TAG, "Uploaded JSON in " + Math.floor(elapsedTime / 1000) + " seconds");
225241

@@ -321,60 +337,57 @@ static byte[] escape(String string) {
321337
return JSON_NULL;
322338
}
323339

340+
// Create a string builder to generate the escaped string.
341+
StringBuilder sb = new StringBuilder(128);
342+
324343
// Surround with quotations.
325-
BUILDER.append('"');
344+
sb.append('"');
326345

327346
int length = string.length(), pos = -1;
328347
while (++pos < length) {
329348
char ch = string.charAt(pos);
330349
switch (ch) {
331350
case '"':
332-
BUILDER.append("\\\"");
351+
sb.append("\\\"");
333352
break;
334353
case '\\':
335-
BUILDER.append("\\\\");
354+
sb.append("\\\\");
336355
break;
337356
case '\b':
338-
BUILDER.append("\\b");
357+
sb.append("\\b");
339358
break;
340359
case '\f':
341-
BUILDER.append("\\f");
360+
sb.append("\\f");
342361
break;
343362
case '\n':
344-
BUILDER.append("\\n");
363+
sb.append("\\n");
345364
break;
346365
case '\r':
347-
BUILDER.append("\\r");
366+
sb.append("\\r");
348367
break;
349368
case '\t':
350-
BUILDER.append("\\t");
369+
sb.append("\\t");
351370
break;
352371
default:
353372
// Reference: http://www.unicode.org/versions/Unicode5.1.0/
354373
if ((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) {
355374
String intString = Integer.toHexString(ch);
356-
BUILDER.append("\\u");
375+
sb.append("\\u");
357376
int intLength = 4 - intString.length();
358377
for (int zero = 0; zero < intLength; zero++) {
359-
BUILDER.append('0');
378+
sb.append('0');
360379
}
361-
BUILDER.append(intString.toUpperCase(Locale.US));
380+
sb.append(intString.toUpperCase(Locale.US));
362381
} else {
363-
BUILDER.append(ch);
382+
sb.append(ch);
364383
}
365384
break;
366385
}
367386
}
368387

369388
// Surround with quotations.
370-
BUILDER.append('"');
371-
372-
try {
373-
return BUILDER.toString().getBytes();
374-
} finally {
375-
// Empty the String buffer.
376-
// This is 20-30% faster than instantiating a new object.
377-
BUILDER.setLength(0);
378-
}
389+
sb.append('"');
390+
391+
return sb.toString().getBytes();
379392
}
380393
}

library/src/main/java/com/loopj/android/http/RequestParams.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public class RequestParams implements Serializable {
9999
protected final static String LOG_TAG = "RequestParams";
100100
protected boolean isRepeatable;
101101
protected boolean useJsonStreamer;
102+
protected String elapsedFieldInJsonStreamer = "_elapsed";
102103
protected boolean autoCloseInputStreams;
103104
protected final ConcurrentHashMap<String, String> urlParams = new ConcurrentHashMap<String, String>();
104105
protected final ConcurrentHashMap<String, StreamWrapper> streamParams = new ConcurrentHashMap<String, StreamWrapper>();
@@ -412,12 +413,25 @@ public String toString() {
412413
return result.toString();
413414
}
414415

415-
public void setHttpEntityIsRepeatable(boolean isRepeatable) {
416-
this.isRepeatable = isRepeatable;
416+
public void setHttpEntityIsRepeatable(boolean flag) {
417+
this.isRepeatable = flag;
417418
}
418419

419-
public void setUseJsonStreamer(boolean useJsonStreamer) {
420-
this.useJsonStreamer = useJsonStreamer;
420+
public void setUseJsonStreamer(boolean flag) {
421+
this.useJsonStreamer = flag;
422+
}
423+
424+
/**
425+
* Sets an additional field when upload a JSON object through the streamer
426+
* to hold the time, in milliseconds, it took to upload the payload. By
427+
* default, this field is set to "_elapsed".
428+
*
429+
* To disable this feature, call this method with null as the field value.
430+
*
431+
* @param value field name to add elapsed time, or null to disable
432+
*/
433+
public void setElapsedFieldInJsonStreamer(String value) {
434+
this.elapsedFieldInJsonStreamer = value;
421435
}
422436

423437
/**
@@ -449,8 +463,10 @@ public HttpEntity getEntity(ResponseHandlerInterface progressHandler) throws IOE
449463
}
450464

451465
private HttpEntity createJsonStreamerEntity(ResponseHandlerInterface progressHandler) throws IOException {
452-
JsonStreamerEntity entity = new JsonStreamerEntity(progressHandler,
453-
!fileParams.isEmpty() || !streamParams.isEmpty());
466+
JsonStreamerEntity entity = new JsonStreamerEntity(
467+
progressHandler,
468+
!fileParams.isEmpty() || !streamParams.isEmpty(),
469+
elapsedFieldInJsonStreamer);
454470

455471
// Add string params
456472
for (ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {

0 commit comments

Comments
 (0)