added support for abort() on android platform

This commit is contained in:
russa
2020-09-16 18:05:30 +02:00
parent 269d5d4c8a
commit 2367d264c1
7 changed files with 178 additions and 30 deletions

View File

@@ -74,6 +74,7 @@
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpResponse.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaObservableCallbackContext.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/cordovahttp/CordovaServerTrust.java" target-dir="src/com/silkimen/cordovahttp"/>
<source-file src="src/android/com/silkimen/http/HttpBodyDecoder.java" target-dir="src/com/silkimen/http"/>
<source-file src="src/android/com/silkimen/http/HttpRequest.java" target-dir="src/com/silkimen/http"/>
@@ -92,4 +93,4 @@
<runs/>
</js-module>
</platform>
</plugin>
</plugin>

View File

@@ -5,6 +5,7 @@ import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
@@ -18,8 +19,6 @@ import com.silkimen.http.HttpRequest.HttpRequestException;
import com.silkimen.http.JsonUtils;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -39,11 +38,11 @@ abstract class CordovaHttpBase implements Runnable {
protected int timeout;
protected boolean followRedirects;
protected TLSConfiguration tlsConfiguration;
protected CallbackContext callbackContext;
protected CordovaObservableCallbackContext callbackContext;
public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout,
boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CallbackContext callbackContext) {
CordovaObservableCallbackContext callbackContext) {
this.method = method;
this.url = url;
@@ -58,7 +57,7 @@ abstract class CordovaHttpBase implements Runnable {
}
public CordovaHttpBase(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
String responseType, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
String responseType, TLSConfiguration tlsConfiguration, CordovaObservableCallbackContext callbackContext) {
this.method = method;
this.url = url;
@@ -74,8 +73,9 @@ abstract class CordovaHttpBase implements Runnable {
public void run() {
CordovaHttpResponse response = new CordovaHttpResponse();
HttpRequest request = null;
try {
HttpRequest request = this.createRequest();
request = this.createRequest();
this.prepareRequest(request);
this.sendBody(request);
this.processResponse(request, response);
@@ -94,10 +94,17 @@ abstract class CordovaHttpBase implements Runnable {
response.setErrorMessage("Request timed out: " + e.getMessage());
Log.w(TAG, "Request timed out", e);
} else {
response.setStatus(-1);
response.setErrorMessage("There was an error with the request: " + e.getCause().getMessage());
Log.w(TAG, "Generic request error", e);
String cause = e.getCause().getMessage();
if(e.getCause() instanceof InterruptedIOException && "thread interrupted".equals(cause.toLowerCase())){
this.setAborted(request, response);
} else {
response.setStatus(-1);
response.setErrorMessage("There was an error with the request: " + cause);
Log.w(TAG, "Generic request error", e);
}
}
} catch (InterruptedException ie) {
this.setAborted(request, response);
} catch (Exception e) {
response.setStatus(-1);
response.setErrorMessage(e.getMessage());
@@ -202,4 +209,17 @@ abstract class CordovaHttpBase implements Runnable {
response.setErrorMessage(HttpBodyDecoder.decodeBody(outputStream.toByteArray(), request.charset()));
}
}
protected void setAborted(HttpRequest request, CordovaHttpResponse response) {
response.setStatus(-8);
response.setErrorMessage("Request was aborted");
if(request != null){
try{
request.disconnect();
} catch(Exception any){
Log.w(TAG, "Failed to close aborted request", any);
}
}
Log.i(TAG, "Request was aborted");
}
}

View File

@@ -9,7 +9,6 @@ import javax.net.ssl.SSLSocketFactory;
import com.silkimen.http.HttpRequest;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.file.FileUtils;
import org.json.JSONObject;
@@ -17,7 +16,7 @@ class CordovaHttpDownload extends CordovaHttpBase {
private String filePath;
public CordovaHttpDownload(String url, JSONObject headers, String filePath, int timeout, boolean followRedirects,
TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
TLSConfiguration tlsConfiguration, CordovaObservableCallbackContext callbackContext) {
super("GET", url, headers, timeout, followRedirects, "text", tlsConfiguration, callbackContext);
this.filePath = filePath;

View File

@@ -5,20 +5,19 @@ import javax.net.ssl.SSLSocketFactory;
import com.silkimen.http.TLSConfiguration;
import org.apache.cordova.CallbackContext;
import org.json.JSONObject;
class CordovaHttpOperation extends CordovaHttpBase {
public CordovaHttpOperation(String method, String url, String serializer, Object data, JSONObject headers,
int timeout, boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
CallbackContext callbackContext) {
CordovaObservableCallbackContext callbackContext) {
super(method, url, serializer, data, headers, timeout, followRedirects, responseType, tlsConfiguration,
callbackContext);
}
public CordovaHttpOperation(String method, String url, JSONObject headers, int timeout, boolean followRedirects,
String responseType, TLSConfiguration tlsConfiguration, CallbackContext callbackContext) {
String responseType, TLSConfiguration tlsConfiguration, CordovaObservableCallbackContext callbackContext) {
super(method, url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
}

View File

@@ -1,6 +1,10 @@
package com.silkimen.cordovahttp;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.Future;
import com.silkimen.http.TLSConfiguration;
@@ -17,17 +21,22 @@ import android.util.Base64;
import javax.net.ssl.TrustManagerFactory;
public class CordovaHttpPlugin extends CordovaPlugin {
public class CordovaHttpPlugin extends CordovaPlugin implements Observer {
private static final String TAG = "Cordova-Plugin-HTTP";
private TLSConfiguration tlsConfiguration;
private HashMap<Integer, Future<?>> reqMap;
private final Object reqMapLock = new Object();
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
this.tlsConfiguration = new TLSConfiguration();
this.reqMap = new HashMap<Integer, Future<?>>();
try {
KeyStore store = KeyStore.getInstance("AndroidCAStore");
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
@@ -73,6 +82,8 @@ public class CordovaHttpPlugin extends CordovaPlugin {
return this.setServerTrustMode(args, callbackContext);
} else if ("setClientAuthMode".equals(action)) {
return this.setClientAuthMode(args, callbackContext);
} else if ("abort".equals(action)) {
return this.abort(args, callbackContext);
} else {
return false;
}
@@ -87,10 +98,14 @@ public class CordovaHttpPlugin extends CordovaPlugin {
boolean followRedirect = args.getBoolean(3);
String responseType = args.getString(4);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout, followRedirect,
responseType, this.tlsConfiguration, callbackContext);
Integer reqId = args.getInt(5);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
cordova.getThreadPool().execute(request);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, headers, timeout, followRedirect,
responseType, this.tlsConfiguration, observableCallbackContext);
Future<?> task = cordova.getThreadPool().submit(request);
this.addReq(reqId, task, observableCallbackContext);
return true;
}
@@ -106,10 +121,14 @@ public class CordovaHttpPlugin extends CordovaPlugin {
boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers,
timeout, followRedirect, responseType, this.tlsConfiguration, callbackContext);
Integer reqId = args.getInt(7);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
cordova.getThreadPool().execute(request);
CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers,
timeout, followRedirect, responseType, this.tlsConfiguration, observableCallbackContext);
Future<?> task = cordova.getThreadPool().submit(request);
this.addReq(reqId, task, observableCallbackContext);
return true;
}
@@ -123,10 +142,14 @@ public class CordovaHttpPlugin extends CordovaPlugin {
boolean followRedirect = args.getBoolean(5);
String responseType = args.getString(6);
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePaths, uploadNames, timeout, followRedirect,
responseType, this.tlsConfiguration, this.cordova.getActivity().getApplicationContext(), callbackContext);
Integer reqId = args.getInt(7);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
cordova.getThreadPool().execute(upload);
CordovaHttpUpload upload = new CordovaHttpUpload(url, headers, filePaths, uploadNames, timeout, followRedirect,
responseType, this.tlsConfiguration, this.cordova.getActivity().getApplicationContext(), observableCallbackContext);
Future<?> task = cordova.getThreadPool().submit(upload);
this.addReq(reqId, task, observableCallbackContext);
return true;
}
@@ -138,10 +161,14 @@ public class CordovaHttpPlugin extends CordovaPlugin {
int timeout = args.getInt(3) * 1000;
boolean followRedirect = args.getBoolean(4);
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout, followRedirect,
this.tlsConfiguration, callbackContext);
Integer reqId = args.getInt(5);
CordovaObservableCallbackContext observableCallbackContext = new CordovaObservableCallbackContext(callbackContext, reqId);
cordova.getThreadPool().execute(download);
CordovaHttpDownload download = new CordovaHttpDownload(url, headers, filePath, timeout, followRedirect,
this.tlsConfiguration, observableCallbackContext);
Future<?> task = cordova.getThreadPool().submit(download);
this.addReq(reqId, task, observableCallbackContext);
return true;
}
@@ -166,4 +193,49 @@ public class CordovaHttpPlugin extends CordovaPlugin {
return true;
}
private boolean abort(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
int reqId = args.getInt(0);
boolean result = false;
// NOTE no synchronized (reqMapLock), since even if the req was already removed from reqMap,
// the worst that would happen calling task.cancel(true) is a result of false
// (i.e. same result as locking & not finding the req in reqMap)
Future<?> task = this.reqMap.get(reqId);
if (task != null && !task.isDone()) {
result = task.cancel(true);
}
callbackContext.success(new JSONObject().put("aborted", result));
return true;
}
private void addReq(final Integer reqId, final Future<?> task, final CordovaObservableCallbackContext observableCallbackContext) {
synchronized (reqMapLock) {
// NOTE there is a small chance that the task may already have tried to remove itself before
// done-status was set (within the request run-thread)
// to prevent that, the synchronized()-lock would need to be set around starting the
// request and adding the entry to reqMap (which seems overkill given that is seems very unlikely)
if(!task.isDone()){
observableCallbackContext.setObserver(this);
this.reqMap.put(reqId, task);
}
}
}
private void removeReq(final Integer reqId) {
synchronized (reqMapLock) {
this.reqMap.remove(reqId);
}
}
@Override
public void update(Observable o, Object arg) {
synchronized (reqMapLock) {
CordovaObservableCallbackContext c = (CordovaObservableCallbackContext) arg;
if (c.getCallbackContext().isFinished()) {
removeReq(c.getRequestId());
}
}
}
}

View File

@@ -17,7 +17,6 @@ import java.net.URI;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -28,7 +27,7 @@ class CordovaHttpUpload extends CordovaHttpBase {
public CordovaHttpUpload(String url, JSONObject headers, JSONArray filePaths, JSONArray uploadNames, int timeout,
boolean followRedirects, String responseType, TLSConfiguration tlsConfiguration,
Context applicationContext, CallbackContext callbackContext) {
Context applicationContext, CordovaObservableCallbackContext callbackContext) {
super("POST", url, headers, timeout, followRedirects, responseType, tlsConfiguration, callbackContext);
this.filePaths = filePaths;

View File

@@ -0,0 +1,58 @@
package com.silkimen.cordovahttp;
import org.apache.cordova.CallbackContext;
import org.json.JSONObject;
import java.util.Observer;
public class CordovaObservableCallbackContext {
private CallbackContext callbackContext;
private Integer requestId;
private Observer observer;
public CordovaObservableCallbackContext(CallbackContext callbackContext, Integer requestId) {
this.callbackContext = callbackContext;
this.requestId = requestId;
}
public void success(JSONObject message) {
this.callbackContext.success(message);
this.notifyObserver();
}
public void error(JSONObject message) {
this.callbackContext.error(message);
this.notifyObserver();
}
public Integer getRequestId() {
return this.requestId;
}
public CallbackContext getCallbackContext() {
return callbackContext;
}
public Observer getObserver() {
return observer;
}
protected void notifyObserver() {
if(this.observer != null){
this.observer.update(null, this);
}
}
/**
* Set an observer that is notified, when {@link #success(JSONObject)}
* or {@link #error(JSONObject)} are called.
*
* NOTE the observer is notified with
* <pre>observer.update(null, cordovaObservableCallbackContext)</pre>
* @param observer
*/
public void setObserver(Observer observer) {
this.observer = observer;
}
}