Go言語によるWebサーバー作成入門 ーテンプレートー

公開:2020/10/21
更新:2020/10/21
5 min読了の目安(約4700字TECH技術記事

(作成2020.10.21)

まずはテキスト出力

main.goという名のファイルを作成し、次の文をペーストしてください。

main.go
package main

import "fmt"

func main() {
	message := "Hello, World!"

	tpl := `
	<!DOCTYPE html>
	<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Document</title>
	</head>
	<body>
		<h1>` + message + `</h1>
	</body>
	</html>
	`
	fmt.Println(tpl)
}

これを実行して、次のように出力されれば正解です。

ただ、tplに代入した文字列を表示しただけです。

htmlファイルを出力する

次の文を実行すると、index.htmlファイルが作成されます。

main.go
package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	message := "Hello, World!"
	str := fmt.Sprint(`
	<!DOCTYPE html>
	<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Document</title>
	</head>
	<body>
		<h1>` + message + `</h1>
	</body>
	</html>
	`)

	nf, err := os.Create("index.html")
	if err != nil {
		log.Fatal("error creating file", err)
	}
	defer nf.Close()

	io.Copy(nf, strings.NewReader(str))
}

os.Createで空ファイルを作成します。最終的にクローズが必要なのでdeferで命令を送っておきます。io.Copyをつかって内容をコピーしますが、コピー元はstrings.NewReaderを使ってインターフェイスにしてつなぎます。この辺はじきに慣れてくると思うので、悩まないでこういうもんだとこのまま覚えましょう。
さぁ、これでWebページが完成しました!、、とは言っても、コードをべた書きしただけなので、これなら最初からHTMLファイルを作っとけよ!という話で、全然ありがたみがないですね。なので次はテンプレートを作成し、後から文字を追加できるようにします。

入力する文字を後から指示する

次のコードを「go run main.go Hello!」と実行してみてください。

main.go
package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	message := os.Args[1]
	fmt.Println(os.Args[0])
	fmt.Println(os.Args[1])
	str := fmt.Sprint(`
	<!DOCTYPE html>
	<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Document</title>
	</head>
	<body>
			<h1>` + message + `</h1>
	</body>
	</html>
	`)

	nf, err := os.Create("index.html")
	if err != nil {
		log.Fatal("error creating file", err)
	}
	defer nf.Close()

	io.Copy(nf, strings.NewReader(str))
}

/* ターミナルでは次の文で実行して:
go run main.go Hello!
*/

まぁ確かに、、ね。という声が聞こえてきそう。。。一応、引数の使い方でした。これもひとつのテンプレート。

外部ファイルをテンプレートとして呼び出す

テンプレートファイルとしてindex.gohtmlを作成します。

index.gohtml
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>Hello, World!</h1>
</body>
</html>

そしてGoファイルを作成します。

main.go
package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	tpl, err := template.ParseFiles("tpl.gohtml")
	if err != nil {
		log.Fatalln(err)
	}

	err = tpl.Execute(os.Stdout, nil)
	if err != nil {
		log.Fatalln(err)
	}
}

これを実行すると、ターミナル上にテンプレートファイルの中身が出力されます。templateパッケージを読み込んでParseFilesでテンプレートファイルをtplに格納して、tpl.Executeで標準出力(画面)に出力しています。でもやっぱりこれでは面白くない、ちゃんとファイルを作成しましょう。

テンプレートを使ってファイルを出力する

main.go
package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	tpl, err := template.ParseFiles("tpl.gohtml")
	if err != nil {
		log.Fatalln(err)
	}

	nf, err := os.Create("index.html")
	if err != nil {
		log.Fatalln("error creating file", err)
	}
	defer nf.Close()

	err = tpl.Execute(nf, nil)
	if err != nil {
		log.Fatalln(err)
	}
}

これでテンプレートファイルの中身がindex.htmlとして出力されます。ファイルのクローズを忘れずに。前の項で使ったtpl.Executeの第1引数に空ファイルを指定して書き込みます。
しかしまだ、あまりありがたみがないコードですね。

複数のテンプレートを扱う ParseFiles

まず、テンプレートを3つほど作成します。

one.txt
いーち、
two.txt
にー、
three.txt
さんっ、だー!

そしてコードはこちら。

main.go
package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	tpl, _ := template.ParseFiles("one.txt")
	_ = tpl.Execute(os.Stdout, nil)
	
	tpl, _ = tpl.ParseFiles("two.txt", "three.txt")
	_ = tpl.ExecuteTemplate(os.Stdout, "two.txt", nil)
	_ = tpl.ExecuteTemplate(os.Stdout, "three.txt", nil)
	_ = tpl.ExecuteTemplate(os.Stdout, "one.txt", nil)
	_ = tpl.Execute(os.Stdout, nil)
}

エラーハンドリングはコードが長くなって読みにくくなるので省略しました。ParseFilesでテンプレートを見込むのはこれまでと同じ。2つのテンプレートを同時に読み込むこともできます。連続してParseFilesを呼び出すとテンプレートは追加されます。そして、tpl.ExecuteTemplateでテンプレートを呼び出します。第2引数で呼び出すテンプレートを指定しています。ExecuteはParseFilesに格納したテンプレートのうちの一番最初が呼び出されます。

複数のテンプレートを扱う ParseGlob

テンプレートを1ファイルずつ指定するのは面倒ですよね。*が使えるParseGlobというのがあります。ここでは先ほどのテンプレートファイルをtemplateディレクトリに移動して実行します。

main.go
package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	tpl, _ := template.ParseGlob("templates/*")
	_ = tpl.Execute(os.Stdout, nil)
	_ = tpl.ExecuteTemplate(os.Stdout, "two.txt", nil)
	_ = tpl.ExecuteTemplate(os.Stdout, "three.txt", nil)
}

コードをちょっとだけカッコつける

main.go
package main

import (
	"log"
	"os"
	"text/template"
)

var tpl *template.Template

func init() {
	tpl = template.Must(template.ParseGlob("templates/*"))
}

func main() {
	_ = tpl.Execute(os.Stdout, nil)
	_ = tpl.ExecuteTemplate(os.Stdout, "two.txt", nil)
	_ = tpl.ExecuteTemplate(os.Stdout, "three.txt", nil)
}

func init()でmain()の先にテンプレートを格納して少しカッコつけてます。また、ParseGlobにMustを呼び出しています。こうするとエラーハンドリングを中で処理してくれるのでコードを書かなくて済みます。

次回に続く