Я побеседовал со службой поддержки AWS, и они сообщили, что API многокомпонентной загрузки спроектирован таким образом, что может принимать только одну последнюю часть размером менее 5 МБ. Следовательно, нам необходимо загрузить части таким образом, чтобы они соответствовали этим требованиям.
Чтобы исправить проблему, мы сделали следующее: всякий раз, когда оставшийся размер части был меньше 5 МБ, мы преобразовали входной поток обратно в строка и вернул строку обратно. Функция, создающая строку, получит эту строку и продолжит строить строку дальше оттуда.
Например, допустим, что вся строка будет иметь размер около 701 МБ. Также допустим, что у нас есть функция buildString (), которая создает строку, которую необходимо загрузить. Функция начинает построение строки с нуля, и когда строка пересекает 100 МБ, мы выполняем загрузку нескольких частей для этой строки (AWS рекомендует загружать объект в нескольких частях, если размер объекта превышает 100 МБ). Допустим, размер встроенной строки составляет 101 МБ, и мы выполняем многостраничную загрузку для этой строки. API составной загрузки AWS предполагает, что составной объект должен быть загружен частями по 5 МБ. Следовательно, для строки размером 101 МБ будут загружены 20 частей по 5 МБ, а для оставшегося 1 МБ - вместо загрузки мы преобразуем ее обратно в строку и отправим обратно в функцию buildString (). BuildString () возьмет эту строку и продолжит построение строки оттуда. Когда длина строки снова превысит 100 МБ, те же шаги будут повторены. Таким образом, каждая загружаемая часть будет иметь размер ровно 5 МБ. Единственная оставшаяся часть этой головоломки заключается в том, что прямо в конце, когда buildString () завершит построение строки и загрузит оставшуюся строку, нам не нужно возвращать строку меньше 5 МБ. Вместо этого он должен быть загружен, поскольку это действительно последняя часть строки размером 701 МБ, которая будет меньше 5 МБ.
Следующая таблица дает лучшее представление: Таблица, показывающая длину строки при каждой загрузке нескольких частей
В этом решении метод загрузки составной части будет выглядеть следующим образом:
public String uploadMultipart(boolean isLastPart, InputStream body, long contentLength) throws IOException {
long partSize = 5242880L;
long filePosition = 0L;
String remainderString = null;
int i;
for (i = partNumber; filePosition < contentLength; ++i) {
if ((contentLength - filePosition) < partSize && !isLastPart) {
/**
* Do not publish the part which is less than 5 MB. Instead, convert it to string and return it back.
* The reason for that is - AWS only wants one last part to be less than 5 MB.
* However, with our custom-built solution to upload the parts while building the string, we would end up with multiple parts less than 5 MB.
* To avoid that, we will not publish the part which is less than 5 MB. We will return the string and continue building the string from there.
* Only if isLastPart is true (which indicates that we have completed building the string), we will not return the remaining string and rather upload it as
* that will indeed be the last part less than 5 MB as expected by AWS.
*/
remainderString = getStringFromInputStream(body);
break;
} else {
partSize = Math.min(partSize, (contentLength - filePosition));
UploadPartRequest uploadRequest = (new UploadPartRequest()).withBucketName(getBucketName()).withKey(getS3Key()).
withUploadId(getUploadId()).withPartNumber(i).withInputStream(body).withPartSize(partSize);
UploadPartResult uploadResult = s3Client.uploadPart(uploadRequest);
partETags.add(uploadResult.getPartETag());
filePosition += partSize;
}
}
partNumber = i;
return remainderString;
}
Обратите внимание, что имя сегмента, ключ S3 и идентификатор загрузки, заданные в объекте UploadPartRequest, должны в этом случае будет одинаковым для каждой составной загрузки. Только тогда AWS узнает, что эти части связаны с одним и тем же объектом, и в конечном итоге соберет их вместе, чтобы получить наш единственный объект размером 701 МБ, когда в конце мы вызовем функцию completeMultipartUpload () AWS.