Skip to content

Latest commit

 

History

History
168 lines (132 loc) · 7.03 KB

File metadata and controls

168 lines (132 loc) · 7.03 KB

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
   	// соджержит отфильтрованные или неэкспортируемые поля
}

Рисунок 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.

Ссылки