Skip to content

Instantly share code, notes, and snippets.

@lynas
Last active February 3, 2025 08:48
Show Gist options
  • Save lynas/6fcdc515ff439a3e12c9a5a42653867c to your computer and use it in GitHub Desktop.
Save lynas/6fcdc515ff439a3e12c9a5a42653867c to your computer and use it in GitHub Desktop.
Upload multipart file to AWS S3
import java.io.InputStream
import java.net.URI
import org.springframework.context.annotation.Bean
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.amazon.awssdk.core.sync.RequestBody
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.S3Configuration
import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest
import software.amazon.awssdk.services.s3.model.Bucket
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadResponse
import software.amazon.awssdk.services.s3.model.CompletedPart
import software.amazon.awssdk.services.s3.model.CreateBucketRequest
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest
import software.amazon.awssdk.services.s3.model.ListBucketsRequest
import software.amazon.awssdk.services.s3.model.HeadObjectRequest
import software.amazon.awssdk.services.s3.model.S3Exception
import software.amazon.awssdk.services.s3.model.UploadPartRequest
class S3FileUpload(
private val s3Properties: S3Properties,
private val s3Client: S3Client,
) {
fun uploadStreamFile(
bucketName: String,
path: String,
inputStream: InputStream,
contentType: String = "application/octet-stream",
publicRead: Boolean = false
): String {
val createMultipartUploadRequest = CreateMultipartUploadRequest.builder()
.bucket(bucketName)
.key(path)
.contentType(contentType)
.build()
val multipartUpload = s3Client.createMultipartUpload(createMultipartUploadRequest)
val uploadId = multipartUpload.uploadId()
val partETags = mutableListOf<CompletedPart>()
val buffer = ByteArray(s3Properties.streamBufferSize)
var partNumber = 1
var bytesRead: Int
try {
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
val partData = buffer.copyOf(bytesRead)
val uploadPartRequest = UploadPartRequest.builder()
.bucket(bucketName)
.key(path)
.uploadId(uploadId)
.partNumber(partNumber)
.contentLength(partData.size.toLong())
.build()
val uploadPartResponse = s3Client.uploadPart(uploadPartRequest, RequestBody.fromBytes(partData))
partETags.add(
CompletedPart.builder()
.partNumber(partNumber)
.eTag(uploadPartResponse.eTag())
.build()
)
partNumber++
}
val completeRequest = CompleteMultipartUploadRequest.builder()
.bucket(bucketName)
.key(path)
.uploadId(uploadId)
.multipartUpload { it.parts(partETags) }
.build()
val completedResponse: CompleteMultipartUploadResponse = s3Client.completeMultipartUpload(completeRequest)
return completedResponse.key()
} catch (e: Exception) {
s3Client.abortMultipartUpload(
AbortMultipartUploadRequest.builder()
.bucket(bucketName)
.key(path)
.uploadId(uploadId)
.build()
)
throw RuntimeException("Multipart upload failed: ${e.message}", e)
} finally {
inputStream.close()
}
}
fun exists(bucketName: String, fileLocation: String): Boolean {
return try {
val result = s3Client.headObject(
HeadObjectRequest.builder()
.bucket(bucketName)
.key(fileLocation)
.build()
)
logger.info { "File exists: $fileLocation S3 Tag: ${result.eTag()}" }
true
} catch (ex: S3Exception) {
logger.error("File dot not exist in S3 $fileLocation", ex)
false
}
}
@Bean
fun localStackAmazonS3(s3Properties: S3Properties): S3Client {
val bucket = CreateBucketRequest.builder()
.bucket(s3Properties.firmwareBucket)
.build()
val s3Client = S3Client.builder()
.region(Region.of(s3Properties.serviceRegion))
.endpointOverride(URI.create(s3Properties.serviceEndpoint))
.credentialsProvider(
StaticCredentialsProvider.create(
AwsBasicCredentials.create(s3Properties.accessKey, s3Properties.secretKey)
)
)
.serviceConfiguration(
S3Configuration.builder()
.pathStyleAccessEnabled(s3Properties.servicePathStyleAccessEnabled)
.chunkedEncodingEnabled(!s3Properties.chunkedEncodingDisabled)
.build()
)
.build()
val existingBuckets = s3Client.listBuckets(ListBucketsRequest.builder().build()).buckets()
val bucketExists = existingBuckets.any { bucket: Bucket -> bucket.name() == s3Properties.firmwareBucket }
if (!bucketExists) {
s3Client.createBucket(bucket)
}
return s3Client
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment