Go言語によるWebサーバー作成入門 ーブログ作成ー

8 min読了の目安(約8000字TECH技術記事

(作成2020.11.2)

(実践)ブログを作成する

これまで紹介してきたことを使ってブログを作成します。テンプレートをindex.gohtmlとして作成し、main.goにて実行しサーバーを立ち上げます。テンプレートをParseFilesで読み込んでExecuteで出力します。ルートにHandleFuncを割り当ててindex関数を呼び出します。favicon.icoはタブについている小さなイメージでしたね。これはNotFoundHandlerで無視します。サーバーの立ち上げはListenAndServeです。DefaultServeMuxを使うので、第2引数にはnilを指定します。もし、この説明でわからないところがあれば、過去の記事を見返してください。

main.go
package main

import (
	"html/template"
	"net/http"
)

var tpl *template.Template

func init() {
	tpl = template.Must(template.ParseFiles("index.gohtml"))
}

func main() {
	http.HandleFunc("/", index)
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func index(w http.ResponseWriter, req *http.Request) {
	tpl.Execute(w, nil)
}
index.gohtml
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>INDEX</title>
</head>
<body>

Hello from index

</body>
</html>

Cookieを作成する

getCookie関数を用意してCookieを作成します。作成はSetCookieでできるのでしたね。Cookieはstructなのでポインタを使って渡します。ユニークIDはサードパーティパッケージgithub.com/google/uuid を使うのでしたね。github.com/satori/go.uuid を使ってもよいです。作成したIDをテンプレートに渡しています。localhost:8080にアクセスしたら「検証-Application」からCookieができていることを確認しましょう。

main.go
package main

import (
	"html/template"
	"net/http"

	"github.com/google/uuid"
)

var tpl *template.Template

func init() {
	tpl = template.Must(template.ParseFiles("index.gohtml"))
}

func main() {
	http.HandleFunc("/", index)
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func index(w http.ResponseWriter, req *http.Request) {
	c := getCookie(w, req) // Cookie構造体のポインタを取得する
	tpl.Execute(w, c.Value) // Valueをテンプレートに渡す
}

// cookieを取得する関数を定義
func getCookie(w http.ResponseWriter, req *http.Request) *http.Cookie {
	c, err := req.Cookie("session")
	// Cookieがなければ作る
	if err != nil {
		sID := uuid.New()
		c = &http.Cookie{
			Name:  "session",
			Value: sID.String(),
		}
		http.SetCookie(w, c)
	}
	return c
}
index.gohtml
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>INDEX</title>
</head>
<body>

<h1>Cookie Value: {{.}}</h1>

</body>
</html>

画像を並べる

画像を表示します。とりあえず、画像を用意するのはおいといてテンプレートに複数の画像ファイルをうまく渡せるかどうか試してみます。複数の項目を渡すときには{{range .}}{{end}}を使うのでしたね。Cookieにも画像ファイル名を渡してみます。

main.go
package main

import (
	"html/template"
	"net/http"
	"strings"

	"github.com/google/uuid"
)

var tpl *template.Template

func init() {
	tpl = template.Must(template.ParseFiles("index.gohtml"))
}

func main() {
	http.HandleFunc("/", index)
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func index(w http.ResponseWriter, req *http.Request) {
	c := getCookie(w, req)
	c = appendValue(w, c) // ファイル名をつなぐ
	xs := strings.Split(c.Value, "|") // スライス[]string
	tpl.Execute(w, xs)
}

func getCookie(w http.ResponseWriter, req *http.Request) *http.Cookie {
	c, err := req.Cookie("session")
	if err != nil {
		sID := uuid.New()
		c = &http.Cookie{
			Name:  "session",
			Value: sID.String(),
		}
		http.SetCookie(w, c)
	}
	return c
}

func appendValue(w http.ResponseWriter, c *http.Cookie) *http.Cookie {
	// TODO: files
	p1 := "dog.jpg"
	p2 := "cat.jpg"
	p3 := "SeaOtter.jpg"

	// append
	s := c.Value
	if !strings.Contains(s, p1) {
		s += "|" + p1
	}
	if !strings.Contains(s, p2) {
		s += "|" + p2
	}
	if !strings.Contains(s, p3) {
		s += "|" + p3
	}
	c.Value = s
	http.SetCookie(w, c) // Cookieにセットしておく
	return c
}
index.gohtml
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>INDEX</title>
</head>
<body>

<h1>Cookie Values:</h1>
{{range .}}
<h2>{{.}}</h2>
{{end}}
</body>
</html>

ファイルをアップロードする

formを使って画像ファイルをアップロードします。ファイル名はハッシュsha256を使って作ります。ファイルアップロードは、os.Createで空のファイルを作成してコピーすることで実行します。

main.go
package main

import (
	"crypto/sha256"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/google/uuid"
)

var tpl *template.Template

func init() {
	tpl = template.Must(template.ParseFiles("index.gohtml"))
}

func main() {
	http.HandleFunc("/", index)
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func index(w http.ResponseWriter, req *http.Request) {
	c := getCookie(w, req)
	
	// form data recieved
	if req.Method == http.MethodPost {
		mf, fh, err := req.FormFile("nf")
		if err != nil {
			fmt.Println(err)
		}
		defer mf.Close()
		
		ext := strings.Split(fh.Filename, ".")[1] // 拡張子
		h := sha256.New() // ファイルを保存するときの名前を作る
		io.Copy(h, mf)
		fname := fmt.Sprintf("%x", h.Sum(nil)) + "." + ext
		
		wd, err := os.Getwd() // get local path
		if err != nil {
			fmt.Println(err)
		}
		path := filepath.Join(wd, "pics", fname)
		nf, err := os.Create(path) // まず空ファイルを作る
		if err != nil {
			fmt.Println(err)
		}
		defer nf.Close()
		
		mf.Seek(0, 0)
		io.Copy(nf, mf) // アップロードファイルのデータを空ファイルにコピー
		
		c = appendValue(w, c, fname) // ファイル名作成は関数の外に出した
	}
	xs := strings.Split(c.Value, "|")
	tpl.Execute(w, xs)
}

func getCookie(w http.ResponseWriter, req *http.Request) *http.Cookie {
	c, err := req.Cookie("session")
	if err != nil {
		sID := uuid.New()
		c = &http.Cookie{
			Name:  "session",
			Value: sID.String(),
		}
		http.SetCookie(w, c)
	}
	return c
}

func appendValue(w http.ResponseWriter, c *http.Cookie, fname string) *http.Cookie {
	s := c.Value
	if !strings.Contains(s, fname) {
		s += "|" + fname
	}
	c.Value = s
	http.SetCookie(w, c)
	return c
}
index.gohtml
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>INDEX</title>
</head>
<body>

<h1>Cookie Values:</h1>
{{range .}}
<h2>{{.}}</h2>
{{end}}

<form method="POST" enctype="multipart/form-data">
    <input type="file" name="nf">
    <input type="submit">
</form>

</body>
</html>

画像を表示する

ここまでくればあとはhtmlにimgタグをつけて表示すれば完成です。ファイル名をクッキーから受け取るところにSplitを使ってバラしています。

main.go
package main

import (
	"crypto/sha256"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/google/uuid"
)

var tpl *template.Template

func init() {
	tpl = template.Must(template.ParseFiles("index.gohtml"))
}

func main() {
	http.HandleFunc("/", index)
	http.Handle("/pics/", http.StripPrefix("/pics", http.FileServer(http.Dir("./pics"))))
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func index(w http.ResponseWriter, req *http.Request) {
	c := getCookie(w, req)
	if req.Method == http.MethodPost {
		mf, fh, err := req.FormFile("nf")
		if err != nil {
			fmt.Println(err)
		}
		defer mf.Close()

		ext := strings.Split(fh.Filename, ".")[1]
		h := sha256.New()
		io.Copy(h, mf)
		fname := fmt.Sprintf("%x", h.Sum(nil)) + "." + ext

		wd, err := os.Getwd()
		if err != nil {
			fmt.Println(err)
		}
		path := filepath.Join(wd, "pics", fname)
		nf, err := os.Create(path)
		if err != nil {
			fmt.Println(err)
		}
		defer nf.Close()

		mf.Seek(0, 0)
		io.Copy(nf, mf)

		c = appendValue(w, c, fname)
	}
	xs := strings.Split(c.Value, "|")
	tpl.Execute(w, xs[1:]) // ファイル名だけを取り出す
}

func getCookie(w http.ResponseWriter, req *http.Request) *http.Cookie {
	c, err := req.Cookie("session")
	if err != nil {
		sID := uuid.New()
		c = &http.Cookie{
			Name:  "session",
			Value: sID.String(),
		}
		http.SetCookie(w, c)
	}
	return c
}

func appendValue(w http.ResponseWriter, c *http.Cookie, fname string) *http.Cookie {
	s := c.Value
	if !strings.Contains(s, fname) {
		s += "|" + fname
	}
	c.Value = s
	http.SetCookie(w, c)
	return c
}
index.gohtml
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>INDEX</title>
</head>
<body>

<h1>Uploaded Pictures:</h1>
{{range .}}
<img src="/pics/{{.}}" width="300px">
{{end}}

<form method="post" enctype="multipart/form-data">
    <input type="file" name="nf">
    <input type="submit">
</form>

</body>
</html>

画像ファイルを2つアップロードしてみました。

これまでの記事でこれだけのことができます。わからないところがあれば、過去の記事を探して確認してみてください。