Commit 28e898d9 authored by Austen Joa's avatar Austen Joa Committed by Evan W. Patton

Add Timeout property to Web component

parent 96af69a5
......@@ -1546,6 +1546,13 @@ public final class YoungAndroidFormUpgrader {
// 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 @@ public class 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 @@ public class 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.PropertyTypeConstants;
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.CookieHandler;
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 @@ public class Web extends AndroidNonvisibleComponent implements Component {
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 @@ public class Web extends AndroidNonvisibleComponent implements Component {
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 @@ public class Web extends AndroidNonvisibleComponent implements Component {
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 class Web extends AndroidNonvisibleComponent implements Component {
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 class Web extends AndroidNonvisibleComponent implements Component {
} 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 class Web extends AndroidNonvisibleComponent implements Component {
} 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 class Web extends AndroidNonvisibleComponent implements Component {
} 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 class Web extends AndroidNonvisibleComponent implements Component {
} 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 class Web extends AndroidNonvisibleComponent implements Component {
} 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 class Web extends AndroidNonvisibleComponent implements Component {
});
}
/**
* Event indicating that a request has finished.
*
......@@ -643,6 +688,16 @@ public class Web extends AndroidNonvisibleComponent implements Component {
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 class Web extends AndroidNonvisibleComponent implements Component {
* @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 class Web extends AndroidNonvisibleComponent implements Component {
});
}
} 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 @@ public class Web extends AndroidNonvisibleComponent implements Component {
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 @@ public class Web extends AndroidNonvisibleComponent implements Component {
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();
}
}
......
// -*- 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 final class ErrorMessages {
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;
......@@ -502,6 +503,8 @@ public final class ErrorMessages {
"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");
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 @@ delimiter byte value is received. </dd>
<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 @@ delimiter byte value is received. </dd>
<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>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment