Я пытался использовать 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()?
Вы правильно используете 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)
}
Я уточнил ответ. Дайте мне знать, если что-то неясно.
Ах, мне потребовалось некоторое время, чтобы понять, что r
в p.r
в Part.Read()
— это io.Reader
. Спасибо!
Нет, multipart.Part является io.Reader , потому что он обладает необходимым методом - Read([]byte) (int, error)
. Интерфейсы неявно удовлетворяются в Go. Я должен сделать дополнение к своему ответу. А пока можно прочитать Эффективное Го и пройти "Тур Го". Позвольте мне также упомянуть книгу «Язык программирования Go» Алана А. А. Донована и Брайана В. Кернигана, глава 7 описывает интерфейсы.
Хм, я делаю .Read() из multipart.Part (pkg.go.dev/mime/multipart#Part.Read), а не io.Reader? Или они действуют по одним и тем же правилам?