この記事の概要
この記事の概要
- Goプロジェクトの作成
-
net/http
を使ってlocalhostに画面をHelloWorldを出力する - Home画面とAbout画面の2画面を作成する
- Go標準パッケージの
html/template
を使って画面描画する -
main.go
の関数を別ファイルに移動して呼び出す - 切り出したファイルをフォルダ格納してmain.goを呼び出す
- さいごに
Goのプロジェクトを作成する。
$ mkdir sample_go
$ cd sample_go
$ go mod init sample_go
modファイルが作成される。
module sample_go
go 1.21.0
Hello World を出力する
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprintf(w, "Hello, world!")
if err != nil {
fmt.Println(err)
}
})
_ = http.ListenAndServe(":8080", nil)
}
$ go run main.go
ブラウザで次のURLを入力する
http://localhost:8080/
Hello, world!がブラウザに出力されました。
ポイント
- "net/http"の標準ライブラリをインポートしている
- http.HandleFunc関数を使って、Webブラウザに描画している
HandleFuncの解説はこちらを読むと理解が深まります。
【Go】net/httpパッケージを読んでhttp.HandleFuncが実行される仕組み
HomeページとAboutページを追加する
次に、HomeページとAboutページを追加していきます。
package main
import (
"fmt"
"net/http"
)
const portNumber = ":8080"
// HomeページをHandlFunc関数の第二引数で呼出
func Home(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "ホームページです。")
}
// AboutページをHandlFunc関数の第二引数で呼出
func About(w http.ResponseWriter, r *http.Request) {
sum := addValues(2, 2)
_, _ = fmt.Fprintf(w, fmt.Sprintf("Aboutページで関数実行 2 + 2 は %d\n", sum))
fmt.Fprintf(w, "アバウトページです。")
}
// x + y の結果を返却する関数
func addValues(x, y int) int {
return x + y
}
func main() {
http.HandleFunc("/", Home)
http.HandleFunc("/about", About)
fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))
_ = http.ListenAndServe(portNumber, nil)
}
パスごとに表示内容を変更出来ています。
ブラウザで次のURLを入力する
http://localhost:8080/
http://localhost:8080/about
ポイント
- Home関数とAbout関数を記述した。
- HandlFunc関数の引数にHome関数とAbout関数を渡した
templateを作成して呼び出す
Goの標準パッケージの"html/template"
パッケージをを使用してtemplateを使いながら画面描画をします。
templates
フォルダを作成して、2つファイルを用意します。
$ mkdir templates
$ touch templates/home.page.tmpl
$ touch templates/about.page.tmpl
このようなディレクトリ構成になります。
.
├── go.mod
├── main.go
├── readme.md
└── templates
├── about.page.tmpl
└── home.page.tmpl
テンプレートは次のように記述していきます。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>ホームページです。</h1>
<p>テキストです。</p>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>アバウトページです。</h1>
</body>
</html>
main.goを編集していきます。
package main
import (
"fmt"
"html/template"
"net/http"
)
const portNumber = ":8080"
// HomeページをHandlFunc関数の第二引数で呼出
func Home(w http.ResponseWriter, r *http.Request) {
renderTemplate(w, "home.page.tmpl")
}
// AboutページをHandlFunc関数の第二引数で呼出
func About(w http.ResponseWriter, r *http.Request) {
renderTemplate(w, "about.page.tmpl")
}
func renderTemplate(w http.ResponseWriter, tmpl string) {
parsedTemplate, _ := template.ParseFiles("./templates/" + tmpl)
err := parsedTemplate.Execute(w, nil)
if err != nil {
fmt.Println("error parsing template:", err)
}
}
// main is the main function
func main() {
http.HandleFunc("/", Home)
http.HandleFunc("/about", About)
fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))
_ = http.ListenAndServe(portNumber, nil)
}
ポイント
- "html/template"をGoの標準からインポートして使っている。
- template.ParseFilesでパスを解析している
- template.Executeで解析したテンプレートを実行している。
handler.go,render.goを作成して関数を切り出す
Home関数、About関数、renderTemplate関数をmain.goから別ファイルに書き出していきます。
package main
import (
"fmt"
"net/http"
)
const portNumber = ":8080"
func main() {
http.HandleFunc("/", Home)
http.HandleFunc("/about", About)
fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))
_ = http.ListenAndServe(portNumber, nil)
}
handlers.goを作成して、main.goからの記述をペーストしていきます。
package main
import (
"net/http"
)
// Home is the handler for the home page
func Home(w http.ResponseWriter, r *http.Request) {
renderTemplate(w, "home.page.tmpl")
}
// About is the handler for the about page
func About(w http.ResponseWriter, r *http.Request) {
renderTemplate(w, "about.page.tmpl")
}
render.goを作成して、main.goから記述をペーストしていきます。
package main
import (
"fmt"
"html/template"
"net/http"
)
func renderTemplate(w http.ResponseWriter, tmpl string) {
parsedTemplate, _ := template.ParseFiles("./templates/" + tmpl)
err := parsedTemplate.Execute(w, nil)
if err != nil {
fmt.Println("error parsing template:", err)
}
}
この状態でmain.goを実行すると下記のエラーが発生します。
$ go run main.go
# command-line-arguments
./main.go:12:23: undefined: Home
./main.go:13:28: undefined: About
ファイルを切り出したことによって、Home関数とAbout関数が定義されていないという扱いになってしまいました。
これを回避するために、handlers.goとrender.goも同時に実行しなくてはいけません。
同時に実行するために、全てのファイル名を記述する方法もあります。
$ go run main.go handlers.go render.go
しかし、それでは面倒臭いので、次のような呼び出し方もできます。
$ go run *.go
このようにファイルを切り出して実行してローカルホストにアクセスしてみても、問題なく画面を描画できていることがわかります。
# 下記にアクセスする
http://localhost:8080/
フォルダを作成して、切り出したファイルを格納していく
先ほどの切り出したファイルをフォルダに移動していきます。
元々のファイル構成がこちらです。
.
├── go.mod
├── handlers.go
├── main.go
├── readme.md
├── render.go
└── templates
├── about.page.tmpl
└── home.page.tmpl
下のような構成に変更します。
.
├── cmd
│ └── web
│ └── main.go
├── go.mod
├── pkg
│ ├── handlers
│ │ └── handlers.go
│ └── render
│ └── render.go
├── readme.md
└── templates
├── about.page.tmpl
└── home.page.tmpl
cmd/web/main.goの編集
変わった場所は、HandleFuncの第二引数が変わっています。
元々Homeという関数を渡していましたが、handlers.Homeという渡し方に変わっています。
前回は同じ階層にmainパッケージを定義してHome関数を使っていましたが、ディレクトリ構成を変えたことで、importしてから使うように変更しています。
package main
import (
"fmt"
"net/http"
"sample_go/pkg/handlers"
)
const portNumber = ":8080"
// main is the main function
func main() {
http.HandleFunc("/", handlers.Home)
http.HandleFunc("/about", handlers.About)
fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))
_ = http.ListenAndServe(portNumber, nil)
}
pkg/handlers/handlers.goの編集
ディレクトリ構成を変えて、pkg/handlersという箇所にhandlers.goを移動しました。
handlers.goの中身で変わっているのは、package名をpackage main
からpackage handlers
に変更しているところです。
さらに、処理の中でRenderTemplate
関数の呼び出し方が変わっています。
元々はmainパッケージの中に定義されてた関数を呼び出していましたが、pkg/render
に移動したので、インポートしてから使っています。
package handlers
import (
"net/http"
"sample_go/pkg/render"
)
// Homeページ
func Home(w http.ResponseWriter, r *http.Request) {
render.RenderTemplate(w, "home.page.tmpl")
}
// Aboutページ
func About(w http.ResponseWriter, r *http.Request) {
render.RenderTemplate(w, "about.page.tmpl")
}
pkg/render/render.goの編集
こちらのrender.goで変わった箇所は、2箇所あります。
- package名が
main
からrender
に変わっている点。 -
renderTemplate
の関数名がRenderTemplate
に変わっている点です。
関数名は、頭文字が小文字の場合はパッケージ内でしか使えない関数になります。
頭文字が大文字の場合は、別のパッケージからも呼び出せる関数になります。
今回のRenderTemplate
関数は、handlers.go
から呼び出されています。
package render
import (
"fmt"
"html/template"
"net/http"
)
// RenderTemplate renders a template
func RenderTemplate(w http.ResponseWriter, tmpl string) {
parsedTemplate, _ := template.ParseFiles("./templates/" + tmpl)
err := parsedTemplate.Execute(w, nil)
if err != nil {
fmt.Println("error parsing template:", err)
}
}
プログラムを実行してみる
main.goを実行してみます。
ディレクトリ構成が変わったとき、どのような呼び出し方をすれば良いでしょうか。
試しに、main.go
の場所まで行ってからプログラムを実行してみます。
$ cd cmd/web
$ go run main.go
このプログラムは失敗します。実行するカレントディレクトリの場所にgo.modがないとプログラムはうまく動きません。
では、go.modの場所まで戻って、main.go
を実行してみます。
$ cd ../../
$ go run cmd/web/main.go
これで描画が出来ています。
# 下記にアクセスする
http://localhost:8080/
templateの共通化をする
home.page.tmpl
とabout.page.tmpl
には共通で使われている記述があります。
そのため、その共通部分をbase.layout.tmpl
として切り出し、共通化する処理をしていきます。
まずは、base.layout.tmplを作成します。
{{define "base"}}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
{{block "css" .}}
{{end}}
</head>
<body>
{{block "content" .}}
{{end}}
{{block "js" .}}
{{end}}
</body>
</html>
{{end}}
{{define "base"}}
から{{end}}
までの部分で共通化したtemplateになります。
そして、中身を見ると、{{block "css" .}}{{end}}
や{{block "content" .}}{{end}}
のように記述して、共通部分を切り出して実装できようにしています。
今回は、{{block "content" .}}{{end}}
の部分にhome.page.tmpl
とabout.page.tmpl
を埋め込んでみます。
{{template "base" .}}
{{define "content"}}
<div class="container">
<div class="row">
<div class="col">
<h1>This is the home page</h1>
<p>This is some text</p>
</div>
</div>
</div>
{{end}}
{{template "base" .}}
{{define "content"}}
<div class="container">
<div class="row">
<div class="col">
<h1>This is the about page</h1>
</div>
</div>
</div>
{{end}}
先ほどbase.layout.tmpl
で定義していた{{define "base"}}をhome.page.tmpl
では、
{{template "base" .}}
として呼び出して使っています。
その中身で{{define "content"}}
から{{end}}
までをcontent部分に埋め込んでいます。
このように記述することで、共通部分を切り出して使うことが出来ます。
さいごに
今回の記事はここまでです。
最後まで読んでいただき、ありがとうございます。