技術向上

プログラミングの学び、気になるテクノロジーやビジネストレンドを発信

formからfileを読み込む【Go】

http.Requestのポインタをレシーバに持つFormFile()を使います。

次のような階層があるとして、

main.go
templates
    index.gohtml
filestore
    (作成したファイルを格納)

formのinputからファイルを読み込み、
filestoreフォルダに新たなファイルを作成、
読み込んだ内容を上記ファイルに書き出す、というプログラムを実装します。

main.goは次のようにします。

var tpl *template.Template

func init() {
    tpl = template.Must(template.ParseGlob("templates/*"))    // templates以下のファイルをキャッシュ
}

func main() {
    http.HandleFunc("/", foo)    // ルートディレクトリにアクセスしたら関数fooを実行
    http.Handle("/favicon.ico", http.NotFoundHandler())    // favicon.icoへのアクセスには404エラーを返す
    http.ListenAndServe(":8080", nil)
}

func foo(w http.ResponseWriter, req *http.Request) {
    var s string
    if req.Method == http.MethodPost {    // POSTの場合
        f, h, err := req.FormFile("q")    // name=qのファイルを格納
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer f.Close()

        bs, err := ioutil.ReadAll(f)    // ファイルの中身を読む
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        s = string(bs)    // byte配列をstringにキャスト

        nf, err := os.Create(filepath.Join("./filestore/", h.Filename))    // filestoreに、読み込んだファイルと同じ名前のファイルを作成
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer nf.Close()

        _, err = nf.Write(bs)    // 作成したファイルに、読み込んだファイルと同じ内容を書き込む
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }
    w.Header().Set("Content-Type", "text/html; charset=utf8")    // templateにこの設定がある場合は不要
    tpl.ExecuteTemplate(w, "index.gohtml", s)    // キャッシュしたtemplateの中からindex.gohtmlを選択し、変数sを渡して実行
}


templateの内容は解説しませんが、
formの中にinput type="file"とinput type="submit"を用意して、
渡される変数を表示するスペースを設けます。


main.goは次のようにも書けます。
ファイル名をhash化しています。

if req.Method == http.MethodPost {
    mf, fh, err := req.FormFile("nf")
    if err != nil {
        fmt.Println(err)
    }
    defer mf.Close()    // 閉じる
    fmt.Println(mf)
    ext := strings.Split(fh.Filename, ".")[1]    // ファイル名から拡張子だけを抽出
    h := sha1.New()    // sha1アルゴリズムでhash化するhを生成
    io.Copy(h, mf)    // 読み込んだファイルの内容をhに書き込む
    fname := fmt.Sprintf("%x", h.Sum(nil)) + "." + ext    // Slice()は引数にnilを指定することで、現在の状態のsliceを返す
    wd, err := os.Getwd()    // working directory(current directory)をルートディレクトリから返す(フルパス)
    if err != nil {
        fmt.Println(err)
    }
    path := filepath.Join(wd, "public", "pics", fname)    // ファイルを保存したい場所を指定
    nf, err := os.Create(path)    // ファイルを作成
    if err != nil {
        fmt.Println(err)
    }
    defer nf.Close()    // 閉じる
    mf.Seek(0, 0)    // hにコピーした段階で、fileのオフセットが最後まで到達したので、最初に位置に戻す必要がある
    io.Copy(nf, mf)
    c = appendValue(w, c, fname)
}


filepath.Join()で指定したディレクトリは、事前に用意する必要があります。

Seek()は、FileがimplementしているSeeker インターフェースのメソッドです。
第1引数にオフセット、第2引数に開始位置(0: 最初 / 1: 現在位置 / 2: 最後)をとります。
例えば第1引数に「-10」、第2引数に「2」をとると、最後から10個前に位置をセットします。