- 投稿日:2022-03-18T17:48:58+09:00
【GO環境構築】環境構築をしてVSCodeで少し動かすまで
ゴール Go言語の基礎が始められる状態にする Go言語の基礎 a tour of Go 環境 mac 環境構築に必要なこと インストール コマンド実行 brew install go 環境変数の設定 パス追加とGOPATHの設定 export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin ディレクトリの作成 環境変数GOPATH配下「bin」「pkg」「src」のフォルダを作成 mkdir -p go/bin mkdir -p go/pkg mkdir -p go/src VSCodeの設定 VSCodeの拡張機能を”go”で検索し、"Go"をインストール goのコードアシストなどのツール類をインストール (表示 -> コマンドパレット)に、"Go:Install/Update Tools"と入力後、全てインストール ソースコードをsrc配下に配置する 実行方法 コマンド実行 go run 〇〇.go VSCode実行 実行ボタン これならデバッグ実行できる 実行ファイル実行 go buildで生成されたファイルを実行する コマンド go build go build hello.go 実行するとコンパイルされる。 実行ファイルが作成される go install go fmt コードフォーマッターのコマンド。インデントや改行位置などを自動調整する。 go test golint Goらしくないコーディングスタイルに対して警告をするコマンド。 感想 どこか懐かしさがある
- 投稿日:2022-03-18T17:17:29+09:00
初めてGo(Gin)でアプリケーションを作る時に直面した壁とその解決法
初めてGoを使用し、文法もよくわからない状態でWebアプリケーションを開発しようとしたとき、他言語と勝手が違ったり、動的な言語(Perl,PHP,Python,Rubyなど)と違って型があったりして「いつもやっていることができない」となった際の事象や解決法を残しておきたいと思いこの記事を書くに至りました。 前提 使用したフレームワーク Gin 動作環境 Docker20.10.8 アプリの詳細 求人サイト 一般的なWebアプリケーションにあるDBからデータを取得して表示する/データを登録するといったことができる Ginの恩恵を受けた箇所 Goのフレームワークは基本的にフルスタックではないので、なにからなにまでGinのお世話になったわけではなく、通信周り(ルーティング)やセキュリティ周り(セッションなど)が主なGinの出番でした。そのため今回紹介するのはこうした部分がメインとなります。 Ginによって楽に実装できたものの、Ginについての情報が少なく、これらの手法を見つけるのに苦労したため、残しておきたいと思います。 Ginで実装時に困ったこと テンプレートファイルの扱い 動的な言語のフレームワークにおいては、特定の領域を指定して、そこにあるテンプレートファイルのpathを返すというようにして扱うと思います。 これと似た形で、Ginでテンプレートを適用する際にはLoadHTMLFiles関数またはLoadHTMLGlob関数でファイルpathを指定する必要があります。 前者は可変長引数で文字列を受け取り、後者はpatternという名で引数が設定されていることから正規表現で設定することが読み取れます。(実際にソースを追ってもらうとそのようになっていることがわかると思います。) こちらの使用例のように main.go e := gin.Default() e.LoadHTMLGlob("/templates/*/*.html") と指定した場合、templatesの直下にindex.htmlがあるとそれを読み込むことはできません。(ちなみに論理和(|)を用いたパターンマッチングはできませんでした) レガシーな技術に慣れていると、トップページ(index.html)などはtemplatesの直下に置きたくなってしまいます。 Webサーバ(apacheやnginx)とは異なり、ディレクトリ構成を変えても何ら問題は出ないのですが、モダンな技術を描き慣れない自分は当初このような感覚がなく、以下のようにしてカバーしていました。 main.go e := gin.Default() templates := []string{ "/templates/index.html" "/templates/member/index.html" "/templates/joboffers/index.html" } e.LoadHTMLFiles(templates...) ...を使用するとスライスを展開できることを活用して、stringスライスを無理やりLoadHTMLFilesに読み込ませる方法です。 しかし、これは当初の未熟な自分が考えた内容であり、テンプレートが増えるたびに配列に文字列を追加しなければならないためおすすめしません。 トップページのindex.htmlなども/templates/top/index.htmlなどに収納し main.go e.LoadHTMLGlob("/templates/*/*.html") で全てのhtmlファイルを読み込む方が良いでしょう。 ただし、この方法ではtemplate/somedirの下にさらにディレクトリ構造を作った場合にはファイルを読み込めなくなってしまうので注意が必要。どうしてもディレクトリの階層を深くしたい場合は私がやっていたようなスライス展開の方法になるでしょう。アプリケーションが軽量であれば動作が重くなったりということもないので、そうした場合には使ってみてください。 セッション GoでSessionを実装したい場合はgorilla/sessionsというパッケージが主流のようですが、GinにはGinのセッションパッケージがあります。 このパッケージも最終的にはgorilla/sessionsに行き着くようなのですが、ルーティング周りなどでGin特有の書き方をしているので素直にgin-contrib/sessionsに頼るのが一番でした。 使い方は以下(自分はセッション管理にRedisを用いることにしたので、Redisの部分から引用します)。 sample.go r := gin.Default() store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret")) r.Use(sessions.Sessions("mysession", store)) r.GET("/incr", func(c *gin.Context) { session := sessions.Default(c) var count int v := session.Get("count") if v == nil { count = 0 } else { count = v.(int) count++ } session.Set("count", count) session.Save() c.JSON(200, gin.H{"count": count}) }) r.Run(":8000") このサンプルコードだと理解できない点が何箇所もあって、自分の話で言うと "secret" is 何? "mysession" is 何? 先にGetしてからSetしたの?? などなど思うところだらけでした。これらで分かったことを書き留めます。 まず、"secret"の文字ですが、セッションにアクセスする際のキーのようなものです。 つまり、文字列はなんでもいいです。 次に、"mysession"の文字ですが、これはクライアント側に保存するクッキーのキー(名前)のようです。 よって、この文字列もなんでもいいです。 ただし、"secret"のほうはある程度長い文字列のほうがセキュリティ的には良いでしょう。 こうして考えると、非常に直感的に、かつ簡単にsessionの機能が利用できるパッケージだなと思います。 これらさえわかってしまえば、あとはGet,Setをする場所を工夫するだけです。 以下にどのように実装したか紹介したいと思います。 main.go store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret")) r.Use(sessions.Sessions("mysession", store)) Redisへのコネクション確立とcookieデータの作成はmain.goで行っておきます。 session.go package sessions // ReadMiddleWare sessions.Defaultを呼び出す func ReadMiddleWare(c *gin.Context) sessions.Session { session := sessions.Default(c) return session } // Manager セッションマネージャを定義するインターフェース type SessionManager interface { Get(*gin.Context) SessionManager Set(*gin.Context) error Destroy(*gin.Context) error } // LoginSession ログイン情報セッション保存 type Login struct { ID int Login bool } // Get セッションから値を取得 => 構造体に格納 func (l *LoginSession) Get(c *gin.Context) SessionManager { session := ReadMiddleWare(c) memberID := session.Get("ID") if memberID != "" && memberID != nil { l.MemberID = memberID.(int) } login := session.Get("login") if login != "" && login != nil { l.Login = login.(bool) } return l } // Set 構造体を受け取る => セッションに各値を格納 func (l LoginSession) Set(c *gin.Context) error { session := ReadMiddleWare(c) session.Set("ID", l.MemberID) session.Set("login", l.Login) // Setしたセッション情報を保存 if err := session.Save(); err != nil { return err } return nil } // Destroy セッションを削除 削除対象のセッションキーは構造体ごとに決まるため関数内で定義する func (l LoginSession) Destroy(c *gin.Context) error { session := ReadMiddleWare(c) keyList := [...]string{"ID", "login"} for _, v := range keyList { session.Delete(v) } // セッション情報の変更を保存 if err := session.Save(); err != nil { return err } return nil } 独自にsessionsパッケージを作成します。 上記の例ではログインセッションを残そうとしています。 Login structに会員IDとログイン状態を定義し、Session型をinterfaceとして定義 Session型はGet,Set,Destroyの3つの関数を持つように定義し、先ほどのLogin structに対してこの3つの関数を作る ログイン状態を確認したい箇所からGetで呼び出す Setは、ログインした際に呼び出しておき、ログイン情報を残す Destroyはログアウトした時などに呼び出す これだけで実装できます。他にセッションを利用したいデータが出てきても、構造体にまとめてGet,Set,Destroyを追加すればいいだけです。 CSRFトークン CSRFの対策を取るためにGET以外のPOSTリクエストなどに対してはトークンが発行されていないと400番台のエラーを返すようにします。これもまた、Ginにはパッケージが用意されています。 main.go e := gin.Default() e.Use(csrf.Middleware(csrf.Options{ Secret: common.GenerateString(32), ErrorFunc: handlerfunc.ErrorCSRF, })) このように使うだけです。先程のセッションとは別に発行してもどちらもeに紐づいているため問題なく利用できます。 common.GenerateStringは独自に実装した関数で、ランダム文字列を返します。 common.go package common import "math/rand" func GenerateString(length int) string { b := make([]byte, length) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } あとはPOSTリクエストを行うあたりの箇所で sample.go packege sample import ( "net/http" "github.com/gin-gonic/gin", csrf "github.com/utrack/gin-csrf" ) e := gin.Default() e.GET("/get", func(c *gin.Context){ token := csrf.GetToken(c) c.HTML(http.StatusOK, "index.html", gin.H{"token": token}) }) e.POST("/post", func(c *gin.Context){ // POST処理 }) このようにPOSTする前にGETで表示したページに対してトークンを返しておき、hiddenパラメータなどで持っておくことで安全にPOSTリクエストを行えます。 https通信 コンテナを使っている影響かRunTLS関数ではうまく動作せず autocertというパッケージを使うと楽でした。 もしRunTLSでうまくいかない時はお試しください。 参考 https://pkg.go.dev/github.com/gin-gonic/gin@v1.7.7 https://qiita.com/koshi_an/items/9754ce406184ce4e0a5e0
- 投稿日:2022-03-18T15:10:45+09:00
go mod vendorとは何ぞや
そもそもvendorとは? vendorは go getでダウンロードするモジュールをGOPATH/pkg/modではなく、プロジェクトのルートディレクトリに置くことができる仕組みのこと。 import時の挙動としては、GOPATHよりも優先してvendorディレクトリのモジュールを探しにいく。 なぜvendorが必要? 1つのPCで複数のGoプロジェクトがあるとする。複数のプロジェクトでそれぞれ違うバージョンのモジュールが必要になったとき、昔のGoはvendorを使うしかなかった。つまり、今は不要!(ただ、あると便利) go mod vendorとは? go mod vendorは go getの後に実行するコマンドで、GOPATHにあるモジュールをプロジェクトルートディレクトリにある vendorディレクトリにコピーするコマンドである。 vendorはgitの管理下に置く こうすることで、git cloneしたらすぐ動作確認ができたり、docker buildやCIで楽をすることができる。 node_modulesをGitHubにpushするのはやばいが、vendorは容量が小さいので大丈夫。