[インデックス 11629] ファイルの概要
このコミットは、Go言語のダッシュボードアプリケーションを go1beta
リリースに合わせて更新するための変更を含んでいます。主な変更点は、Go標準ライブラリのAPI変更への対応であり、具体的にはエラーハンドリングの os.Error
から組み込みの error
インターフェースへの移行、パッケージパスの変更(例: http
から net/http
)、および時間型 (datastore.Time
から time.Time
) の更新が含まれます。これにより、ダッシュボードが新しいGoのバージョンで正しく動作するようになります。
コミット
commit 22185aed742bba5e85b1e4bac68e4c50be84a227
Author: Andrew Gerrand <adg@golang.org>
Date: Mon Feb 6 09:26:32 2012 +1100
dashboard: update to go1beta
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5624056
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/22185aed742bba5e85b1e4bac68e4c50be84a227
元コミット内容
dashboard: update to go1beta
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5624056
変更の背景
このコミットの背景には、Go言語のバージョンアップ、特に go1beta
リリースに向けた標準ライブラリのAPI変更があります。Go言語は、バージョン1.0の安定版リリースに向けて、APIの安定化と整理を進めていました。その過程で、いくつかの重要な変更が導入されました。
主な変更点は以下の通りです。
os.Error
からerror
インターフェースへの移行: Go 1.0より前のバージョンでは、エラーを表現するためにos.Error
型が広く使われていました。しかし、Go 1.0では、より柔軟で標準的なエラーハンドリングを可能にするために、組み込みのerror
インターフェースが導入され、os.Error
は非推奨となりました。この変更により、エラーを返す関数はos.Error
の代わりにerror
インターフェースを返すように修正する必要がありました。- 標準ライブラリのパッケージパスの整理:
http
パッケージがnet/http
に、template
パッケージがhtml/template
やtext/template
に分割・移動されるなど、標準ライブラリのパッケージ構造が整理されました。これにより、コード内でこれらのパッケージをインポートしている箇所を新しいパスに更新する必要が生じました。 time
パッケージの変更: 時間の扱いに関するAPIも一部変更され、datastore.Time
のような特定の型から、より汎用的なtime.Time
型への移行が必要となりました。また、time.Seconds()
のような関数が非推奨となり、time.Now().Unix()
やtime.Now().Add()
のような新しいAPIが推奨されるようになりました。hmac
パッケージのAPI変更:hmac.NewMD5
のような特定のハッシュ関数に特化したコンストラクタが非推奨となり、より汎用的なhmac.New(hash.Hash, key []byte)
の形式に統一されました。
これらの変更は、Go言語のAPIをより一貫性があり、使いやすく、将来にわたって安定したものにするための重要なステップでした。GoダッシュボードはGo言語のビルドやテストの状態を監視する重要なツールであるため、これらのAPI変更に追従し、go1beta
環境で正しく動作するように更新する必要がありました。
前提知識の解説
Go言語のエラーハンドリング (os.Error
から error
へ)
Go言語では、エラーは戻り値として扱われることが一般的です。Go 1.0より前のバージョンでは、os
パッケージに定義された os.Error
インターフェースがエラーを表すために使用されていました。これは、以下のように定義されていました。
package os
type Error interface {
String() string
}
しかし、Go 1.0からは、より汎用的な組み込みの error
インターフェースが導入されました。
package builtin
type error interface {
Error() string
}
この変更の主な理由は、os.Error
が os
パッケージに依存していたため、エラーの概念がファイルシステム操作に限定されるような印象を与えていたことです。新しい error
インターフェースは、Go言語全体でエラーを表現するための標準的な方法となり、任意の型が Error() string
メソッドを実装することでエラーとして扱えるようになりました。これにより、エラーの表現がより柔軟になり、カスタムエラー型の作成が容易になりました。
このコミットでは、関数シグネチャの os.Error
を error
に変更し、os.NewError
の呼び出しを errors.New
に置き換えることで、この新しいエラーハンドリングの慣習に準拠しています。
Go標準ライブラリのパッケージ構造の変更
Go言語の初期開発段階では、標準ライブラリのパッケージ構造が頻繁に調整されていました。特に、Go 1.0の安定化プロセスにおいて、パッケージの役割と責任を明確にするための再編成が行われました。
http
からnet/http
: ネットワークプロトコルに関連する機能は、より一般的なnet
パッケージの下に統合されました。これにより、HTTPクライアントおよびサーバー機能はnet/http
パッケージに移動しました。template
からhtml/template
およびtext/template
: テンプレートエンジンは、その用途に応じてHTMLエスケープを自動的に行うhtml/template
と、プレーンテキストを生成するtext/template
に分割されました。これにより、セキュリティ(XSS攻撃の防止など)と柔軟性の両方が向上しました。
これらの変更は、Goの標準ライブラリが大規模なアプリケーション開発においてより堅牢で使いやすくなることを目指したものでした。
Google App Engine Go SDK と datastore.Time
Google App Engine (GAE) は、Googleのインフラストラクチャ上でウェブアプリケーションを構築・ホストするためのプラットフォームです。Go言語はGAEでサポートされており、GAEのデータストア(NoSQLデータベース)と連携するためのSDKが提供されています。
Go 1.0より前のGAE Go SDKでは、データストアに時間を保存するために datastore.Time
というカスタム型が使用されていました。しかし、Go 1.0のリリースに伴い、Go標準ライブラリの time.Time
型がより成熟し、データストアとの連携も time.Time
を直接使用するように変更されました。これにより、GAEアプリケーションはGoの標準的な時間型をそのまま利用できるようになり、コードの移植性と一貫性が向上しました。
hmac
パッケージのAPI変更
crypto/hmac
パッケージは、HMAC (Keyed-Hash Message Authentication Code) を実装するための機能を提供します。Goの初期バージョンでは、特定のハッシュ関数(例: MD5)に特化した hmac.NewMD5
のようなコンストラクタが存在しました。しかし、より汎用的な設計として、hmac.New
関数が導入され、任意の hash.Hash
インターフェースを実装するハッシュ関数を受け入れるようになりました。これにより、HMACの計算に使用するハッシュ関数を柔軟に選択できるようになりました。
このコミットでは、hmac.NewMD5([]byte(secretKey(c)))
が hmac.New(md5.New, []byte(secretKey(c)))
に変更されており、このAPIの変更に対応しています。
技術的詳細
このコミットで行われた技術的な変更は、主にGo言語の go1beta
リリースに伴うAPIの非互換性に対応するためのものです。
-
エラー型の変更:
os.Error
型が使用されていたすべての関数シグネチャがerror
型に変更されました。os.NewError("...")
の呼び出しはerrors.New("...")
に置き換えられました。これには、build/build.go
、build/handler.go
、build/test.go
、build/ui.go
、build/key.go
など、多くのファイルが影響を受けています。err.String()
メソッドの呼び出しはerr.Error()
に変更されました。これはerror
インターフェースのメソッド名に合わせたものです。
-
パッケージインポートパスの変更:
"http"
パッケージのインポートは"net/http"
に変更されました。これはbuild/handler.go
、build/init.go
、build/test.go
、build/ui.go
、cache/cache.go
など、HTTP関連の処理を行うすべてのファイルに適用されています。"json"
パッケージのインポートは"encoding/json"
に変更されました。これはbuild/handler.go
とbuild/test.go
に影響しています。"template"
パッケージのインポートは"text/template"
または"html/template"
に変更されました。build/notify.go
では"template"
が"text/template"
に変更され、template.Must
の呼び出しもtemplate.New("notify.txt").Funcs(template.FuncMap(tmplFuncs)).ParseFiles("build/notify.txt")
のように、より明示的なParseFiles
を使用するように変更されています。build/ui.go
では"exp/template/html"
と"template"
が削除され、"html/template"
が追加されています。これはHTMLテンプレートの処理に特化したパッケージを使用するためです。
-
時間型の変更:
Commit
構造体のTime
フィールドの型がdatastore.Time
からtime.Time
に変更されました。time.Seconds()
の使用箇所がtime.Now().Unix()
に変更され、datastore.Time(tCommitTime * 1e6)
のようなデータストア固有の時間変換が不要になりました。- テストコード (
build/test.go
) では、tCommitTime
の初期化がtime.Seconds() - 60*60*24*7
からtime.Now().Add(-time.Hour * 24 * 7)
に変更され、tCommitTime += 60 * 60 * 12
がtCommitTime.Add(time.Hour)
に変更されています。これはtime.Time
型のメソッドを利用したよりGoらしい時間の操作です。
-
HMACハッシュ関数の指定方法の変更:
build/handler.go
のbuilderKey
関数内で、hmac.NewMD5([]byte(secretKey(c)))
がhmac.New(md5.New, []byte(secretKey(c)))
に変更されました。これは、hmac.New
関数がhash.Hash
インターフェースを実装する任意のハッシュ関数を受け入れるようになったためです。
-
sha1.Sum()
の引数変更:build/build.go
のPutLog
関数内で、h.Sum()
がh.Sum(nil)
に変更されました。Sum
メソッドは、Go 1.0で引数としてバイトスライスを受け取るようになり、そのスライスにハッシュ値を追記するようになりました。nil
を渡すことで、新しいスライスが割り当てられ、ハッシュ値が返されます。
これらの変更は、Go言語のAPIの進化と安定化の過程を反映しており、Go 1.0以降のバージョンでコードが正しく動作するための必須の更新でした。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルと行に集中しています。
-
misc/dashboard/app/app.yaml
:--- a/misc/dashboard/app/app.yaml +++ b/misc/dashboard/app/app.yaml @@ -6,7 +6,7 @@ application: golang-org version: build runtime: go -api_version: 3 +api_version: go1beta handlers: - url: /static
- App EngineアプリケーションのAPIバージョンを
3
からgo1beta
に更新。
- App EngineアプリケーションのAPIバージョンを
-
misc/dashboard/app/build/build.go
:--- a/misc/dashboard/app/build/build.go +++ b/misc/dashboard/app/build/build.go @@ -8,11 +8,12 @@ import ( "bytes" "compress/gzip" "crypto/sha1" + "errors" "fmt" "io" "io/ioutil" - "os" "strings" + "time" "appengine" "appengine/datastore" @@ -41,7 +42,7 @@ func (p *Package) Key(c appengine.Context) *datastore.Key { } // LastCommit returns the most recent Commit for this Package. -func (p *Package) LastCommit(c appengine.Context) (*Commit, os.Error) { +func (p *Package) LastCommit(c appengine.Context) (*Commit, error) { var commits []*Commit _, err := datastore.NewQuery("Commit"). Ancestor(p.Key(c)). @@ -58,7 +59,7 @@ func (p *Package) LastCommit(c appengine.Context) (*Commit, os.Error) { } // GetPackage fetches a Package by path from the datastore. -func GetPackage(c appengine.Context, path string) (*Package, os.Error) { +func GetPackage(c appengine.Context, path string) (*Package, error) { p := &Package{Path: path} err := datastore.Get(c, p.Key(c), p) if err == datastore.ErrNoSuchEntity { @@ -80,7 +81,7 @@ type Commit struct { User string Desc string `datastore:",noindex"` - Time datastore.Time + Time time.Time // ResultData is the Data string of each build Result for this Commit. // For non-Go commits, only the Results for the current Go tip, weekly, @@ -100,19 +101,19 @@ func (com *Commit) Key(c appengine.Context) *datastore.Key { return datastore.NewKey(c, "Commit", key, 0, p.Key(c)) } -func (c *Commit) Valid() os.Error { +func (c *Commit) Valid() error { if !validHash(c.Hash) { - return os.NewError("invalid Hash") + return errors.New("invalid Hash") } if c.ParentHash != "" && !validHash(c.ParentHash) { // empty is OK - return os.NewError("invalid ParentHash") + return errors.New("invalid ParentHash") } return nil } // AddResult adds the denormalized Reuslt data to the Commit's Result field. // It must be called from inside a datastore transaction. -func (com *Commit) AddResult(c appengine.Context, r *Result) os.Error { +func (com *Commit) AddResult(c appengine.Context, r *Result) error { if err := datastore.Get(c, com.Key(c), com); err != nil { return fmt.Errorf("getting Commit: %v", err) } @@ -192,12 +193,12 @@ func (r *Result) Key(c appengine.Context) *datastore.Key { return datastore.NewKey(c, "Result", key, 0, p.Key(c)) } -func (r *Result) Valid() os.Error { +func (r *Result) Valid() error { if !validHash(r.Hash) { - return os.NewError("invalid Hash") + return errors.New("invalid Hash") } if r.PackagePath != "" && !validHash(r.GoHash) { - return os.NewError("invalid GoHash") + return errors.New("invalid GoHash") } return nil } @@ -214,7 +215,7 @@ type Log struct { CompressedLog []byte } -func (l *Log) Text() ([]byte, os.Error) { +func (l *Log) Text() ([]byte, error) { d, err := gzip.NewReader(bytes.NewBuffer(l.CompressedLog)) if err != nil { return nil, fmt.Errorf("reading log data: %v", err) @@ -226,14 +227,14 @@ func (l *Log) Text() ([]byte, os.Error) { return b, nil } -func PutLog(c appengine.Context, text string) (hash string, err os.Error) { +func PutLog(c appengine.Context, text string) (hash string, err error) { h := sha1.New() io.WriteString(h, text) b := new(bytes.Buffer) z, _ := gzip.NewWriterLevel(b, gzip.BestCompression) io.WriteString(z, text) z.Close() - hash = fmt.Sprintf("%x", h.Sum()) + hash = fmt.Sprintf("%x", h.Sum(nil)) key := datastore.NewKey(c, "Log", hash, 0, nil) _, err = datastore.Put(c, key, &Log{b.Bytes()}) return @@ -252,29 +253,29 @@ func (t *Tag) Key(c appengine.Context) *datastore.Key { return datastore.NewKey(c, "Tag", t.Kind, 0, p.Key(c)) } -func (t *Tag) Valid() os.Error { +func (t *Tag) Valid() error { if t.Kind != "weekly" && t.Kind != "release" && t.Kind != "tip" { - return os.NewError("invalid Kind") + return errors.New("invalid Kind") } if !validHash(t.Hash) { - return os.NewError("invalid Hash") + return errors.New("invalid Hash") } return nil } // Commit returns the Commit that corresponds with this Tag. -func (t *Tag) Commit(c appengine.Context) (*Commit, os.Error) { +func (t *Tag) Commit(c appengine.Context) (*Commit, error) { com := &Commit{Hash: t.Hash} err := datastore.Get(c, com.Key(c), com) return com, err } // GetTag fetches a Tag by name from the datastore. -func GetTag(c appengine.Context, tag string) (*Tag, os.Error) { +func GetTag(c appengine.Context, tag string) (*Tag, error) { t := &Tag{Kind: tag} if err := datastore.Get(c, t.Key(c), t); err != nil { if err == datastore.ErrNoSuchEntity { - return nil, os.NewError("tag not found: " + tag) + return nil, errors.New("tag not found: " + tag) } return nil, err } @@ -286,11 +287,11 @@ func GetTag(c appengine.Context, tag string) (*Tag, os.Error) { // Packages returns packages of the specified kind. // Kind must be one of "external" or "subrepo". -func Packages(c appengine.Context, kind string) ([]*Package, os.Error) { +func Packages(c appengine.Context, kind string) ([]*Package, error) { switch kind { case "external", "subrepo": default: - return nil, os.NewError(`kind must be one of "external" or "subrepo"`) + return nil, errors.New(`kind must be one of "external" or "subrepo"`) } var pkgs []*Package q := datastore.NewQuery("Package").Filter("Kind=", kind)
os
インポートの削除とerrors
,time
の追加。os.Error
をerror
に、os.NewError
をerrors.New
に変更。Commit
構造体のTime
フィールドをdatastore.Time
からtime.Time
に変更。sha1.Sum()
の呼び出しをh.Sum()
からh.Sum(nil)
に変更。
-
misc/dashboard/app/build/handler.go
:--- a/misc/dashboard/app/build/handler.go +++ b/misc/dashboard/app/build/handler.go @@ -6,10 +6,11 @@ package build import ( "crypto/hmac" + "crypto/md5" + "encoding/json" + "errors" "fmt" - "http" - "json" - "os" + "net/http" "appengine" "appengine/datastore" @@ -28,7 +29,7 @@ const commitsPerPage = 30 // each new commit at tip. // // This handler is used by a gobuilder process in -commit mode. -func commitHandler(r *http.Request) (interface{}, os.Error) { +func commitHandler(r *http.Request) (interface{}, error) { c := appengine.NewContext(r) com := new(Commit) @@ -56,7 +57,7 @@ func commitHandler(r *http.Request) (interface{}, os.Error) { \treturn nil, fmt.Errorf("validating Commit: %v", err) } defer cache.Tick(c) - tx := func(c appengine.Context) os.Error { + tx := func(c appengine.Context) error { return addCommit(c, com) } return nil, datastore.RunInTransaction(c, tx, nil) @@ -64,7 +65,7 @@ func commitHandler(r *http.Request) (interface{}, os.Error) { // addCommit adds the Commit entity to the datastore and updates the tip Tag. // It must be run inside a datastore transaction. -func addCommit(c appengine.Context, com *Commit) os.Error { +func addCommit(c appengine.Context, com *Commit) error { var tc Commit // temp value so we don't clobber com err := datastore.Get(c, com.Key(c), &tc) if err != datastore.ErrNoSuchEntity { @@ -94,7 +95,7 @@ func addCommit(c appengine.Context, com *Commit) os.Error { \treturn fmt.Errorf("testing for parent Commit: %v", err) } if n == 0 { - return os.NewError("parent commit not found") + return errors.New("parent commit not found") } } // update the tip Tag if this is the Go repo @@ -115,7 +116,7 @@ func addCommit(c appengine.Context, com *Commit) os.Error { // request body and updates the Tag entity for the Kind of tag provided. // // This handler is used by a gobuilder process in -commit mode. -func tagHandler(r *http.Request) (interface{}, os.Error) { +func tagHandler(r *http.Request) (interface{}, error) { if r.Method != "POST" { return nil, errBadMethod(r.Method) } @@ -143,7 +144,7 @@ type Todo struct { // todoHandler returns the next action to be performed by a builder. // It expects "builder" and "kind" query parameters and returns a *Todo value. // Multiple "kind" parameters may be specified. -func todoHandler(r *http.Request) (interface{}, os.Error) { +func todoHandler(r *http.Request) (interface{}, error) { c := appengine.NewContext(r) now := cache.Now(c) key := "build-todo-" + r.Form.Encode() @@ -151,7 +152,7 @@ func todoHandler(r *http.Request) (interface{}, os.Error) { if cache.Get(r, now, key, &todo) { return todo, nil } - var err os.Error + var err error builder := r.FormValue("builder") for _, kind := range r.Form["kind"] { var data interface{} @@ -183,7 +184,7 @@ func todoHandler(r *http.Request) (interface{}, os.Error) { // If provided with non-empty packagePath and goHash args, it scans the first // 20 Commits in Num-descending order for the specified packagePath and // returns the first that doesn't have a Result for this builder and goHash. -func buildTodo(c appengine.Context, builder, packagePath, goHash string) (interface{}, os.Error) { +func buildTodo(c appengine.Context, builder, packagePath, goHash string) (interface{}, error) { p, err := GetPackage(c, packagePath) if err != nil { return nil, err @@ -251,7 +252,7 @@ func buildTodo(c appengine.Context, builder, packagePath, goHash string) (interf // packagesHandler returns a list of the non-Go Packages monitored // by the dashboard. -func packagesHandler(r *http.Request) (interface{}, os.Error) { +func packagesHandler(r *http.Request) (interface{}, error) { kind := r.FormValue("kind") c := appengine.NewContext(r) now := cache.Now(c) @@ -273,7 +274,7 @@ func packagesHandler(r *http.Request) (interface{}, os.Error) { // creates a new Result entity, and updates the relevant Commit entity. // If the Log field is not empty, resultHandler creates a new Log entity // and updates the LogHash field before putting the Commit entity. -func resultHandler(r *http.Request) (interface{}, os.Error) { +func resultHandler(r *http.Request) (interface{}, error) { if r.Method != "POST" { return nil, errBadMethod(r.Method) } @@ -296,7 +297,7 @@ func resultHandler(r *http.Request) (interface{}, os.Error) { } res.LogHash = hash } - tx := func(c appengine.Context) os.Error { + tx := func(c appengine.Context) error { // check Package exists if _, err := GetPackage(c, res.PackagePath); err != nil { return fmt.Errorf("GetPackage: %v", err) @@ -338,7 +339,7 @@ func logHandler(w http.ResponseWriter, r *http.Request) { w.Write(b) } -type dashHandler func(*http.Request) (interface{}, os.Error) +type dashHandler func(*http.Request) (interface{}, error) type dashResponse struct { Response interface{} @@ -349,7 +350,7 @@ type dashResponse struct { // the request has an unsuitable method. type errBadMethod string -func (e errBadMethod) String() string { +func (e errBadMethod) Error() string { return "bad method: " + string(e) } @@ -363,14 +364,14 @@ func AuthHandler(h dashHandler) http.HandlerFunc { // request body when calling r.FormValue. r.Form = r.URL.Query() - var err os.Error + var err error var resp interface{} // Validate key query parameter for POST requests only. key := r.FormValue("key") builder := r.FormValue("builder") if r.Method == "POST" && !validKey(c, key, builder) { - err = os.NewError("invalid key: " + key) + err = errors.New("invalid key: " + key) } // Call the original HandlerFunc and return the response. @@ -382,7 +383,7 @@ func AuthHandler(h dashHandler) http.HandlerFunc { dashResp := &dashResponse{Response: resp} if err != nil { c.Errorf("%v", err) - dashResp.Error = err.String() + dashResp.Error = err.Error() } w.Header().Set("Content-Type", "application/json") if err = json.NewEncoder(w).Encode(dashResp); err != nil { @@ -394,7 +395,7 @@ func keyHandler(w http.ResponseWriter, r *http.Request) { func keyHandler(w http.ResponseWriter, r *http.Request) { builder := r.FormValue("builder") if builder == "" { - logErr(w, r, os.NewError("must supply builder in query string")) + logErr(w, r, errors.New("must supply builder in query string")) return } c := appengine.NewContext(r) @@ -433,12 +434,12 @@ func validKey(c appengine.Context, key, builder string) bool { } func builderKey(c appengine.Context, builder string) string { - h := hmac.NewMD5([]byte(secretKey(c))) + h := hmac.New(md5.New, []byte(secretKey(c))) h.Write([]byte(builder)) - return fmt.Sprintf("%x", h.Sum()) + return fmt.Sprintf("%x", h.Sum(nil)) } -func logErr(w http.ResponseWriter, r *http.Request, err os.Error) { +func logErr(w http.ResponseWriter, r *http.Request, err error) { appengine.NewContext(r).Errorf("Error: %v", err) w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, "Error: ", err)
http
,json
,os
インポートの削除とcrypto/md5
,encoding/json
,errors
,net/http
の追加。os.Error
をerror
に、os.NewError
をerrors.New
に変更。errBadMethod
型のString()
メソッドをError()
メソッドに変更。hmac.NewMD5
をhmac.New(md5.New, ...)
に変更。h.Sum()
をh.Sum(nil)
に変更。
-
misc/dashboard/app/build/test.go
:--- a/misc/dashboard/app/build/test.go +++ b/misc/dashboard/app/build/test.go @@ -10,15 +10,15 @@ import ( "appengine" "appengine/datastore" "bytes" + "encoding/json" + "errors" "fmt" - "http" - "http/httptest" "io" - "json" - "os" + "net/http" + "net/http/httptest" + "net/url" "strings" "time" - "url" ) func init() { @@ -41,14 +41,14 @@ var testPackages = []*Package{ testPackage, } -var tCommitTime = time.Seconds() - 60*60*24*7 +var tCommitTime = time.Now().Add(-time.Hour * 24 * 7) func tCommit(hash, parentHash string) *Commit { - tCommitTime += 60 * 60 * 12 // each commit should have a different time + tCommitTime.Add(time.Hour) // each commit should have a different time return &Commit{ Hash: hash, ParentHash: parentHash, - Time: datastore.Time(tCommitTime * 1e6), + Time: tCommitTime, User: "adg", Desc: "change description", } @@ -233,9 +233,9 @@ func testHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "PASS") } -func nukeEntities(c appengine.Context, kinds []string) os.Error { +func nukeEntities(c appengine.Context, kinds []string) error { if !appengine.IsDevAppServer() { - return os.NewError("can't nuke production data") + return errors.New("can't nuke production data") } var keys []*datastore.Key for _, kind := range kinds {
http
,http/httptest
,json
,os
,url
インポートの削除とencoding/json
,errors
,net/http
,net/http/httptest
,net/url
の追加。tCommitTime
の初期化と更新方法をtime.Time
型のメソッドを使用するように変更。os.Error
をerror
に、os.NewError
をerrors.New
に変更。
-
misc/dashboard/app/build/ui.go
:--- a/misc/dashboard/app/build/ui.go +++ b/misc/dashboard/app/build/ui.go @@ -9,14 +9,13 @@ package build import ( "bytes" - "exp/template/html" - "http" - "os" + "errors" + "html/template" + "net/http" "regexp" "sort" "strconv" "strings" - "template" "appengine" "appengine/datastore" @@ -25,7 +24,6 @@ import ( func init() { http.HandleFunc("/", uiHandler) - html.Escape(uiTemplate) } // uiHandler draws the build status page. @@ -96,7 +94,7 @@ type Pagination struct { // goCommits gets a slice of the latest Commits to the Go repository. // If page > 0 it paginates by commitsPerPage. -func goCommits(c appengine.Context, page int) ([]*Commit, os.Error) { +func goCommits(c appengine.Context, page int) ([]*Commit, error) { q := datastore.NewQuery("Commit"). Ancestor((&Package{}).Key(c)). Order("-Time"). @@ -140,7 +138,7 @@ type PackageState struct {\n } // TagStateByName fetches the results for all Go subrepos at the specified Tag. -func TagStateByName(c appengine.Context, name string) (*TagState, os.Error) { +func TagStateByName(c appengine.Context, name string) (*TagState, error) { tag, err := GetTag(c, name) if err != nil { return nil, err @@ -173,7 +171,7 @@ type uiTemplateData struct { } var uiTemplate = template.Must( - template.New("ui").Funcs(tmplFuncs).ParseFile("build/ui.html"), + template.New("ui.html").Funcs(tmplFuncs).ParseFiles("build/ui.html"), ) var tmplFuncs = template.FuncMap{ @@ -293,13 +291,13 @@ func shortUser(user string) string { var repoRe = regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\\-]+)(\\.[a-z0-9\\-]+)?$`) // repoURL returns the URL of a change at a Google Code repository or subrepo. -func repoURL(hash, packagePath string) (string, os.Error) { +func repoURL(hash, packagePath string) (string, error) { if packagePath == "" { return "https://code.google.com/p/go/source/detail?r=" + hash, nil } m := repoRe.FindStringSubmatch(packagePath) if m == nil { - return "", os.NewError("unrecognized package: " + packagePath) + return "", errors.New("unrecognized package: " + packagePath) } url := "https://code.google.com/p/" + m[1] + "/source/detail?r=" + hash if len(m) > 2 {
exp/template/html
,http
,os
,template
インポートの削除とerrors
,html/template
,net/http
の追加。html.Escape(uiTemplate)
の削除。os.Error
をerror
に、os.NewError
をerrors.New
に変更。template.Must
の呼び出しをtemplate.New("ui.html").Funcs(tmplFuncs).ParseFiles("build/ui.html")
に変更。
コアとなるコードの解説
これらの変更は、Go言語の go1beta
リリースで導入されたAPIの変更に、Goダッシュボードアプリケーションを適応させるためのものです。
-
app.yaml
のapi_version: go1beta
: これは、Google App Engine上でGoアプリケーションを実行する際に、使用するGoのAPIバージョンを指定するものです。go1beta
を指定することで、アプリケーションがGo 1.0のベータ版APIを使用することをApp Engineに伝えます。これにより、新しいAPIの動作が保証されます。 -
os.Error
からerror
への移行:- Go 1.0では、エラーハンドリングの標準として組み込みの
error
インターフェースが採用されました。これにより、os.Error
を使用していたすべての関数シグネチャがerror
に変更され、エラーを生成する際にはerrors
パッケージのNew
関数が使用されるようになりました。 - 例:
func (p *Package) LastCommit(c appengine.Context) (*Commit, os.Error)
がfunc (p *Package) LastCommit(c appengine.Context) (*Commit, error)
に変更され、return os.NewError("invalid Hash")
がreturn errors.New("invalid Hash")
に変更されています。 err.String()
がerr.Error()
に変更されたのも、error
インターフェースのメソッド名に合わせたものです。
- Go 1.0では、エラーハンドリングの標準として組み込みの
-
パッケージインポートパスの変更:
http
からnet/http
、json
からencoding/json
、template
からtext/template
またはhtml/template
への変更は、Go標準ライブラリのパッケージ構造の整理によるものです。これにより、より論理的で一貫性のあるパッケージ構成が実現され、開発者は目的の機能を見つけやすくなりました。- 特に
html/template
の導入は、ウェブアプリケーションにおけるXSS攻撃を防ぐための自動エスケープ機能を提供し、セキュリティを向上させます。html.Escape(uiTemplate)
の削除は、html/template
が自動的にエスケープ処理を行うため、明示的なエスケープが不要になったことを示しています。
-
datastore.Time
からtime.Time
への移行:Commit
構造体内のTime
フィールドがdatastore.Time
からGo標準ライブラリのtime.Time
に変更されたことで、データストアとの連携がよりシームレスになり、Goの標準的な時間操作関数を直接利用できるようになりました。- テストコードにおける
tCommitTime
の初期化と更新方法の変更は、time.Time
型のメソッド (time.Now().Add()
) を使用することで、より自然で読みやすい時間の操作が可能になったことを示しています。
-
hmac.NewMD5
からhmac.New(md5.New, ...)
への変更:- これは
crypto/hmac
パッケージのAPIがより汎用的に設計された結果です。以前は特定のハッシュ関数に特化したコンストラクタがありましたが、新しいAPIではhmac.New
関数に任意のhash.Hash
インターフェースを実装するハッシュ関数(例:md5.New()
)を渡すことで、柔軟にHMACを生成できるようになりました。
- これは
-
sha1.Sum()
からsha1.Sum(nil)
への変更:crypto/sha1
パッケージのSum
メソッドのシグネチャ変更に対応したものです。Go 1.0以降、Sum
メソッドはバイトスライスを引数として受け取り、そのスライスにハッシュ値を追記するようになりました。nil
を渡すことで、新しいスライスが割り当てられ、ハッシュ値が返されます。
これらの変更は、GoダッシュボードがGo言語の進化に追従し、最新のGo環境で安定して動作するための重要なステップでした。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/22185aed742bba5e85b1e4bac68e4c50be84a227
- Gerrit Change-Id: https://golang.org/cl/5624056
参考にした情報源リンク
- Go 1 Release Notes: https://go.dev/doc/go1
- Go 1 and the Future of Go Programs: https://go.dev/blog/go1
- Effective Go (Error Handling): https://go.dev/doc/effective_go#errors
- Go App Engine SDK Documentation (当時の情報に基づく): https://cloud.google.com/appengine/docs/standard/go/datastore/reference (現在のドキュメントは変更されている可能性があります)
crypto/hmac
package documentation: https://pkg.go.dev/crypto/hmaccrypto/sha1
package documentation: https://pkg.go.dev/crypto/sha1time
package documentation: https://pkg.go.dev/timenet/http
package documentation: https://pkg.go.dev/net/httpencoding/json
package documentation: https://pkg.go.dev/encoding/jsonhtml/template
package documentation: https://pkg.go.dev/html/templatetext/template
package documentation: https://pkg.go.dev/text/template