App Script sends 405 response when trying to send a POST request - google-apps-script

I have published an app script publicly (Anyone, even anonymous) with a doPost method as follow,
function doPost(e){
var sheet = SpreadsheetApp.getActiveSheet();
var length = e.contentLength;
var body = e.postData.contents;
var jsonString = e.postData.getDataAsString();
var jsonData = JSON.parse(jsonString);
sheet.appendRow([jsonData.title, length]);
var MyResponse = "works";
return ContentService.createTextOutput(MyResponse).setMimeType(ContentService.MimeType.JAVASCRIPT);
}
When I sent a Post request with a JSON object with Advanced Rest Client it all works and return a 200 OK response. But when I try to send a post request with the react axios from a locally hosted react app it sends a 405 Response.
XMLHttpRequest cannot load https://script.google.com/macros/s/AKfycbzyc2CG9xLM-igL3zuslSmNY2GewL5seTWpMpDIQr_5eCod7_U/exec. Response for preflight has invalid HTTP status code 405
I have enabled cross origin resource sharing in the browser as well. The function that sends the POST request is as follow,
axios({
method:'post',
url:'https://script.google.com/macros/s/AKfycbzyc2CG9xLM-igL3zuslSmNY2GewL5seTWpMpDIQr_5eCod7_U/exec',
data: {
"title": 'Fred',
"lastName": 'Flintstone'
}
}).then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

You missed the important part:
Response for preflight has invalid HTTP status code 405.
Your browser is making a preflight request, which uses the OPTIONS HTTP method. This is to check whether the server will allow the POST request – the 405 status code is sent in the response to the OPTIONS request, not your POST request.
A CORS preflight request is a CORS request that checks to see if the CORS protocol is understood. Source
Additionally, for HTTP request methods that can cause side-effects on server's data (in particular, for HTTP methods other than GET, or for POST usage with certain MIME types), the specification mandates that browsers "preflight" the request, soliciting supported methods from the server with an HTTP OPTIONS request method, and then, upon "approval" from the server, sending the actual request with the actual HTTP request method. Source
Some requests don’t trigger a CORS preflight. Those are called "simple requests" in this article [...] Source
This article section details the conditions a request has to meet to be considered a "simple request".
[...] "preflighted" requests first send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send. Cross-site requests are preflighted like this since they may have implications to user data. Source
This article section details the conditions which cause a request to be preflighted.
In this case, the following is causing the request to be preflighted:
[...] if the Content-Type header has a value other than the following:
application/x-www-form-urlencoded
multipart/form-data
text/plain
The value for the Content-Type header is set to application/json;charset=utf-8 by axios. Using text/plain;charset=utf-8 or text/plain fixes the problem:
axios({
method: 'post',
url: 'https://script.google.com/macros/s/AKfycbzyc2CG9xLM-igL3zuslSmNY2GewL5seTWpMpDIQr_5eCod7_U/exec',
data: {
title: 'Fred',
lastName: 'Flintstone',
},
headers: {
'Content-Type': 'text/plain;charset=utf-8',
},
}).then(function (response) {
console.log(response);
}).catch(function (error) {
console.log(error);
});

Another way for someone who has this problem in the future:
(in my case, using 'Content-Type': 'text/plain;charset=utf-8' doesn't work)
According to this doc https://github.com/axios/axios#using-applicationx-www-form-urlencoded-format
Instead of using text/plain;charset=utf-8 as the accepted answer, you can use application/x-www-form-urlencoded:
const axios = require('axios')
const qs = require('qs')
const url = 'https://script.google.com/macros/s/AKfycbzyc2CG9xLM-igL3zuslSmNY2GewL5seTWpMpDIQr_5eCod7_U/exec'
const data = {
"title": 'Fred',
"lastName": 'Flintstone'
}
const options = {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify(data),
url
}
axios(options)
.then(function(response) {
console.log(response.data)
})
.catch(function(error) {
console.log(error)
})

I think you need to return JSON data. It is possible that you need to return JSONP to a request from a browser, but here is what I think you need to do:
return ContentService.createTextOutput(JSON.stringify({message: MyResponse})).setMimeType(ContentService.MimeType.JSON);
If that doesn't work, it is probably that you need to return JSONP to run in the browser. Here is some documentation to help you out: https://developers.google.com/apps-script/guides/content#serving_jsonp_in_web_pages

Related

Cannot authenticate via Postman (Bearer token)

So I am trying around with an application that I did not code, and I am quite new to REST APIs etc, so I am having some issues with that.
From my front-end Next.js application I am sending an Axios request to the backend with a Bearer token header
const config: AxiosRequestConfig = {
method,
url: `${BASE_URL}${url}`,
headers: isFormData ? { 'Content-Type': 'multipart/form-data' } : {},
data: isFormData ? params : ( params ? { ...params } : null ),
validateStatus: function (status) {
return status >= 200 && status < 300 // default
}
}
config.headers['Authorization'] = `Bearer ${userStore.token}`
Everything works, so if a user is authenticated and has the token in the userStore the request is successful and the backend responds with the given json data for a certain api route.
Postman Image
Though when I try to fetch the json data directly from the API route in Postman, and provide the Authorization header for this request, I get an error not authorized response from the backend. What am I missing here?
try selecting 'bearer token' under authorization and give the token directly.

Angular HTTP post not accepting JSON response?

I created an API in laravel and tested in postman and it is working perfect. But when I try it from angular it works fine for returning a string text but not for JSON response
I searched it on internet and found setting content-type:application/json and tried with different ways for setting content type in header but issue still persist
var obj = JSON.parse('{"email":"ab#gm.com","password":"12345678"}');
//1st type of header
var headers_object = new HttpHeaders().set('Content-Type',
'application/json');
const httpOptions = {
headers: headers_object
};
//2nd type of header
var HTTPOptions = {
headers: new HttpHeaders({
'Accept':'application/json',
'Content-Type': 'application/json'
}),
'responseType': 'application/json' as 'json'
}
return this.http.post<any>(`http://127.0.0.1:8000/api/auth/login`, obj,HTTPOptions ).subscribe(resp => {
console.log(resp);
});
Postman Output
browser network request/response
return this.http.post(http://127.0.0.1:8000/api/auth/login, obj,HTTPOptions ).map((resp: Response) => resp.json())
hopefully this will work
Basically, you are sending "string JSON" instead JSON Object, send Javascript Object directly instead of string will solve your issue,
Use the below-mentioned way to post JSON data to the server,
var httpOptions = {
headers: new HttpHeaders({
'Accept':'application/json',
'Content-Type': 'application/json'
})
};
var dataToPost = {"email":"ab#gm.com","password":"12345678"};
this.http.post('http://127.0.0.1:8000/api/auth/login', dataToPost, httpOptions)
.subscribe(resp => {
console.log(resp);
});
It was due to CORB.
Cross-Origin Read Blocking (CORB) is an algorithm that can identify
and block dubious cross-origin resource loads in web browsers before
they reach the web page. CORB reduces the risk of leaking sensitive
data by keeping it further from cross-origin web pages.
https://www.chromestatus.com/feature/5629709824032768
Solution run chrome in disabled web security mode.
This worked for me https://stackoverflow.com/a/42086521/6687186
Win+R and paste
chrome.exe --user-data-dir="C:/Chrome dev session" --disable-web-security

How to send JSON in a POST request with NodeJS

I'm trying to send a POST request to an endpoint which accepts JSON and it doesn't work. Do I have to send any specific parameter in order to let the network know it is encoded as JSON?
Here is the simple request I've so far:
var request = require('request')
var cookie = '**Here the cookie copied from the Network tab from the Chrome Dev Tools Bar**'
var UA = '**Here the UA copied from the Network tab from the Chrome Dev Tools Bar**'
var JSONformData = {"jsonrpc":"2.0","method":"LMT_split_into_sentences","params":{"texts":["Text"],"lang":{"lang_user_selected":"auto","user_preferred_langs":["EN","ES"]}},"id":8}
var URL = 'https://www.deepl.com/jsonrpc'
request.cookie(cookie)
request.post({
url: URL,
headers: {
'User-Agent': UA
},
form: JSONformData
}, function(error, response, body) {
console.log(response)
}
)
If you are sending JSON data then you don't need to specify the form, instead specify the json for data in the options object:
request.post({
url: URL,
headers: {
'User-Agent': UA
},
json: JSONformData
}, function(error, response, body) {
console.log(response)
})

Can't send a post request when the 'Content-Type' is set to 'application/json'

I am working on a React application and I am using fetch to send a request. I have made a Sign Up form recently and now I am integrating it with it's API. Previously the API accepted url encoded data and it was all working fine. but now that the requirement has changed and the API accepts data in JSON, I had to change the content-type header from 'application/x-www-form-urlencoded' to 'application/json'. But I get the following error:
Fetch API cannot load http://local.moberries.com/api/v1/candidate.
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:3000' is therefore not allowed
access. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.
I have even set the 'Access-Control-Allow-Headers' in the API but it still doesn't work. Here is the relevant code for the client side:
sendFormData() {
let {user} = this.props;
var formData = {
first_name: ReactDOM.findDOMNode(this.refs.firstName).value,
last_name: ReactDOM.findDOMNode(this.refs.lastName).value,
city: ReactDOM.findDOMNode(this.refs.currentCity).value,
country: ReactDOM.findDOMNode(this.refs.currentCountry).value,
is_willing_to_relocate: user.selectedOption,
cities: relocateTo,
professions: opportunity,
skills: skills,
languages: language,
min_gross_salary: minSal,
max_gross_salary: maxSal,
email: ReactDOM.findDOMNode(this.refs.email).value,
password: ReactDOM.findDOMNode(this.refs.password).value
};
var request = new Request('http://local.moberries.com/api/v1/candidate', {
method: 'POST',
mode: 'cors',
headers: new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json'
})
});
var requestBody = JSON.stringify(formData);
console.log(requestBody);
fetch(request, {body: requestBody})
.then(
function (response) {
if (response.status == 200 || response.status == 201) {
return response.json();
} else {
console.log('Failure!', response.status);
}
}).then(function (json) {
var responseBody = json;
console.log(typeof responseBody, responseBody);
});
}
And here is the relevant API code:
class Cors
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers: Origin, Content-Type, application/json');
}
}
I really can't figure out the problem. Any kind of help will be appreciated.
It turns out that CORS only allows some specific content types.
The only allowed values for the Content-Type header are:
application/x-www-form-urlencoded
multipart/form-data
text/plain
Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
To set the content type to be 'application/json', I had to set a custom content type header in the API. Just removed the last header and added this one:
->header('Access-Control-Allow-Headers', 'Content-Type');
and it is working all good.
You are violating the 'Same Origin Policy'. A website www.example.com may never be able to load resources from any website www.example.net other than from itself.
During development however, sometimes one needs to be able to do that. To bypass this:
either move your origin to http://local.moberries.com,
or move the api (which you are accessing) to your localhost.
Other than that, there are ways to temporarily turn off restrictions of these kinds in some browsers (esp. Chrome), methods of which usually require more and more effort in the subsequent updates of the browser. Search Google about how to turn on Cross-Origin Resource Sharing in your version of the browser.
Or, as the error prompt suggests, introduce a header allowing requests to be entertained from non-origins. More information is in the documentation for Access Control CORS

HTTP Request returning empty element

I try to get a JSON object from a webservice with
MashupPlatform.http.makeRequest(url, {
method: 'GET',
requestHeaders: {"Accept": "application/json"},
forceProxy: true,
onSuccess: function (response) {
console.log("response: " + JSON.stringify(response));
success(response);
},
onFailure: function (response) {
error(response);
},
onComplete: function () {
complete();
}
});
but in the console every time an empty element ({}) gets logged. If I use curl to request that exact same URL I get the response I need. Is the wirecloud proxy unable to request application/json? In my browsers network analysis I see the request including the correct response, but the success function seems to not get that data.
WireCloud proxy supports application/json without any problem. Although the problem may be caused by other parameters, I think that your problem is related to a bad access to the response data. You should use response.responseText instead of using directly the response object (see this link for more info).