Files
build-web-application-with-…/ja/04.5.md
2016-03-25 21:19:06 +09:00

158 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
}
上のコード例では以下のようにファイルのアップロードを出力します。
![](images/4.5.upload2.png?raw=true)
図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>)