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 { ...@@ -1546,6 +1546,13 @@ public final class YoungAndroidFormUpgrader {
// No properties need to be modified to upgrade to version 5. // No properties need to be modified to upgrade to version 5.
srcCompVersion = 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; return srcCompVersion;
} }
......
...@@ -2577,7 +2577,10 @@ Blockly.Versioning.AllUpgradeMaps = ...@@ -2577,7 +2577,10 @@ Blockly.Versioning.AllUpgradeMaps =
4: "noUpgrade", 4: "noUpgrade",
// AI2: Added method UriDecode // AI2: Added method UriDecode
5: "noUpgrade" 5: "noUpgrade",
// AI2: Added property Timeout and event TimedOut
6: "noUpgrade"
}, // End Web upgraders }, // End Web upgraders
......
...@@ -469,8 +469,10 @@ public class YaVersion { ...@@ -469,8 +469,10 @@ public class YaVersion {
// - Label component version incremented to 5 // - Label component version incremented to 5
// For YOUNG_ANDROID_VERSION 189: // For YOUNG_ANDROID_VERSION 189:
// - FORM_COMPONENT_VERSION was incremented to 25 // - 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 ............................... // ............................... Blocks Language Version Number ...............................
...@@ -1222,7 +1224,10 @@ public class YaVersion { ...@@ -1222,7 +1224,10 @@ public class YaVersion {
// - Added method XMLTextDecode // - Added method XMLTextDecode
// For WEB_COMPONENT_VERSION 5: // For WEB_COMPONENT_VERSION 5:
// - Added method UriDecode // - 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: // For WEBVIEWER_COMPONENT_VERSION 2:
// - The CanGoForward and CanGoBack methods were added // - The CanGoForward and CanGoBack methods were added
......
...@@ -21,7 +21,9 @@ import com.google.appinventor.components.common.PropertyTypeConstants; ...@@ -21,7 +21,9 @@ import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.components.runtime.collect.Lists; import com.google.appinventor.components.runtime.collect.Lists;
import com.google.appinventor.components.runtime.collect.Maps; 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.PermissionException;
import com.google.appinventor.components.runtime.errors.RequestTimeoutException;
import com.google.appinventor.components.runtime.util.AsynchUtil; import com.google.appinventor.components.runtime.util.AsynchUtil;
import com.google.appinventor.components.runtime.util.ErrorMessages; import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.FileUtil; import com.google.appinventor.components.runtime.util.FileUtil;
...@@ -51,6 +53,7 @@ import java.net.CookieHandler; ...@@ -51,6 +53,7 @@ import java.net.CookieHandler;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.ProtocolException; import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
...@@ -133,6 +136,7 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -133,6 +136,7 @@ public class Web extends AndroidNonvisibleComponent implements Component {
final boolean allowCookies; final boolean allowCookies;
final boolean saveResponse; final boolean saveResponse;
final String responseFileName; final String responseFileName;
final int timeout;
final Map<String, List<String>> requestHeaders; final Map<String, List<String>> requestHeaders;
final Map<String, List<String>> cookies; final Map<String, List<String>> cookies;
...@@ -142,6 +146,7 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -142,6 +146,7 @@ public class Web extends AndroidNonvisibleComponent implements Component {
allowCookies = web.allowCookies; allowCookies = web.allowCookies;
saveResponse = web.saveResponse; saveResponse = web.saveResponse;
responseFileName = web.responseFileName; responseFileName = web.responseFileName;
timeout = web.timeout;
requestHeaders = processRequestHeaders(web.requestHeaders); requestHeaders = processRequestHeaders(web.requestHeaders);
Map<String, List<String>> cookiesTemp = null; Map<String, List<String>> cookiesTemp = null;
...@@ -186,6 +191,7 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -186,6 +191,7 @@ public class Web extends AndroidNonvisibleComponent implements Component {
private YailList requestHeaders = new YailList(); private YailList requestHeaders = new YailList();
private boolean saveResponse; private boolean saveResponse;
private String responseFileName = ""; private String responseFileName = "";
private int timeout = 0;
/** /**
* Creates a new Web component. * Creates a new Web component.
...@@ -325,6 +331,31 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -325,6 +331,31 @@ public class Web extends AndroidNonvisibleComponent implements Component {
this.responseFileName = 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.") @SimpleFunction(description = "Clears all cookies for this Web component.")
public void ClearCookies() { public void ClearCookies() {
if (cookieHandler != null) { if (cookieHandler != null) {
...@@ -364,6 +395,9 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -364,6 +395,9 @@ public class Web extends AndroidNonvisibleComponent implements Component {
} catch (FileUtil.FileException e) { } catch (FileUtil.FileException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD, form.dispatchErrorOccurredEvent(Web.this, METHOD,
e.getErrorMessageNumber()); e.getErrorMessageNumber());
} catch (RequestTimeoutException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_REQUEST_TIMED_OUT, webProps.urlString);
} catch (Exception e) { } catch (Exception e) {
Log.e(LOG_TAG, "ERROR_UNABLE_TO_GET", e); Log.e(LOG_TAG, "ERROR_UNABLE_TO_GET", e);
form.dispatchErrorOccurredEvent(Web.this, METHOD, form.dispatchErrorOccurredEvent(Web.this, METHOD,
...@@ -438,6 +472,9 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -438,6 +472,9 @@ public class Web extends AndroidNonvisibleComponent implements Component {
} catch (FileUtil.FileException e) { } catch (FileUtil.FileException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD, form.dispatchErrorOccurredEvent(Web.this, METHOD,
e.getErrorMessageNumber()); e.getErrorMessageNumber());
} catch (RequestTimeoutException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_REQUEST_TIMED_OUT, webProps.urlString);
} catch (Exception e) { } catch (Exception e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD, form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_UNABLE_TO_POST_OR_PUT_FILE, path, webProps.urlString); ErrorMessages.ERROR_WEB_UNABLE_TO_POST_OR_PUT_FILE, path, webProps.urlString);
...@@ -511,6 +548,9 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -511,6 +548,9 @@ public class Web extends AndroidNonvisibleComponent implements Component {
} catch (FileUtil.FileException e) { } catch (FileUtil.FileException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD, form.dispatchErrorOccurredEvent(Web.this, METHOD,
e.getErrorMessageNumber()); e.getErrorMessageNumber());
} catch (RequestTimeoutException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_REQUEST_TIMED_OUT, webProps.urlString);
} catch (Exception e) { } catch (Exception e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD, form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_UNABLE_TO_POST_OR_PUT_FILE, path, webProps.urlString); ErrorMessages.ERROR_WEB_UNABLE_TO_POST_OR_PUT_FILE, path, webProps.urlString);
...@@ -548,6 +588,9 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -548,6 +588,9 @@ public class Web extends AndroidNonvisibleComponent implements Component {
} catch (FileUtil.FileException e) { } catch (FileUtil.FileException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD, form.dispatchErrorOccurredEvent(Web.this, METHOD,
e.getErrorMessageNumber()); e.getErrorMessageNumber());
} catch (RequestTimeoutException e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_REQUEST_TIMED_OUT, webProps.urlString);
} catch (Exception e) { } catch (Exception e) {
form.dispatchErrorOccurredEvent(Web.this, METHOD, form.dispatchErrorOccurredEvent(Web.this, METHOD,
ErrorMessages.ERROR_WEB_UNABLE_TO_DELETE, webProps.urlString); ErrorMessages.ERROR_WEB_UNABLE_TO_DELETE, webProps.urlString);
...@@ -605,6 +648,9 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -605,6 +648,9 @@ public class Web extends AndroidNonvisibleComponent implements Component {
} catch (FileUtil.FileException e) { } catch (FileUtil.FileException e) {
form.dispatchErrorOccurredEvent(Web.this, functionName, form.dispatchErrorOccurredEvent(Web.this, functionName,
e.getErrorMessageNumber()); e.getErrorMessageNumber());
} catch (RequestTimeoutException e) {
form.dispatchErrorOccurredEvent(Web.this, functionName,
ErrorMessages.ERROR_WEB_REQUEST_TIMED_OUT, webProps.urlString);
} catch (Exception e) { } catch (Exception e) {
form.dispatchErrorOccurredEvent(Web.this, functionName, form.dispatchErrorOccurredEvent(Web.this, functionName,
ErrorMessages.ERROR_WEB_UNABLE_TO_POST_OR_PUT, text, webProps.urlString); ErrorMessages.ERROR_WEB_UNABLE_TO_POST_OR_PUT, text, webProps.urlString);
...@@ -613,7 +659,6 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -613,7 +659,6 @@ public class Web extends AndroidNonvisibleComponent implements Component {
}); });
} }
/** /**
* Event indicating that a request has finished. * Event indicating that a request has finished.
* *
...@@ -643,6 +688,16 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -643,6 +688,16 @@ public class Web extends AndroidNonvisibleComponent implements Component {
EventDispatcher.dispatchEvent(this, "GotFile", url, responseCode, responseType, fileName); 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 * 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 { ...@@ -869,7 +924,7 @@ public class Web extends AndroidNonvisibleComponent implements Component {
* @throws IOException * @throws IOException
*/ */
private void performRequest(final CapturedProperties webProps, byte[] postData, String postFile, String httpVerb) private void performRequest(final CapturedProperties webProps, byte[] postData, String postFile, String httpVerb)
throws IOException { throws RequestTimeoutException, IOException {
// Open the connection. // Open the connection.
HttpURLConnection connection = openConnection(webProps, httpVerb); HttpURLConnection connection = openConnection(webProps, httpVerb);
...@@ -909,6 +964,15 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -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 { } finally {
connection.disconnect(); connection.disconnect();
} }
...@@ -929,6 +993,8 @@ public class Web extends AndroidNonvisibleComponent implements Component { ...@@ -929,6 +993,8 @@ public class Web extends AndroidNonvisibleComponent implements Component {
throws IOException, ClassCastException, ProtocolException { throws IOException, ClassCastException, ProtocolException {
HttpURLConnection connection = (HttpURLConnection) webProps.url.openConnection(); HttpURLConnection connection = (HttpURLConnection) webProps.url.openConnection();
connection.setConnectTimeout(webProps.timeout);
connection.setReadTimeout(webProps.timeout);
if (httpVerb.equals("PUT") || httpVerb.equals("DELETE")){ 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 // 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 { ...@@ -1074,14 +1140,16 @@ public class Web extends AndroidNonvisibleComponent implements Component {
return file.getAbsolutePath(); 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 // According to the Android reference documentation for HttpURLConnection: If the HTTP response
// indicates that an error occurred, getInputStream() will throw an IOException. Use // indicates that an error occurred, getInputStream() will throw an IOException. Use
// getErrorStream() to read the error response. // getErrorStream() to read the error response.
try { try {
return connection.getInputStream(); return connection.getInputStream();
} catch (SocketTimeoutException e) {
throw e; //Rethrow exception - should not attempt to read stream for timeouts
} catch (IOException e1) { } catch (IOException e1) {
// Use the error response. // Use the error response for all other IO Exceptions.
return connection.getErrorStream(); 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 { ...@@ -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_BUILD_REQUEST_DATA_NOT_TWO_ELEMENTS = 1113;
public static final int ERROR_WEB_UNABLE_TO_DELETE = 1114; 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_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 // Contact picker (and PhoneNumberPicker) errors
public static final int ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER = 1107; public static final int ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER = 1107;
public static final int ERROR_PHONE_UNSUPPORTED_SEARCH_IN_CONTACT_PICKING = 1108; public static final int ERROR_PHONE_UNSUPPORTED_SEARCH_IN_CONTACT_PICKING = 1108;
...@@ -501,7 +502,9 @@ public final class ErrorMessages { ...@@ -501,7 +502,9 @@ public final class ErrorMessages {
errorMessages.put(ERROR_WEB_BUILD_REQUEST_DATA_NOT_TWO_ELEMENTS, errorMessages.put(ERROR_WEB_BUILD_REQUEST_DATA_NOT_TWO_ELEMENTS,
"Unable to build request data: element %s does not contain two elements"); "Unable to build request data: element %s does not contain two elements");
errorMessages.put(ERROR_WEB_UNABLE_TO_DELETE, 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 // Contact picker (and PhoneNumberPicker) errors
errorMessages.put(ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER, errorMessages.put(ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER,
"The software used in this app cannot extract contacts from this type of phone."); "The software used in this app cannot extract contacts from this type of phone.");
......
...@@ -707,6 +707,8 @@ delimiter byte value is received. </dd> ...@@ -707,6 +707,8 @@ delimiter byte value is received. </dd>
<dd>Whether the response should be saved in a file.</dd> <dd>Whether the response should be saved in a file.</dd>
<dt><code>Url</code></dt> <dt><code>Url</code></dt>
<dd>The URL for the web request.</dd> <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> </dl>
<h3>Events</h3> <h3>Events</h3>
...@@ -715,6 +717,8 @@ delimiter byte value is received. </dd> ...@@ -715,6 +717,8 @@ delimiter byte value is received. </dd>
<dd>Event indicating that a request has finished.</dd> <dd>Event indicating that a request has finished.</dd>
<dt><code>GotText(text url, number responseCode, text responseType, text responseContent)</code></dt> <dt><code>GotText(text url, number responseCode, text responseType, text responseContent)</code></dt>
<dd>Event indicating that a request has finished.</dd> <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> </dl>
<h3>Methods</h3> <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