Skip to content
Permalink
Browse files

Add Timeout property to Web component

  • Loading branch information
austenjoa authored and ewpatton committed Sep 15, 2019
1 parent 96af69a commit 28e898d9b2f9afa8b737527a5668b30d6f1ee5b2
@@ -1546,6 +1546,13 @@ private static int upgradeWebProperties(Map<String, JSONValue> componentProperti
// No properties need to be modified to upgrade to version 5.
srcCompVersion = 5;
}
if (srcCompVersion < 6) {
// The Timeout property and TimedOut event were added.
// No properties need to be modified to upgrade to version 6.
// Timeout defaults to 0, so prior components will maintain the same web request
// timeout behavior.
srcCompVersion = 6;
}
return srcCompVersion;
}

@@ -2577,7 +2577,10 @@ Blockly.Versioning.AllUpgradeMaps =
4: "noUpgrade",

// AI2: Added method UriDecode
5: "noUpgrade"
5: "noUpgrade",

// AI2: Added property Timeout and event TimedOut
6: "noUpgrade"

}, // End Web upgraders

@@ -469,8 +469,10 @@ private YaVersion() {
// - Label component version incremented to 5
// For YOUNG_ANDROID_VERSION 189:
// - FORM_COMPONENT_VERSION was incremented to 25
// For YOUNG_ANDROID_VERSION 190:
// - WEB_COMPONENT_VERSION was incremented to 6

public static final int YOUNG_ANDROID_VERSION = 189;
public static final int YOUNG_ANDROID_VERSION = 190;

// ............................... Blocks Language Version Number ...............................

@@ -1222,7 +1224,10 @@ private YaVersion() {
// - Added method XMLTextDecode
// For WEB_COMPONENT_VERSION 5:
// - Added method UriDecode
public static final int WEB_COMPONENT_VERSION = 5;
// For WEB_COMPONENT_VERSION 6:
// - The Timeout property was added.
// - The TimedOut event was added for timed out web requests.
public static final int WEB_COMPONENT_VERSION = 6;

// For WEBVIEWER_COMPONENT_VERSION 2:
// - The CanGoForward and CanGoBack methods were added
@@ -21,7 +21,9 @@
import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.components.runtime.collect.Lists;
import com.google.appinventor.components.runtime.collect.Maps;
import com.google.appinventor.components.runtime.errors.IllegalArgumentError;
import com.google.appinventor.components.runtime.errors.PermissionException;
import com.google.appinventor.components.runtime.errors.RequestTimeoutException;
import com.google.appinventor.components.runtime.util.AsynchUtil;
import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.FileUtil;
@@ -51,6 +53,7 @@
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
@@ -133,6 +136,7 @@
final boolean allowCookies;
final boolean saveResponse;
final String responseFileName;
final int timeout;
final Map<String, List<String>> requestHeaders;
final Map<String, List<String>> cookies;

@@ -142,6 +146,7 @@
allowCookies = web.allowCookies;
saveResponse = web.saveResponse;
responseFileName = web.responseFileName;
timeout = web.timeout;
requestHeaders = processRequestHeaders(web.requestHeaders);

Map<String, List<String>> cookiesTemp = null;
@@ -186,6 +191,7 @@
private YailList requestHeaders = new YailList();
private boolean saveResponse;
private String responseFileName = "";
private int timeout = 0;

/**
* Creates a new Web component.
@@ -325,6 +331,31 @@ public void ResponseFileName(String responseFileName) {
this.responseFileName = responseFileName;
}

/**
* Returns the number of milliseconds that each request will wait for a response before they time out.
* If set to 0, then the request will wait for a response indefinitely.
*/
@SimpleProperty(category = PropertyCategory.BEHAVIOR,
description = "The number of milliseconds that a web request will wait for a response before giving up. " +
"If set to 0, then there is no time limit on how long the request will wait.")
public int Timeout() {
return timeout;
}

/**
* Returns the number of milliseconds that each request will wait for a response before they time out.
* If set to 0, then the request will wait for a response indefinitely.
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_NON_NEGATIVE_INTEGER,
defaultValue = "0")
@SimpleProperty
public void Timeout(int timeout) {
if (timeout < 0){
throw new IllegalArgumentError("Web Timeout must be a non-negative integer.");
}
this.timeout = timeout;
}

@SimpleFunction(description = "Clears all cookies for this Web component.")
public void ClearCookies() {
if (cookieHandler != null) {
@@ -364,6 +395,9 @@ public void run() {
} catch (FileUtil.FileException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
e.getErrorMessageNumber());
} catch (RequestTimeoutException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_REQUEST_TIMED_OUT, webProps.urlString);
} catch (Exception e) {
Log.e(LOG_TAG, "ERROR_UNABLE_TO_GET", e);
form.dispatchErrorOccurredEvent(Web.this, METHOD,
@@ -438,6 +472,9 @@ public void run() {
} catch (FileUtil.FileException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
e.getErrorMessageNumber());
} catch (RequestTimeoutException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_REQUEST_TIMED_OUT, webProps.urlString);
} catch (Exception e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_UNABLE_TO_POST_OR_PUT_FILE, path, webProps.urlString);
@@ -511,6 +548,9 @@ public void run() {
} catch (FileUtil.FileException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
e.getErrorMessageNumber());
} catch (RequestTimeoutException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_REQUEST_TIMED_OUT, webProps.urlString);
} catch (Exception e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_UNABLE_TO_POST_OR_PUT_FILE, path, webProps.urlString);
@@ -548,6 +588,9 @@ public void run() {
} catch (FileUtil.FileException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
e.getErrorMessageNumber());
} catch (RequestTimeoutException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_REQUEST_TIMED_OUT, webProps.urlString);
} catch (Exception e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_UNABLE_TO_DELETE, webProps.urlString);
@@ -605,6 +648,9 @@ public void run() {
} catch (FileUtil.FileException e) {
form.dispatchErrorOccurredEvent(Web.this, functionName,
e.getErrorMessageNumber());
} catch (RequestTimeoutException e) {
form.dispatchErrorOccurredEvent(Web.this, functionName,
ErrorMessages.ERROR_WEB_REQUEST_TIMED_OUT, webProps.urlString);
} catch (Exception e) {
form.dispatchErrorOccurredEvent(Web.this, functionName,
ErrorMessages.ERROR_WEB_UNABLE_TO_POST_OR_PUT, text, webProps.urlString);
@@ -613,7 +659,6 @@ public void run() {
});
}


/**
* Event indicating that a request has finished.
*
@@ -643,6 +688,16 @@ public void GotFile(String url, int responseCode, String responseType, String fi
EventDispatcher.dispatchEvent(this, "GotFile", url, responseCode, responseType, fileName);
}

/**
* Event indicating that a request has timed out.
*
* @param url the URL used for the request
*/
@SimpleEvent
public void TimedOut(String url) {
// invoke the application's "TimedOut" event handler.
EventDispatcher.dispatchEvent(this, "TimedOut", url);
}

/**
* Converts a list of two-element sublists, representing name and value pairs, to a
@@ -869,7 +924,7 @@ public String HtmlTextDecode(String htmlText) {
* @throws IOException
*/
private void performRequest(final CapturedProperties webProps, byte[] postData, String postFile, String httpVerb)
throws IOException {
throws RequestTimeoutException, IOException {

// Open the connection.
HttpURLConnection connection = openConnection(webProps, httpVerb);
@@ -909,6 +964,15 @@ public void run() {
});
}

} catch (SocketTimeoutException e) {
// Dispatch timeout event.
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
TimedOut(webProps.urlString);
}
});
throw new RequestTimeoutException();
} finally {
connection.disconnect();
}
@@ -929,6 +993,8 @@ private static HttpURLConnection openConnection(CapturedProperties webProps, Str
throws IOException, ClassCastException, ProtocolException {

HttpURLConnection connection = (HttpURLConnection) webProps.url.openConnection();
connection.setConnectTimeout(webProps.timeout);
connection.setReadTimeout(webProps.timeout);

if (httpVerb.equals("PUT") || httpVerb.equals("DELETE")){
// Set the Request Method; GET is the default, and if it is a POST, it will be marked as such
@@ -1074,14 +1140,16 @@ private static String saveResponseContent(HttpURLConnection connection,
return file.getAbsolutePath();
}

private static InputStream getConnectionStream(HttpURLConnection connection) {
private static InputStream getConnectionStream(HttpURLConnection connection) throws SocketTimeoutException {
// According to the Android reference documentation for HttpURLConnection: If the HTTP response
// indicates that an error occurred, getInputStream() will throw an IOException. Use
// getErrorStream() to read the error response.
try {
return connection.getInputStream();
} catch (SocketTimeoutException e) {
throw e; //Rethrow exception - should not attempt to read stream for timeouts
} catch (IOException e1) {
// Use the error response.
// Use the error response for all other IO Exceptions.
return connection.getErrorStream();
}
}
@@ -0,0 +1,22 @@
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2019 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0

package com.google.appinventor.components.runtime.errors;

import com.google.appinventor.components.runtime.util.ErrorMessages;

import java.io.IOException;

/**
* Runtime error indicating that a network request has timed out.
*/
public class RequestTimeoutException extends IOException {
final int errorNumber;

public RequestTimeoutException() {
super();
this.errorNumber = ErrorMessages.ERROR_WEB_REQUEST_TIMED_OUT;
}
}
@@ -138,6 +138,7 @@
public static final int ERROR_WEB_BUILD_REQUEST_DATA_NOT_TWO_ELEMENTS = 1113;
public static final int ERROR_WEB_UNABLE_TO_DELETE = 1114;
public static final int ERROR_WEB_XML_TEXT_DECODE_FAILED = 1115;
public static final int ERROR_WEB_REQUEST_TIMED_OUT = 1117; //Continuing from number after contact picker
// Contact picker (and PhoneNumberPicker) errors
public static final int ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER = 1107;
public static final int ERROR_PHONE_UNSUPPORTED_SEARCH_IN_CONTACT_PICKING = 1108;
@@ -501,7 +502,9 @@
errorMessages.put(ERROR_WEB_BUILD_REQUEST_DATA_NOT_TWO_ELEMENTS,
"Unable to build request data: element %s does not contain two elements");
errorMessages.put(ERROR_WEB_UNABLE_TO_DELETE,
"Unable to delete a resource with the specified URL: %s");
"Unable to delete a resource with the specified URL: %s");
errorMessages.put(ERROR_WEB_REQUEST_TIMED_OUT,
"Took longer then timeout period to receive data from the URL: %s");
// Contact picker (and PhoneNumberPicker) errors
errorMessages.put(ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER,
"The software used in this app cannot extract contacts from this type of phone.");
@@ -707,6 +707,8 @@ <h3>Properties</h3>
<dd>Whether the response should be saved in a file.</dd>
<dt><code>Url</code></dt>
<dd>The URL for the web request.</dd>
<dt><code>Timeout</code></dt>
<dd>The number of milliseconds that the Web component will wait for a response to a web request. If 0, then the Web component will wait indefinitely.</dd>
</dl>

<h3>Events</h3>
@@ -715,6 +717,8 @@ <h3>Events</h3>
<dd>Event indicating that a request has finished.</dd>
<dt><code>GotText(text url, number responseCode, text responseType, text responseContent)</code></dt>
<dd>Event indicating that a request has finished.</dd>
<dt><code>TimedOut(text url)</code></dt>
<dd>Event indicating that a request has timed out before completing.</dd>
</dl>

<h3>Methods</h3>

0 comments on commit 28e898d

Please sign in to comment.
You can’t perform that action at this time.