はじめに
Go言語の連載4回目です。今回はGoのWeb Application Frameworkについて紹介します。
代表的なGoのWAF
皆さんはWAFがどのようなものかご存知だとは思いますが、改めて簡単に説明します。WAFは動的なウェブサイトなどの開発をサポートするアプリケーションフレームワークです。WAFが提供する代表的な機能としては、ORM、テンプレートエンジン、セッション管理などが挙げられます。本ブログでよく取り上げるRailsもWAFの一種です。
GoにおけるWAFは大きく2つに分けられます。1つは軽量なWAF、もう一つはフルスタックなWAFです。Goでは大きくて多機能なアプリケーションを作るのではなく、小さくてシンプルなアプリケーションを組み合わせて使うことが良いとされています。そのため、軽量なWAFが主流なようです。
軽量なWAF
代表的なWAFとして「Echo」と「Gin」を紹介します。
Echoは2015年に登場した新しいWAFです。RESTful APIをつくるのに向いているようです。Echoの設定に関してはこちらが参考になります。
GinはMartiniというGoの初期からあるWAFを参考に、より高速に動作するように実装されたWAFです。Martiniと比べて40倍ほど高速なようです。
フルスタックなWAF
代表的なWAFとして「Beego」と「Revel」を紹介します。
Beegoは主に中華圏で人気のあるWAFです。専用のCLIツールがある、LiveReloadされる等の特徴があります。くわしくはこちらをご覧ください。
RevelはRailsやPlay Frameworkから影響を受けたWAFです。他の言語でMVCフレームワークを使用した経験があるかたには親しみやすい作りになっているようです。Revelの設定に関してはこちらが参考になります。
Ginを使ってみる
それでは、実際にGinを使ってみます。事前準備としてGinをインストールします。
1 | $ go get -u github.com/gin-gonic/gin |
クエリパラメータ+ポストパラメータ
クエリパラメータおよびポストパラメータへのアクセスは下記のように行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package main import ( "github.com/gin-gonic/gin" "fmt" ) func main() { router := gin.Default() router.POST("/post", func(c *gin.Context) { id := c.Query("id") page := c.DefaultQuery("page", "0") name := c.PostForm("name") message := c.PostForm("message") fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) }) router.Run() } |
ポストパラメータは PostForm か DefaultPostForm で取得できます。同様に、クエリパラメータは Query か DefaultQuery で取得できます。
ファイルアップロード
ファイルのアップロードは下記のように行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 省略 func main() { router := gin.Default() // Set a lower memory limit for multipart forms (default is 32 MiB) // router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // single file file, _ := c.FormFile("file") log.Println(file.Filename) // Upload the file to specific dst. c.SaveUploadedFile(file, file.Filename) c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) }) router.Run() } |
アップロードしたファイルは SaveUploadedFile で保存することができます。
URLのグループ化
下記のようにURLをグループ化することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // 省略 func main() { router := gin.Default() // Simple group: v1 v1 := router.Group("/v1") { v1.POST("/login", loginEndpoint) v1.POST("/submit", submitEndpoint) v1.POST("/read", readEndpoint) } // Simple group: v2 v2 := router.Group("/v2") { v2.POST("/login", loginEndpoint) v2.POST("/submit", submitEndpoint) v2.POST("/read", readEndpoint) } router.Run() } |
上記の例では /v1/login と /v2/login のようにURLをグループ化することができます。
ログ出力
ログ出力は下記のように行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package main import ( "github.com/gin-gonic/gin" "os" "io" ) func main() { // Disable Console Color, you don't need console color when writing the logs to file. gin.DisableConsoleColor() // Logging to a file. f, _ := os.Create("gin.log") gin.DefaultWriter = io.MultiWriter(f) // Use the following code if you need to write the logs to file and console at the same time. // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) router := gin.Default() router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) router.Run() } |
このようにすると、 os.Create で指定したファイル名でログが出力されます。
JSONのバインド
JSONのバインドは下記のように行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package main import ( "github.com/gin-gonic/gin" "net/http" ) type Login struct { User string `json:"user" binding:"required"` Password string `json:"password" buinding:"required"` } func main() { router := gin.Default() // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login if err := c.ShouldBindJSON(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if json.User != "manu" || json.Password != "123" { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) return } c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) router.Run() } |
User string `json:"user" binding:"required"` のようにJSONのキーを指定します。binding:”required” を指定した場合、バリューが空の場合にエラーが返ります。
HTMLのレンダリング
HTMLのレンダリングは下記のように行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { router := gin.Default() router.LoadHTMLGlob("templates/**/*") router.GET("/posts/index", func(c *gin.Context) { c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ "title": "Posts", }) }) router.GET("/users/index", func(c *gin.Context) { c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ "title": "Users", }) }) router.Run() } |
そして、templates/posts/index.tmpl と templates/users/index.tmpl を以下のように作成します。
1 2 3 4 5 6 7 | {{ define "posts/index.tmpl" }} <html><h1> {{ .title }} </h1> <p>Using posts/index.tmpl</p> </html> {{ end }} |
1 2 3 4 5 6 7 | {{ define "users/index.tmpl" }} <html><h1> {{ .title }} </h1> <p>Using users/index.tmpl</p> </html> {{ end }} |
{{.title}} の部分には main で渡している title の値が表示されます。
リダイレクト
リダイレクトは下記のように行います。
1 2 3 4 5 6 7 8 | // 省略 func main() { r := gin.Default() r.GET("/test", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") }) r.Run() } |
localhost:8080/test にアクセスすると http://www.google.com/ にリダレクトされます。
GinのMiddlewareの紹介
Ginにはミドルウェアを追加することで様々な機能を追加することができます。ここでは、Session、Secure、Zapを紹介します。
Session
Sessionはセッション管理のミドルウェアです。Cookie、Redisの他にMongoDBなどにも対応しています。Sessionを使ったサンプルコードは下記のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package main import ( "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() store := cookie.NewStore([]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() } |
localhost:8080/incr にアクセスすると、レスポンスのcountが1、2、3と増加していきます。
Secure
SecureはSSL通信のためのミドルウェアです。サンプルコードは下記のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | package main import ( "github.com/gin-contrib/secure" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.Use(secure.New(secure.Config{ AllowedHosts: []string{"example.com", "ssl.example.com"}, SSLRedirect: true, SSLHost: "ssl.example.com", STSSeconds: 315360000, STSIncludeSubdomains: true, FrameDeny: true, ContentTypeNosniff: true, BrowserXssFilter: true, ContentSecurityPolicy: "default-src 'self'", IENoOpen: true, ReferrerPolicy: "strict-origin-when-cross-origin", SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"}, })) router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) router.Run() } |
Zap
Zapは高速で低アロケーションなロギングライブラリです。サンプルコードは下記のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package main import ( "github.com/gin-gonic/gin" "go.uber.org/zap" "time" ) func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { logger, _ := zap.NewDevelopment() logger.Info("Hello zap", zap.String("key", "value"), zap.Time("now", time.Now())) c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() } |
さいごに
GoのWeb Application Frameworkをいくつか紹介し、その中の一つであるGinの基本的な使い方とミドルウェアを紹介しました。今後はORMなどについて調べていこうと思います。
Go記事の連載などは、こちらをご覧ください。