or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication.mdcontent-processing.mdcontent-types.mdcookies.mddate-utilities.mdheader-parsing.mdheaders.mdhttp-message-extensions.mdhttp-methods-status.mdindex.mdmultipart.mdparameters.mdurl-encoding.mdurl-handling.md
tile.json

multipart.mddocs/

Multipart Handling

Multipart form data processing with support for form fields, file uploads, binary data, and comprehensive multipart content management.

Capabilities

Part Data Base Class

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?
}

Form Item Part

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)

File Item Part

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?
}

Binary Item Part

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)

Binary Channel Item Part

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)

Multipart Data Interface

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
    }
}

Multipart Extension Functions

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

Types

/**
 * 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