Go Advent Calendarの4日目ですね。わたくしが担当です。
今回は記事が少ない(?)Goでのファイルアップロードについて書いていこうかなと思います。
よろしくお願いします。
流れを組み立てる
理解があってるのかわからないのですが、Go言語はWebアプリケーションを作る際にハンドラを登録して、そのハンドラに登録された処理を実行するような仕組みになっているようです。
流れを次のようにしようかなと思います。
- まずはアップロード画面を表示する(upload.html)
- actionで別のハンドラに処理を渡す(/saveを指定)
- 渡した先でファイルの保存処理を行う(/save)
- 最後にアップロードする画面に戻る(リダイレクト)
かつ、エラーが起こった時はエラーページに飛ばそうと思うので、エラーページを扱うことも考えると、
- アップロード画面
- 保存処理
- エラーページ
の3つのハンドラが必要になりますね。
なお、自分はInteliJIDEAを使っていますので、その前提で話を進めていきます。
コードを書いていく
テンプレートとなるhtmlファイルはこちら
<!DOCTYPE html>
<!-- template用のhtmlファイル -->
<html>
<head>
<title>ファイルアップロードテスト</title>
</head>
<body>
<div class="container">
<h1>ファイルアップロードテスト</h1>
<form method="post" action="/save" enctype="multipart/form-data">
<fieldset>
<input type="file" name="upload_files" id="upload_files" multiple="multiple">
<input type="submit" name="submit" value="アップロード開始">
</fieldset>
</form>
</div>
</body>
</html>
main.goはこんな感じ
package main
import (
"html/template"
"io"
"net/http"
"os"
"fmt"
)
//「/save」用のハンドラ
func saveHandler(w http.ResponseWriter, r *http.Request) {
//MultipartReaderを用いて受け取ったファイルを読み込み
reader, err := r.MultipartReader()
//エラーが発生したら抜ける
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
//forで複数ファイルがある場合に、すべてのファイルが終わるまで読み込む
for {
part, err := reader.NextPart()
if err == io.EOF {
break
}
//ファイル名がない場合はスキップする
if part.FileName() == "" {
continue
}
//uploadedfileディレクトリに受け取ったファイル名でファイルを作成
uploadedFile, err := os.Create("<アップロードしたい場所へのパス>" + part.FileName())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
uploadedFile.Close()
redirectToErrorPage(w,r)
return
}
//作ったファイルに読み込んだファイルの内容を丸ごとコピー
_, err = io.Copy(uploadedFile, part)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
uploadedFile.Close()
redirectToErrorPage(w,r)
return
}
}
//uploadページにリダイレクト
http.Redirect(w,r,"/upload",http.StatusFound)
}
//「/upload」用のハンドラ
func uploadHandler(w http.ResponseWriter, r *http.Request) {
var templatefile = template.Must(template.ParseFiles("<templateファイルへのフルパス>"))
templatefile.Execute(w, "upload.html")
}
//「/errorPage」用のハンドラ
func errorPageHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w,"%s","<p>Internal Server Error</p>")
}
//errorが起こった時にエラーページに遷移する
func redirectToErrorPage(w http.ResponseWriter, r *http.Request) {
http.Redirect(w,r,"/errorPage",http.StatusFound)
}
func main() {
//ハンドラの登録
http.HandleFunc("/upload", uploadHandler)
http.HandleFunc("/save",saveHandler)
http.HandleFunc("/errorPage",errorPageHandler)
//サーバーの開始
http.ListenAndServe(":8080", nil)
}
main.go内の<~~パス>という部分は、適宜自分の環境に合わせて変更してください。
<p>とか</p>はhtmlタグだから変えなくていいですヨ。
(※例えば「C:/Go/MyAppTest/」など)
これらを用意して、shift + F10で実行します。
実行したら、http://localhost:8080/upload にアクセスして、アップロードするファイルを選び、アップロード開始を押せばアップロードできると思います。
終えてみて
作ってて思ったけど、InternalServerErrorのステータス返してるのにリダイレクトするの、わかりにくくしてる気がするからやめたほうがよかったかも。
Goって周りの強い人たちがめっちゃやってるからこんなひ弱な自分が参加してもいいのだろうかとすごく迷ったのですがやってよかったです。今までふわっとしてた部分が少しづつわかってきた気がします。
超初心者向けの記事だったのでかなり物足りなく感じた皆様が多いかと思いますが。。。。
今後がっつり触っていきたい言語のひとつになりました。
ちゃんと強い人たちにぼこぼこにされながら頑張っていきます。
次は@cubicdaiya様ですのでそちらもよろしくお願いいたします。
ではみなさま、良い12月を。
2016/12/05追記:
@luccafort様よりタイポミスの指摘がございましたので訂正させていただきました。
訂正した場所は以下です。
訂正前:これらを用意して、shfit + F10
↓
訂正後:これらを用意して、shift + F10
指摘してくださりありがとうございます。大変助かりました。