【Go】GoでClient Hintsを扱う

GoのClient Hintsパーサを書いたので使い方メモ

github.com

インストール

go get github.com/cateiru/go-client-hints/v2

使い方

goclienthints.Parse(&r.Header)のようにして引数にhttp.Headerを渡すことでパースをし、structで返却します。

clienthint.IsSupportClientHints(&r.Header)というメソッドも追加しており、こちらはClient Hintsのヘッダが存在しているかをチェックしてboolで返却します。

このメソッドはFireFoxでClient Hintsが実装されていないため追加したものです。実際にこのパッケージを使用する場合、IsSupportClientHintsでClientHintsがサポートされているかを判定してtrueの場合はパース、falseの場合はUser-Agentから判定するといいかと思います。

package example

import (
    "fmt"
    "net/http"

    clienthint "github.com/cateiru/go-client-hints/v2"
)

func Handler(w http.ResponseWriter, r *http.Request) {
    clientHints, err := clienthint.Parse(&r.Header)
    if err != nil {
        return
    }

    // Sec-CH-UA field
    fmt.Println("Brand: ", clientHints.Brand.Brand)
    fmt.Println("Brand Version: ", clientHints.BrandVersion)
    fmt.Println("Brands: ", clientHints.Brands)

    // Sec-Ch-Ua-Platform filed
    fmt.Println("Platform: ", clientHints.Platform)

    // Sec-CH-UA-Platform-Version filed
    fmt.Println("Platform Version: ", clientHints.PlatformVersion)

    // Sec-Ch-Ua-Mobile filed
    fmt.Println("IsMobile: ", clientHints.IsMobile)

    // Sec-CH-UA-Arch filed
    fmt.Println("Arch: ", clientHints.Architecture)

    // Sec-CH-UA-Bitness filed
    fmt.Println("Bitness: ", clientHints.Bitness)

    // Sec-CH-UA-Model filed
    fmt.Println("Model: ", clientHints.Model)

    // Sec-Ch-Ua-Full-Version filed
    fmt.Println("Full Version: ", clientHints.FullVersion)
}

技術的な話

Client Hintsのヘッダの値はRFC 8941で記述されています。このパッケージでは、github.com/dunglas/httpsfvを使用してRFC 8941の形式をパースしています。

また、Sec-CH-UAヘッダでは" Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"のように複数のブランド名とダミーのブランド名が混在しています。全てをブランド名-バージョンの配列でパースできても使いづらいと感じたため、予めブランド名を配列として定義しておきそれに一致するものを優先的に返却するようにしています。

現在は以下のようなブランド名をサポートしています。

// Brand array
// A array of brands that Sec-Ch-Ua prefers to compare.
var PrimaryBrands = []string{
    "Google Chrome",
    "Chrome",
    "Microsoft Edge",
    "Edge",
    "Brave Browser",
    "Brave",
    "Yandex",
    "CocCoc",
}

// This array will be used to try to get the brand name
// if it is not in the PrimaryBrands array.
var SecondaryBrands = []string{
    "Chromium",
}

SecondaryPrimaryのブランド名が全て一致しなかった場合に比較されます。

バグ報告など

issueに投稿していただけるとありがたいです。

特に、Sec-Ch-Uaヘッダのブランド名は既存のブラウザ名を配列で保持しているので…

リンク

GitHub

github.com

ドキュメント

pkg.go.dev