Files
2021-06-21 22:01:42 +07:00

169 lines
7.0 KiB
Markdown
Raw Permalink 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, и Вы хотите, чтобы пользователи закачивали туда свои фотографии. Как можно реализовать эту функцию?
Для этого нужно добавить в форму, через которую будут закачиваться фотографии, со свойством `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>
```
Для работы с этой формой мы должны добавить функцию на сервере:
```
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 Вывод информации на сервере после получения файла
## Загрузка файлов с помощью клиента
Я показал Вам пример, как можно использовать форму для загрузки файлов. Мы можем сделать так, чтобы загружать файлы через форму без участия человека:
```
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.
Если у Вас есть другие поля, которые нужно писать в данные, такие, как имя пользователя, вызывайте по необходимости метод `multipart.WriteField`.
## Ссылки
- [Содержание](preface.md)
- Предыдущий раздел: [Дублирование отправки](04.4.md)
- Следующий раздел: [Итоги раздела](04.6.md)