Golang Webアプリケーション開発

この記事では、GoでのWebアプリケーションの開発を検討します。 この資料には根本的に新しい知識は含まれておらず、私と同じように新しく造成された言語研究者向けに設計されています。 とはいえ、あなた自身のためにいくつかの新鮮なアイデアを見つけてほしい。



一部の読者は、「自転車の構築」について質問する場合があります。これらはすべて、好奇心とGolang言語を知りたいという強い関心の成果です。



システム管理とプロジェクト開発



統一されたシステムのアイデアを断片的に得るために、この点について簡単に説明します。 最終的に、CIサーバーはgitリポジトリからプロジェクトを収集し、必要なアーキテクチャ用の本格的なrpmパッケージを形成します。これはsystemdサービスとしてシステムにインストールされます。



[Unit] Description=Description After=network.target Requires=mysqld.service [Service] Type=simple User=nginx Group=nginx WorkingDirectory=/usr/share/project_name StandardOutput=journal StandardError=journal ExecStart=/usr/share/project_name/project_name Restart=always [Install] WantedBy=multi-user.target
      
      





systemdシステムマネージャーは以下を扱います。
  1. Webサービスを開始するための依存関係を確立する(上記のmysqldの例のように)。
  2. アプリケーションがクラッシュした場合のリスポーン。
  3. オプションStandardOutputおよびStandardError、サービスロギングのおかげ。 アプリケーションからシステムログに書き込むには、次を呼び出します。
     log.Println("Server is preparing to start")
          
          





先に、httpサーバーがインストールされ、静的(nginxなど)が返されます。



Webアプリケーションのインストール、更新、およびロールバックは完全にlinux-systemのパッケージマネージャー(yum / dnf / rpm)で行われます。その結果、この非自明なタスクが簡単で信頼できるものになることがあります。



基本的なロジック



いくつかのタスクについては、既製のツールキットGorillaツールキットを使用し、基本的には独自のやや高度なツールキットを作成します。



アプリケーションの初期化


アプリケーションには、起動時に一度だけ変更されるオブジェクトがあります-これらは、構成構造、ルーター、データベースアクセスオブジェクト、およびテンプレートです。 統合とその便利な使用のために、アプリケーション構造を作成します。



 type MapRoutes map[string]Controller type Application struct { Doc AbstractPage Config Config DB SQL routes MapRoutes }
      
      





応募方法
 // Routes       URL' func (app *Application) Routes(r MapRoutes) { app.routes = r } func (app *Application) Run() { r := mux.NewRouter() r.StrictSlash(true) for url, ctrl := range app.routes { r.HandleFunc(url, obs(ctrl)) } http.Handle("/", r) listen := fmt.Sprintf("%s:%d", app.Config.Net.Listen_host, app.Config.Net.Listen_port) log.Println("Server is started on", listen) if err := http.ListenAndServe(listen, nil); err != nil { log.Println(err) } }
      
      







もちろん、アプリケーションのApplicationオブジェクトは次のようになります。



 var appInstance *Application // GetApplication   Application func GetApplication() *Application { if appInstance == nil { appInstance = new(Application) // Init code appInstance.Config = loadConfig("config.ini") appInstance.Doc = make(AbstractPage) appInstance.routes = make(MapRoutes) // ... } return appInstance }
      
      





したがって、アプリケーションの使用は非常に簡単です。



main.go
 package main import ( "interfaces/app" "interfaces/handlers" "log" ) func init() { log.SetFlags(log.LstdFlags | log.Lshortfile) } func main() { log.Println("Server is preparing to start") Application := app.GetApplication() if Application.Config.Site.Disabled { log.Println("Site is disabled") Application.Routes(app.MapRoutes{"/": handlers.HandleDisabled{}}) } else { Application.Routes(app.MapRoutes{ "/": handlers.HandleHome{}, "/v1/ajax/": handlers.HandleAjax{}, //   "/{url:.*}": handlers.Handle404{}, }) } Application.Run() log.Println("Exit") }
      
      







* Contextを使用したhttpHandler


ここで最も興味深いのは、ルーターのインストールです。



 for url, ctrl := range app.routes { r.HandleFunc(url, obs(ctrl)) }
      
      





実際には、Gorillaツールキットのルーターでは、標準の「net / http」ライブラリと同様に、ハンドラー(コントローラー)の機能がfunc(http.ResponseWriter、* http.Request)のような関数に削減されます。 簡単な操作でコントローラーからコントローラーにコードを複製しないように、別の種類のコントローラーに興味があります。



 func ProductHandler(ctx *Context) { // ... }
      
      





ここで、Contextは、Cookie、セッション、およびその他の状況依存構造を操作するための便利なツールです。 より詳細には、コントローラー内の要求コンテキストだけでなく、データベース、構成、つまり そしてApplicationオブジェクトに。 これを行うために、関数wrapper obs(handler Controller)func(http.ResponseWriter、* http.Request)を導入します。これは、必要なコントローラーのタイプ(Controllerインターフェースを受け取り、 r.HandleFunc()に必要な関数のタイプを返し、同時にすべてを行います。コントローラーが実行される前のアドオンアクション-ContextApplicationオブジェクトの作成。



関数obs()、コントローラーおよびHTTPController
 type Controller interface { GET(app *ContextApplication) POST(app *ContextApplication) PUT(app *ContextApplication) DELETE(app *ContextApplication) PATCH(app *ContextApplication) OPTIONS(app *ContextApplication) HEAD(app *ContextApplication) TRACE(app *ContextApplication) CONNECT(app *ContextApplication) } // obs         func obs(handler Controller) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { ctx := context.New(w, req) app := GetApplication() doc := app.Doc.Clone("") doc["Ctx"] = ctx doc["User"] = ctx.User() contextApp := &ContextApplication{ctx, doc, app.Config, app.DB} switch ctx.Input.Method() { case "GET": handler.GET(contextApp); case "POST": handler.POST(contextApp); case "PUT": handler.PUT(contextApp); case "DELETE": handler.DELETE(contextApp); case "PATCH": handler.PATCH(contextApp); case "OPTIONS": handler.OPTIONS(contextApp); case "HEAD": handler.HEAD(contextApp); case "TRACE": handler.TRACE(contextApp); case "CONNECT": handler.CONNECT(contextApp); default: http.Error(ctx.Response(), "Method not allowed", http.StatusMethodNotAllowed) } } } // HTTPController     ,      //     . type HTTPController struct {} func (h HTTPController) GET(app *ContextApplication) { http.Error(app.Ctx.Response(), "Method not allowed", http.StatusMethodNotAllowed) } func (h HTTPController) POST(app *ContextApplication) { http.Error(app.Ctx.Response(), "Method not allowed", http.StatusMethodNotAllowed) } func (h HTTPController) PUT(app *ContextApplication) { http.Error(app.Ctx.Response(), "Method not allowed", http.StatusMethodNotAllowed) } func (h HTTPController) DELETE(app *ContextApplication) { http.Error(app.Ctx.Response(), "Method not allowed", http.StatusMethodNotAllowed) } func (h HTTPController) PATCH(app *ContextApplication) { http.Error(app.Ctx.Response(), "Method not allowed", http.StatusMethodNotAllowed) } func (h HTTPController) OPTIONS(app *ContextApplication) { http.Error(app.Ctx.Response(), "Method not allowed", http.StatusMethodNotAllowed) } func (h HTTPController) HEAD(app *ContextApplication) { http.Error(app.Ctx.Response(), "Method not allowed", http.StatusMethodNotAllowed) } func (h HTTPController) TRACE(app *ContextApplication) { http.Error(app.Ctx.Response(), "Method not allowed", http.StatusMethodNotAllowed) } func (h HTTPController) CONNECT(app *ContextApplication) { http.Error(app.Ctx.Response(), "Method not allowed", http.StatusMethodNotAllowed) }
      
      







* ContextApplication
 type ContextApplication struct { Ctx *context.Context Doc AbstractPage Config Config DB SQL }
      
      







コントローラーの作成


これで、コントローラーを作成する準備がすべて整いました。



ハンドルカスタム
 import ( "interfaces/app" ) type HandleCustom struct { app.HTTPController } func (h HandleCustom) GET(app *app.ContextApplication) { app.Ctx.SendHTML("html data here") } func (h HandleCustom) POST(app *app.ContextApplication) { // and so on... }
      
      







新しいコントローラーを作成するプロセスは、組み込みのapp.HTTPControllerオブジェクトのメソッド(GET、POSTなど)を書き換えることです。 メソッドを書き換えない場合、ビルトインが呼び出され、クライアントに「許可されていないメソッド」が返されます(この動作は他のものに変更できます)。



コンテキスト


コンテキストは基本的に、コンテキスト依存変数の処理を簡素化する一連のメソッドで構成されています。 実装を書くつもりはありません。何が問題なのかが明確になるように、いくつかのメソッドを簡単にリストします。



 func (c *Context) NotFound() // NotFound sends page with 404 http code from template tpls/404.tpl func (c *Context) Redirect(url string) // Redirect sends http redirect with 301 code func (c *Context) Redirect303(url string) // Redirect303 sends http redirect with 303 code func (c *Context) SendJSON(data string) int // SendJSON sends json-content (data) func (c *Context) SendXML(data string) // SendXML sends xml-content (data) func (c *Context) GetCookie(key string) string // GetCookie return cookie from request by a given key. func (c *Context) SetCookie(name string, value string, others ...interface{}) // SetCookie set cookie for response. func (c *Context) CheckXsrfToken() bool // CheckXsrfToken  token func (c *Context) User() User // User    func (c *Context) Session(name string) (*Session, error) // Session   func (s *Session) Clear() // Clear    //  ..
      
      







テンプレートエンジン


標準ライブラリには、「html /テンプレート」と呼ばれる素晴らしいパッケージがあります。 機能を少し拡張して使用します。



 // loadTemplate load template from tpls/%s.tpl func loadTemplate(Name string) *html.Template { funcMap := html.FuncMap{ "html": func(val string) html.HTML { return html.HTML(val) }, "typo": func(val string) string { return typo.Typo(val) }, "mod": func(args ...interface{}) interface{} { if len(args) == 0 { return "" } name := args[0].(string) ctx := new(context.Context) if len(args) > 1 { ctx = args[1].(*context.Context) } modules := reflect.ValueOf(modules.Get()) mod := modules.MethodByName(name) if (mod == reflect.Value{}) { return "" } inputs := make([]reflect.Value, 0) inputs = append(inputs, reflect.ValueOf(ctx)) ret := mod.Call(inputs) return ret[0].Interface() }, } return html.Must(html.New("*").Funcs(funcMap).Delims("{{%", "%}}").ParseFiles("tpls/" + Name + ".tpl")) }
      
      





AngularJSとの互換性のために、区切り文字を「{{}}」から「{{%%}}」に変更しますが、あまり便利ではないことを認めます。

上記の3つのパイプライン関数の詳細:

  1. html-入力パラメーターのタイプをHTMLに変更して、テンプレートがHTML行をエスケープしないようにします。 時々便利です テンプレートでの使用例:
     <div>{{% .htmlString | html %}}</div>
          
          



  2. typo-いくつかの印刷規則に従ったテキスト処理。 テンプレートでの使用例:
     <h1>{{% .title | typo %}}</h1>
          
          



  3. mod-テンプレート本体から直接モジュールを起動します。 使用例:
     <div>{{% mod "InformMenu" %}}</div>
          
          







 type AbstractPage map[string]interface{}
      
      





AbstractPageは、テンプレートで使用するための入力コンテナです。 例を挙げます。



コードで値を埋める
 func (h HandleCustom) GET(app *app.ContextApplication) { doc := app.Doc.Clone("custom") //   AbstractPage,    custom.tpl doc["V1"] = "V1" doc["V2"] = 555 result := doc.Compile() app.Ctx.SendHTML(result) }
      
      







custom.tpl
 {{%define "*"%}} <ul> <li>{{% .V1 %}}</li> <li>{{% .V2 %}}</li> </ul> {{%end%}}
      
      







AbstractPageには2つのメソッドがあります。

  1. Clone()メソッド
     // Clone    AbstractPage c     func (page AbstractPage) Clone(tplName string) AbstractPage { doc := make(AbstractPage) for k, v := range page { doc[k] = v } doc["__tpl"] = tplName return doc }
          
          







    すべての値をコピーして、新しいAbstractPageコンテナーを作成します。 この操作の意味は、AbstractPageの上位レベルから値を継承することです。
  2. コンパイル()メソッド
     // Compile return page formatted with template from tpls/%d.tpl func (page AbstractPage) Compile() string { var data bytes.Buffer for k, v := range page { switch val := v.(type) { case AbstractPage: { page[k] = html.HTML(val.Compile()) } case func()string: { page[k] = val() } } } //     (ctx   doc["Ctx"]) getTpl(page["__tpl"].(string)).Execute(&data, page) return data.String() }
          
          







    テンプレートを実行し、結果のHTMLコードを生成します。




まとめ



私の意見では、それは柔軟かつ非常に簡単になりました。 開発の残りの部分は、特定のコントローラーとモジュールの実装に関連しており、これらは本質的に互いに独立しています。



Goは、多くの人と同様に私を無関心なままにしていないことに注意したいと思います。



参照資料



1. github.com/dblokhin/typo-いくつかの印刷規則に従ってテキストを処理するためのgolangパッケージ。



All Articles