*書き殴り気味の完全な個人的メモです。

1. フロントから GAS へのリクエストの投げ方 <Content-Type 別>

GAS はプリフライトをさばくことはできない。

  • Content-Type application/json -> アウト
  • Content-Type x-www-form-urlencoded -> オーケー
  • Content-Type text/plain -> オーケー

1-1. Content-Type が application/json だと CORS でアウト

const postReservation = async (name, email) => {
  const endPoint = 'https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec';
  const res = await fetch(endPoint, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ name, email }),
  });
  if (res.ok) console.log('okです!'); // しかし残念ながらokになることはありません。
};

1-2. Content-Type が x-www-form-urlencoded だと オーケー

const postReservation = async (name, email) => {
  const endPoint = 'https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec';
  const res = await fetch(endPoint, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: `name=${name}&email=${email}`,
  });
  if (res.ok) console.log('okです!'); // okですね。
};

1-3. Content-Type が text/plain でも オーケー

const postReservation = async (name, email) => {
  const endPoint = 'https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec';
  const res = await fetch(endPoint, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'text/plain',
    },
    body: `${name}&&&${email}`,
  });
  if (res.ok) console.log('okです!'); // okですね。
};

2. GAS でのフロントからのリクエストの受け取り方 <Content-Type 別>

2-1. application/json の場合(ただし CORS に引っかかるので実質機能しない)

const doPost = (e) => {
  const jsonString = e.postData.getDataAsString();
  const data = JSON.parse(jsonString);
  const { name, email } = data;
  // 以降処理は続く...
};

2-2. x-www-form-urlencoded の場合

const doPost = (e) => {
  const name = e.parameters['name'][0];
  const email = e.parameters['email'][0];
  // 以降処理は続く...
};

2-3. text/plain の場合

const doPost = (e) => {
  let text = e.postData.getDataAsString();
  const [name, email] = text.split('&&&'); // フロントからリクエスト送る時に区切り文字を何にしているかによります
  // 以降処理は続く...
};

3. GAS からフロントへのリクエストの返し方

GAS からのレスポンスは、成功だと 302 FOUND が返り、失敗だと 200 OK が返る。

200 OK が返ってきてるのにエラーだなんて気づけないでしょ…。

  • 302 FOUND(つまり成功)だと Access-Control-Allow-Origin * で返ってくる。
  • 200 OK(つまり失敗)だと Access-Control-Allow-Origin は設定されずに返ってくる。つまり CORS エラー。

「200 OK なのに Access-Control-Allow-Origin が設定されていないということは、GAS は標準で CORS 許可してないのか。」と納得してたのに。実際にはそれは、「200 OK が成功を意味すると思うな。全てを疑ってかかれ。」という GAS からの訓告でした。

3-1. GAS の return の書き方 200 OK(失敗)と 302 FOUND(成功)の場合

const doPost = (e) => {
  // もろもろ処理が終わったとして、あとはreturnするだけ...。

  // これだと 200 OK(失敗)
  return 'Hello World';

  // これも 200 OK(失敗)
  const res = { greet: 'Hello World' };
  return JSON.stringify(res);

  // 正解はこれです。これで 302 FOUND(成功)
  return ContentService.createTextOutput('Hello World');
};

4. ということでフロントと GAS の正しい書き方まとめ

ここでは、Content-Type x-www-form-urlencoded を使います。

4-1. フロント側からのリクエストの処理

export const postReservation = async (name, email) => {
  const endPoint =
    'https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec';
  let res = await fetch(endPoint, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: `name=${name}&email=${email}`,
  });
  if (!res.ok) return 'ダメよ'; // エラー処理を行う
  // (必要ならばresの中身を使って)以降正常系の処理を続ける...
  res = await res.json();
  console.log(res);
};

4-2. GAS 側でリクエストを受けた際の処理

const doPost = (e) => {
  // リクエストを取得
  const name = e.parameters['name'][0];
  const email = e.parameters['email'][0];
  // スプレッドシート(ss)を取得
  const ss = SpreadsheetApp.openById(SpreadsheetApp.getActiveSpreadsheet().getId());
  // シート(sheet)を取得
  const sheet = ss.getSheetByName('Sheet_1');
  // シートに書き込み
  sheet.appendRow([name, email]);
  // レスポンスを返す
  return ContentService.createTextOutput('ok');
};

5. 日本語入力に対応させる方法

  • 日本語で入力された内容を送った場合、GAS 側でスプレッドシートに書き込むと空白になってしまいます。
  • フロントから POST する時に、encodeURI() しておくことで、スプレッドシートにちゃんと書き込まれるようになりました。
const endPoint = 'https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec';
export const postReservation = async (name, email) => {
  const res = await fetch(endPoint, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: `name=${encodeURI(name)}&email=${encodeURI(email)}`,
  });
  if (!res.ok) return 'ダメよ'; // エラー処理を行う
  // (必要ならばresの中身を使って)以降正常系の処理を続ける...
  res = await res.json();
  console.log(res);
};

以上

GAS ややこしや 😡