add response type "arraybuffer" support for android

This commit is contained in:
Sefa Ilkimen
2019-06-12 23:01:15 +02:00
parent 7b485507dc
commit f91727e14a
12 changed files with 128 additions and 42 deletions
@@ -30,6 +30,7 @@ abstract class CordovaHttpBase implements Runnable {
protected String method;
protected String url;
protected String serializer = "none";
protected String responseType;
protected Object data;
protected JSONObject headers;
protected int timeout;
@@ -38,7 +39,8 @@ abstract class CordovaHttpBase implements Runnable {
protected CallbackContext callbackContext;
public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout,
boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CallbackContext callbackContext) {
this.method = method;
this.url = url;
@@ -47,18 +49,20 @@ abstract class CordovaHttpBase implements Runnable {
this.headers = headers;
this.timeout = timeout;
this.followRedirects = followRedirects;
this.responseType = responseType;
this.tlsConfiguration = tlsConfiguration;
this.callbackContext = callbackContext;
}
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
String responseType, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
this.method = method;
this.url = url;
this.headers = headers;
this.timeout = timeout;
this.followRedirects = followRedirects;
this.responseType = responseType;
this.tlsConfiguration = tlsConfiguration;
this.callbackContext = callbackContext;
}
@@ -158,17 +162,19 @@ abstract class CordovaHttpBase implements Runnable {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
request.receive(outputStream);
ByteBuffer rawOutput = ByteBuffer.wrap(outputStream.toByteArray());
String decodedBody = HttpBodyDecoder.decodeBody(rawOutput, request.charset());
response.setStatus(request.code());
response.setUrl(request.url().toString());
response.setHeaders(request.headers());
if (request.code() >= 200 && request.code() < 300) {
response.setBody(decodedBody);
if ("text".equals(this.responseType)) {
String decoded = HttpBodyDecoder.decodeBody(outputStream.toByteArray(), request.charset());
response.setBody(decoded);
} else {
response.setData(outputStream.toByteArray());
}
} else {
response.setErrorMessage(decodedBody);
response.setErrorMessage(HttpBodyDecoder.decodeBody(outputStream.toByteArray(), request.charset()));
}
}
}
@@ -19,7 +19,7 @@ class CordovaHttpDownload extends CordovaHttpBase {
public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
super("GET", url, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
super("GET", url, headers, timeout, followRedirects, "text", tlsConfiguration, callbackContext);
this.filePath = filePath;
}
@@ -10,14 +10,16 @@ import org.json.JSONObject;
class CordovaHttpOperation extends CordovaHttpBase {
public CordovaHttpOperation(String method, String url, String serializer, Object data, JSONObject headers,
int timeout, boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
int timeout, boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CallbackContext callbackContext) {
super(method, url, serializer, data, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
super(method, url, serializer, data, headers, timeout, followRedirects, responseType, tlsConfiguration,
callbackContext);
}
public CordovaHttpOperation(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
String responseType, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
super(method, url, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
super(method, url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
}
}
@@ -83,9 +83,10 @@ public class CordovaHttpPlugin extends CordovaPlugin {
JSONObject headers = args.getJSONObject(1);
int timeout = args.getInt(2) * 1000;
boolean followRedirect = args.getBoolean(3);
String responseType = args.getString(4);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout, followRedirect,
this.tlsConfiguration, callbackContext);
responseType, this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(request);
@@ -101,9 +102,10 @@ public class CordovaHttpPlugin extends CordovaPlugin {
JSONObject headers = args.getJSONObject(3);
int timeout = args.getInt(4) * 1000;
boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers,
timeout, followRedirect, this.tlsConfiguration, callbackContext);
timeout, followRedirect, responseType, this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(request);
@@ -117,9 +119,10 @@ public class CordovaHttpPlugin extends CordovaPlugin {
String uploadName = args.getString(3);
int timeout = args.getInt(4) * 1000;
boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePath, uploadName, timeout, followRedirect,
this.tlsConfiguration, callbackContext);
responseType, this.tlsConfiguration, callbackContext);
cordova.getThreadPool().execute(upload);
@@ -1,5 +1,7 @@
package com.silkimen.cordovahttp;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -9,15 +11,18 @@ import org.json.JSONObject;
import android.text.TextUtils;
import android.util.Log;
import android.util.Base64;
class CordovaHttpResponse {
private int status;
private String url;
private Map<String, List<String>> headers;
private String body;
private byte[] rawData;
private JSONObject fileEntry;
private boolean hasFailed;
private boolean isFileOperation;
private boolean isRawResponse;
private String error;
public void setStatus(int status) {
@@ -36,6 +41,11 @@ class CordovaHttpResponse {
this.body = body;
}
public void setData(byte[] rawData) {
this.isRawResponse = true;
this.rawData = rawData;
}
public void setFileEntry(JSONObject entry) {
this.isFileOperation = true;
this.fileEntry = entry;
@@ -61,6 +71,9 @@ class CordovaHttpResponse {
} else if (this.isFileOperation) {
json.put("headers", new JSONObject(getFilteredHeaders()));
json.put("file", this.fileEntry);
} else if (this.isRawResponse) {
json.put("headers", new JSONObject(getFilteredHeaders()));
json.put("data", Base64.encodeToString(this.rawData, Base64.DEFAULT));
} else {
json.put("headers", new JSONObject(getFilteredHeaders()));
json.put("data", this.body);
@@ -20,9 +20,10 @@ class CordovaHttpUpload extends CordovaHttpBase {
private String uploadName;
public CordovaHttpUpload(String url, JSONObject headers, String filePath, String uploadName, int timeout,
boolean followRedirects, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CallbackContext callbackContext) {
super("POST", url, headers, timeout, followRedirects, tlsConfiguration, callbackContext);
super("POST", url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
this.filePath = filePath;
this.uploadName = uploadName;
}
@@ -10,21 +10,28 @@ import java.nio.charset.MalformedInputException;
public class HttpBodyDecoder {
private static final String[] ACCEPTED_CHARSETS = new String[] { "UTF-8", "ISO-8859-1" };
public static String decodeBody(ByteBuffer rawOutput, String charsetName)
public static String decodeBody(byte[] body, String charsetName)
throws CharacterCodingException, MalformedInputException {
return decodeBody(ByteBuffer.wrap(body), charsetName);
}
public static String decodeBody(ByteBuffer body, String charsetName)
throws CharacterCodingException, MalformedInputException {
if (charsetName == null) {
return tryDecodeByteBuffer(rawOutput);
return tryDecodeByteBuffer(body);
} else {
return decodeByteBuffer(body, charsetName);
}
return decodeByteBuffer(rawOutput, charsetName);
}
private static String tryDecodeByteBuffer(ByteBuffer rawOutput) throws CharacterCodingException, MalformedInputException {
private static String tryDecodeByteBuffer(ByteBuffer buffer)
throws CharacterCodingException, MalformedInputException {
for (int i = 0; i < ACCEPTED_CHARSETS.length - 1; i++) {
try {
return decodeByteBuffer(rawOutput, ACCEPTED_CHARSETS[i]);
return decodeByteBuffer(buffer, ACCEPTED_CHARSETS[i]);
} catch (MalformedInputException e) {
continue;
} catch (CharacterCodingException e) {
@@ -32,13 +39,13 @@ public class HttpBodyDecoder {
}
}
return decodeBody(rawOutput, ACCEPTED_CHARSETS[ACCEPTED_CHARSETS.length - 1]);
return decodeBody(buffer, ACCEPTED_CHARSETS[ACCEPTED_CHARSETS.length - 1]);
}
private static String decodeByteBuffer(ByteBuffer rawOutput, String charsetName)
private static String decodeByteBuffer(ByteBuffer buffer, String charsetName)
throws CharacterCodingException, MalformedInputException {
return createCharsetDecoder(charsetName).decode(rawOutput).toString();
return createCharsetDecoder(charsetName).decode(buffer).toString();
}
private static CharsetDecoder createCharsetDecoder(String charsetName) {
+32
View File
@@ -62,6 +62,18 @@ const helpers = {
}, done);
}, done);
}, done);
},
// adopted from: https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
hashArrayBuffer: function (buffer) {
var hash = 0;
var byteArray = new Uint8Array(buffer);
for (var i = 0; i < byteArray.length; i++) {
hash = ((hash << 5) - hash) + byteArray[i];
hash |= 0; // Convert to 32bit integer
}
return hash;
}
};
@@ -629,6 +641,26 @@ const tests = [
result.data.content.should.be.equal("<?xml version='1.0' encoding='us-ascii'?>\n\n<!-- A SAMPLE set of slides -->\n\n<slideshow \n title=\"Sample Slide Show\"\n date=\"Date of publication\"\n author=\"Yours Truly\"\n >\n\n <!-- TITLE SLIDE -->\n <slide type=\"all\">\n <title>Wake up to WonderWidgets!</title>\n </slide>\n\n <!-- OVERVIEW -->\n <slide type=\"all\">\n <title>Overview</title>\n <item>Why <em>WonderWidgets</em> are great</item>\n <item/>\n <item>Who <em>buys</em> WonderWidgets</item>\n </slide>\n\n</slideshow>");
}
},
{
description: 'should fetch binary correctly when response type "arraybuffer" is given',
expected: 'resolved: {"hash":-1032603775,"byteLength":35588}',
func: function (resolve, reject) {
var url = 'https://httpbin.org/image/jpeg';
var options = { method: 'get', responseType: 'arraybuffer' };
var success = function (response) {
resolve({
hash: helpers.hashArrayBuffer(response.data),
byteLength: response.data.byteLength
});
};
cordova.plugin.http.sendRequest(url, options, success, reject);
},
validationFunc: function (driver, result) {
result.type.should.be.equal('resolved');
result.data.hash.should.be.equal(-1032603775);
result.data.byteLength.should.be.equal(35588);
}
}
// @TODO: not ready yet
// {
// description: 'should authenticate correctly when client cert auth is configured with a PKCS12 container',
+2 -1
View File
@@ -5,6 +5,7 @@
var pluginId = module.id.slice(0, module.id.lastIndexOf('.'));
var exec = require('cordova/exec');
var base64 = require('cordova/base64');
var messages = require(pluginId + '.messages');
var globalConfigs = require(pluginId + '.global-configs');
var jsUtil = require(pluginId + '.js-util');
@@ -12,7 +13,7 @@ var ToughCookie = require(pluginId + '.tough-cookie');
var lodash = require(pluginId + '.lodash');
var WebStorageCookieStore = require(pluginId + '.local-storage-store')(ToughCookie, lodash);
var cookieHandler = require(pluginId + '.cookie-handler')(window.localStorage, ToughCookie, WebStorageCookieStore);
var helpers = require(pluginId + '.helpers')(jsUtil, cookieHandler, messages);
var helpers = require(pluginId + '.helpers')(jsUtil, cookieHandler, messages, base64);
var urlUtil = require(pluginId + '.url-util')(jsUtil);
var publicInterface = require(pluginId + '.public-interface')(exec, cookieHandler, urlUtil, helpers, globalConfigs);
+20 -1
View File
@@ -1,8 +1,9 @@
module.exports = function init(jsUtil, cookieHandler, messages) {
module.exports = function init(jsUtil, cookieHandler, messages, base64) {
var validSerializers = ['urlencoded', 'json', 'utf8'];
var validCertModes = ['default', 'nocheck', 'pinned', 'legacy'];
var validClientAuthModes = ['none', 'systemstore', 'buffer'];
var validHttpMethods = ['get', 'put', 'post', 'patch', 'head', 'delete', 'upload', 'download'];
var validResponseTypes = ['text','arraybuffer'];
var interface = {
b64EncodeUnicode: b64EncodeUnicode,
@@ -15,6 +16,7 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
checkTimeoutValue: checkTimeoutValue,
checkFollowRedirectValue: checkFollowRedirectValue,
injectCookieHandler: injectCookieHandler,
injectRawResponseHandler: injectRawResponseHandler,
injectFileEntryHandler: injectFileEntryHandler,
getMergedHeaders: getMergedHeaders,
getProcessedData: getProcessedData,
@@ -28,6 +30,7 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
interface.checkForValidStringValue = checkForValidStringValue;
interface.checkKeyValuePairObject = checkKeyValuePairObject;
interface.checkHttpMethod = checkHttpMethod;
interface.checkResponseType = checkResponseType;
interface.checkHeadersObject = checkHeadersObject;
interface.checkParamsObject = checkParamsObject;
interface.resolveCookieString = resolveCookieString;
@@ -95,6 +98,10 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
return checkForValidStringValue(validHttpMethods, method, messages.INVALID_HTTP_METHOD);
}
function checkResponseType(type) {
return checkForValidStringValue(validResponseTypes, type, messages.INVALID_RESPONSE_TYPE);
}
function checkSerializer(serializer) {
return checkForValidStringValue(validSerializers, serializer, messages.INVALID_DATA_SERIALIZER);
}
@@ -227,6 +234,17 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
}
}
function injectRawResponseHandler(responseType, cb) {
return function (response) {
// arraybuffer
if (responseType === validResponseTypes[1]) {
response.data = base64.toArrayBuffer(response.data);
}
cb(response);
}
}
function injectFileEntryHandler(cb) {
return function (response) {
cb(createFileEntry(response.file));
@@ -302,6 +320,7 @@ module.exports = function init(jsUtil, cookieHandler, messages) {
return {
method: checkHttpMethod(options.method || validHttpMethods[0]),
responseType: checkResponseType(options.responseType || validResponseTypes[0]),
serializer: checkSerializer(options.serializer || globals.serializer),
timeout: checkTimeoutValue(options.timeout || globals.timeout),
followRedirect: checkFollowRedirectValue(options.followRedirect || globals.followRedirect),
+11 -10
View File
@@ -1,18 +1,19 @@
module.exports = {
ADDING_COOKIES_NOT_SUPPORTED: 'advanced-http: "setHeader" does not support adding cookies, please use "setCookie" function instead',
DATA_TYPE_MISMATCH: 'advanced-http: "data" argument supports only following data types:',
MANDATORY_SUCCESS: 'advanced-http: missing mandatory "onSuccess" callback function',
MANDATORY_FAIL: 'advanced-http: missing mandatory "onFail" callback function',
INVALID_HTTP_METHOD: 'advanced-http: invalid HTTP method, supported methods are:',
INVALID_DATA_SERIALIZER: 'advanced-http: invalid serializer, supported serializers are:',
INVALID_SSL_CERT_MODE: 'advanced-http: invalid SSL cert mode, supported modes are:',
INVALID_CLIENT_AUTH_ALIAS: 'advanced-http: invalid client certificate alias, needs to be a string or undefined',
INVALID_CLIENT_AUTH_MODE: 'advanced-http: invalid client certificate authentication mode, supported modes are:',
INVALID_CLIENT_AUTH_OPTIONS: 'advanced-http: invalid client certificate authentication options, needs to be an object',
INVALID_CLIENT_AUTH_ALIAS: 'advanced-http: invalid client certificate alias, needs to be a string or undefined',
INVALID_CLIENT_AUTH_RAW_PKCS: 'advanced-http: invalid PKCS12 container, needs to be an array buffer',
INVALID_CLIENT_AUTH_PKCS_PASSWORD: 'advanced-http: invalid PKCS12 container password, needs to be a string',
INVALID_HEADERS_VALUE: 'advanced-http: header values must be strings',
INVALID_TIMEOUT_VALUE: 'advanced-http: invalid timeout value, needs to be a positive numeric value',
INVALID_CLIENT_AUTH_RAW_PKCS: 'advanced-http: invalid PKCS12 container, needs to be an array buffer',
INVALID_DATA_SERIALIZER: 'advanced-http: invalid serializer, supported serializers are:',
INVALID_FOLLOW_REDIRECT_VALUE: 'advanced-http: invalid follow redirect value, needs to be a boolean value',
INVALID_PARAMS_VALUE: 'advanced-http: invalid params object, needs to be an object with strings'
INVALID_HEADERS_VALUE: 'advanced-http: header values must be strings',
INVALID_HTTP_METHOD: 'advanced-http: invalid HTTP method, supported methods are:',
INVALID_PARAMS_VALUE: 'advanced-http: invalid params object, needs to be an object with strings',
INVALID_RESPONSE_TYPE: 'advanced-http: invalid response type, supported types are:',
INVALID_SSL_CERT_MODE: 'advanced-http: invalid SSL cert mode, supported modes are:',
INVALID_TIMEOUT_VALUE: 'advanced-http: invalid timeout value, needs to be a positive numeric value',
MANDATORY_FAIL: 'advanced-http: missing mandatory "onFail" callback function',
MANDATORY_SUCCESS: 'advanced-http: missing mandatory "onSuccess" callback function',
};
+5 -4
View File
@@ -143,22 +143,23 @@ module.exports = function init(exec, cookieHandler, urlUtil, helpers, globalConf
url = urlUtil.appendQueryParamsString(url, urlUtil.serializeQueryParams(options.params, true));
var headers = helpers.getMergedHeaders(url, options.headers, globalConfigs.headers);
var onSuccess = helpers.injectCookieHandler(url, success);
var onFail = helpers.injectCookieHandler(url, failure);
var onSuccess = helpers.injectCookieHandler(url, helpers.injectRawResponseHandler(options.responseType, success));
switch (options.method) {
case 'post':
case 'put':
case 'patch':
var data = helpers.getProcessedData(options.data, options.serializer);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, data, options.serializer, headers, options.timeout, options.followRedirect]);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, data, options.serializer, headers, options.timeout, options.followRedirect, options.responseType]);
case 'upload':
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'uploadFile', [url, headers, options.filePath, options.name, options.timeout, options.followRedirect]);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', 'uploadFile', [url, headers, options.filePath, options.name, options.timeout, options.followRedirect, options.responseType]);
case 'download':
var onDownloadSuccess = helpers.injectCookieHandler(url, helpers.injectFileEntryHandler(success));
return exec(onDownloadSuccess, onFail, 'CordovaHttpPlugin', 'downloadFile', [url, headers, options.filePath, options.timeout, options.followRedirect]);
default:
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, headers, options.timeout, options.followRedirect]);
return exec(onSuccess, onFail, 'CordovaHttpPlugin', options.method, [url, headers, options.timeout, options.followRedirect, options.responseType]);
}
}