KDOC 55: giteaのコードを読んだメモ

go-gitea/giteaを読んだメモ。バックエンドはGoで書かれている。

memo

コンパイル

コンパイルしている部分。タスク名が動的になっていてわかりにくい。

$(EXECUTABLE): $(GO_SOURCES) \((TAGS_PREREQ) CGO_CFLAGS="\)(CGO_CFLAGS)“ $(GO) build $(GOFLAGS) \((EXTRA_GOFLAGS) -tags '\)(TAGS)’ -ldflags ‘-s -w $(LDFLAGS)’ -o $@

routerを起動している部分

webRoutes := routers.NormalRoutes()

DB設定部分

setting packageにまとめられている。パッケージで変数化されている。

// Database holds the database settings Database = struct {

変数はグローバル変数にしつつ↑、セットはパッケージ内の関数でやる。

func loadDBSetting(rootCfg ConfigProvider) { sec := rootCfg.Section(“database”) Database.Type = DatabaseType(sec.Key(“DB_TYPE”).String())

Database.Host = sec.Key(“HOST”).String() Database.Name = sec.Key(“NAME”).String() Database.User = sec.Key(“USER”).String()

DB初期化部分

routerでのDBの初期化は↓でやっている。

// InitEngine initializes the xorm.Engine and sets it as db.DefaultContext func InitEngine(ctx context.Context) error {

InitEngineの中でデータベースを初期化する。

xormEngine, err := newXORMEngine()

InitEngineの中でSetDefaultEngine()して初期化したデータベースをDefaultContextに設定する。

// SetDefaultEngine sets the default engine for db func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) { x = eng DefaultContext = &Context{ Context: ctx, e: x, } }

セットできているかの確認文。

mustInitCtx(ctx, common.InitDBEngine)

変数default contextに設定する

設定している部分。

// SetDefaultEngine sets the default engine for db func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) { x = eng DefaultContext = &Context{ Context: ctx, e: x, } }

テストとかではdefault contextを使って、dbを簡単に起動している。そうでない部分では、起動時に明示して上書きしているようだ。

APIContextをセットしている部分

DBのデータなどはcontext.APIContextで伝達している。APIContextにはBaseが埋め込まれている。

// APIContext is a specific context for API service type APIContext struct { *Base

Cache cache.Cache

Doer *user_model.User // current signed-in user IsSigned bool IsBasicAuth bool

ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer

Repo *Repository Org *APIOrganization Package *Package }

Baseにはcontext.Contextが埋め込まれている。

type Base struct { originCtx context.Context contextValues []contextValuePair

Resp ResponseWriter Req *http.Request

/ Data is prepared by ContextDataStore middleware, this field only refers to the pre-created/prepared ContextData. / Although it’s mainly used for MVC templates, sometimes it’s also used to pass data between middlewares/handler Data middleware.ContextData

// Locale is mainly for Web context, although the API context also uses it in some cases: message response, form validation Locale translation.Locale }

// APIContexter returns apicontext as middleware func APIContexter() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { base, baseCleanUp := NewBaseContext(w, req) ctx := &APIContext{ Base: base, Cache: mc.GetCache(), Repo: &Repository{PullRequest: &PullRequest{}}, Org: &APIOrganization{}, }

contextからdbインスタンスを取り出す

has, err := db.GetEngine(ctx).ID(task.RepoID).Get(&repo)

DBコンテキストは異なる

DBのためだけにありそう。defaultContextが入るのはこれ。contextを構造体に埋め込むのはよくないとされている。

// Context represents a db context type Context struct { context.Context e Engine transaction bool }

/ DefaultContext is the default context to run xorm queries in / will be overwritten by Init with HammerContext var DefaultContext context.Context

context.Contextはインターフェースなので、DefaultContextにはdb.Contextが入る。

このDBコンテキストは、Engine()を実装しているのでEnginedインターフェースを満たす。

// Engined structs provide an Engine type Engined interface { Engine() Engine }

// GetEngine will get a db Engine from this context or return an Engine restricted to this context func GetEngine(ctx context.Context) Engine { if e := getEngine(ctx); e != nil { return e } return x.Context(ctx) }

// getEngine will get a db Engine from this context or return nil func getEngine(ctx context.Context) Engine { if engined, ok := ctx.(Engined); ok { return engined.Engine() } enginedInterface := ctx.Value(enginedContextKey) if enginedInterface != nil { return enginedInterface.(Engined).Engine() } return nil }

// Engine returns db engine func (ctx *Context) Engine() Engine { return ctx.e }

DBオブジェクト宣言

xormのオブジェクト作成はxorm.NewEngine()で行う。

engine, err = xorm.NewEngine(“postgresschema”, connStr)

テストで、どうやってDefaultContextをセットしているか

テストでたくさん使っている。便利だ。これはいつセットされているか。

最初にunittest.PrepareTestDatabase()する。この中でDefaultContextに対してクエリが実行されている。そのあとDefaultContextを直に取得して実行する。

assert.NoError(t, unittest.PrepareTestDatabase()) token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3}) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0)

dbはcontextのvalueで持つべきじゃないらしい。リクエストスコープでないから。ダメじゃんと思ったけれども、contextの内部で持ってるわけではなく構造体で持ってるからいいのか。

APIコンテキストはどうやって生成しているか

// APIContexter returns apicontext as middleware func APIContexter() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { base, baseCleanUp := NewBaseContext(w, req) ctx := &APIContext{ Base: base, Cache: mc.GetCache(), Repo: &Repository{PullRequest: &PullRequest{}}, Org: &APIOrganization{}, } defer baseCleanUp()

ctx.Base.AppendContextValue(apiContextKey, ctx) ctx.Base.AppendContextValueFunc(git.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })

// If request sends files, parse them here otherwise the Query() can’t be parsed and the CsrfToken will be invalid. if ctx.Req.Method = "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { if err : ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), “EOF”) { // 32MB max size ctx.InternalServerError(err) return } }

httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, “no-transform”) ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)

next.ServeHTTP(ctx.Resp, ctx.Req) }) } }

ミドルウェアとして登録されている。

m.Use(context.APIContexter())

DB関係なくないか。どこでセットされているか。

トランザクションコンテキストは必要。なぜ。

マイグレーションコマンド

マイグレーションはタスクで行う。あるいは、Webの起動時にも関数が用意されている。

func runMigrate(ctx *cli.Context) error {

テスト時にどうやってDB初期化されているか

  • マイグレーション
  • DefaultContext初期化

が必要なはずだが見当たらない。

unittest.PrepareTestDatabase() を見るが、そのときにはすでにDefaultContextからDBが取得できている。

各test packageにあるtest_main.goにある、TestMain関数内でunittest.MainTest()している。MainTest内でDBを初期化してる。テスト時に必ず実行される。

func MainTest(m *testing.M, testOpts …*TestOptions) {