158 lines
6.7 KiB
Markdown
158 lines
6.7 KiB
Markdown
# 4.5 ファイルのアップロード処理
|
||
|
||
ユーザによるファイルのアップロードを処理したいとします。例えば、現在Instagramのようなホームページを作成しているとします。ユーザが撮影した写真を保存する必要があります。このような要求はどのように実現するのでしょうか?
|
||
|
||
フォームにファイルをアップロードさせるためには、まずformの`enctype`属性を追加する必要があります。`enctype`属性には以下の3つの種類があります:
|
||
|
||
application/x-www-form-urlencoded 送信前にすべての文字列をエンコードする(デフォルト)
|
||
multipart/form-data 文字列に対してエンコードしません。ファイルのアップロードウィジェットを含むフォームを使用するときはこの値が必要です。
|
||
text/plain 空白を"+"記号に置き換えます。ただし、特殊文字に対してエンコードは行われません。
|
||
|
||
そのため、フォームのhtmlコードはこのようになります:
|
||
|
||
<html>
|
||
<head>
|
||
<title>ファイルアップロード</title>
|
||
</head>
|
||
<body>
|
||
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
|
||
<input type="file" name="uploadfile" />
|
||
<input type="hidden" name="token" value="{{.}}"/>
|
||
<input type="submit" value="upload" />
|
||
</form>
|
||
</body>
|
||
</html>
|
||
|
||
サーバでは、handlerFuncをひとつ追加します:
|
||
|
||
http.HandleFunc("/upload", upload)
|
||
|
||
// /uploadを処理するロジック
|
||
func upload(w http.ResponseWriter, r *http.Request) {
|
||
fmt.Println("method:", r.Method) //リクエストを受け取るメソッド
|
||
if r.Method == "GET" {
|
||
crutime := time.Now().Unix()
|
||
h := md5.New()
|
||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||
|
||
t, _ := template.ParseFiles("upload.gtpl")
|
||
t.Execute(w, token)
|
||
} else {
|
||
r.ParseMultipartForm(32 << 20)
|
||
file, handler, err := r.FormFile("uploadfile")
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
return
|
||
}
|
||
defer file.Close()
|
||
fmt.Fprintf(w, "%v", handler.Header)
|
||
f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
return
|
||
}
|
||
defer f.Close()
|
||
io.Copy(f, file)
|
||
}
|
||
}
|
||
|
||
上のコードでは、ファイルのアップロードを処理するためには`r.ParseMultipartForm`をコールする必要があります。引数には`maxMemory`が表示されています。`ParseMultipartForm`をコールした後、アップロードするファイルは`maxMemory`のサイズのメモリに保存されます。もしファイルのサイズが`maxMemory`を超えた場合、残った部分はシステムのテンポラリファイルに保存されます。`r.FormFile`によって上のファイルハンドルを取得することができます。その後実例の中では`io.Copy`を使ってファイルを保存しています。
|
||
|
||
>その他のファイルではないフィールド情報を取得する時は`r.ParseForm`をコールする必要はありません。必要な時はGoが自動でコールします。また`ParseMultipartForm`を一度コールすると、その後にもう一度コールしても効果はありません。
|
||
|
||
上の実例を通して、ファイルのアップロードには主に3ステップの処理があることが分かります:
|
||
|
||
1. フォームにenctype="multipart/form-data"を追加する。
|
||
2. サーバで`r.ParseMultipartForm`をコールし、アップロードするファイルをメモリとテンポラリファイルに保存する。
|
||
3. `r.FormFile`を使用して、ファイルハンドルを取得し、ファイルに対して保存等の処理を行う。
|
||
|
||
ファイルhandlerはmultipart.FileHeaderです。この中には以下のような構造体が保存されています。
|
||
|
||
type FileHeader struct {
|
||
Filename string
|
||
Header textproto.MIMEHeader
|
||
// contains filtered or unexported fields
|
||
}
|
||
|
||
上のコード例では以下のようにファイルのアップロードを出力します。
|
||
|
||

|
||
|
||
図4.5 ファイルのアップロードを行った後サーバが受け取った情報の出力
|
||
|
||
## クライアントによるファイルのアップロード
|
||
|
||
上の例でどのようにフォームからファイルをアップロードするのか示しました。その後サーバでファイルを処理しますが、Goは実はクライアントのフォームによるファイルのアップロードをエミュレートする機能をサポートしています。詳しい使用方法は以下の例をご覧ください:
|
||
|
||
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"fmt"
|
||
"io"
|
||
"io/ioutil"
|
||
"mime/multipart"
|
||
"net/http"
|
||
"os"
|
||
)
|
||
|
||
func postFile(filename string, targetUrl string) error {
|
||
bodyBuf := &bytes.Buffer{}
|
||
bodyWriter := multipart.NewWriter(bodyBuf)
|
||
|
||
//キーとなる操作
|
||
fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
|
||
if err != nil {
|
||
fmt.Println("error writing to buffer")
|
||
return err
|
||
}
|
||
|
||
//ファイルハンドル操作をオープンする
|
||
fh, err := os.Open(filename)
|
||
if err != nil {
|
||
fmt.Println("error opening file")
|
||
return err
|
||
}
|
||
defer fh.Close()
|
||
|
||
//iocopy
|
||
_, err = io.Copy(fileWriter, fh)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
contentType := bodyWriter.FormDataContentType()
|
||
bodyWriter.Close()
|
||
|
||
resp, err := http.Post(targetUrl, contentType, bodyBuf)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
resp_body, err := ioutil.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
fmt.Println(resp.Status)
|
||
fmt.Println(string(resp_body))
|
||
return nil
|
||
}
|
||
|
||
// sample usage
|
||
func main() {
|
||
target_url := "http://localhost:9090/upload"
|
||
filename := "./astaxie.pdf"
|
||
postFile(filename, target_url)
|
||
}
|
||
|
||
|
||
上の例ではクライアントが如何にサーバに対し一つのファイルをアップロードするのかご説明しました。クライアントはmultipart.Writeを通してファイルの本文をバッファの中に書き込みます。その後、httpのPostメソッドをコールしてバッファからサーバに転送します。
|
||
|
||
>もしあなたが他にusernameといった普通のフィールドを同時に書き込む場合は、multipartのWriteFieldメソッドをコールして、その他の似たようなフィールドを複数書き込むことができます。
|
||
|
||
## links
|
||
* [目次](<preface.md>)
|
||
* 前へ: [フォームの多重送信の防止](<04.4.md>)
|
||
* 次へ: [まとめ](<04.6.md>)
|