かけ足で学ぶ Golang その2 〜HTTPサーバでHelloWorld〜


f:id:Naotsugu:20170417231928p:plain

前回、

etc9.hatenablog.com

の続きです。

Hello, World on HTTP

Go には簡易な Http サーバのパッケージがバンドルされています。

net/http をインポートするだけで簡単に Http サーバを動かすことができます。

package main

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

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World")
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

実行して http://localhost:8080/ にアクセスします。

f:id:Naotsugu:20170412221145p:plain

簡単にHttpサーバを作れましたね。 では少し中身を見ていきましょう。

http.HandleFunc("/", handler) で URLパターンとそれに応答する関数を指定しています。

この関数は以下のように定義されています。

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

このように Go では関数をオブジェクトのように扱えます。

サーバの起動は http.ListenAndServe(":8080", nil) としてリスンを開始します。

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

Go では変数定義を [変数名] [変数の型] として定義します。addr stringaddr という名前の string 型の変数を意味します。

では関数の後ろにある error とは何でしょうか。これは関数の戻り値の型を意味します。

error は builtin パッケージに以下のようなインターフェースとして定義されています。

package builtin
type error interface {
    Error() string
}

Error() という string を返すメソッドを持った、何らかのインスタンス。となります。


では、私達の作った handler関数を見てみましょう。

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World")
}

wr という引数を取り、それぞれ http.ResponseWriter という型と *http.Request という型となっています。

http.ResponseWriter は先程の error と同じようにインターフェースとして定義されています。

package http

type ResponseWriter interface {
    // ...
}

http.Request は同じ http パッケージに struct型 として定義されています。

type Request struct {
    Method string
    // ...
}

C言語の struct と同じように、他の型のコンテナとなります。メソッドの無いクラスですね(Go では struct を使った継承構造も定義できます)。

RequestMethod の先頭が大文字なのは、struct やその属性をパッケージ外部に公開することを意味します。 小文字とした場合はパッケージの使用者側からは不可視になります。


話を戻して、*http.Request の先頭にあるアスタリスクはC言語と同じくポインタ型を意味します。

Goでは引数は全て値渡しで引数が全てコピーされます。 呼び出し元で関数内で行った副作用の結果が必要な場合にはポインタでやり取りする必要があります。

REST サーバ

先程の例を少し変えて、JSON を返すサーバを作ってみましょう。 encoding/json を使うことで JSON も簡単に扱うことができます。

最初に JSON の元となる struct型を定義します。

type User struct {
    Id    int     `json:"id"`
    Name  string  `json:"name"`
    Email string  `json:"e-mail"`
}

先程見た struct型 と異なり、型の後に何か付いていますね。 これはタグといい、Java で言うアノテーションのようなメタ情報を定義したものです。

今回利用する encoding/json では、タグの情報からJSONのプロパティ名を作成してくれるようになっているので定義しています。

JSON文字列は json.Marshal() を使って簡単に作成できます。

u := User{ Id : 1, Name : "Thome", Email : "thome@example.com"}

json, err := json.Marshal(u)
if err != nil {
    log.Fatal(err)
}

struct型は Composite literals で初期化できます。つまり {} で初期値を設定してインスタンスを生成できます。 := は通常 var u User のように定義する変数ですが、型推論で暗黙定義するものです。

json.Marshal() は戻り値を2つ返します。これを json, err で受けています。err を見てログ出力しています(Go ではif分の括弧は不要です)。


では先程の例を書き換えてみましょう。

package main

import (
    "log"
    "net/http"
    "encoding/json"
)

type User struct {
    Id    int     `json:"id"`
    Name  string  `json:"name"`
    Email string  `json:"e-mail"`
}

func handler(w http.ResponseWriter, r *http.Request) {

    u := User{ Id : 1, Name : "Thome", Email : "thome@example.com"}

    json, err := json.Marshal(u)
    if err != nil {
        log.Fatal(err)
    }

    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    w.Write(json)
}

func main() {
    http.HandleFunc("/", handler)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal("Error ListenAndServe : ", err)
    }
}

実行して http://localhost:8080/ にアクセスします。

f:id:Naotsugu:20170412222822p:plain

JSON 文字列が取得できましたね。


次回は、

etc9.hatenablog.com