GCPのCloud Run Functions(旧Cloud Functions)でCORS対応のリクエストを行う方法について

JavaScriptを有効にしてください

前書き

ブラウザはセキュリティのため、デフォルトでは異なるオリジンのリソースにアクセスできません(同一オリジンポリシー)。

リソースにアクセスできるようにするためには、サーバーがHTTPレスポンスヘッダーを通じて、ブラウザにリソースへのアクセスを許可する必要があります。
このアクセス許可がないとEnsure CORS requesting origin matches resource's allowed originのエラーがブラウザのコンソールのログに出力されます。
CORSのエラー

サーバーからのレスポンスデータが受け取れないだけで、サーバー自体は成功(200)のステータスを返していると思います。(サーバー側でCORSに関してエラーを返す場合はこの限りではありません)

Cloud Run functionsを利用する場合、基本的に異なるオリジンのAPIにアクセスすることになるため、サーバー側(Cloud Run functions側)でHTTPレスポンスヘッダーを通じて、ブラウザにリソースへのアクセスを許可する必要があります。

CORSエラーの解決方法

CORSエラーの解決方法は簡単で、先述したようにCloud Run functionsのAPIでレスポンスヘッダーを設定してあげるだけです。

 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
import functions_framework
import json  # JSONを扱うためのライブラリをインポート

@functions_framework.http
def cors_test(request):
    # リクエストのオリジンを取得
    origin = request.headers.get("Origin")

    # プリフライトリクエスト(OPTIONS)への対応
    if request.method == "OPTIONS":
        headers = {
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
            "Access-Control-Allow-Headers": "Content-Type",
            "Access-Control-Max-Age": "3600",
        }
        return ("", 204, headers)

    # メインリクエストへのレスポンス
    headers = {"Access-Control-Allow-Origin": "*"}

    # JSON形式のレスポンス
    response_body = {
        "message": "CORS test",
        "status": "success"
    }

    return (json.dumps(response_body), 200, headers)

プリフライトリクエストについては後述 します。
上記コードのようにAccess-Control-Allow-Origin*を設定すると、すべてのオリジンを許可するということになります。

このコードはHTTP CORS を参考にしています。

*を使用してもいいケース

先述したコードでは*を使用してすべてのオリジンを許可していますが、例えば、天気情報や通貨換算といったパブリックAPIの場合はあらゆるオリジンからのアクセスが見込まれるため*の方が利便性の観点から適しています。
また開発環境の場合も*で問題ありません。

*を避けるべきケース

*を避けるべきケースはクッキーやセッション情報を扱う場合、*を使用すると安全性が損なわれるため使用は避けるべきです。

また、APIを使用するオリジンが事前に分かっている場合は基本的に使用を避けましょう。

複数のオリジンを設定したい場合

*を設定せずに許可したいオリジンを設定する場合についてですが、
Access-Control-Allow-Originは1つのオリジンしか設定できないため、設定前にオリジンの判定を行う必要があります。
例えば下記のようなコードになります。

 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
34
35
36
37
38
39
40
41
42
43
44
45
import functions_framework
import json  # JSONを扱うためのライブラリをインポート

# 許可するオリジンのリスト
ALLOWED_ORIGINS = [
    "https://api1.example.com",  # 信頼できるオリジン
    "https://api2.example.com",
]

@functions_framework.http
def cors_test(request):
    # リクエストのオリジンを取得
    origin = request.headers.get("Origin")

    # 初期レスポンスヘッダー
    headers = {}

    # プリフライトリクエスト(OPTIONS)への対応
    if request.method == "OPTIONS":
        if origin in ALLOWED_ORIGINS:
            headers = {
                "Access-Control-Allow-Origin": origin,
                "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
                "Access-Control-Allow-Headers": "Content-Type",
                "Access-Control-Max-Age": "3600",
            }
        else:
            # オリジンが許可されていない場合、CORSエラーを返す
            return ("Forbidden", 403, {"Content-Type": "text/plain"})
        return ("", 204, headers)

    # メインリクエストへのレスポンス
    if origin in ALLOWED_ORIGINS:
        headers["Access-Control-Allow-Origin"] = origin
    else:
        # オリジンが許可されていない場合、エラーレスポンスを返す
        return ("Forbidden", 403, {"Content-Type": "text/plain"})

    # JSON形式のレスポンス
    response_body = {
        "message": "CORS test",
        "status": "success"
    }

    return (json.dumps(response_body), 200, headers)

上記コードでは、ALLOWED_ORIGINSのリストに定義されているオリジンのみリソースのアクセスを許可することになります。

プリフライトリクエストとは?

プリフライトリクエストのイメージ

以下のような会話をイメージしてください:

ブラウザ:
「ねえサーバーさん、このデータちょっと取ってもいい?でもその前に、安全かどうか確認してから実際に取りたいんだけど、どう?」

サーバー:
「OKだよ!君のリクエストを受け付けるよ!具体的には、この方法とこのヘッダーなら許可するよ!」

プリフライトリクエストが必要な理由

ブラウザは、勝手に他のサーバーにアクセスすると危険なので、上記イメージのように「いきなりリクエストを送るのではなく、事前に確認」を行います。

これは、「クロスオリジンリクエスト」という特別な場合にのみ必要です。

プリフライトリクエストの流れ

事前確認リクエスト(OPTIONSリクエスト)を送信

ブラウザが「プリフライトリクエスト」という特別なリクエストをサーバーに送信。
このリクエストには「どんな操作をしていいか?」という質問が含まれています。
例:

1
2
3
4
5
OPTIONS /api/data HTTP/1.1
Host: example.com
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

サーバーが許可情報を返す

サーバーが「こういうリクエストなら受け付けるよ」と答えます。
例:

1
2
3
4
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type

ブラウザがリクエストを送信

安全確認が取れたら、実際のリクエスト(GETやPOST)を送信します。

プリフライトリクエストが発生する条件

プリフライトリクエストが必要になるのは、リクエストが特別な場合だけです。以下の条件を満たす場合に発生します:

  1. HTTPメソッドが特別:
    PUT, DELETE, PATCH など(GET, POST, HEAD は対象外ですが後述するカスタムヘッダーを使用する場合や、特別なContent-Typeを指定する場合はプリフライトリクエストが発生します)。

  2. カスタムヘッダーを使用:
    例えば、Authorization や X-Custom-Header など独自のヘッダーを使用する場合。

  3. Content-Type が特別:
    application/json など、特定の種類を使う場合。

プリフライトリクエストがない場合との違い

通常のリクエスト(プリフライトなし)

ブラウザは直接リクエストを送る。
サーバーが許可すればレスポンスを返す。

プリフライトリクエストありの場合

事前に「OPTIONS」リクエストを送信して確認。
確認が成功すれば、次に本番のリクエストを送る。

プリフライトリクエストの例

プリフライトリクエスト

1
2
3
4
5
OPTIONS /api/resource HTTP/1.1
Host: api.example.com
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

サーバーの応答

1
2
3
4
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type

実際のリクエスト

1
2
3
4
5
6
POST /api/resource HTTP/1.1
Host: api.example.com
Origin: http://localhost:3000
Content-Type: application/json

{"key": "value"}

Content-Typeapplication/jsonなのでプリフライトリクエストが発生している

プリフライトリクエストが問題になる場合

パフォーマンスの低下

プリフライトリクエストでは、通常のリクエストよりも1回多くサーバーと通信が発生します。これが全体のパフォーマンスを低下させる原因になることがあります。

サーバーの設定ミス

  • サーバーが OPTIONS リクエストを正しく処理していないと、リクエストが失敗します。

プリフライトリクエストを回避する方法

  • 単純なリクエストにする:

    • GETPOST を使い、特別なヘッダーや Content-Type を避けます。
  • 結果をキャッシュする:

    • Access-Control-Max-Age を設定して、プリフライトリクエストを一定期間キャッシュします。
    1
    
    Access-Control-Max-Age: 3600
    

プリフライトリクエストのまとめ

プリフライトリクエストは、「ブラウザがサーバーに安全確認をするための事前チェック」です。

  • 流れ:

    1. ブラウザがサーバーに「このリクエストしていい?」と事前確認。
    2. サーバーが「OK!」と許可を返す。
    3. ブラウザが本番リクエストを送信。
  • ポイント:

    • 必要な場合とそうでない場合がある。
    • パフォーマンスに影響を与えるため、回避できる場合は回避する工夫が必要。

全体のまとめ

CORSは、ブラウザのセキュリティを高めるための仕組みですが、適切に設定しないとエラーやパフォーマンスの低下が発生します。本番環境では特定のオリジンを明示的に許可し、開発環境では*を使うなど、適切な設定を行うことが重要です。また、プリフライトリクエストが不要な設計を心がけることで、パフォーマンスの改善が期待できます。


スポンサーリンク

共有

もふもふ
著者
もふもふ
プログラマ。汎用系→ゲームエンジニア→Webエンジニア→QAエンジニア