GASからのCROSエラーの解決方法


 

少しハマりましたので、忘備録として。
参考サイト https://knmts.com/become-engineer-31/

この記事を書いてから、同目的の別記事を書いています。こちらの方がシンプルかもしれません。
https://i-say.net/note/1100/

以下の記事にある「mode: ‘no-cors’」の設定は、あまり今は推奨しません。
上記のページで解決できるので、上記のページを先にお読み下さい。

1. はじめに

Google Apps Script(GAS)を使用してGoogleスプレッドシートやその他のGoogle Appsと連携するアプリケーションを開発する際、フロントエンドからのリクエストがCORS(Cross-Origin Resource Sharing)ポリシーによってブロックされることがあります。これは、セキュリティ上の理由からブラウザが異なるオリジン間のリソース共有を制限するため発生します。本記事では、このCORSエラーを解決する方法を詳しく解説します。

2. CORSエラーの原因と基本的な理解

CORSエラーは、フロントエンドからGASへのリクエストが特定の条件を満たさない場合に発生します。GASは、application/json形式のContent-Typeを持つプリフライトリクエストを処理できないため、この形式でリクエストを送るとCORSエラーが発生することがあります。

3. 解決策

  • Content-Typeの変更: application/jsonではなく、x-www-form-urlencodedtext/plainを使用してリクエストを送信します。
  • リクエストの形式の調整: リクエストボディを適切にエンコードし、GASが受け入れ可能な形式でデータを送信します。
  • レスポンスの正しい解釈: GASからのレスポンスを適切に解釈し、200 OKが必ずしも成功を意味しないことを理解します。成功時は302 FOUNDが返され、Access-Control-Allow-Originヘッダーが設定されます。

4. 実践例

具体的なコード例を交えて、フロントエンドからGASへのリクエストの正しい送り方、GASでのリクエストの受け取り方、そしてフロントエンドへの適切なレスポンスの返し方を示します。

JavaScriptでのfetchメソッドの使用例:(フォーム入力でname,email値を受けている想定)
x-www-form-urlencodedの場合

document.getElementById('reservation-form').addEventListener('submit', function(e) {
  e.preventDefault(); // フォームのデフォルトの送信を防止

  // フォームからデータを取得
  const name = document.getElementById('name').value;
  const email = document.getElementById('email').value;

  // GASのエンドポイントURL
  const endPoint = 'https://script.google.com/macros/s/あなたのスクリプトのID/exec';

  // リクエストのボディを `application/x-www-form-urlencoded` 形式で構築
  const formData = new URLSearchParams();
  formData.append('name', name);
  formData.append('email', email);

  fetch(endPoint, {
    method: 'POST',
    mode: 'no-cors', // CORSポリシーによる制限を回避
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: formData
  })
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.text(); // ここではtext()を使用していますが、実際にはGASのレスポンスに合わせて適宜変更してください。
  })
  .then(data => {
    console.log(data); // 成功した場合の処理
  })
  .catch(error => {
    console.error('There was a problem with your fetch operation:', error);
  });
});




text/plainの例

document.getElementById('dataForm').addEventListener('submit', function(e) {
    e.preventDefault(); // フォームの通常の送信を防止

    // フォームからデータを取得
    const name = document.getElementById('name').value;
    const email = document.getElementById('email').value;

    // GASのエンドポイントURL (あなたのウェブアプリのURLに置き換えてください)
    const endPoint = 'https://script.google.com/macros/s/あなたのスクリプトのID/exec';

    // リクエストのボディを構築
    const data = `name=${name}&&email=${email}`;

    fetch(endPoint, {
        method: 'POST',
        headers: {
            'Content-Type': 'text/plain',
        },
        body: data
    })
    .then(response => {
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        return response.text(); // 実際にはGASからのテキストレスポンスを受け取ります
    })
    .then(data => {
        console.log('Success:', data);
    })
    .catch(error => {
        console.error('Error:', error);
    });
});

受け手のGAS例 指定のgoogle Docに追加書き込みという想定

function doPost(e) {
  // GoogleドキュメントのID (あなたのドキュメントに置き換えてください)
  var docId = 'あなたのドキュメントID';
  var doc = DocumentApp.openById(docId);
  var body = doc.getBody();

  // リクエストからデータを取得
  var requestData = e.postData.contents;
  var contentType = e.postData.type;

  // コンテンツタイプに応じたデータの処理
  if (contentType === 'application/x-www-form-urlencoded') {
    // URLエンコードされたデータを解析
    var data = parseUrlEncoded(requestData);
    var textToInsert = '名前: ' + data.name + ', メールアドレス: ' + data.email;
  } else if (contentType === 'text/plain') {
    // テキストデータを直接使用
    var parts = requestData.split('&&');
    var textToInsert = '名前: ' + parts[0].split('=')[1] + ', メールアドレス: ' + parts[1].split('=')[1];
  } else {
    // サポートされていないコンテンツタイプ
    return ContentService.createTextOutput('Unsupported content type: ' + contentType).setMimeType(ContentService.MimeType.TEXT);
  }

  // ドキュメントにテキストを追加
  body.appendParagraph(textToInsert);

  // 成功レスポンスを返す
  return ContentService.createTextOutput('Data added to document successfully').setMimeType(ContentService.MimeType.TEXT);
}

// application/x-www-form-urlencoded形式のデータを解析する関数
function parseUrlEncoded(data) {
  var result = {};
  var pairs = data.split('&');
  for (var i = 0; i < pairs.length; i++) {
    var pair = pairs[i].split('=');
    result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
  }
  return result;
}

ちなみに、postをtext/plainで送り、内容をcontentで送信している場合は、以下のようにシンプルでいけました。

function doPost(e) {
  var docId = ""; // Google ドキュメントの ID
  var doc = DocumentApp.openById(docId);
  var body = doc.getBody();
  
  // 'content' パラメーターの値を取得
  var content = e.postData.getDataAsString();
  
  
  // ドキュメントに 'content' の値を追加
  body.appendParagraph(content);
  
  // 変更を保存
  doc.saveAndClose();
  
  var result = { 
    status: 'success',
    message: 'データが正常に処理されました'
  };

  return ContentService.createTextOutput('Success');
}

5. まとめ

GASを使用する際にCORSエラーに直面した場合、上記の解決策を試すことで問題を解決できる可能性があります。重要なのは、リクエストとレスポンスの処理方法を適切に調整し、GASの仕様とブラウザのセキュリティポリシーの両方を理解することです。

この記事が、GASを使った開発におけるCORSエラーの解決に役立つことを願っています。

WEBプログム、WEBデザインなどの制作については、以下を御覧ください。

WEBプログム、WEBデザインなどの制作