ラベル Gin の投稿を表示しています。 すべての投稿を表示
ラベル Gin の投稿を表示しています。 すべての投稿を表示

2023/10/18

CORSに対応する [Go]

背景

 HTML/JavaScript/TypeScriptのダウンロード元と Ajaxでのアクセス先が異なる場合、Cross Resource Origin Sharing(以下CORSと略)ポリシーに従う必要があります。AjaxやCORSについての詳細はいろいろなところで説明されているので、ここでは割愛します。

 Go+GinでのCORS対応の実装についてです。


前提

 使用するソフトウェアのバージョンは以下のとおりです。

  • Go : 1.20
  • Gin : 1.9.0


 また、すべてのアクセス先に対してCORSアクセスを許可する設定であるAccess-Control-Allow-Originヘッダへの"*"の設定ではなく、特定のアクセス先に対してのみ許可するものとします。


gin-contrib/cors

 最初に見つけたのが「gin-contrib/cors」です。Ginの公式なCORSのパッケージのようです。ドキュメントはこちら。スニペットを見る限り使い方も簡単で、Ginのミドルウェアとして設定するだけです。
 しかし「Canonical example」のスニペットを参考に実装してみたのですが、まったくCORSポリシーを解決できませんでした。OPTIONSリクエストに対して、レスポンスヘッダにはAccess-Control-Allow-Originヘッダが付いていません。しばらく設定を変えたりして試してみたのですが、全く解決できませんでした。

 この状況をググってみて見つかったのがStackOverflowの記事「Go gin framework CORS」です。質問者はGoの標準ライブラリのみを使用していますが、OPTIONSに続くアクセスが発生せず、期待動作しないと言っています。
 回答を見ると複数パターンあるようです。
  • Goの標準ライブラリのみで実装の修正。質問者はこれで解決したもよう。
  • gin-contrib/corsの使用を勧めるもの。これで動いたといっているのは1名のみ。
  • rs/corsを勧めるもの。
  • 独自ライブラリを勧めるもの。

 自分の使っているGo/Ginのバージョンに比べて、gin-contrib/corsの最新リリースは2022/07/05なので幾分か古く、そのためGo/Ginの更新について行っていない可能性があります。Go/Ginのバージョンダウンまでして確認するほどgin-contrib/corsにこだわることもないので、このパッケージは諦めました。

標準ライブラリでの実装

 変なライブラリを入れたくないので、結局標準ライブラリのみで実装することにしました。実際にCORSを解決できた最小限の実装が以下です。Ginのミドルウェアにしています。


func main() {
    router := gin.Default()
    router.Use(corsSetting)
    // リクエストハンドラを設定 router.GET(...) etc.
}

func corsSetting(ginctx *gin.Context) {
    // リクエスト元の確認
    requestheader := ginctx.Request.Header
    requestorigin := requestheader.Get("origin")
    if リクエストの確認 {
        // レスポンスヘッダの設定
        responseheader := ginctx.Writer.Header()
        responseheader.Set("Access-Control-Allow-Origin", requestorigin)
        responseheader.Set("Access-Control-Allow-Headers", "Content-Type")
    }
}


 OPTIONSに続くアクセスのために、Access-Control-Allow-Headersレスポンスヘッダに"Content-Type"を設定しています。


2023/05/07

GCSからHTMLテンプレートを読み込んでHTTPレスポンスで返す [Go]

 ほぼ自分用の忘備録です。


背景

 Go+Gin構成のwebアプリでは、ファイルとして用意したテンプレートをHTTPレスポンスで返すのは簡単です。以下のようなスニペットになります。


func main() {
    router := gin.Default()
    router.LoadHTMLFiles("HTMLテンプレートファイルのパス")
    router.GET("リクエストエンドポイントのuri", handler)
}

func handler(ginctx *gin.Context) {
    ginctx.HTML(http.StatusOK, "HTMLテンプレートファイル", gin.H{
        キー: バリュー
    })
}


課題

 HTMLテンプレートをGoogle Cloud Storage(以下GCSと略)に置いておき、そこから読み込んで返したい場合、直接GCSから取得してくれる関数はGinにはありません。


解決

 以下のスニペットで可能になりました。エラー処理のコードは省略しています。これでいいのかちょっと不安な箇所もありますが、とりあえず期待動作はしています。


func main() {
    router := gin.Default()
    router.GET("リクエストエンドポイントのuri", handler)
}

func handler(ginctx *gin.Context) {
    // GCSからHTMLテンプレートをロード
    ctx := context.Background()
    storageclient, _ := storage.NewClient(ctx)
    reader, _ := storageclient.Bucket("バケット名")
    	.Object("HTMLテンプレートのオブジェクト名").NewReader(ctx)
    content_bin, _ := io.ReadAll(reader)
    reader.Close()
    
    // GCSから読み出した結果はバイナリなので文字列に変換
    content_str, _ := *(*string)(unsafe.Pointer(&content_bin)
    
    // テンプレートとして登録
    router := gin.Default()
    template, _ := template.New("").Delims("{{", "}}").Funcs(router.FuncMap).Parse(content_str)
    router.SetHTMLTemplate(template)

    // レスポンスで返す
    html := router.HTMLRender.Instance("", gin.H{
        キー: バリュー
    })
    ginctx.Render(http.StatusOK, html)
}

 バイナリ→文字列の変換には「忘れがちなGoでbyteをstringに変換する方法をベンチマークしてみた。」を参考にしました。