Uploader un fichier en multipart avec Kotlin et Javascript
Catégories
JavaScript KotlinQu'est-ce que le multipart ?
La particularité du multipart est que nous pouvons uploader des fichiers de très gros volumes, jusqu'au Terabyte
.
Le fonctionnement du multipart est un peu particulier. En général, lors de l'upload d'un fichier standard, le fichier entier est envoyé sous forme d'un objet unique. objet
unique. Avec le multipart, on découpe le fichier en plusieurs morceaux pour ensuite le reconstituer avec toutes les parties.
Création du fichier multipart
1.Le fichier est découpé en plusieurs parties, appelées chunks. Dans ce cas, les segments sont de 5 Mo
chacun.
const chunkSize = 1024 * 1024 * 5; // 5 MB chunk size
const totalChunks = Math.ceil(file.size / chunkSize);
const createFileChunks = (file, chunkSize) => {
const chunks: Blob[] = [];
let offset = 0;
while (offset < file.size) {
chunks.push(file.slice(offset, offset + chunkSize));
offset += chunkSize;
}
return chunks;
}
- Le multipart est créé en envoyant le nom du fichier à uploader ainsi que le nombre total de chunks.
const initResponst = async () => {
const response = await fetch("/folders/upload/init", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
filename: file.name,
total_chunks: totalChunks,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
- La création du fichier multipart est envoyée au serveur S3, en veillant à retourner l'id de l'upload pour continuer le processus.
val uploads = mutableMapOf<String, MutableList<CompletedPart>>()
suspend fun initiateMultipartUpload(client: S3Client, key: String): String? {
val multipartRes = client.createMultipartUpload {
checksumAlgorithm = ChecksumAlgorithm.Sha256
bucket = S3Config.bucketName
this.key = key
}
uploads[multipartRes.uploadId!!] = mutableListOf()
return multipartRes.uploadId
}
Uploader la totalité des parties du fichier sous forme de chunk
- Le formData est créé et envoyé à l'API..
for (let index = 0; index < chunks.length; index++) {
const chunk = chunks[index];
const formData = new FormData();
formData.append('uploadId', uploadId.toString());
formData.append('chunkNumber', (index + 1).toString());
formData.append('totalChunks', totalChunks.toString());
formData.append('file', chunk, file.name);
await fetch("/folders/upload", {
method: "POST",
headers: {
"Content-Type": "multipart/form-data",
},
body: formData,
});
}
- Récupèrer le formData pour ensuite uploader le chunk envoyé.
Grâce à la méthode receiveMultipart()
de Ktor, permet facilement la récupération du formData
pour l'envoyer au serveur s3.
post {
val multipart = call.receiveMultipart()
var uploadId: String? = null
var chunkNumber: Int? = null
var totalChunks: Int? = null
var originalFileName: String? = null
var fileBytes: ByteArray? = null
multipart.forEachPart { part ->
when (part) {
is PartData.FormItem -> {
when (part.name) {
"uploadId" -> uploadId = part.value
"chunkNumber" -> chunkNumber = part.value.toIntOrNull()
"totalChunks" -> totalChunks = part.value.toIntOrNull()
}
}
is PartData.FileItem -> {
originalFileName = part.originalFileName
fileBytes = part.streamProvider().readBytes()
}
else -> {}
}
part.dispose()
}
if (uploadId != null && chunkNumber != null && totalChunks != null && originalFileName != null && fileBytes != null) {
try {
S3Config.makeClient()?.let {
S3SystemService.uploadMultipart(it, originalFileName, uploadId, chunkNumber!!, fileBytes, totalChunks!!)
}
call.respond(HttpStatusCode.OK, "Chunk $chunkNumber uploaded successfully")
} catch (e: S3Exception) {
call.respond(HttpStatusCode.BadRequest, "Error uploading chunk ${e.message}")
}
} else {
call.respond(HttpStatusCode.BadRequest, "Invalid upload data")
}
}
Ensuite, le chunk est uploadé sur le serveur S3 et stocké pour reconstituer le fichier.
suspend fun uploadMultipart(client: S3Client, key: String, uploadId: String?, chunkNumber: Int, fileBytes: ByteArray?, totalChunks: Int): String? {
try {
val part = client.uploadPart(UploadPartRequest {
bucket = S3Config.bucketName
this.key = key
this.uploadId = uploadId
partNumber = chunkNumber
body = ByteStream.fromBytes(fileBytes!!)
}).let {
CompletedPart {
checksumSha256 = it.checksumSha256
partNumber = chunkNumber
eTag = it.eTag
}
}
uploads[uploadId!!]?.add(part)
} catch (e: S3Exception) {
println("Error uploading file: ${e.message}")
}
return uploadId
}
Reconstitution du fichier
- Indiquer la fin de l'upload du fichier pour le reconstituer.
await fetch("/folders/upload/complete", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
upload_id: uploadId.toString(),
filename: file.name,
}),
});
- On reconstruit le fichier sur le serveur S3.
Comme on peut le voir, l'uploadId a suivi tout le processus d'upload multipart. Grâce à cet identifiant et au nom du fichier, S3 retrouve les morceaux et reconstitue le fichier.
suspend fun completeMultipartUpload(client: S3Client, remotePath: String, uplId: String?) {
client.completeMultipartUpload(CompleteMultipartUploadRequest {
bucket = S3Config.bucketName
this.key = remotePath
this.uploadId = uplId
multipartUpload = CompletedMultipartUpload {
parts = uploads[uplId!!]
bucket = S3Config.bucketName
key = remotePath
uploadId = uplId
}
}).also { uploads.remove(uplId) }
}