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個前に位置をセットします。