Как перейти от io.ReadCloser к io.ReadSeeker?

Я пытаюсь загрузить файл с S3 и загрузить этот файл в другое ведро в S3. Копировать API не будет работать здесь, потому что мне сказали не использовать его.

Получение объекта из S3 имеет response.Body, который io.ReadCloser и для загрузки этого файла, полезная нагрузка принимает Body, что io.ReadSeeker.

Единственный способ понять это - сохранить response.Body в файл, а затем передать этот файл как io.ReadSeeker. Для этого потребуется сначала записать весь файл на диск, а затем прочитать весь файл с диска, который звучит довольно неправильно.

Что я хотел бы сделать:

resp, _ := conn.GetObject(&s3.GetObjectInput{Key: "bla"})
conn.PutObject(&s3.PutObjectInput{Body: resp.Body}) // resp.Body is an io.ReadCloser and the field type expects an io.ReadSeeker

Вопрос: как я могу перейти от io.ReadCloser к io.ReadSeeker наиболее эффективным способом?

Ответ 1

io.ReadSeeker - это интерфейс, который группирует базовые методы Read() и Seek(). Определение метода Seek():

Seek(offset int64, whence int) (int64, error)

Реализация метода Seek() требует возможности искать в любом месте источника, что требует, чтобы весь источник был доступен или воспроизводимым. Файл является прекрасным примером, файл постоянно сохраняется на вашем диске, и любая его часть может быть прочитана в любое время.

response.Body реализуется для чтения из базового TCP-соединения. Чтение из базового TCP-соединения дает вам данные, которые отправляет вам клиент с другой стороны. Данные не кэшируются, и клиент не будет отправлять вам данные по запросу. Поэтому response.Body не реализует io.Seeker (и, следовательно, io.ReadSeeker).

Итак, чтобы получить io.ReadSeeker от io.Reader или io.ReadCloser, вам нужно что-то, что кэширует все данные, так что по запросу он может искать в любом месте.

Этот механизм кэширования может записывать его в файл, как вы упомянули, или вы можете прочитать все в памяти, в []byte, используя ioutil.ReadAll(), а затем вы можете использовать bytes.NewReader(), чтобы получить io.ReadSeeker от []byte. Конечно, это имеет свои ограничения: все содержимое должно вписываться в память, а также вы можете не захотеть зарезервировать этот объем памяти для этой операции копирования файлов.

В целом, реализация io.Seeker или io.ReadSeeker требует, чтобы все исходные данные были доступны, поэтому лучше всего записать его в файл или для чтения небольших файлов в []byte и потоковой передачи содержимое этого байтового фрагмента.

Ответ 2

В качестве альтернативы используйте github.com/aws/aws-sdk-go/service/s3/s3manager.Uploader, который принимает вход io.Reader.

Я предполагаю, что причина, по которой PutObject принимает io.ReadSeeker вместо io.Reader, заключается в том, что запросы на s3 должны быть подписаны (и иметь длину содержимого), но вы не можете сгенерировать подпись до тех пор, пока у вас не будет все данные. Поток-y способ сделать это будет заключаться в том, чтобы буферизовать входные данные в куски, когда они входят, и использовать многостраничную загрузку api для загрузки каждого фрагмента отдельно. Это (я думаю), что s3manager.Uploader делает за кулисами.