GoでWebサーバーを構築

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が使用されます。

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にコードを記述します。

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

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

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の中身を読み取る関数を作成し、読書内容が記載されたファイルを読み取りたいと思います。

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を追加します。

type BookList struct {
    Books []string
}

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

viewHandlerを変更します。

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で表示できるように変更します。

<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にフォームを追加します。

<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>
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ではフレームワークを使わなくてアプリケーション開発行える機能がたくさん備わってて便利ですね。