# 4.5 ファイルのアップロード処理
ユーザによるファイルのアップロードを処理したいとします。例えば、現在Instagramのようなホームページを作成しているとします。ユーザが撮影した写真を保存する必要があります。このような要求はどのように実現するのでしょうか？

フォームにファイルをアップロードさせるためには、まずformの`enctype`属性を追加する必要があります。`enctype`属性には以下の３つの状態あります：

	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が自動でコールします。また`ParseMultipartFrom`を一度コールすると、その後にもう一度コールしても効果はありません。

上の実例を通して、ファイルのアップロードには主に3ステップの処理があることが分かります：

1. フォームにenctype="multipart/form-data"を追加する。
2. サーバで`r.ParseMultipartForm`をコールし、アップロードするファイルをメモリとテンポラリファイルに保存する。
3. `r.FormFile`を使用して、ファイルハンドルを取得し、ファイルに対して保存等の処理を行う。

ファイルhandlerはmultipart.FileHnadlerです。この中には以下のような構造体が保存されています。

	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
		}

		//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>)

