http://revel.github.io/
前回に引き続き、GolangのWebFramework、Revelのホームページを読んでいきたいと思います。Overviewからです
Overview
この項ではフレームワークの様々な機能を紹介していきます。
- Routing: 簡素な記法と型安全なリバースルーティングを提供するルーティング
- Controllers: 簡単なデータバインディングとバリデーションを提供しエンドポイントとなるコントローラ
- Templates: Goテンプレートをシンプルにし、スケーリングできるようにしたテンプレート
- Interceptors: アクションの前後で各コントローラごとに呼び出せるインターセプタ
- Filters: より全体をカバーできるフィルタ
Routing
Revelは宣言型のルーティング構文を使用します。ルーティングは1ファイルに定義されます。構文はパラメータを除外したURIとマッチするシンプルなものです。さらに/hotels/:id
のようにパラメータ付のルートも指定できます。
GET /login Application.Login # 単純なパス
GET /hotels/ Hotels.Index # /hotelsまたは/hotels/にマッチ
GET /hotels/:id Hotels.Show # :idをパラメータとして取得
WS /hotels/:id/feed Hotels.Feed # WebSockets.
POST /hotels/:id/:action Hotels.:action # 定義されたアクションを自動的にルートとして作成
GET /public/*filepath Static.Serve("public") # 静的ファイル
* /:controller/:action :controller.:action # ルーティング自動構築
リバースルートは型安全な記法で作られます。
// Show the hotel information.
func (c Hotels) Show(id int) revel.Result {
hotel := HotelById(id)
return c.Render(hotel)
}
// Save the updated hotel information and redirect back to Show.
func (c Hotels) Save(hotel Hotel) revel.Result {
// validate and save hotel
return c.Redirect(routes.Hotels.Show(hotel.Id))
}
Controllers
全てのアクションはコントローラのメソッドです。これにより以下の利点があります。
- Data binding: Revelはクエリストリングまたはフォーム送信データを、単純な値か構造体にバインディングし、アクションメソッドの引数として渡してくれます。またパラメータマップに直接アクセスすることも可能です。
- Validation:
c.Validation.Required(verifyPassword)
のように、バリデート用のヘルパメソッドがあります。 - Flash:
c.Flash.Success("Welcome, " + user.Name)
のように、一度のみ利用できるクッキーデータがメッセージのために用意されてます。 - Session:
c.Session["user"] = user.Username
のように、セッションは暗号化されたmap[string]string型のクッキーです。 - Results:
c.Redirect(routes.Hotels.Index())
のように、リダイレクトはリバースルーティングの恩恵を受けれます。テンプレートレンダリングでは、あなたのデータはローカル変数として利用できます。
例:
// app/controllers/app.go
type MyApplication struct {
*revel.Controller
}
func (c MyApplication) Register() revel.Result {
title := "Register"
return c.Render(title)
}
func (c MyApplication) SaveUser(user models.User, verifyPassword string) revel.Result {
c.Validation.Required(verifyPassword)
c.Validation.Required(verifyPassword == user.Password).
Message("Password does not match")
user.Validate(c.Validation)
if c.Validation.HasErrors() {
c.Validation.Keep()
c.FlashParams()
return c.Redirect(routes.Application.Register())
}
user.HashedPassword, _ = bcrypt.GenerateFromPassword(
[]byte(user.Password), bcrypt.DefaultCost)
err := c.Txn.Insert(&user)
if err != nil {
panic(err)
}
c.Session["user"] = user.Username
c.Flash.Success("Welcome, " + user.Name)
return c.Redirect(routes.Hotels.Index())
}
Templates
慣例通り、RevelはWebアプリケーションのビューデザインをGoテンプレートで行います。Regisiterテンプレートを見てみましょう。
Note:
- Revelは使用されるアクション名を自動的に探し出します
- fieldはフォーム用のバリデーションエラー値を保存したマップを返すシンプルなヘルパ関数です。必要があれば他の関数もヘルパとして渡すこともできます。
- Renderメソッドの引数として明示された値はテンプレート中で使用可能です。(例ではtitleがheader.html内で使用されてます)
{{template "header.html" .}}
<h1>Register:</h1>
<form action="/register" method="POST">
{{with $field := field "user.Username" .}}
<p class="{{$field.ErrorClass}}">
<strong>Username:</strong>
<input type="text" name="{{$field.Name}}" size="16" value="{{$field.Flash}}"> *
<span class="error">{{$field.Error}}</span>
</p>
{{end}}
{{/* other fields */}}
<p class="buttons">
<input type="submit" value="Register"> <a href="/">Cancel</a>
</p>
</form>
{{template "footer.html" .}}
Interceptors
インターセプタは、リクエストの前後で呼び出される、コントローラのメソッドです。またランタイムエラー時に呼び出すこともできます。コントローラを別のコントローラに組み込むことで、開発者はインターセプタとフィールドを多くのコントローラでシェアできます。
例として、データベースモジュールを挙げます。このモジュールは初期化時にコネクションを開き、グローバルハンドルDb
にそれを保持します。加えて、db.Transactional型のフィールドであるsql.Txnを用い、トランザクションをインターセプタとして実現しています。
// github.com/revel/modules/db/app/db.go
var Db *sql.DB
func Init() {
// Read configuration.
Driver, _ = revel.Config.String("db.driver")
Spec, _ = revel.Config.String("db.spec")
// Open a connection.
Db, _ = sql.Open(Driver, Spec)
}
// Transactional adds transaction management to your controller.
type Transactional struct {
*revel.Controller
Txn *sql.Tx
}
func (c *Transactional) Begin() revel.Result {
c.Txn, _ = Db.Begin()
return nil
}
func (c *Transactional) Commit() revel.Result {
_ = c.Txn.Commit()
c.Txn = nil
return nil
}
func (c *Transactional) Rollback() revel.Result {
_ = c.Txn.Rollback()
c.Txn = nil
return nil
}
func init() {
revel.InterceptMethod((*Transactional).Begin, revel.BEFORE)
revel.InterceptMethod((*Transactional).Commit, revel.AFTER)
revel.InterceptMethod((*Transactional).Rollback, revel.PANIC)
}
これをアプリケーションのコントローラにミックスインすることで、開発者は簡単にトランザクションを利用できます。
type Bookings struct {
*revel.Controller
db.Transactional // Adds .Txn
user.Login // Adds .User
}
func (c Bookings) ShowFirstBooking() revel.Result {
row := c.Txn.QueryRow(`
select id, hotel_id, user_id, price, nights
from Booking
where UserId = ?
limit 1`, c.User.Id)
...
return c.Render(booking)
}
Filters
フィルタはアプリケーションのミドルウェアです。フィルタは単純な関数であり以下のような形式で定義します。
type Filter func(c *Controller, filterChain []Filter)
インターセプタフレームワークのような複雑なビルドインの機能もフィルタとして定義されています。
// github.com/revel/revel/intercept.go
var InterceptorFilter = func(c *Controller, fc []Filter) {
defer invokeInterceptors(FINALLY, c)
defer func() {
if err := recover(); err != nil {
invokeInterceptors(PANIC, c)
panic(err)
}
}()
// Invoke the BEFORE interceptors and return early, if we get a result.
invokeInterceptors(BEFORE, c)
if c.Result != nil {
return
}
fc[0](c, fc[1:])
invokeInterceptors(AFTER, c)
}
Revelはデフォルトのフィルターをカスタマイズできます。これは、本当に簡単で、使いたいフィルタを選ぶだけです。
// Filters is the default set of global filters.
// It may be set by the application on initialization.
var Filters = []Filter{
PanicFilter, // Recover from panics and display an error page instead.
RouterFilter, // Use the routing table to select the right Action
FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
ParamsFilter, // Parse parameters into Controller.Params.
SessionFilter, // Restore and write the session cookie.
FlashFilter, // Restore and write the flash cookie.
ValidationFilter, // Restore kept validation errors from cookie.
I18nFilter, // Resolve the requested language
InterceptorFilter, // Run interceptors around the action.
ActionInvoker, // Invoke the action.
}
ほぼ全てのRevelの機能はフィルタとして実装されています。そしてそれらはコンフィグの一部であり開発者が見ることができます。これがRevelを理解しやすくし、またモジュール性を高める要因となっています。
モジュール性の証拠として、メインサーバハンドラがいかにシンプルかをご覧にください。
// github.com/revel/revel/server.go
func handleInternal(w http.ResponseWriter, r *http.Request, ws *websocket.Conn) {
var (
req = NewRequest(r)
resp = NewResponse(w)
c = NewController(req, resp)
)
req.Websocket = ws
Filters[0](c, Filters[1:])
if c.Result != nil {
c.Result.Apply(req, resp)
}
}
以上になります。ちょっと実際にやってみないと、よくわからないところもありましたね。チュートリアルがあるようなので、そちらで補えるといいですね。