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