はじめてのGo―シンプルな言語仕様,型システム,並行処理

第4章 標準パッケージ―JSON,ファイル,HTTP,HTMLを扱う

この記事を読むのに必要な時間:およそ 8.5 分

io/ioutilパッケージ

ファイルの操作は,io/ioutilパッケージを用いるとより簡単に行うことができます。

全体の読み出し

ioutil.ReadAll()は,引数にio.Readerを渡すと,その中身をすべて読み出し,[]byte型で返します。先ほどの*os.Fileの読み出しでは直接Read()を呼んでいたため十分な長さの[]byteを用意する必要がありましたが,これを用いると不要になります。

// ファイルの中身をすべて読み出す
file, _ := os.Open("./file.txt")
message, err := ioutil.ReadAll(file)

これはio.Readerを実装したすべての型で使用できるため,ファイルI/O以外のネットワークプログラミングなどでも重宝します。

ファイルの読み書き

ファイル操作に特化したメソッドも用意されています。

ioutil.ReadFile()は,ファイル名を指定するとその中身をすべて[]byteとして読み出します。

// ファイルの中身をすべて読み出す
message, err := ioutil.ReadFile("./file.txt")

iotuil.WriteFile()は,ファイル名を指定するとそこに[]byte型のデータを書き込みます。第三引数にはファイルのパーミッションを8進数で指定します。

message := []byte("hello world\n")
err := ioutil.WriteFile("./file.txt", message, 0666)

これらを用いて先ほどの操作を書き直すと,次のようになります。

package main

import (
    "fmt"
    "io/ioutil"
    "log"
)

func main() {
    // ファイルにメッセージを書き込む
    hello := []byte("hello world\n")
    err := ioutil.WriteFile("./file.txt", hello, 0666)
    if err != nil { // エラー処理
        log.Fatal(err)
    }

// ファイルの中身を全て読み出す
    message, err := ioutil.ReadFile("./file.txt")
    if err != nil { // エラー処理
        log.Fatal(err)
    }
    fmt.Print(string(message)) // 文字列にして表示
}

特にファイルI/Oを扱う場合は,io/ioutilパッケージは非常に便利なので覚えておくとよいでしょう。

net/httpパッケージ

net/httpパッケージには,HTTPサーバやクライアントを作成するためのAPIがそろっています。ここでは,このパッケージを用いて簡単なHTTPサーバを実装してみたいと思います注1⁠。

注1)
あくまでサンプルでありセキュリティの観点は省略します。

hello world サーバ

まず,net/httpパッケージを用いてhello worldを返す簡単なサーバを実装すると,次のようになります。

package main

import (
    "fmt"
    "net/http"

)

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

    fmt.Fprint(w, "hello world")
}

func main() {
    http.HandleFunc("/", IndexHandler)
    http.ListenAndServe(":3000", nil)
}

ここでは,最初にhttp.HandleFunc()でルーティングの設定をします。http.HandleFunc()は次のような定義になっています。

func HandleFunc(pattern string,
    handler func(ResponseWriter, *Request))

第一引数はパスのパターンで,リクエストを受けたときはこのパターンに一致したハンドラが実行されます。今回はルートパス/に対するリクエストに処理を登録します。

第二引数は2つの引数を受け取る関数になっており,ここではIndexHandlerで実装しています。Requestにはリクエストの情報が入っており,それをもとに組み立てた結果をResponseWriterに書き込むことでレスポンスを返せます。ResponseWriterは名前のとおりio.Writerなので,ここではfmt.Fprint()を用いて文字列を書き込んでいます。

最後にmain()では,http.ListenAndServe()にポートを指定してサーバを起動しています。第二引数は今回は使わないためnilを指定します。

このプログラムを実行し,ブラウザからhttp://localhost:3000/にアクセスして,hello worldが表示されれば成功です図1⁠。

$ go run server.go

図1 hello-server

図1 hello-server

JSON/HTMLサーバ

ここではPOSTで送信されたJSONデータをファイルに保存し,リクエストに応じてファイルから読み出したデータをHTMLに格納して返す,ごく簡単なHTTPサーバを実装してみましょう。

このサーバは,次のようなPersonデータを扱うことにします。

type Person struct {
    ID int `json:"id"`
    Name string `json:"name"`
}

サーバは,クライアントからPerson型のJSONをPOSTで受け取ると,id値に対応したファイルを作成し,そこにNameの値を格納します。またGETでは,クエリパラメータとしてidで指定された値のファイルを読み出し,HTMLに整形して返します。

POST

処理はPersonHandlerに実装し,それを/personsのパスに対して登録します。ここではPOSTリクエストを処理するため,http.Request.Methodの値で分岐し,JSON内のIDの値からファイルを作り,その中にNameの値を書き込んでいます。

処理が成功した場合はレスポンスとして201 CREATEDを返すため,ResponseWriter.WriteHeader()にnet/httpパッケージに定義されたステータスコードを指定してレスポンスを返します。

package main

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

type Person struct {
    ID int `json:"id"`
    Name string `json:"name"`
}

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

    fmt.Fprint(w, "hello world")
}

func PersonHandler(w http.ResponseWriter,
    r *http.Request) {
    defer r.Body.Close() // 処理の最後にBodyを閉じる

    if r.Method == "POST" {
        // リクエストボディをJSONに変換
        var person Person
        decoder := json.NewDecoder(r.Body)
        err := decoder.Decode(&person)
        if err != nil { // エラー処理
            log.Fatal(err)
        }

        // ファイル名を {id}.txtとする
        filename := fmt.Sprintf("%d.txt", person.ID)
        file, err := os.Create(filename) // ファイルを生成
        if err != nil {
            log.Fatal(err)
        }
        defer file.Close()

        // ファイルにNameを書き込む
        _, err = file.WriteString(person.Name)
        if err != nil {
            log.Fatal(err)
        }

        // レスポンスとしてステータスコード201を送信
        w.WriteHeader(http.StatusCreated)
    }
}

func main() {
    http.HandleFunc("/", IndexHandler)
    http.HandleFunc("/persons", PersonHandler)
    http.ListenAndServe(":3000", nil)
}

サーバを起動したら,JSONをPOSTで送ってみましょう。curlコマンドが使える環境では,次の方法でリクエストできます。

$ curl http://localhost:3000/persons -d '{"id":1,"name":"gopher"}' 

idを1としたため,成功していれば1.txtが作られ,中にgopherが格納されているはずです。

$ cat 1.txt
gopher
GET

GETが来た場合は,クエリパラメータのid値を用いて該当するファイルに格納された名前を読み出し,その情報をHTMLに埋め込んでレスポンスします。

クエリパラメータはResponseWriter.URL.Query().Get()から取得できます。この値は文字列であるため,数値に変換するにはstrconvパッケージのAtoi()を用います。

// パラメータを取得
id, err := strconv.Atoi(r.URL.Query().Get("id"))
if err != nil {
    log.Fatal(err)
}

ここでは,指定されたidに対するファイルを開き,その中に格納された値を埋め込んだHTMLを返すようにしましょう。HTMLの生成には標準のテンプレートエンジンを使用します。テンプレートエンジンの使い方は次節で解説します。

著者プロフィール