Multipart form data processing with support for form fields, file uploads, binary data, and comprehensive multipart content management.
Base class for all multipart content parts with common properties and disposal handling.
/**
* Base class for multipart content parts
* @param dispose function to clean up part resources
* @param headers headers specific to this part
*/
sealed class PartData(
val dispose: () -> Unit,
val headers: Headers
) {
/** Parsed Content-Disposition header */
val contentDisposition: ContentDisposition?
/** Parsed Content-Type header */
val contentType: ContentType?
/** Part name from Content-Disposition */
val name: String?
}Multipart part containing form field data as a string value.
/**
* Form field part containing string value
* @param value the form field value
* @param dispose cleanup function
* @param partHeaders part-specific headers
*/
class FormItem(
val value: String,
dispose: () -> Unit,
partHeaders: Headers
) : PartData(dispose, partHeaders)Multipart part containing file upload data with input stream provider.
/**
* File upload part with input stream provider
* @param provider function providing Input stream for file content
* @param dispose cleanup function
* @param partHeaders part-specific headers
*/
class FileItem(
val provider: () -> Input,
dispose: () -> Unit,
partHeaders: Headers
) : PartData(dispose, partHeaders) {
/** Original filename from Content-Disposition */
val originalFileName: String?
}Multipart part containing binary data with input stream provider.
/**
* Binary data part with input stream provider
* @param provider function providing Input stream for binary content
* @param dispose cleanup function
* @param partHeaders part-specific headers
*/
class BinaryItem(
val provider: () -> Input,
dispose: () -> Unit,
partHeaders: Headers
) : PartData(dispose, partHeaders)Multipart part containing binary data with channel provider for streaming.
/**
* Binary data part with streaming channel provider
* @param provider function providing ByteReadChannel for streaming content
* @param partHeaders part-specific headers
*/
class BinaryChannelItem(
val provider: () -> ByteReadChannel,
partHeaders: Headers
) : PartData({ }, partHeaders)Interface for reading multipart data streams with sequential part access.
/**
* Interface for reading multipart data streams
*/
interface MultiPartData {
/**
* Read next part from multipart stream
* @return next PartData or null if no more parts
*/
suspend fun readPart(): PartData?
companion object {
/** Empty multipart data with no parts */
val Empty: MultiPartData
}
}Utility functions for processing multipart data streams.
/**
* Process each part in multipart data
* @param partHandler suspend function to handle each part
*/
suspend fun MultiPartData.forEachPart(partHandler: suspend (PartData) -> Unit)
/**
* Read all parts into a list (use with caution for large uploads)
* @return list of all parts
*/
suspend fun MultiPartData.readAllParts(): List<PartData>Usage Examples:
import io.ktor.http.content.*
import io.ktor.http.*
// Process multipart form data
suspend fun handleMultipartUpload(multipart: MultiPartData) {
multipart.forEachPart { part ->
when (part) {
is PartData.FormItem -> {
println("Form field '${part.name}': ${part.value}")
// Handle regular form field
part.dispose()
}
is PartData.FileItem -> {
println("File upload '${part.name}': ${part.originalFileName}")
// Read file content
val input = part.provider()
val bytes = input.readBytes()
// Process file (save, validate, etc.)
processUploadedFile(part.originalFileName, bytes, part.contentType)
// Clean up resources
input.close()
part.dispose()
}
is PartData.BinaryItem -> {
println("Binary data '${part.name}'")
// Read binary content
val input = part.provider()
val bytes = input.readBytes()
// Process binary data
processBinaryData(bytes, part.contentType)
input.close()
part.dispose()
}
is PartData.BinaryChannelItem -> {
println("Streaming binary data '${part.name}'")
// Read streaming content
val channel = part.provider()
val buffer = ByteArray(8192)
while (!channel.isClosedForRead) {
val bytesRead = channel.readAvailable(buffer)
if (bytesRead > 0) {
// Process chunk
processDataChunk(buffer, bytesRead)
}
}
channel.close()
part.dispose()
}
}
}
}
// Read all parts at once (for small forms)
suspend fun processSmallForm(multipart: MultiPartData) {
val allParts = multipart.readAllParts()
allParts.forEach { part ->
// Access part properties
println("Part name: ${part.name}")
println("Content type: ${part.contentType}")
println("Content disposition: ${part.contentDisposition}")
// Process part based on type
when (part) {
is PartData.FormItem -> {
handleFormField(part.name, part.value)
}
is PartData.FileItem -> {
handleFileUpload(part)
}
// ... handle other types
}
// Always dispose parts
part.dispose()
}
}
// Create form submission data (conceptual)
fun createFormData(): Map<String, String> {
val formData = mutableMapOf<String, String>()
// This would typically be populated from actual multipart parsing
formData["username"] = "testuser"
formData["email"] = "test@example.com"
formData["description"] = "User profile information"
return formData
}
// Handle file uploads with validation
suspend fun handleFileUpload(filePart: PartData.FileItem) {
val filename = filePart.originalFileName
val contentType = filePart.contentType
// Validate file
when {
filename == null -> {
throw IllegalArgumentException("Filename is required")
}
contentType == null -> {
throw IllegalArgumentException("Content type is required")
}
!isAllowedFileType(contentType) -> {
throw IllegalArgumentException("File type not allowed: $contentType")
}
}
// Read and process file
val input = filePart.provider()
try {
val bytes = input.readBytes()
// Validate file size
if (bytes.size > MAX_FILE_SIZE) {
throw IllegalArgumentException("File too large: ${bytes.size} bytes")
}
// Save file
saveUploadedFile(filename, bytes)
} finally {
input.close()
filePart.dispose()
}
}
// Utility functions (would be implemented based on requirements)
fun processUploadedFile(filename: String?, bytes: ByteArray, contentType: ContentType?) {
// Implementation for file processing
}
fun processBinaryData(bytes: ByteArray, contentType: ContentType?) {
// Implementation for binary data processing
}
fun processDataChunk(buffer: ByteArray, length: Int) {
// Implementation for streaming data processing
}
fun handleFormField(name: String?, value: String) {
// Implementation for form field handling
}
fun isAllowedFileType(contentType: ContentType): Boolean {
// Implementation for file type validation
return contentType.match(ContentType.Image.Any) ||
contentType.match(ContentType.Application.Pdf)
}
fun saveUploadedFile(filename: String, bytes: ByteArray) {
// Implementation for file saving
}
const val MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB/**
* Content disposition header representation
* (Defined in HTTP content utilities)
*/
// ContentDisposition is defined in the HTTP content module
/**
* Input stream for reading data
* (Provided by Ktor I/O utilities)
*/
// Input is from io.ktor.utils.io
/**
* Byte read channel for streaming
* (Provided by Ktor I/O utilities)
*/
// ByteReadChannel is from io.ktor.utils.io