13

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);
          });
TheMaster
  • 32,296
  • 6
  • 31
  • 56
Achala Dissanayake
  • 752
  • 3
  • 13
  • 28

3 Answers3

18

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);
});
Spooky
  • 2,848
  • 8
  • 24
  • 39
  • "the 405 status code is sent in the response to the OPTIONS request, not your POST request" this part I knew, but I was under the impression that browsers send pre-flights for all post requests. "if the Content-Type header has a value other than the following:..." this part I didn't know. Although I manged a work around , which I can't exactly remember right now, I would mark this as the answer without testing since what I was looking for was also avoiding the prefilght. Thank you for the answer :) – Achala Dissanayake Oct 19 '17 at 18:22
  • This helped me avoiding preflight (and thus 405), thanks a ton!!! – Jay Dadhania Dec 17 '19 at 02:19
2

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

Jordan Rhea
  • 1,081
  • 14
  • 28
1

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)
  })
Hoang Trinh
  • 2,253
  • 1
  • 24
  • 47