Предположим, у Вас есть веб-сайт наподобие 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
один раз достаточен - многократные вызовы ничего не меняют.
Для загрузки файлов мы используем следующие три шага:
- Добавить в форму
enctype="multipart/form-data"
. - Вызвать на стороне сервера
r.ParseMultipartForm
, чтобы сохранить файл в память или во временный файл. - Вызвать
r.FormFile
для обработки файла и сохранения его в файловую систему.
Хэндлером для файла является multipart.FileHeader
. У него следующая структура:
type FileHeader struct {
Filename string
Header textproto.MIMEHeader
// соджержит отфильтрованные или неэкспортируемые поля
}
Рисунок 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
.
- Содержание
- Предыдущий раздел: Дублирование отправки
- Следующий раздел: Итоги раздела