LoginSignup
4
2

More than 3 years have passed since last update.

[Go,Rust,React] AWS APIGatewayでもCORSがしたい

Last updated at Posted at 2020-06-20

CORS = ブラウザからAPI叩く時にいつも引っかかるやーつ

WebページとAPIがポート番号も含めて同じであれば何も問題ないですが、
そうでない場合はクロスオリジンドメイン関連の設定しないとブラウザがエラーを表示します。
なお、APIのアクセス自体は飛んでいることもあります。

開発環境

  • Rust 1.4.3
  • Go 1.12
  • React 16.13
  • Serverless Framework

細かい設定や構成はこっち -> [AWSLambda x LINEBot] GoとRustでも連携がしたい

結局、何が必要なのか

ブラウザ側

CORSオプションをつける必要があります。
意外とこれを忘れていたりしてハマったりします。

  fetch(
    `${process.env.REACT_APP_API_HOST}/${env}/api/hoge/fuga`,
    {
      method: "GET",
      mode: "cors",
      cache: "no-cache",
    }
  )
    .then(res => ...)
    .catch(err => console.log(err))

サーバー側

さて、サーバー側です。
今回はServerlessFrameworkを使っているので、そちらにもかける設定があるのですが、
結論から言うと、設定ファイルだけでは完結せず、サーバー側でヘッダーを返す必要があります。
なので、全てサーバーで処理してしまいます。

必要なレスポンスヘッダー

ヘッダー名 具体例 説明
Content-Type application/json CORSとは直接関係ありませんが、JSONレスポンスの場合は使用
Access-Control-Allow-Origin http://localhost:3000 ここのアクセス元ならアクセスして良いよ、と言うヘッダーです
大体ここでハマります
ワイルドカードは使わない方が良いです
Access-Control-Allow-Methods OPTIONS,POST,GET 許可するメソッド
PUTやPATCH、DELETEを実装した時に追加忘れに注意
Access-Control-Allow-Headers Origin,Authorization,Accept,X-Requested-With 許可するヘッダー
これも追加忘れに注意
Access-Control-Allow-Credential true 認証用ヘッダーの許可
アクセストークンでの認証処理を書いた時にこれを追加し忘れてハマる

アクセストークンをヘッダーに付与する場合、Access-Control-Allow-Origin にワイルドカード "*"は使えません!

ネットのサンプルではワイルドカードを指定している例が多いですが、認証機能を実装した際にブラウザに怒られてハマります。。
MDNの説明

そうすると、サーバー側ではリクエストのOriginヘッダーから、Access-Control-Allow-Originを生成することになります。

Rustのコード

ソースコードは以下のようになります。
Access-Control-Allow-Origin にはリクエストのOriginをそのまま指定しています。
Origin のOは大文字です。(まあ規格通りです)

src/bin/api.rs
#![deny(warnings)]

use log;
use simple_logger;

use lambda_http::{lambda, Body, IntoResponse, Request, RequestExt, Response};
use lambda_runtime::{error::HandlerError, Context};

use アプリケーションロジックとか;
use DBの実装とか;

fn main() {
    simple_logger::init_with_level(log::Level::Debug).unwrap();
    lambda!(handler)
}

fn handler(r: Request, c: Context) -> Result<impl IntoResponse, HandlerError> {
    // メソッド名で処理を振り分ける
    let res: Result<String, String> = match c.function_name.split("-").collect::<Vec<&str>>()[2] {
        "postHoge" => match &r.body() {
            Body::Text(text) => {
                let mut event: post_hoge::Event = serde_json::from_str(text.as_str()).unwrap();
                // URLのパスからパラメータを取得する
                event.client_id = r.path_parameters().get("clientId").unwrap().to_string();
                ...
                match post_hoge::main::<DynamoDb>(event) {
                    Ok(res) => Ok(serde_json::to_string(&res).unwrap()),
                    Err(err) => Err(err),
                }
            }
            _ => Err(String::from("request body is not text.")),
        },
       _ => Err(format!("no such method {}", &c.function_name)),
    };

    return match res {
        Ok(res) => Ok(Response::builder()
            .status(200)
            .header("Content-Type", "application/json")
            .header("Access-Control-Allow-Methods", "OPTIONS,POST,GET")
            .header("Access-Control-Allow-Credential", "true")
            .header(
                "Access-Control-Allow-Origin",
                match r.headers().get("Origin") {
                    Some(o) => o.to_str().unwrap(),
                    None => "",
                },
            )
            .header(
                "Access-Control-Allow-Headers",
                "Origin,Authorization,Accept,X-Requested-With",
            )
            .body(res)
            .expect("failed to render response")),
        Err(err) => Ok(Response::builder()
            .status(404)
            .body(err)
            .expect("failed to render response")),
    };
}

最近Rust触り始めたとはいえなかなかコードが汚い。。。

Goのコード

こちらもAccess-Control-Allow-Origin にはリクエストのOriginをそのまま指定しています。
ただし origin の o は小文字です。(ナンテコッタイ)

line_api/main.go
package main

import (
    "strings"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-lambda-go/lambdacontext"
)

func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

    headers := map[string]string{
        "Content-Type":                    "application/json",
        "Access-Control-Allow-Origin":     request.Headers["origin"], // こっちは小文字!
        "Access-Control-Allow-Methods":    "OPTIONS,POST,GET",
        "Access-Control-Allow-Headers":    "Origin,Authorization,Accept,X-Requested-With",
        "Access-Control-Allow-Credential": "true",
    }

    var res string
    var err error

    // メソッド名で処理を振り分ける
    fn := strings.Split(lambdacontext.FunctionName, "line")[1]
    switch fn {
    case "CheckShop":
        res, err = checkShop(request)
    default:
        return events.APIGatewayProxyResponse{
            Headers:    headers,
            Body:       "no method",
            StatusCode: 404,
        }, err
    }

    return events.APIGatewayProxyResponse{
        Headers:    headers,
        Body:       res,
        StatusCode: 200,
    }, err
}

func main() {
    lambda.Start(Handler)
}

おまけ。GAE/Goのコード

app/app.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "os"

    "github.com/gorilla/handlers"
    "github.com/gorilla/mux"

    // 自分のAPIのロジックとか
    ...
}

func main() {
    // DB接続処理やら
    ...

    // 認証周りとかAPIのパス設定とか
    router := mux.NewRouter()
    routerClient := router.PathPrefix("/api/client/v1").Subrouter()
    routerClient.Use(api.ClientAuthMiddleware)
    routerClient.HandleFunc("/hello", api.ClientHello)
    routerClient.HandleFunc("/auth", api.ClientAuth)
    ...

    // この辺で必要なヘッダーを返す
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), handlers.CORS(
        handlers.AllowCredentials(),
        handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Content-Length", "Authenticate"}),
        handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE"}),
        handlers.AllowedOriginValidator(func(_ string) bool { return true }),
    )(router)))
}
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2