I am trying to update my kimono API via Google Script in Sheets. There are many urls in the sheet, but I've only shown 2 for this example.
I am receiving HTTP error 404. I've checked, and the apikey and id are fine.
How can I determine what's really wrong?
function PostParameters2() {
var parameters = {
apikey: "--apikey--",
urls: [
"https://twitter.com/search?q=%23running",
"https://twitter.com/search?q=%23swimming"
]
};
var data = JSON.stringify(parameters);
var url = 'https://kimonolabs.com/kimonoapis/--apiId--/update';
var options = {
'method': 'POST',
'content-Type': 'application/json',
'payload': data
};
var response = UrlFetchApp.fetch(url, options);
Logger.log(response.getResponseCode());
}
When debugging external host communication with UrlFetchApp, there are a few tools available to you.
It helps to muteHttpExceptions so you can view them. (In fact, you can simply write your code to handle them yourself, and only throw for exceptions you really don't expect.)
This is done by adding 'muteHttpExceptions' : true to your fetch parameters.
With exceptions muted, fetch won't throw an exception, instead it will pass failure response codes in the HTTPResponse. From there, you can extract the response code and content text for debugging (or automatic handling).
Modify your code like this, to log errors:
var response = UrlFetchApp.fetch(url, options);
var rc = response.getResponseCode();
if (rc !== 200) {
// HTTP Error
Logger.log("Response (%s) %s",
rc,
response.getContentText() );
// Could throw an exception yourself, if appropriate
}
Run, and here's what we see:
[15-08-27 11:18:06:688 EDT] Response (404.0) {
"error": true,
"message": "Could not find API"
}
Some APIs give very rich error messages. This one, not so much. But it does tell us that we have the URL correct, but that the service couldn't find the API we want. Next, dig into why that is so.
We can examine the fetch options and parameters by using getRequest(). Add this line just above the existing fetch() call, and put a breakpoint on the fetch().
var test = UrlFetchApp.getRequest(url, options);
Start the function in the debugger, and when the breakpoint is hit, examine the contents of test carefully.
A common problem is with the encoding of the POST payload. You hand-encoded # to %23 and used JSON.stringify(), so no problem there.
Checking the remaining options, we see that the contentType isn't 'application/json'.
So now you look at your code and find that the name for contentType was mis-typed as content-Type. Remove the hyphen, and try again.
Keep going until you've identified and fixed any other bugs.
Another tip is to use encodeURIComponent() to escape restricted characters in your fetch parameters, rather than hand-encoding them. It simplifies your code, because you can write the "real" characters like # instead of their UTF-8 escape sequences like %23.
Updated code:
function PostParameters2() {
var parameters = {
apikey: "--apikey--",
urls: [
encodeURIComponent("https://twitter.com/search?q=#running"),
encodeURIComponent("https://twitter.com/search?q=#swimming")
]
};
var data = JSON.stringify(parameters);
var url = 'https://kimonolabs.com/kimonoapis/--apiId--/update';
var options = {
'method': 'POST',
'contentType': 'application/json',
'payload': data,
'muteHttpExceptions' : true
};
var test = UrlFetchApp.getRequest(url, options);
var response = UrlFetchApp.fetch(url, options);
var rc = response.getResponseCode();
if (rc !== 200) {
// HTTP Error
Logger.log("Response (%s) %s",
rc,
response.getContentText() );
// Could throw an exception yourself, if appropriate
}
else {
// Successful POST, handle response normally
var responseText = response.getContentText();
Logger.log(responseText);
}
}
Related
I have some issue with using Fetch API JavaScript method when sending some simple formData like so:
function register() {
var formData = new FormData();
var textInputName = document.getElementById('textInputName');
var sexButtonActive = document.querySelector('#buttonsMW > .btn.active');
var imagesInput = document.getElementById('imagesInput');
formData.append('name', textInputName.value);
if (sexButtonActive != null){
formData.append('sex', sexButtonActive.html())
} else {
formData.append('sex', "");
}
formData.append('images', imagesInput.files[0]);
fetch('/user/register', {
method: 'POST',
data: formData,
})
.then(response => response.json());
}
document.querySelector("form").addEventListener("submit", register);
And on the server side (FastAPI):
#app.post("/user/register", status_code=201)
def register_user(name: str = Form(...), sex: str = Form(...), images: List[UploadFile] = Form(...)):
try:
print(name)
print(sex)
print(images)
return "OK"
except Exception as err:
print(err)
print(traceback.format_exc())
return "Error"
After clicking on the submit button I get Error 422: Unprocessable entity. So, if I'm trying to add header Content-Type: multipart/form-data, it also doesn't help cause I get another Error 400: Bad Request. I want to understand what I am doing wrong, and how to process formData without such errors?
The 422 response body will contain an error message about which field(s) is missing or doesn’t match the expected format. Since you haven't provided that (please do so), my guess is that the error is triggered due to how you defined the images parameter in your endpoint. Since images is expected to be a List of File(s), you should instead define it using the File type instead of Form. For example:
images: List[UploadFile] = File(...)
^^^^
When using UploadFile, you don't have to use File() in the default value of the parameter. Hence, the below should also work:
images: List[UploadFile]
Additionally, in the frontend, make sure to use the body (not data) parameter in the fetch() function to pass the FormData object (see example in MDN Web Docs). For instance:
fetch('/user/register', {
method: 'POST',
body: formData,
})
.then(res => {...
Please have a look at this answer, as well as this answer, which provide working examples on how to upload multiple files and form data to a FastAPI backend, using Fetch API in the frontend.
As for manually specifying the Content-Type when sending multipart/form-data, you don't have to (and shouldn't) do that, but rather let the browser set the Content-Type—please take a look at this answer for more details.
So, I found that I has error in this part of code:
formData.append('images', imagesInput.files[0]);
Right way to upload multiple files is:
for (const image of imagesInput.files) {
formData.append('images', image);
}
Also, we should use File in FastAPI method arguments images: List[UploadFile] = File(...) (instead of Form) and change data to body in JS method. It's not an error, cause after method called, we get right type of data, for example:
Name: Bob
Sex: Man
Images: [<starlette.datastructures.UploadFile object at 0x7fe07abf04f0>]
I get two different errors depending on if I GET or POST, but my autodesk rep assures me that using postman (not javascript) the resource does exist and he can get an authentication token.
If I do:
var url = "https://developer.api.autodesk.com/authentication/v1/authenticate";
var options = {
"method": "GET",
"headers":{"Content-Type": "application/x-www-form-urlencoded",},
"body": {
"client_id" : "Z---F",
"client_secret" : "m---8",
"grant_type": "client_credentials",
"scope": "data:read"
}
}
;
console.log(url);
console.log("Options:\n"+JSON.stringify(options));
var res = UrlFetchApp.fetch(url, options).getContentText();
Logger.log(res);
}
I get
Exception: Request failed for https://developer.api.autodesk.com returned code 404. Truncated server >response: { "developerMessage":"The requested resource does not exist.", "moreInfo": >https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/error_hand... (use muteHttpExceptions >option to examine full response)
if I do
var url = "https://developer.api.autodesk.com/authentication/v1/authenticate";
var options = {
"method": "POST",
"headers":{"Content-Type": "application/x-www-form-urlencoded",},
"body": {
"client_id" : "Z---F",
"client_secret" : "m---8",
"grant_type": "client_credentials",
"scope": "data:read"
}
}
;
console.log(url);
console.log("Options:\n"+JSON.stringify(options));
var res = UrlFetchApp.fetch(url, options).getContentText();
Logger.log(res);
}
I get
Exception: Request failed for https://developer.api.autodesk.com returned code 400. Truncated server >response: {"developerMessage":"The required parameter(s) client_id,client_secret,grant_type not present >in the request","errorCode":"AUTH-008","more info":"h... (use muteHttpExceptions option to examine full >response)
Any thoughts why it would say a resource doesn't exist that does exist? There is no personal info on here so the fact that he is doing it versus me doing it shouldn't matter. All the posts I could find on this issue were dealing with the curl version of this call rather than a javascript with options bundle
The URL is definitely valid (here's the API reference for this endpoint), so I would recommend debugging the actual code that's making the request, for example:
debug the UrlFetchApp class (where is it coming from btw?) and its fetch method, making sure that it's no modifying the url you're passing in
if the code is running in a browser, try looking at the DevTools Network tab and see if the request actually goes out to https://developer.api.autodesk.com/authentication/v1/authenticate
if the code is running in a browser, try using the built-in Fetch API instead
Also, I'm not sure if this is something handled automatically by the UrlFetchApp but specifying the content type of your request as application/x-www-form-urlencoded and then passing in a JSON object as the body doesn't seem right. As you can see in the documentation the request body should look more like this:
client_id=<your client id>&
client_secret=<your client secret>&
grant_type=client_credentials&
scope=data:read
I'm having trouble using Google Apps to interact with a management software called Kissflow.
function fun2() {
var id = "yyy";
var apisecretkey = "xxx";
var url ='https://'+id+'.kissflow.com/api/1/verify';
var options = {
method: 'post',
headers : {"Authorization" : " Basic " + Utilities.base64Encode(id + ":" + apisecretkey)},
payload: {
"grant_type": "client_credentials",
"scope": "basic+user"
},
muteHttpExceptions: true
};
var response = JSON.parse(UrlFetchApp.fetch(url, options).getContentText());
}
I would like to run this simple example of the API documentation, the goal is for me to be able to send data to the software through my interactions in a spreadsheet, for example. If you can help me in this I will be very grateful, I am new with API's :)
The following error appears: SyntaxError: unexpected token <in JSON at position 0 (line 30, file "Code") I don't know if I'm using this function correctly.
Kissflow API Documentation
JSON.parse(UrlFetchApp.fetch(url, options).getContentText())
Remove JSON.parse() from above line and output the response on your console using Logger.log() or to your browser log using console.log() and see the result. If there are errors it'll show more user friendly error message.
While I'm not familiar with the API, I suspect the issue is related to this line:
var response = JSON.parse(UrlFetchApp.fetch(url, options).getContentText());
You're fetching the response (which is probably JSON), then getting the content text of that response, then trying to parse that text as if it were JSON.
So (solution 1) if you set your response variable equal to
var response = UrlFetchApp.fetch(url, options).getContentText();
and try printing that variable to the console, you may find you have something you can work with.
Alternatively (solution 2), you might try:
var response = JSON.parse(UrlFetchApp.fetch(url, options));
if you'd rather have a JavaScript object to work with instead of a string (and assuming the .fetch method you're calling does indeed provide a JSON-formatted response.)
I am doing a server request with my firefox sdk, which replies a XML file.
I am parsing the XML file for two special values and put them into a global array (in my onComplete function).
My Problem is that the array does not save the values in element 0 and 1 and I don't know why!
My second Problem is that I want to call a request in my addon for the current tab url (that means more than one time). I know the request module says "Each Request object is designed to be used once" but is it possible to call it more than once?
I always get this error :
Error: This request object has been used already. You must create a new one to make a new request.
var Request = require("request").Request;
var {Cc, Ci} = require("chrome");
var parser = Cc["#mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
var setURL = "example.org";
var exampleArray = new Array();
function parseResponse(response) {
var xml = parser.parseFromString(response.text, "application/xml");
exampleArray[0] = xml.getElementsByTagName("example")[0].getAttribute("example"); // integer value
exampleArray[1] = xml.getElementsByTagName("example2")[0].getAttribute("example2"); //integer value
}
var exampleRequest = Request({
url: "http://www.example.com",
onComplete: parseResponse,
})
exampleRequest.get();
console.log("exampleArray" + exampleArray.length); //always 0
Thanks
Just wrap your request call in a function that's reusable. Request is a constructor, so you can instantiate several requests. But each request can only request one URL.
function request (url) {
Request({
url: url
onComplete: parseResponse,
}).get();
}
For the XML issue, just console log the individual nodes, probably not what you expect somewhere.
Using getAllResponseHeaders in the xhr object, is possible to get all the response headers after an ajax call.
But I can't found a way to get the Request headers string, is that possible ?
If this is for debugging purposes then you can just use Firebug or Chrome Developer Tools (and whatever the feature is called in IE) to examine the network traffic from your browser to the server.
An alternative would be to use something like this script:
$.ajax({
url: 'someurl',
headers:{'foo':'bar'},
complete: function() {
alert(this.headers.foo);
}
});
However I think only the headers already defined in headers is available (not sure what happens if the headers are altered (for instance in beforeSend).
You could read a bit more about jQuery ajax at: http://api.jquery.com/jQuery.ajax/
EDIT: If you want to just catch the headers on all calls to setRequestHeader on the XMLHttpRequest then you can just wrapp that method. It's a bit of a hack and of course you would need to ensure that the functions wrapping code below is run before any of the requests take place.
// Reasign the existing setRequestHeader function to
// something else on the XMLHtttpRequest class
XMLHttpRequest.prototype.wrappedSetRequestHeader =
XMLHttpRequest.prototype.setRequestHeader;
// Override the existing setRequestHeader function so that it stores the headers
XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
// Call the wrappedSetRequestHeader function first
// so we get exceptions if we are in an erronous state etc.
this.wrappedSetRequestHeader(header, value);
// Create a headers map if it does not exist
if(!this.headers) {
this.headers = {};
}
// Create a list for the header that if it does not exist
if(!this.headers[header]) {
this.headers[header] = [];
}
// Add the value to the header
this.headers[header].push(value);
}
Now, once the headers have been set on an XMLHttpRequest instance we can get them out by examining xhr.headers e.g.
var xhr = new XMLHttpRequest();
xhr.open('get', 'demo.cgi');
xhr.setRequestHeader('foo','bar');
alert(xhr.headers['foo'][0]); // gives an alert with 'bar'
Something you could to is use Sinon's FakeXMLHttpRequest to replace your browser's XHR. It's described in this document on how to use it for testing but I'm pretty sure you can use the module for your debugging purposes.
What you need to do is:
var requests;
this.xhr = sinon.useFakeXMLHttpRequest();
this.xhr.onCreate = function(xhr) {
requests.push(xhr);
}
And then later on, you can check your requests array for headers by:
console.log(requests[0].requestHeaders);
To access your request headers.