2022/08/18


Go言語で画像ファイルか確認してみる - Qiita

Go言語で書かれたサーバーサイドでアップロードされたファイルが画像ファイルか確認する必要があったため今後の備忘録として記載します。

https://qiita.com/tebakane/items/b7a47379659d42364c8d

実は Go にはそれ専用の関数が用意されています。

http package - net/http - Go Packages

Package http provides HTTP client and server implementations. Get, Head, Post, and PostFo...

https://pkg.go.dev/net/http#DetectContentType

バイト列を指定して以下の様に呼び出すだけです。

ct := http.DetectContentType(b)
if strings.HasPrefix(ct, "image/") {
    // 画像ファイル
}

簡単ですね。image.Decode を使い画像を一定のサイズにリサイズしてディスクに保存するといったケースであれば引用元の方法でも良いですが、そうでないならば画像のマジック部分だけで判定できるの DetectContentType を使った方が良いでしょう。

Posted at by



2022/04/01


Go で URL を扱う時は通常、net/url を使います。その際、例えばベースとなる URL にパスを足そうと思うと意外としんどかったりしました。

package main

import (
    "fmt"
    "log"
    "net/url"
    "path"
)

func main() {
    endpoint := "https://example.com"
    u, err := url.Parse(endpoint)
    if err != nil {
        log.Fatal(err)
    }
    u.Path = path.Join(u.Path, "foo""bar""baz")
    endpoint = u.String()
    fmt.Println(endpoint)
}

一部の方は「ハァッ?文字列で足せばいいじゃん」と思われるかもしれませんが、URL のパスを扱うという行為は実はとても難しい事なのです。path.Join を使う事で、不要な / が混じらない効果があります。また以下の様に相対パスを扱う場合

u.Path = path.Join(u.Path, "foo""..""baz")

https:///example.com/foo/baz に修正してくれるといった効果もあります。そういう点で僕は Go で URL を扱う場合は面倒でも url.Parse を使う事をオススメしています。しかしたかが元が文字列の URL にパスを足す為だけにこれだけのコードを書くのは、いくら Explicit の精神とは言え面倒に感じるのは共感できます。

net/url: add JoinPath, URL.JoinPath · golang/go@604140d · GitHub

Builds on CL 332209. Fixes #47005 Change-Id: I82708dede05d79a196ca63f5a4e7cb5ac9a041ea GitHub-Last-R...

https://github.com/golang/go/commit/604140d93111f89911e17cb147dcf6a02d2700d0
Go の master ブランチで net/urlJoinPathURL.JoinPath が追加されました。上記のコードであれば以下の様に簡単にパスを結合する事ができる様になりました。 package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    endpoint := "https://example.com"
    var err error
    endpoint, err = url.JoinPath(endpoint, "foo""bar")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(endpoint)
}

URL のパスを組み立てる為に URL を作らなくても良いので、ずいぶん楽になりました。

改訂2版 みんなのGo言語 改訂2版 みんなのGo言語
松木 雅幸, mattn, 藤原 俊一郎, 中島 大一, 上田 拓也, 牧 大輔, 鈴木 健太
技術評論社 Kindle版 / ¥2,350 (2019年08月01日)
 
発送可能時間:

Posted at by



2022/01/27


動的型付けプログラミング言語や、class を持つようなプログラミング言語を使う場合、そのフィールドメンバの定義順などを意識する事は少ないかもしれません。

Go は struct という、C言語が持っている構造体と同じ様にメモリ配置され、構造体そのものをレシーバとして、メソッドを定義しつつプログラミングを行います。struct はデータ構造そのものを示し、それが大きくなるにつれ、当然ながらメモリ使用量も多くなります。

さて、例えば以下のコードを実行すると、struct 100 個分のサイズは幾らになるでしょうか。

package main

import (
    "fmt"
    "unsafe"
)

type Account struct {
    TimeZone struct {
        Name       string `json:"name"`
        UtcOffset  int    `json:"utc_offset"`
        TzinfoName string `json:"tzinfo_name"`
    } `json:"time_zone"`
    Protected                bool   `json:"protected"`
    ScreenName               string `json:"screen_name"`
    AlwaysUseHTTPS           bool   `json:"always_use_https"`
    UseCookiePersonalization bool   `json:"use_cookie_personalization"`
    SleepTime                struct {
        Enabled   bool        `json:"enabled"`
        EndTime   interface{} `json:"end_time"`
        StartTime interface{} `json:"start_time"`
    } `json:"sleep_time"`
    GeoEnabled                bool   `json:"geo_enabled"`
    Language                  string `json:"language"`
    DiscoverableByEmail       bool   `json:"discoverable_by_email"`
    DiscoverableByMobilePhone bool   `json:"discoverable_by_mobile_phone"`
    DisplaySensitiveMedia     bool   `json:"display_sensitive_media"`
    AllowContributorRequest   string `json:"allow_contributor_request"`
    AllowDmsFrom              string `json:"allow_dms_from"`
    AllowDmGroupsFrom         string `json:"allow_dm_groups_from"`
    SmartMute                 bool   `json:"smart_mute"`
    TrendLocation             []struct {
        Name        string `json:"name"`
        CountryCode string `json:"countryCode"`
        URL         string `json:"url"`
        Woeid       int    `json:"woeid"`
        PlaceType   struct {
            Name string `json:"name"`
            Code int    `json:"code"`
        } `json:"placeType"`
        Parentid int    `json:"parentid"`
        Country  string `json:"country"`
    } `json:"trend_location"`
}

func main() {
    var accounts [100]Account
    fmt.Println(unsafe.Sizeof(accounts))
}

実行すると 22400 (22kb) と表示されます。前述の様に、Go の struct はC言語と同じ様にメモリ上に配置されます。したがって、フィールドの配置順を気を付けるだけでメモリを節約できる事になります。以下のコマンドを実行して fieldalignment をインストールして下さい。

go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest

struct が定義されているソースファイルを引数に指定するか、以下の様に実行して下さい。

fieldalignment .

すると以下の様に表示されます。

.../main.go:8:14: struct of size 224 could be 192
.../main.go:9:11: struct with 32 pointer bytes could be 24
.../main.go:18:27: struct with 40 pointer bytes could be 32
.../main.go:32:30: struct with 96 pointer bytes could be 72

一般的に実行バイナリを生成するプログラミング言語は、データをメモリに載せる際にアクセスしやすいようにワードサイズ(コンピュータがメモリを読み書きする際の単位)の倍数の位置にデータを配置します。このためワードサイズに満たないフィールドの直後に、パディングという隙間を意図的に挿れることで調整しています。これにより上記の様に改善可能な空白が生まれてしまいます。

fieldalignment コマンドはフィールドの定義順を変える事で、メモリ上のアライメントにうまく配置できる様にするツールです。fieldalignment に -fix フラグを付けて実行すると、実際に struct が書き換えられます。

fieldalignment -fix .

書き換えられたソースコードは以下の通り。

package main

import (
    "fmt"
    "unsafe"
)

type Account struct {
    SleepTime struct {
        Enabled   bool        `json:"enabled"`
        EndTime   interface{} `json:"end_time"`
        StartTime interface{} `json:"start_time"`
    } `json:"sleep_time"`
    TimeZone struct {
        Name       string `json:"name"`
        UtcOffset  int    `json:"utc_offset"`
        TzinfoName string `json:"tzinfo_name"`
    } `json:"time_zone"`
    AllowDmGroupsFrom       string `json:"allow_dm_groups_from"`
    ScreenName              string `json:"screen_name"`
    AllowDmsFrom            string `json:"allow_dms_from"`
    AllowContributorRequest string `json:"allow_contributor_request"`
    Language                string `json:"language"`
    TrendLocation           []struct {
        Name        string `json:"name"`
        CountryCode string `json:"countryCode"`
        URL         string `json:"url"`
        Woeid       int    `json:"woeid"`
        PlaceType   struct {
            Name string `json:"name"`
            Code int    `json:"code"`
        } `json:"placeType"`
        Parentid int    `json:"parentid"`
        Country  string `json:"country"`
    } `json:"trend_location"`
    GeoEnabled                bool `json:"geo_enabled"`
    DiscoverableByMobilePhone bool `json:"discoverable_by_mobile_phone"`
    DisplaySensitiveMedia     bool `json:"display_sensitive_media"`
    UseCookiePersonalization  bool `json:"use_cookie_personalization"`
    AlwaysUseHTTPS            bool `json:"always_use_https"`
    Protected                 bool `json:"protected"`
    SmartMute                 bool `json:"smart_mute"`
    DiscoverableByEmail       bool `json:"discoverable_by_email"`
}

func main() {
    var accounts [100]Account
    fmt.Println(unsafe.Sizeof(accounts))
}

実行すると struct 100 個のサイズは 19200 (19kb) になりました。struct 内のフィールドの定義順に意味がある場合には使えませんが、単に JSON を出力するだけであれば試してみては如何でしょうか。

Posted at by