以前、Beegoを使って、アプリケーションの開発をしましたが、本記事ではもう一歩手前レベルの記事を書きます。
Go言語は、Webフレームワーク(Beego)を使わなくてもアプリケーションサーバーを構築するのに優れた言語です。
今回は、Go言語のみの機能を使って、アプリケーションサーバーを構築しましょう。
<目次>
学習履歴
プロジェクト作成
次のフォルダとファイルを作成してください。
1 2 3 |
mkdir golang_app cd golang_app touch main.go |
ページの読み込み/保存機能の作成
main.goにページ読み取り機能を付与します。
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 33 34 35 36 37 |
main.go package main import ( "fmt" "io/ioutil" ) type Page struct { Title string Body []byte } // Save Page Info to test.txt func (p *Page) save() error { filename := p.Title + ".txt" return ioutil.WriteFile(filename, p.Body, 0600) } func loadPage(title string) (*Page, error) { filename := title + ".txt" body, err := ioutil.ReadFile(filename) if err != nil { return nil, err } return &Page{Title: title, Body: body}, nil } func main() { p1 := &Page{Title: "test", Body: []byte("This is a sample page.")} p1.save() p2, _ := loadPage(p1.Title) fmt.Println(string(p2.Body)) } |
Page Structには、ページ情報を格納する予定ですが、今は暫定で固定値(test, This is…)を指定しています。
loadPage関数を実行すると、「Title+.txt」のファイル名でをローカルにテキストファイルを作成します。
その後save関数にて、Bodyの内容をファイルに書き込みます。
プログラムを実行してみましょう。
1 |
go run main.go |
ローカルにtest.txtが作成されたか確認してください。
1 2 3 |
cat test.txt This is a sample page. |
アプリケーションサーバーの構築
先ほどのコードをアプリケーションサーバーとして動作するように書き換えましょう。
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 33 34 35 36 37 38 39 40 41 42 43 44 |
main.go package main import ( "fmt" "io/ioutil" "log" "net/http" ) type Page struct { Title string Body []byte } // Save Page Info to test.txt func (p *Page) save() error { filename := p.Title + ".txt" return ioutil.WriteFile(filename, p.Body, 0600) } func loadPage(title string) (*Page, error) { filename := title + ".txt" body, err := ioutil.ReadFile(filename) if err != nil { return nil, err } return &Page{Title: title, Body: body}, nil } func viewHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("test") // /view/test -> test title := r.URL.Path[len("/view/"):] p, _ := loadPage(title) fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body) } func main() { http.HandleFunc("/view/", viewHandler) log.Fatal(http.ListenAndServe(":8080", nil)) } |
「net/http」パッケージの(http.ListenAndServe)を呼ぶと簡単にアプリケーションサーバー化できます。
次のコマンドで、アプリケーションサーバーを起動します。
1 |
$ go run main.go |
別のテンプレートを立ち上げて、curlを打ってみましょう。Page情報が取得できるはずです。
1 2 |
$ curl localhost:8080/view/test <h1>test</h1><div>This is a sample page.</div>$ |
テンプレート
テンプレートを作成します。
1 2 |
touch edit.html touch view.html |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
edit.htlm <h1>Editing {{.Title}}</h1> <form action="/save/{{.Title}}" method="POST"> <div> <textarea name="body" cols="80" rows="20">{{printf "%s" .Body}}</textarea> </div> <div> <input type="submit" value="Save"> </div> </form> |
1 2 3 4 5 6 7 8 |
view.html <h1>{{.Title}}</h1> <p>[<a href="/edit/{{.Title}}">Edit</a>]</p> <div>{{printf "%s" .Body}}</div> |
Djangoのテンプレートエンジンと似ていますね。
「.Title」や「.Body」は、main.goのPageモデルのプロパティです。
main.goを書き直してみましょう。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
main.go package main import ( "html/template" "io/ioutil" "log" "net/http" ) type Page struct { Title string Body []byte } // Render template with Page parameter func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { t, _ := template.ParseFiles(tmpl + ".html") t.Execute(w, p) } // Save Page Info to test.txt func (p *Page) save() error { filename := p.Title + ".txt" return ioutil.WriteFile(filename, p.Body, 0600) } func loadPage(title string) (*Page, error) { filename := title + ".txt" body, err := ioutil.ReadFile(filename) if err != nil { return nil, err } return &Page{Title: title, Body: body}, nil } func viewHandler(w http.ResponseWriter, r *http.Request) { // /view/test -> test title := r.URL.Path[len("/view/"):] p, _ := loadPage(title) // view.html renderTemplate(w, "view", p) } func editHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/edit/"):] p, err := loadPage(title) if err != nil { p = &Page{Title: title} } renderTemplate(w, "edit", p) } func main() { http.HandleFunc("/view/", viewHandler) http.HandleFunc("/edit/", editHandler) log.Fatal(http.ListenAndServe("localhost:8080", nil)) } |
main.goを実行してみましょう。
1 |
go run main.go |
「localhost:8080/view/test」にアクセスしてください。


ページ遷移は正常にできているようですね。save(保存機能)ボタンについては、この後に実装します。
保存とリダイレクト機能の実装
次は、ページの保存と編集ページにリダイレクトする機能を作成します。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
main.go package main import ( "html/template" "io/ioutil" "log" "net/http" ) type Page struct { Title string Body []byte } // Render template with Page parameter func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { t, _ := template.ParseFiles(tmpl + ".html") t.Execute(w, p) } // Save Page Info to test.txt func (p *Page) save() error { filename := p.Title + ".txt" return ioutil.WriteFile(filename, p.Body, 0600) } func loadPage(title string) (*Page, error) { filename := title + ".txt" body, err := ioutil.ReadFile(filename) if err != nil { return nil, err } return &Page{Title: title, Body: body}, nil } func viewHandler(w http.ResponseWriter, r *http.Request) { // /view/test -> test title := r.URL.Path[len("/view/"):] p, err := loadPage(title) // fix: add err if err != nil { http.Redirect(w, r, "/edit/"+title, http.StatusFound) return } // view.html renderTemplate(w, "view", p) } func editHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/edit/"):] p, err := loadPage(title) if err != nil { p = &Page{Title: title} } renderTemplate(w, "edit", p) } // new func saveHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/save/"):] body := r.FormValue("body") p := &Page{Title: title, Body: []byte(body)} err := p.save() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) } func main() { http.HandleFunc("/view/", viewHandler) http.HandleFunc("/edit/", editHandler) http.HandleFunc("/save/", saveHandler) log.Fatal(http.ListenAndServe("localhost:8080", nil)) } |
main.goを実行してみましょう。
1 |
go run main.go |
動作確認をしましょう。編集ページで「EDIT」を入力してからsaveボタンを押下します。


問題なく更新されたようですね。
おまけ:キャッシュ化
テンプレートの情報をキャッシュして、処理のスピードアップをしましょう。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
main.go package main import ( "html/template" "io/ioutil" "log" "net/http" "regexp" // new ) type Page struct { Title string Body []byte } // new var templates = template.Must(template.ParseFiles("edit.html", "view.html")) // Render template with Page parameter func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { // fix err := templates.ExecuteTemplate(w, tmpl+".html", p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } // Save Page Info to test.txt func (p *Page) save() error { filename := p.Title + ".txt" return ioutil.WriteFile(filename, p.Body, 0600) } func loadPage(title string) (*Page, error) { filename := title + ".txt" body, err := ioutil.ReadFile(filename) if err != nil { return nil, err } return &Page{Title: title, Body: body}, nil } func viewHandler(w http.ResponseWriter, r *http.Request, title string) { // fix: Add title // delete: title p, err := loadPage(title) // fix: add err if err != nil { http.Redirect(w, r, "/edit/"+title, http.StatusFound) return } // view.html renderTemplate(w, "view", p) } func editHandler(w http.ResponseWriter, r *http.Request, title string) { // fix: Add title // delete: title p, err := loadPage(title) if err != nil { p = &Page{Title: title} } renderTemplate(w, "edit", p) } func saveHandler(w http.ResponseWriter, r *http.Request, title string) { // fix: Add Title // delete: title body := r.FormValue("body") p := &Page{Title: title, Body: []byte(body)} err := p.save() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) } var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$") func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { m := validPath.FindStringSubmatch(r.URL.Path) if m == nil { http.NotFound(w, r) return } fn(w, r, m[2]) } } func main() { // new http.HandleFunc("/view/", makeHandler(viewHandler)) http.HandleFunc("/edit/", makeHandler(editHandler)) http.HandleFunc("/save/", makeHandler(saveHandler)) log.Fatal(http.ListenAndServe("localhost:8080", nil)) } |
おわりに
いかがだったでしょうか。
Go言語では、少ないコードで簡単にアプリケーションサーバーを立てることができます。
私にとって、Go言語は、Pythonの次に好きな言語になりつつありますね^^
それでは、また!
コメントを残す
コメントを投稿するにはログインしてください。