My Note

自己理解のためのブログ

GoのWebフレームワーク Echo で CORS を設定する

はじめに

最近、Echoで個人開発をしていて(最近の記事に記載)CORSの設定をまだしていなかったので今回追加しました。 CORSについても改めて整理。

CORSについて

■ CORSとは

  • Cross-Origin Resource Sharing の略
  • あるオリジンで読み込まれたリソースのhtml 、スクリプトなどが異なるオリジンのリソースと通信する場合に制限される
  • 目的は他オリジンに悪影響を与えないようにするため
  • 自身のオリジンから別のオリジンへアクセスするのを許可する (クロスオリジン)
  • Origin オリジンとは??
    • 『 スキーム + ホスト + ポート番号 』
  • クロスオリジンリクエスト例
    • https://domain-a.com:8000 --> https://domain-a.com:8080 ( ポート番号が異なる )
    • https://domain-a.com:8000 --> https://domain-b.com:8000 ( ホストが異なる)

Ref) Cross-Origin Resource Sharing (CORS) - HTTP | MDN

コード

ディレクトリ構成

├── README.md
├── TEST_Client
│   └── cors
│       ├── index.php
│       └── script.js
├── api/ (省略)
├── conf
│   └── config.go
├── docker
│   ├── mysql
│   └── redis
├── docker-compose.yml
├── go.mod
├── go.sum
├── handler
│   ├── auth.go
│   └── top.go
├── main.go
├── model
│   ├── crypto.go
│   ├── db.go
│   ├── stock.go
│   └── user.go
├── staticcheck.conf
└── view
    ├── login.html
    ├── mypage.html
    ├── signup.html
    └── top.html
   // CORS //
    e.Use(middleware.CORS())
    e.Use(middleware.CORSWithConfig(
        middleware.CORSConfig{
            // Method
            AllowMethods: []string{
                http.MethodGet,
                http.MethodPut,
                http.MethodPost,
                http.MethodDelete,
            },
            // Header
            AllowHeaders: []string{
                echo.HeaderOrigin,
            },
            // Origin
            /*
               AllowOrigins: []string{
                   "http://localhost:8080",
                   "http://localhost:9999",
               },*/
        }))
  • main.go (全体)
package main

import (
    "io"
    "net/http"
    "text/template"

    "github.com/gorilla/sessions"
    "github.com/labstack/echo-contrib/session"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"

    "github.com/yhidetoshi/apiEchoGAE-local/api"
    "github.com/yhidetoshi/apiEchoGAE-local/conf"
    "github.com/yhidetoshi/apiEchoGAE-local/handler"
)

var e = createMux()

func createMux() *echo.Echo {
    e := echo.New()
    return e
}

type TemplateRender struct {
    templates *template.Template
}

func (t *TemplateRender) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
    return t.templates.ExecuteTemplate(w, name, data)
}

func main() {
    // Echoインスタンス作成 //
    http.Handle("/", e)
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    e.Use(middleware.Gzip())

    // CORS //
    e.Use(middleware.CORS())
    e.Use(middleware.CORSWithConfig(
        middleware.CORSConfig{
            // Method
            AllowMethods: []string{
                http.MethodGet,
                http.MethodPut,
                http.MethodPost,
                http.MethodDelete,
            },
            // Header
            AllowHeaders: []string{
                echo.HeaderOrigin,
            },
            // Origin
            /*
               AllowOrigins: []string{
                   "http://localhost:8080",
                   "http://localhost:9999",
               },*/
        }))

    cGmo := api.ClientCryptoGMO{}
    cGmo.NewClientCryptoGMO()
    cCc := api.ClientCryptoCoincheck{}
    cCc.NewClientCryptoCoincheck()

    // Template
    renderer := &TemplateRender{
        templates: template.Must(template.ParseGlob("view/*.html")),
    }
    e.Renderer = renderer

    // セッション
    e.Use(session.Middleware(sessions.NewCookieStore([]byte(conf.SIGNING_KEY))))


    // ルーティング //
    // ヘルスチェック
    apiG := e.Group("/api")
    apiG.GET("/healthcheck", api.Healthcheck) // /api/healthcheck (/apiを省略する形になる)

    // 貴金属の価格を取得
    apiG.GET("/metal", api.FetchMetal)

    // ISINコードを引数に基準価格を取得
    apiG.GET("/investment-trust/:isin", api.FetchInvestTrust)

    // 仮想通貨の価格を取得
    apiG.GET("/crypto/gmo", cGmo.FetchCryptoGMO)
    apiG.GET("/crypto/coincheck", cCc.FetchCryptoCoincheck)

    // HTMLページ
    e.GET("/", handler.ShowTopHTML)
    e.GET("/signup", handler.ShowSignUpHTML)
    e.POST("/signup", handler.SignUp)
    e.GET("/login", handler.ShowLoginHTML)
    e.GET("/mypage", handler.ShowMyPageHTML)

    e.POST("/login", handler.Login)
    e.POST("/logout", handler.Logout)
    e.GET("/restricted", handler.ShowRestrictedPage)

    apiG.Use(middleware.JWTWithConfig(handler.JWTConfig))
    apiG.GET("/private", handler.ShowData)

    e.Start(":8080")
}

動作確認

CORSを検証するための準備

├── TEST_Client
│   └── cors
│       ├── index.php
│       └── script.js
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script src="./script.js"></script>
  </head>
  <body>
    CORS Test Page
  </body>
</html>
  • script.js
var xhr = new XMLHttpRequest()
var url = 'http://localhost:8080/'

const handler = () => {
  // コンソールに出力
  console.log(xhr.responseText)
}

const getRequest = () => {
  xhr.open('GET', url)
  xhr.onloadend = handler
  xhr.send()
}

document.addEventListener('DOMContentLoaded', () => {
  getRequest()
})
  • クライアントを起動 ( Portは任意 )
    • $ php -S localhost:9999

検証

  • クライアント( php + js ) localhost:9999。 Echoを localhost:8080 で起動する。
  • localhost:9999 --> localhost:8080 でクロスオリジンリクエストを発生させる。

CORS設定をしない場合

f:id:yhidetoshi:20220407111614p:plain

Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'http://localhost:9999' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

f:id:yhidetoshi:20220407111506p:plain

AllowHeaders を追加

AllowHeaders: []string{
                echo.HeaderOrigin,
            },

f:id:yhidetoshi:20220407113015p:plain

AllowMethods を追加

AllowMethods: []string{
                http.MethodGet,
                http.MethodPut,
                http.MethodPost,
                http.MethodDelete,
            },

f:id:yhidetoshi:20220407112835p:plain

AllowOrigins を追加

AllowOrigins: []string{
    "http://localhost:8080",
    "http://localhost:9999",
},

f:id:yhidetoshi:20220407112921p:plain

→ それぞれ blocked by CORS policy: No 'Access-Control-Allow-Origin' header のエラーを解消することができた。