doGet, doPostContent-Type:x-www-form-urlencodedでリクエストすればいい。

前回

  1. GASでmpurseのアドレスとプロフィール情報をSpreadSheetに保存する
  2. GASにGETやPOSTしたらCORSエラーになった

今回

CORSエラーを回避する。

情報源

CORSエラーの原因はContent-Type:application/jsonのせいだった。これはGASサーバの仕様と思われる。それに合わせてx-www-form-urlencodedでリクエストする。GAS側もそれにあわせて引数を受け取るように書く。

工程

  1. Googleアカウントを作成する
  2. Google Driveでスプレッドシートを作成する
  3. スプレッドシートのメニュー→拡張機能Apps Scriptをクリックする
  4. 後述するGASコードを入力する
  5. デプロイする
  6. デプロイしたIDやURLを使ってリクエストする

GAS コード

doGet

GETリクエストされたときの処理。スプレッドシートに登録されたデータをJSONで返す。

もし引数にaddressがあれば、そのアドレスのプロフィール情報だけを返す。もし引数にaddressがなければ、全件返す。

function doGet(e) {
  const ss        = SpreadsheetApp.getActiveSpreadsheet();
  const sheet     = ss.getSheetByName('list');
  const sheetData = sheet.getRange('A2:D' + sheet.getLastRow()).getValues();

  const address = e.parameter.address;
  if (address) { // 引数にアドレスが存在するなら
    const addrs = sheet.getRange('A2:A' + sheet.getLastRow()).getValues();
    const targetIdRow = addrs.findIndex(addr=>addr[0]==address);
    if (0 <= targetIdRow) { // 一致するアドレスが既存ならそのレコードだけ返す
      const i = targetIdRow + 2
      const r = sheet.getRange(`A${i}:D${i}`).getValues()[0];
      return ContentService.createTextOutput(JSON.stringify({address:r[0], profile:r[1], created:r[2], updated:r[3]})).setMimeType(ContentService.MimeType.JSON);
    } else { return ContentService.createTextOutput(JSON.stringify({error:'指定したアドレスの情報は存在しません。'})).setMimeType(ContentService.MimeType.JSON); }
  }
  const responseList = [];
  sheetData.map(function(d) {
    responseList.push({address:d[0], profile:d[1], created:d[2], updated:d[3]});
  });
  return ContentService.createTextOutput(JSON.stringify(responseList)).setMimeType(ContentService.MimeType.JSON);
}

doPost

POSTリクエストされたときの処理。渡されたアドレスとプロフィール情報をスプレッドシートに登録する。すでに存在するなら上書きする。

function doPost(e) {
  console.log(e);
  const ss       = SpreadsheetApp.getActiveSpreadsheet();
  const sheet    = ss.getSheetByName('list');
  requiredParameterError(e, 'address')
  requiredParameterError(e, 'profile')
  const address = e.parameter.address;
  const profile = e.parameter.profile;

  const addrs = sheet.getRange('A2:A' + sheet.getLastRow()).getValues();
  let targetIdRow = addrs.findIndex(addr=>addr[0]==address);
  const now = new Date().toISOString();
  if (0 <= targetIdRow) { // 一致するアドレスが既存なら上書きする
    targetIdRow += 2;
    sheet.getRange(targetIdRow, 2).setValue(profile);
    sheet.getRange(targetIdRow, 4).setValue(now);
  } else { // 一致するアドレスが未存なら新規追加する
    sheet.appendRow([address, profile, now, now]);
  }
  return ContentService.createTextOutput(JSON.stringify({address:address, profile:profile})).setMimeType(ContentService.MimeType.JSON);
}
function requiredParameterError(e, name) {
  if (!e.parameter[name]) {
    return ContentService.createTextOutput(JSON.stringify({error:`必須パラメータ${name}が存在しません。`})).setMimeType(ContentService.MimeType.JSON);
  }
}

リクエストする

GASをリクエストする方法も書いておく。

リクエストする(curl編)

実際にリクエストされると好き勝手に改変できてしまうため、ID値などを伏せておく。コードの中には書いてあるので意味はないかもしれないが。

こうすればcurlでリクエストできる、という技術情報メモとして書き残しておく。

GET

ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
URL="https://script.google.com/macros/s/${ID}/exec"
curl -L "$URL"

POST

ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
URL="https://script.google.com/macros/s/${ID}/exec"
ADDRESS=MEHCqJbgiNERCH3bRAtNSSD9uxPViEX1nu
PROFILE='{\"name\":\"ytyaru\",\"url\":\"https://ytyaru.github.io/\",\"avatar\":\"https://s3.arkjp.net/misskey/thumbnail-4820c61e-3194-48a6-b3a7-7d7319a67966.png\",\"mastodon\":{\"mstdn.jp\":[{\"id\":\"233143\",\"username\":\"ytyaru\"}]},\"misskey\":{\"misskey.io\":[{\"id\":\"918va5mxda\",\"username\":\"ytyaru\"}]}}'
JSON='{"address":"'"$ADDRESS"'","profile":"'"$PROFILE"'"}'
curl -H 'Accept: application/json' \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     -d "address=$ADDRESS" \
     -d "profile=$JSON" \
     -L "$URL"

ポイントは2つ。

  • Content-Type: application/x-www-form-urlencodedapplication/jsonでなく)
  • -dで引数を渡す(-Fでなく)

リクエストする(JavaScript編)

GET

async get(url, headers) {
    const data = {
        method: 'GET',
        headers: this.getJsonHeaders(headers),
    }
    const res = await fetch(url, data)
    const json = await res.json()
    return json
}
getDefaultJsonHeaders() { return {
    'Accept': 'application/json',
    'Content-Type': 'application/x-www-form-urlencoded',
}}

POST

async post(address, profile) {
    const data = {}
    data.method = 'POST'
    data.headers = {
        'Accept': 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded',
    }
    const params = new URLSearchParams();
    params.append('address', address)
    params.append('profile', JSON.stringify(profile))
    data.body = params;
    const res = await fetch(this.#getUrl(), data).catch(e=>console.error(e))
    const json = await res.json()
    return json
}

ポイントは2つ。

  • Content-Type: application/x-www-form-urlencodedapplication/jsonでなく)
  • bodyに渡す引数はURLSearchParams型である(address=...&profile=...のような文字列でなく)