04.5.md 7.0 KB
Newer Older
S
04.6  
Slava Zgordan 已提交
1 2
# 4.5 Загрузка файлов

K
KirillMerz 已提交
3
Предположим, у Вас есть веб-сайт наподобие Instagram, и Вы хотите, чтобы пользователи закачивали туда свои фотографии. Как можно реализовать эту функцию?
S
04.6  
Slava Zgordan 已提交
4

K
KirillMerz 已提交
5
Для этого нужно добавить в форму, через которую будут закачиваться фотографии, со свойством `enctype`. Оно имеет три значения:
S
04.6  
Slava Zgordan 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

```
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>
```


Для работы с этой формой мы должны добавить функцию на сервере:

```
http.HandleFunc("/upload", upload)

// обработка закачки
func upload(w http.ResponseWriter, r *http.Request) {
   	fmt.Println("Метод:", 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` один раз достаточен - многократные вызовы ничего не меняют.

Для загрузки файлов мы используем следующие три шага:

1. Добавить в форму `enctype="multipart/form-data"`.
2. Вызвать на стороне сервера `r.ParseMultipartForm`, чтобы сохранить файл в память или во временный файл.
3. Вызвать `r.FormFile` для обработки файла и сохранения его в файловую систему.

Хэндлером для файла является `multipart.FileHeader`. У него следующая структура:

```
type FileHeader struct {
   	Filename string
   	Header   textproto.MIMEHeader
   	// соджержит отфильтрованные или неэкспортируемые поля
}
```

![](images/4.5.upload2.png?raw=true)

Рисунок 4.5 Вывод информации на сервере после получения файла

S
04.6  
Slava Zgordan 已提交
93
## Загрузка файлов с помощью клиента
S
04.6  
Slava Zgordan 已提交
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161

Я показал Вам пример, как можно использовать форму для загрузки файлов. Мы можем сделать так, чтобы загружать файлы через форму без участия человека:

```
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("ошибка записи в буфер")
        return err
    }

    // процедура открытия файла
    fh, err := os.Open(filename)
    if err != nil {
        fmt.Println("ошибка открытия файла")
        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
}

// пример использования
func main() {
    target_url := "http://localhost:9090/upload"
    filename := "./astaxie.pdf"
    postFile(filename, target_url)
}
```


Этот пример показывает, как можно использовать клиента для загрузки файлов. Он использует `multipart.Write` для того, чтобы записывать файлы в кэш, и посылает их на сервер посредством метода POST.

S
04.6  
Slava Zgordan 已提交
162
Если у Вас есть другие поля, которые нужно писать в данные, такие, как имя пользователя, вызывайте по необходимости метод `multipart.WriteField`.
S
04.6  
Slava Zgordan 已提交
163 164 165 166 167 168

## Ссылки

- [Содержание](preface.md)
- Предыдущий раздел: [Дублирование отправки](04.4.md)
- Следующий раздел: [Итоги раздела](04.6.md)