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は大文字です。(まあ規格通りです)
#![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 は小文字です。(ナンテコッタイ)
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のコード
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)))
}