UrlFetchApp.fetch() Exception: DNS error in Google Forms Trigger - google-apps-script

I've attached the following code to a Submit Button in Google Forms:
function generateQueryString(data) {
const params = [];
for (var d in data)
params.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
return params.join('&');
}
function ctrlqFormSubmit(event) {
// The event is a FormResponse object:
// https://developers.google.com/apps-script/reference/forms/form-response
var formResponse = event.response;
// Gets all ItemResponses contained in the form response
// https://developers.google.com/apps-script/reference/forms/form-response#getItemResponses()
var itemResponses = formResponse.getItemResponses();
// Gets the actual response strings from the array of ItemResponses
var responses = itemResponses.map(function getResponse(e) { return e.getResponse(); });
// Post the payload as JSON to our Cloud Function
var response = UrlFetchApp.fetch('http://<**MY DOMAIN HERE**:3001>/formdir?' + generateQueryString(responses));
Logger.log(response.getContentText());
}
Unfortunately, the Trigger fails with the message:
Exception: DNS error: http://**MY DOMAIN HERE**:3001/formdir?0=Name&1=&2=Biz&3=Research&4=&5=&6=&7=&8=Other&9=&10=&11=&12=&13=&14=
at ctrlqFormSubmit(SubmitFcn:18:30)
But if I copy this exact string in my browser, I get the expected result. Also, when I use NSLOOKUP with this domain on the command line, the IP's return as expected. The domain even resolves when I go to https://dns.google/...
The text of error itself seems to indicate the function is working, and it's a GET request, which is why I'm using Query Parameters, but the DNS name is not resolving in the script. I even used one of the IPs and got the same error.

SOLVED: Even though the domain used above was public-facing, the AWS Load Balancer it uses was configured with a "REDIRECT" to an internal domain. It was this second domain that caused the DNS Error, even though the message references the external domain. I changed the load balancer action from "redirect" to "forward", and now all is well.

Related

OAuth2 Library Yields Property Storage Quota Error

I have a script using Google's OAuth2 library, which recently started to fail with the error
You have exceeded the property storage quota. Please remove some properties and try again.
I inspected the properties and did not find any unexpected differences from my original configuration. The full stringified text length of my properties is around 5000 characters, which is well below the 500kB/property store quota and the longest individual property ~2500 characters (below the 9kB/value quota).
I've discovered that this error only occurs when I use Google's OAuth2 library with a service account. However, if I include the library dist directly in my project, the error disappears.
Why is there a difference in how the properties service behaves between the library and the local copy if they appear to be the same versions?
(In other scripts where I use the OAuth2 library with a standard authorization code grant flow, I have no issues.)
This script will replicate the error, but requires that you create a service account and set the correct script properties as defined in getAdminDirectory_(). (Using Rhino interpreter.)
/**
* Get a GSuite User.
*/
function getUser() {
var email = "name#exampledomain.com";
var user = AdminDirectory_getUser_(email);
Logger.log(user);
}
/**
* Gets a user from the GSuite organization by their email address.
* #returns {User} - https://developers.google.com/admin-sdk/directory/v1/reference/users#resource
*/
function AdminDirectory_getUser_(email) {
var service = getAdminDirectory_();
if (service.hasAccess()) {
var url = "https://www.googleapis.com/admin/directory/v1/users/" + email;
var options = {
method: "get",
headers: {
Authorization: "Bearer " + service.getAccessToken()
}
};
var response = UrlFetchApp.fetch(url, options);
var result = JSON.parse(response.getContentText());
return result;
} else {
throw service.getLastError();
}
}
/**
* Configures the service.
*/
function getAdminDirectory_() {
// Get service account from properties
var scriptProperties = PropertiesService.getScriptProperties();
var serviceAccount = JSON.parse(scriptProperties.getProperty("service_account"));
// Email address of the user to impersonate.
var user_email = scriptProperties.getProperty("service_account_user");
return OAuth2.createService("AdminDirectory:" + user_email)
// Set the endpoint URL.
.setTokenUrl("https://oauth2.googleapis.com/token")
// Set the private key and issuer.
.setPrivateKey(serviceAccount.private_key)
.setIssuer(serviceAccount.client_email)
// Set the name of the user to impersonate. This will only work for
// Google Apps for Work/EDU accounts whose admin has setup domain-wide
// delegation:
// https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority
.setSubject(user_email)
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(scriptProperties)
// Set the scope. This must match one of the scopes configured during the
// setup of domain-wide delegation.
.setScope("https://www.googleapis.com/auth/admin.directory.user");
}
This seems to be a known issue. Consider adding a star(on top left) to this issue to let Google developers know that you're affected. Consider adding a comment to the tracker with details requested from #7
Possible solutions:
As said in the question, directly use the library code by copy pasting instead of using the buggy library feature.
Switch to v8-#21
Wait for random/Auto resolution -#14

Triggering GAS function via URL

Very new to this, but giving it a shot. I am trying to set up an Arduino motion sensor to trigger a script. At this point, my goal is to trigger a script via URL. I found this code below that I am working through, but I continue to get this error when running/debugging.
TypeError: Cannot read property "parameter" from undefined. (line 4, file "Code")
I have been looking into e.parameter object, but have not been able to make any headway
function doGet(e) {
Logger.log(e)
var passedString,whatToReturn;
passedString = e.parameter.searchStringName;
if (passedString === 'tylog') {
whatToReturn = tylog(); //Run function One
};
return ContentService.createTextOutput(whatToReturn);
};
var mns = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Monster")
var tyl = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("tyLog")
var tyd = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("tyData")
var twl = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("twLog")
var twd = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("twData")
var tym = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("tyMaster")
var twm = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("twMaster")
var test = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("test")
var tydate = tyd.getRange('A2');
var tydur = tyd.getRange(2, 2);
// Start functions
function start() {
tyl.getRange('A1').setValue(new Date());
twl.getRange('A1').setValue(new Date());
}
//Log Typhoon ride
function tylog() {
tyl.getRange(tyl.getLastRow() + 1, 1).setValue(new Date());
}
//Log Twister ride
function twlog() {
twl.getRange(twl.getLastRow() + 1, 1).setValue(new Date());
}
//Send Data to both logs and clear
function tyclear() {
tyd.getRange('A2:H2').copyTo(tym.getRange(tym.getLastRow() + 1, 1), {contentsOnly: true});
twd.getRange('A2:H2').copyTo(twm.getRange(twm.getLastRow() + 1, 1), {contentsOnly: true});
tyl.getRange('A1:A100').clearContent();
twl.getRange('A1:A100').clearContent();
}
URL Request:
https://script.google.com/macros/s/AKfycbxC5zYevR1IhfFcUMjmIqUaQ1dKNHTm4mhmWBq_Rc9HgemJQ6Q/exec?searchStringName=tylog
I put this into a new project by itself and it still returned undefined​.
function doGet(e) {
var passedString,whatToReturn;
passedString = e.parameter.searchStringName;
if (passedString === 'functionOne') {
whatToReturn = functionOne(); //Run function One
};
return ContentService.createTextOutput(whatToReturn);
};
function functionOne() {
var something;
return ContentService.createTextOutput("Hello, world!"); }
I believe that your URL should be https://script.google.com/macros/s/AKfycbxC5zYevR1IhfFcUMjmIqUaQ1dKNHTm4mhmWBq_Rc9HgemJQ6Q/exec?searchStringName=functionOne
After pondering this question for a while it makes no sense to require a return from functionOne. I was getting the client server communication mixed up with the Get request process. For most Get requests the request suggests some type of response since in general we're looking for some type of content to be displayed. In this situation that may not be required since the requestor is a machine.
The use of e.parameter.paramname; just enables us to send key/value pairs from within our querystring that we can recover to redirect our server actions.
2020 UPD:
Upon revisiting the question, I noticed that the OP runs the doGet trigger in the context of script editor, hence the e becoming undefined (as it is only constructed when a request hits the URL with an HTTP request with GET method).
Thus, the answer to the debugging part is:
When running a trigger manually from the script editor, event object will be unavailable
The answer to the running part is as a result of an extended discussion:
When assigning a result of the function, one has to put the return statement inside the function, and the tylog function did not return anything.
Also note that any change to a Web App code, unless accessing it via /dev endpoint (i.e. via /exec endpoint), won't be available until after redeployment.
References
Web Apps guide

AUTHENTICATION_FAILED when querying CloudKit public database with CloudKit Web Services API in Google Apps Script

I'm trying to use the CloudKit Web Services API to fetch Article records from my production CloudKit container's public database within Google Apps Script.
My request is based on the documentation on this page.
Here's my code:
// variables for the CloudKit request URL
var path = "https://api.apple-cloudkit.com";
var version = "1";
var container = "iCloud.com.companyname.My-Container-Name";
var environment = "production";
var database = "public";
var token = "8888888888888_my_actual_token_88888888888888888"
function showArticles() {
// assemble the URL
var url = path + "/database/" + version + "/" + container + "/" + environment + "/" + database + "/records/query?ckAPIToken=" + token;
// specify the record type to query
var query = {
recordType: "Article"
};
// specify the payload for the POST request
var payload = {
query : query
};
// set up the fetch options for the fetch request
var options = {
method : "POST",
payload : payload
};
// make the request
var response = UrlFetchApp.fetch(url, options);
Logger.log(response);
}
UrlFetchApp.fetch(url, options) fails with this error:
Request failed for https://api.apple-cloudkit.com/database/1/iCloud.<?>-Container-Name/development/public/records/query?ckAPIToken=8888888888888_my_actual_token_88888888888888888 returned code 401. Truncated server response: {"uuid":"7d8a8547-ad08-4090-b4b3-917868a42f6f","serverErrorCode":"AUTHENTICATION_FAILED","reason":"no auth method found"} (use muteHttpExceptions option to examine full response) (line 30, file "Code")
I've been troubleshooting for a few hours and I can't figure out what I'm doing wrong. I've tried it with a separate token on my development environment, too, and the same thing happens.
This page mentions the ckWebAuthToken parameter and says "if omitted and required, the request fails," but I can't find anything that says what requests require a ckWebAuthToken. I'm assuming I don't need ckWebAuthToken since the records I'm trying to access are in my container's public database, and I'm getting an AUTHENTICATION_FAILED error rather an AUTHENTICATION_REQUIRED error.
One part that confuses me is this URL that comes up in the error message:
https://api.apple-cloudkit.com/database/1/iCloud.<?>-Container-Name/development/public/records/query?ckAPIToken=8888888888888_my_actual_token_88888888888888888
I would expect it to be:
https://api.apple-cloudkit.com/database/1/iCloud.com.companyname.My-Container-Name/development/public/records/query?ckAPIToken=8888888888888_my_actual_token_88888888888888888
But I can't tell if that's actually the URL that's being requested, and when I log the url variable everything looks fine.
Thanks in advance for any troubleshooting tips or solutions!
UPDATE
I tried using Postman, and the request worked with same endpoint and POST data. It looks like the container component of the URL is getting corrupted by the Google Apps Script UrlFetchApp.fetch() method. The <?> seems to only show up when com. is in the URL.
I'm not sure why this is the answer, but I was able to get it working by using JSON.stringify() on the payload in options:
var options = {
method : "POST",
payload : JSON.stringify(payload)
};

Url Parameter Error in Google Appscript with ContentService, MimeType XML

I am getting some issue when taking value form parameter here is my simple code in Google App script and deployed service. what is the issue ?
function doGet(e) {
var num = e.parameter.num;
var result=false;
result=(num%2==0);
if(result){
return ContentService.createTextOutput(result).setMimeType(ContentService.MimeType.XML);
}else{
return ContentService.createTextOutput(result).setMimeType(ContentService.MimeType.XML);
}
}
https://script.google.com/macros/s/AKfycbz86LRyPqowhg_ajj48oM13aESMPms30tbne-_p9sWwJVcaQzg/exec?num=20
Here is google appscript deployed url
This error I am getting when I am hitting this url
and code running error in App-script Environment
it seems that the issue might come from the modulo operation you are trying to apply to a string value, when I try this code it runs without error
function doGet(e) {
var num = Number(e.parameter.num);// make it a number before testing parity
var result=false;
result=(num%2==0);
var xmlContent = '<mydata>' + result+ num + '</mydata>';// added num value for test purpose
if(result){
return ContentService.createTextOutput(xmlContent).setMimeType(ContentService.MimeType.XML);
}else{
return ContentService.createTextOutput(xmlContent).setMimeType(ContentService.MimeType.XML);
}
}
That said, I suppose this is just a test code because I don't really see what it can be used for and the xml output is not valid but I'll leave you with that issue.

bad request with UrlFetchApp

Looking for some help connecting to this service and returning the xml.
Here are the instructions (from here):
The state of the inputs and relays can be monitored by sending a
request to port 80 (or port specified in setup) for the XML page
state.xml. The relays can be controlled by sending GET requests to the
same page on port 80 (or port specified in setup). This can be
demonstrated by entering commands into the URL line of a web browser.
Request the current state: http://"ip address"/state.xml
...
If the control password is enabled in the WebRelay-DualTM unit and
the state.xml page is requested through a browser, the browser will
prompt the user for the password. If the XML request is sent from
another application and not a browser, the html request will need to
contain the password encoded using the base 64 encoding scheme. The
html request header without the password looks like this:
GET /state.xml?relay1State=1&noReply=1 HTTP/1.1 (Ends with two \r\n)
The html request header with the password looks like this:
GET /state.xml?relay1State=1&noReply=1 HTTP/1.1(\r\n here)
Authorization: Basic bm9uZTp3ZWJyZWxheQ== (Ends with two \r\n)
where bm9uZTp3ZWJyZWxheQ== is the base 64 encoded version of the
user name and password none:webrelay
Code:
function webRelay(){
//working url http://75.65.130.27/state.xml
var url = 'http://75.65.130.27/';
var params = encodeURIComponent('state.xml');
Logger.log(params);
var headers = {
"Authorization" : "Basic" + Utilities.base64Encode('none:webrelay')
};
var options =
{
"method" : "get",
"headers" : headers
};
var state = UrlFetchApp.fetch(url+params, options);
Logger.log('1: '+state);
Logger.log(parse(state));
}
function parse(txt) {
var doc = Xml.parse(txt, true);
}
Any help is much appreciated.
There are a couple of coding errors that you can easily take care of:
In the Authorization header you need a space after "Basic".
Authorization : "Basic " + Utilities.base64Encode(username+':'+password)
urlFetchApp.fetch() returns an HTTP Response object, so you need to extract the contents for parsing.
var result = UrlFetchApp.fetch(url, options);
var state = result.getContentText();
You aren't returning anything from your parse() function.
You should check result.getResponseCode() after .fetch(), and handle errors before proceeding with parsing.
That said, I keep getting Bad request: http://75.65.130.27/state.xml errors, so something is still not right. This is an HTTP 400 response, and google's servers don't return anything to the script debugger to dig into it. You should check the username & password, although I'd expect a 401-Unauthorized response if they were wrong. I tried including a payload of relay1State=2, and got the same Bad request result. If you can capture the HTTP Request hitting your server, there may be a clue to what is malformed. This could also be the result of a firewall.
Once that's sorted, this tutorial should help with the XML Parsing.
Here's my edit of your code:
function webRelay(){
var url = 'http://75.65.130.27/state.xml';
var username = "none";
var password = "webrelay";
var headers =
{
Authorization : "Basic " + Utilities.base64Encode(username+':'+password)
}
var options =
{
"method" : "get",
"headers": headers
};
// Getting "bad request" here - check the username & password
var result = UrlFetchApp.fetch(url, options);
var state=result.getContentText();
// You should check state.getResponseCode()
Logger.log('1: '+state);
Logger.log(parse(state));
}
function parse(txt) {
var doc = Xml.parse(txt, true);
return doc; // Return results
}