https://kotlinlang.org logo
#ktor
Title
# ktor
l

Laurence

08/11/2020, 8:23 PM
Hi Everyone 🙂 This is my first post here! I have been trying to figure out how to upload a file to S3 using bits and pieces that I found online, but just can't get it to work. I want to use
call.receiveMultipart()
and then use the input stream, but when I test using postman it just hangs and doesn't fail with an error. In Gradle I have:
Copy code
// AWS S3
  implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.7')
  implementation 'com.amazonaws:aws-java-sdk-s3'
For the S3 client and upload function I am using:
Copy code
private val s3Client = AmazonS3Client(
    BasicAWSCredentials(S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY)
  ).apply {
    withRegion<AmazonWebServiceClient>(Regions.US_EAST_1)
    setS3ClientOptions(
      S3ClientOptions.builder()
        .setPathStyleAccess(true).build()
    )
  }


fun uploadFileToS3(key: String, inputStream: InputStream, metadata: ObjectMetadata): PutObjectResult =
   s3Client.putObject(BUCKET_NAME, key, inputStream, metadata)
Then the route is calling:
Copy code
val multiPartData = call.receiveMultipart()

        multiPartData.forEachPart { part ->
          when (part) {
            is PartData.FileItem -> {

              val key = "profile-image/${part.originalFileName ?: "unknown-file"}"

              part.streamProvider()
                .use { inputStream ->

                  val bytes: ByteArray = IOUtils.toByteArray(inputStream)
                  val metadata = ObjectMetadata()
                  metadata.contentLength = bytes.size.toLong()
                  val byteArrayInputStream = ByteArrayInputStream(bytes)

                  AwsS3.uploadFileToS3(
                    key,
                    byteArrayInputStream,
                    metadata
                  )
                }
            }
            else -> call.respond(HttpStatusCode.InternalServerError, JSONObject(mapOf("err" to "Error uploading file")))
          }
        }

        call.respond(
          HttpStatusCode.OK,
          JSONObject(mapOf("msg" to "Image uploaded successfully"))
        )
Hoping someone can spot where I'm going wrong 🙂 Thanks!
e

Eric Grimsborn

08/11/2020, 9:43 PM
I am sorry if this does not answer your question but would it be easier to just create a signed url and let the client put the file directly to s3? (you need v2 of aws sdk though). Gradle instructions: https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/setup-project-gradle.html Docs for signed urls: https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/examples-s3-presign.html
l

Laurence

08/12/2020, 6:25 AM
I was hoping to do as little work in the client as possible, but I'll definitely take a look at your suggestion 🙂 Thank you.
e

Eric Grimsborn

08/12/2020, 9:22 AM
The thing is that your client code goes from (pseudocode)
Copy code
await put("/uploadurl", file);
to
Copy code
const uploadUrl = await get("/get-upload-url");
await put(uploadUrl, file);
without having to handle all the multipart stuff which means much less code to write and maintain overall
4 Views