🚀

GoでWebサーバーを構築

2022/09/15に公開

Goで、フレームワークを使わなくてもサーバーを構築してアプリ開発することができます。
Goの機能のみを使って、簡易的なアプリを作ってみたいと思います。

プロジェクト作成

$ mkdir list-app
$ touch server.go

net/httpパッケージ

HTTPを扱うパッケージで、HTTPクライアントとHTTPサーバーを実装するために必要な機能が提供されています。HTTPサーバー用の機能を使用することで、簡単にWebサーバーを立てることができます。

・http.HandleFunc

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
指定したパターンとハンドラー関数をDefaultServeMuxに登録します。

・http.ListenAndServe

func ListenAndServe(addr string, handler Handler) error
TCPネットワークアドレスでリッスン(第一引数)、ハンドラは通常はnil(第二引数)で、その場合はDefaultServeMuxが使用されます。

server.go
package main

import (
	"fmt"
	"log"
	"net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
	hello := []byte("Hello World!!!")
	_, err := w.Write(hello)
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	http.HandleFunc("/hello", helloHandler)
	fmt.Println("Server Start Up........")
	log.Fatal(http.ListenAndServe("localhost:8080", nil))
}
$ go run server.go
Server Start Up........

Webサーバーが立ち上がったのでlocalhost:8080/helloにアクセスします。

リストアプリの作成

好きな言語を追加していくだけの、簡単なアプリケーションを作成したいと思います。
HTMLファイルを返すことができるようにします。

$ touch view.html

view.htmlにコードを記述します。

view.html
<h1>読んだ書籍</h1>
<div>
    <p>・スッキリわかるSQL入門</p>
    <P>・達人に学ぶSQL徹底指南書</P>
    <P>・達人に学ぶDB設計 徹底指南書</P>
</div>

view.htmlをサーバーのレスポンスとして返すようにします。
viewHandlerを作成し、template.ParseFilesで引数に渡したhtmlをパースします。
Executeメソッドを使用。第一引数に出力先、第二引数にテンプレートに埋め込みたいデータを渡します。
今回は、渡すデータがないのでnilとします。

server.go
package main

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

func viewHandler(w http.ResponseWriter, r *http.Request) {
	html, err := template.ParseFiles("view.html")
	if err != nil {
		log.Fatal(err)
	}
	if err := html.Execute(w, nil); err != nil {
		log.Fatal(err)
	}
}

func main() {
	http.HandleFunc("/view", viewHandler)
	fmt.Println("Server Start Up........")
	log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

サーバーを起動します。

 $ go run server.go
Server Start Up........

Webサーバーが立ち上がったのでlocalhost:8080/viewにアクセスします。

データをテンプレートに埋め込む

読書した内容を記載したファイルを作成し、その中身をテンプレートに埋め込むように、コードを追加していきたいと思います。

$ touch reading.txt

ファイルの中身を読み取る

reading.txtの中身を読み取る関数を作成し、読書内容が記載されたファイルを読み取りたいと思います。

server.go
func fileRead(fileName string) []string {
	var bookList []string
	file, err := os.Open(fileName)
	if os.IsNotExist(err) {
		return nil
	}
	defer file.Close()
	scaner := bufio.NewScanner(file)
	for scaner.Scan() {
		bookList = append(bookList, scaner.Text())
	}
	return bookList
}

func viewHandler(w http.ResponseWriter, r *http.Request) {
	bookList := fileRead("reading.txt")
	fmt.Println(bookList)
	html, err := template.ParseFiles("view.html")
	if err != nil {
		log.Fatal(err)
	}
	if err := html.Execute(w, nil); err != nil {
		log.Fatal(err)
	}
}

ファイルの中身を保持できるようにstructを追加します。

server.go
type BookList struct {
	Books []string
}

func New(books []string) *BookList {
	return &BookList{Books: books}
}

viewHandlerを変更します。

server.go
func viewHandler(w http.ResponseWriter, r *http.Request) {
	bookList := fileRead("reading.txt")
	html, err := template.ParseFiles("view.html")
	if err != nil {
		log.Fatal(err)
	}
	getBooks := New(bookList)
	if err := html.Execute(w, getBooks); err != nil {
		log.Fatal(err)
	}
}

reading.txtを出力できるようになったので、これをhtmlで表示できるように変更します。

view.html
<h1>読んだ書籍</h1>
<div>
    {{ range .Books }}
    <p>{{.}}</p>
    {{ end }}
</div>

サーバーを起動します。

 $ go run server.go
Server Start Up........

Webサーバーが立ち上がったのでlocalhost:8080/viewにアクセスします。

違いが分かるよう、reading.txtに読んだ本を追加してます。
しっかり表示できてますね。

フォームを追加

読んだ本が増えるたびに追加していきたいので、フォームを作成して、フォームからのデータをreading.txtに書き込むように変更したい思います。
view.htmlにフォームを追加します。

view.html
<h1>読んだ書籍</h1>

<form action="/view/create" method="post">
    <div><input type="text" name="value"></div>
    <div><input type="submit" value="追加"></div>
</form>
<div>
    {{ range .Books }}
    <p>{{.}}</p>
    {{ end }}
</div>
server.go
package main

import (
	"bufio"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
)

type BookList struct {
	Books []string
}

func New(books []string) *BookList {
	return &BookList{Books: books}
}

func fileRead(fileName string) []string {
	var bookList []string
	file, err := os.Open(fileName)
	if os.IsNotExist(err) {
		return nil
	}
	defer file.Close()
	scaner := bufio.NewScanner(file)
	for scaner.Scan() {
		bookList = append(bookList, scaner.Text())
	}
	return bookList
}

func viewHandler(w http.ResponseWriter, r *http.Request) {
	bookList := fileRead("reading.txt")
	html, err := template.ParseFiles("view.html")
	if err != nil {
		log.Fatal(err)
	}
	getBooks := New(bookList)
	if err := html.Execute(w, getBooks); err != nil {
		log.Fatal(err)
	}
}

func createHandler(w http.ResponseWriter, r *http.Request) {
	formValue := r.FormValue("value")
	file, err := os.OpenFile("reading.txt", os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(0600))
	defer file.Close()
	if err != nil {
		log.Fatal(err)
	}
	_, err = fmt.Fprintln(file, formValue)
	if err != nil {
		log.Fatal(err)
	}
	http.Redirect(w, r, "/view", http.StatusFound)
}

func main() {
	http.HandleFunc("/view", viewHandler)
	http.HandleFunc("/view/create", createHandler)
	fmt.Println("Server Start Up........")
	log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

サーバーを起動します。

 $ go run server.go
Server Start Up........

Webサーバーが立ち上がったのでlocalhost:8080/viewにアクセスします。
フォームから読んだ本を追加してみます。

追加できてますね。
このようにして、Goではフレームワークを使わなくてアプリケーション開発行える機能がたくさん備わってて便利ですね。

Discussion