Google Formへaxiosで非同期通信しようとした時に出るCROSエラーの回避

GoogleFormをカスタマイズして自サイト(Vue.js + Firebase)で利用しようとしてハマったこと。
フォーム内容送信時にGoogleForm専用の送信完了画面に飛ぶのが嫌
→とりあえず非同期通信(axios)で送信してみる
→CROSのエラー↓が出る
「Failed to load https://docs.google.com/forms/XXXXXX/formResponse: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.」

まず、エラーが出てても非同期通信はうまくいきます。
参考:Googleフォームをカスタマイズして導入、Ajax処理を行う

でもエラーハンドリングできないの嫌だよね。回避しよう。


そもそもなぜエラーが出るのか

ブラウザ(特にChrome)では、セキュリティ対策としてオリジン (ドメイン、プロトコル、ポート番号) の違うクライアントとサーバで通信することに制限かけているらしい。クロスドメインってやつですね。
基本的にはCROSを許可する(=サーバサイドのHTTPヘッダーで特定のオリジンからのアクセスを許可する)ことでこのエラーは解決できるみたいなんだけど、どうやらGoogle FormではHTTPヘッダーの設定ができないらしい。(探しきれなかったけどもしかしたらGASでは方法があるかも。要検討)
CROS詳しくはここがよかった
オリジン間リソース共有 (CORS)


回避方法:CROSプロキシを経由して通信を行う

似たような事例の場合、自分でローカルにプロキシサーバ立てて経由するのが一般的みたいだけど、今回はサーバレス(Firebase)で開発してたので公開されてるCORSプロキシを使用してみた。セキュリティにそこまで気を使わなくてもいい個人のWebサイト程度ならこの方法でいいんじゃないかなーと思う。
今回使用したのは以下のプロキシ
cors-anywhere


実際のソースはこんな感じになった。submitするurlの前にプロキシurl連結しただけ。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import axios from 'axios'
 
export default {
 data () {
  return {
   isSubmitComplete: false,
   email: '',
   name: '',
   title: '',
   message: ''
  }
 },
 methods: {
  onSubmit: function () {
 
   const submitParams = new FormData()
   submitParams.append("entry.XXXXXX", this.email);
   submitParams.append("entry.XXXXXX", this.name);
   submitParams.append("entry.XXXXXX", this.title);
   submitParams.append("entry.XXXXXX", this.message);
 
   const CORS_PROXY = 'https://cors-anywhere.herokuapp.com/'
   const GOOGLE_FORM_ACTION = 'https://docs.google.com/forms/XXXXXX/formResponse'
 
   // ajax
   axios.post(CORS_PROXY + GOOGLE_FORM_ACTION, submitParams).then(res => {
    this.isSubmitComplete = true
   }).catch(
    this.isSubmitComplete = true
   )
  }
 }
}


その他回避方法

フリーのプロキシを経由することに抵抗がある場合、「submit時の遷移先を非表示のiframeに設定することでエラーを起こさせない」という方法もある。
非同期通信せずに通常通りフォームの内容をsubmitして、送信完了画面をiframeに表示するように設定し、更にそのiframeを非表示に設定することによってエラーを回避する。
formタグにtarget属性を設定することによって遷移先を新規ウィンドウorフレームに表示することができる
詳細はここで
結果が表示されるウィンドウを指定する


コードはこんな感じで
1
2
3
4
5
<form method="post" action="https://docs.google.com/forms/XXXXXX" target="submitComplate">
<input type="submit" value="送信">
</form>
<iframe name="submitComplate" srcdoc="<p>送信完了(非表示)</p>
" style="display:none;"></iframe>

シンプル。エラーハンドリングはできないけど、とりあえずエラー回避だけするって場合はこれでもいい。



あとは未検証なんだけど、非同期通信で送信するデータの形式を「json」ではなく「jsonp」にすることでCROSエラーを回避できる場合があるらしい。
ただしaxiosではデータ形式にjsonpは設定できないので、やる場合はaxiosの公式ドキュメントで推奨されてる方法が良さそう。