Go - правильное использование multipart Part.Read

Я пытался использовать multipart.Part, чтобы помочь читать очень большие загрузки файлов (> 20 ГБ) с HTTP, поэтому я написал приведенный ниже код, который, кажется, работает хорошо:

func ReceiveMultipartRoute(w http.ResponseWriter, r *http.Request) {
    mediatype, p, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
    if err != nil {
       //...
    }
    if mediatype != "multipart/form-data" {
        //...
    }
    boundary := p["boundary"]
    reader := multipart.NewReader(r.Body, boundary)
    buffer := make([]byte, 8192)
    for {
        part, err := reader.NextPart()
        if err != nil {
            // ... 
        }
    
        f, err := os.CreateTemp("", part.FileName())
        if err != nil {
            // ...
        }
    
        for {
            numBytesRead, err := part.Read(buffer)
            // People say not to read if there's an err, but then I miss the last chunk?
            f.Write(buffer[:numBytesRead])
            if err != nil {
                if err == io.EOF {
                    break
                } else {
                    // error, abort ...
                    return
                }
            }
        }
    }
}

Однако в самом внутреннем цикле for я обнаружил, что мне нужно читать из part.Read даже до проверки EOF, поскольку я замечаю, что пропущу последний кусок, если сделаю это заранее и сломаюсь. Тем не менее, я замечаю во многих других статьях/постах, где люди проверяют наличие ошибок/EOF и break-ing, если они есть, без использования последнего чтения. Я неправильно/безопасно использую multipart.Part.Read()?

Шаблоны Angular PrimeNg
Шаблоны Angular PrimeNg
Как привнести проверку типов в наши шаблоны Angular, использующие компоненты библиотеки PrimeNg, и настроить их отображение с помощью встроенной...
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Если вы веб-разработчик (или хотите им стать), то вы наверняка гик и вам нравятся "Звездные войны". А как бы вы хотели, чтобы фоном для вашего...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Начала с розового дизайна
Начала с розового дизайна
Pink Design - это система дизайна Appwrite с открытым исходным кодом для создания последовательных и многократно используемых пользовательских...
Шлюз в PHP
Шлюз в PHP
API-шлюз (AG) - это сервер, который действует как единая точка входа для набора микросервисов.
14 Задание: Типы данных и структуры данных Python для DevOps
14 Задание: Типы данных и структуры данных Python для DevOps
проверить тип данных используемой переменной, мы можем просто написать: your_variable=100
1
0
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы правильно используете multipart.Part.

multipart.Part является конкретной реализацией io.Reader . Соответственно, следует руководствоваться условностями и следовать рекомендациям io.Reader. Цитата из документации:

Вызывающие должны всегда обрабатывать возвращенные n > 0 байтов, прежде чем рассматривать ошибку err. Это правильно обрабатывает ошибки ввода-вывода, возникающие после чтения некоторых байтов, а также оба допустимых поведения EOF.


Также обратите внимание, что в примере вы копируете данные из io.Reader в os.File . os.File реализует интерфейс io.ReaderFrom , поэтому вы можете использовать метод File.ReadFrom() для копирования данных.

_, err := file.ReadFrom(part)
// non io.EOF
if err != nil {
    return fmt.Errorf("copy data: %w", err)
}

Если вам нужно использовать буфер, вы можете использовать функцию io.CopyBuffer() . Но обратите внимание, что вам нужно скрыть реализацию io.ReaderFrom , иначе буфер не будет использоваться для выполнения копирования. См. примеры: 1 , 2 , 3.

_, err := io.CopyBuffer(writeFunc(file.Write), part, buffer)
// non io.EOF
if err != nil {
    return fmt.Errorf("copy data: %w", err)
}

type writeFunc func([]byte) (int, error)

func (write writeFunc) Write(data []byte) (int, error) {
        return write(data)
}

Хм, я делаю .Read() из multipart.Part (pkg.go.dev/mime/multipart#Part.Read), а не io.Reader? Или они действуют по одним и тем же правилам?

kekpirat 25.11.2022 09:15

Я уточнил ответ. Дайте мне знать, если что-то неясно.

Andrei Vasilev 27.11.2022 17:42

Ах, мне потребовалось некоторое время, чтобы понять, что r в p.r в Part.Read() — это io.Reader. Спасибо!

kekpirat 28.11.2022 12:03

Нет, multipart.Part является io.Reader , потому что он обладает необходимым методом - Read([]byte) (int, error). Интерфейсы неявно удовлетворяются в Go. Я должен сделать дополнение к своему ответу. А пока можно прочитать Эффективное Го и пройти "Тур Го". Позвольте мне также упомянуть книгу «Язык программирования Go» Алана А. А. Донована и Брайана В. Кернигана, глава 7 описывает интерфейсы.

Andrei Vasilev 28.11.2022 22:01

Другие вопросы по теме