0
# Content Handling
1
2
Advanced content processing including form data, multipart uploads, progress monitoring, content transformation, and support for various content types with streaming capabilities.
3
4
## Capabilities
5
6
### Outgoing Content Types
7
8
Different types of content that can be sent with HTTP requests.
9
10
```kotlin { .api }
11
/**
12
* Base class for outgoing HTTP content
13
*/
14
sealed class OutgoingContent {
15
/** Content length if known */
16
abstract val contentLength: Long?
17
18
/** Content type */
19
abstract val contentType: ContentType?
20
21
/** Content from byte array */
22
class ByteArrayContent(
23
val bytes: ByteArray
24
) : OutgoingContent() {
25
override val contentLength: Long get() = bytes.size.toLong()
26
override val contentType: ContentType? = null
27
}
28
29
/** Content from string with encoding */
30
class TextContent(
31
val text: String,
32
override val contentType: ContentType
33
) : OutgoingContent() {
34
override val contentLength: Long get() = text.toByteArray().size.toLong()
35
}
36
37
/** Content from ByteReadChannel for streaming */
38
class ReadChannelContent(
39
val readFrom: ByteReadChannel,
40
override val contentLength: Long? = null,
41
override val contentType: ContentType? = null
42
) : OutgoingContent()
43
44
/** Content using ByteWriteChannel */
45
class WriteChannelContent(
46
val body: suspend ByteWriteChannel.() -> Unit,
47
override val contentLength: Long? = null,
48
override val contentType: ContentType? = null
49
) : OutgoingContent()
50
51
/** Represents no content */
52
object NoContent : OutgoingContent() {
53
override val contentLength: Long = 0
54
override val contentType: ContentType? = null
55
}
56
}
57
58
/**
59
* Empty content object
60
*/
61
object EmptyContent : OutgoingContent() {
62
override val contentLength: Long = 0
63
override val contentType: ContentType? = null
64
}
65
```
66
67
**Usage Examples:**
68
69
```kotlin
70
import io.ktor.client.request.*
71
import io.ktor.http.*
72
import io.ktor.utils.io.*
73
74
val client = HttpClient()
75
76
// Byte array content
77
val imageBytes = File("image.jpg").readBytes()
78
client.post("https://api.example.com/upload") {
79
setBody(OutgoingContent.ByteArrayContent(imageBytes))
80
contentType(ContentType.Image.JPEG)
81
}
82
83
// Text content with encoding
84
client.post("https://api.example.com/text") {
85
setBody(OutgoingContent.TextContent(
86
"Hello, World!",
87
ContentType.Text.Plain.withCharset(Charsets.UTF_8)
88
))
89
}
90
91
// Streaming content from channel
92
val channel = ByteChannel()
93
launch {
94
channel.writeStringUtf8("Streaming data...")
95
channel.close()
96
}
97
98
client.post("https://api.example.com/stream") {
99
setBody(OutgoingContent.ReadChannelContent(channel))
100
}
101
102
// Write channel content
103
client.post("https://api.example.com/generate") {
104
setBody(OutgoingContent.WriteChannelContent({ writeChannel ->
105
writeChannel.writeStringUtf8("Generated content: ")
106
repeat(1000) { i ->
107
writeChannel.writeStringUtf8("$i ")
108
}
109
}))
110
}
111
```
112
113
### Form Data Handling
114
115
Handle URL-encoded and multipart form data.
116
117
```kotlin { .api }
118
/**
119
* URL-encoded form data content
120
*/
121
class FormDataContent(
122
val formData: Parameters
123
) : OutgoingContent()
124
125
/**
126
* Multipart form data content
127
*/
128
class MultiPartFormDataContent(
129
val parts: List<PartData>,
130
val boundary: String = generateBoundary(),
131
override val contentType: ContentType =
132
ContentType.MultiPart.FormData.withParameter("boundary", boundary)
133
) : OutgoingContent()
134
135
/**
136
* Form data builder DSL
137
*/
138
fun formData(block: FormBuilder.() -> Unit): List<PartData>
139
140
class FormBuilder {
141
/** Add text part */
142
fun append(key: String, value: String, headers: Headers = Headers.Empty)
143
144
/** Add file part */
145
fun append(
146
key: String,
147
filename: String,
148
contentType: ContentType? = null,
149
size: Long? = null,
150
headers: Headers = Headers.Empty,
151
block: suspend ByteWriteChannel.() -> Unit
152
)
153
154
/** Add bytes part */
155
fun append(
156
key: String,
157
value: ByteArray,
158
headers: Headers = Headers.Empty
159
)
160
}
161
```
162
163
**Usage Examples:**
164
165
```kotlin
166
import io.ktor.client.request.forms.*
167
import io.ktor.http.*
168
169
// URL-encoded form
170
val formParameters = parametersOf(
171
"username" to listOf("john_doe"),
172
"email" to listOf("john@example.com"),
173
"age" to listOf("30")
174
)
175
176
client.post("https://api.example.com/register") {
177
setBody(FormDataContent(formParameters))
178
}
179
180
// Multipart form data
181
val multipartData = formData {
182
append("username", "john_doe")
183
append("email", "john@example.com")
184
append("profile_picture", "avatar.jpg", ContentType.Image.JPEG) {
185
writeFully(File("avatar.jpg").readBytes())
186
}
187
append("document", File("resume.pdf").readBytes())
188
}
189
190
client.post("https://api.example.com/profile") {
191
setBody(MultiPartFormDataContent(multipartData))
192
}
193
194
// Form submission helpers
195
client.submitForm(
196
url = "https://api.example.com/login",
197
formParameters = parametersOf(
198
"username" to listOf("john"),
199
"password" to listOf("secret")
200
)
201
)
202
203
client.submitFormWithBinaryData(
204
url = "https://api.example.com/upload",
205
formData = formData {
206
append("file", "document.pdf", ContentType.Application.Pdf) {
207
writeFileContent(File("document.pdf"))
208
}
209
append("description", "Important document")
210
}
211
)
212
```
213
214
### Progress Monitoring
215
216
Monitor upload and download progress.
217
218
```kotlin { .api }
219
/**
220
* Progress listener for monitoring transfer progress
221
*/
222
typealias ProgressListener = (bytesSentTotal: Long, contentLength: Long) -> Unit
223
224
/**
225
* Monitor upload progress
226
*/
227
fun HttpRequestBuilder.onUpload(listener: ProgressListener)
228
229
/**
230
* Monitor download progress
231
*/
232
fun HttpRequestBuilder.onDownload(listener: ProgressListener)
233
```
234
235
**Usage Examples:**
236
237
```kotlin
238
val largeFile = File("large-file.zip")
239
240
client.post("https://api.example.com/upload") {
241
setBody(largeFile.readBytes())
242
243
// Monitor upload progress
244
onUpload { bytesSentTotal, contentLength ->
245
val progress = (bytesSentTotal.toDouble() / contentLength * 100).toInt()
246
println("Upload progress: $progress% ($bytesSentTotal / $contentLength bytes)")
247
}
248
}
249
250
// Download with progress monitoring
251
client.get("https://api.example.com/download/large-file") {
252
onDownload { bytesReceivedTotal, contentLength ->
253
val progress = if (contentLength > 0) {
254
(bytesReceivedTotal.toDouble() / contentLength * 100).toInt()
255
} else {
256
0
257
}
258
println("Download progress: $progress% ($bytesReceivedTotal / $contentLength bytes)")
259
}
260
}
261
```
262
263
### Part Data Types
264
265
Handle different types of multipart data.
266
267
```kotlin { .api }
268
/**
269
* Base class for multipart data parts
270
*/
271
sealed class PartData {
272
/** Part headers */
273
abstract val headers: Headers
274
275
/** Part name */
276
val name: String? get() = headers[HttpHeaders.ContentDisposition]
277
?.let { ContentDisposition.parse(it).name }
278
279
/** Form field part */
280
class FormItem(
281
val value: String,
282
override val headers: Headers
283
) : PartData()
284
285
/** File part */
286
class FileItem(
287
val provider: () -> ByteReadChannel,
288
override val headers: Headers
289
) : PartData() {
290
/** Original filename */
291
val originalFileName: String? get() = headers[HttpHeaders.ContentDisposition]
292
?.let { ContentDisposition.parse(it).parameter("filename") }
293
}
294
295
/** Binary data part */
296
class BinaryItem(
297
val provider: () -> ByteReadChannel,
298
override val headers: Headers
299
) : PartData()
300
301
/** Binary channel part */
302
class BinaryChannelItem(
303
val provider: () -> ByteReadChannel,
304
override val headers: Headers
305
) : PartData()
306
}
307
308
/**
309
* Content disposition header utilities
310
*/
311
class ContentDisposition private constructor(
312
val disposition: String,
313
val parameters: List<HeaderValueParam>
314
) {
315
companion object {
316
fun parse(value: String): ContentDisposition
317
318
val Inline = ContentDisposition("inline", emptyList())
319
val Attachment = ContentDisposition("attachment", emptyList())
320
val FormData = ContentDisposition("form-data", emptyList())
321
}
322
323
fun parameter(name: String): String?
324
fun withParameter(name: String, value: String): ContentDisposition
325
val name: String?
326
val filename: String?
327
}
328
```
329
330
### Content Transformation
331
332
Transform and process content during transmission.
333
334
```kotlin { .api }
335
/**
336
* Content encoding for compression
337
*/
338
enum class ContentEncoding(val value: String) {
339
GZIP("gzip"),
340
DEFLATE("deflate"),
341
COMPRESS("compress"),
342
IDENTITY("identity")
343
}
344
345
/**
346
* Content transformation utilities
347
*/
348
object ContentTransformation {
349
/** Compress content with gzip */
350
suspend fun gzip(content: OutgoingContent): OutgoingContent
351
352
/** Compress content with deflate */
353
suspend fun deflate(content: OutgoingContent): OutgoingContent
354
355
/** Transform content with custom function */
356
suspend fun transform(
357
content: OutgoingContent,
358
transformer: suspend (ByteReadChannel) -> ByteReadChannel
359
): OutgoingContent
360
}
361
```
362
363
**Usage Examples:**
364
365
```kotlin
366
// Manual content transformation
367
val originalContent = OutgoingContent.TextContent("Large text content...", ContentType.Text.Plain)
368
val compressedContent = ContentTransformation.gzip(originalContent)
369
370
client.post("https://api.example.com/data") {
371
setBody(compressedContent)
372
header(HttpHeaders.ContentEncoding, ContentEncoding.GZIP.value)
373
}
374
375
// Custom transformation
376
val transformedContent = ContentTransformation.transform(originalContent) { channel ->
377
// Custom transformation logic
378
val transformed = ByteChannel()
379
channel.copyTo(transformed) // Apply transformation
380
transformed
381
}
382
```
383
384
### Streaming Content
385
386
Handle streaming content for large files and real-time data.
387
388
```kotlin { .api }
389
/**
390
* Channel writer content for streaming
391
*/
392
class ChannelWriterContent(
393
val body: suspend ByteWriteChannel.() -> Unit,
394
override val contentLength: Long? = null,
395
override val contentType: ContentType? = null
396
) : OutgoingContent()
397
398
/**
399
* Input stream content for reading from streams
400
*/
401
class InputStreamContent(
402
val inputStream: InputStream,
403
override val contentLength: Long? = null,
404
override val contentType: ContentType? = null
405
) : OutgoingContent()
406
```
407
408
**Usage Examples:**
409
410
```kotlin
411
// Streaming upload
412
client.post("https://api.example.com/stream-upload") {
413
setBody(ChannelWriterContent { channel ->
414
// Stream data in chunks
415
repeat(1000) { chunk ->
416
val data = generateChunkData(chunk)
417
channel.writeFully(data)
418
channel.flush()
419
delay(10) // Simulate real-time streaming
420
}
421
})
422
}
423
424
// Upload from input stream
425
val fileInputStream = FileInputStream("large-file.dat")
426
client.post("https://api.example.com/upload-stream") {
427
setBody(InputStreamContent(
428
inputStream = fileInputStream,
429
contentLength = File("large-file.dat").length(),
430
contentType = ContentType.Application.OctetStream
431
))
432
}
433
```
434
435
### Content Utilities
436
437
Utility functions for content handling.
438
439
```kotlin { .api }
440
/**
441
* Content utility functions
442
*/
443
object ContentUtils {
444
/** Get content length if available */
445
fun getContentLength(content: OutgoingContent): Long?
446
447
/** Check if content is empty */
448
fun isEmpty(content: OutgoingContent): Boolean
449
450
/** Convert content to byte array */
451
suspend fun toByteArray(content: OutgoingContent): ByteArray
452
453
/** Convert content to string */
454
suspend fun toString(content: OutgoingContent, charset: Charset = Charsets.UTF_8): String
455
}
456
457
/**
458
* Content type utilities
459
*/
460
fun ContentType.withCharset(charset: Charset): ContentType
461
fun ContentType.withParameter(name: String, value: String): ContentType
462
fun ContentType.match(other: ContentType): Boolean
463
fun ContentType.match(pattern: ContentType): Boolean
464
```
465
466
## Types
467
468
```kotlin { .api }
469
// Content type definitions
470
class ContentType private constructor(
471
val contentType: String,
472
val contentSubtype: String,
473
val parameters: List<HeaderValueParam> = emptyList()
474
) {
475
fun withParameter(name: String, value: String): ContentType
476
fun withoutParameters(): ContentType
477
fun match(other: ContentType): Boolean
478
479
companion object {
480
fun parse(value: String): ContentType
481
482
object Any {
483
val Any = ContentType("*", "*")
484
}
485
486
object Application {
487
val Any = ContentType("application", "*")
488
val Atom = ContentType("application", "atom+xml")
489
val Json = ContentType("application", "json")
490
val JavaScript = ContentType("application", "javascript")
491
val OctetStream = ContentType("application", "octet-stream")
492
val FontWoff = ContentType("application", "font-woff")
493
val Rss = ContentType("application", "rss+xml")
494
val Xml = ContentType("application", "xml")
495
val Zip = ContentType("application", "zip")
496
val GZip = ContentType("application", "gzip")
497
val FormUrlEncoded = ContentType("application", "x-www-form-urlencoded")
498
val Pdf = ContentType("application", "pdf")
499
}
500
501
object Audio {
502
val Any = ContentType("audio", "*")
503
val MP4 = ContentType("audio", "mp4")
504
val MPEG = ContentType("audio", "mpeg")
505
val OGG = ContentType("audio", "ogg")
506
}
507
508
object Image {
509
val Any = ContentType("image", "*")
510
val GIF = ContentType("image", "gif")
511
val JPEG = ContentType("image", "jpeg")
512
val PNG = ContentType("image", "png")
513
val SVG = ContentType("image", "svg+xml")
514
val XIcon = ContentType("image", "x-icon")
515
}
516
517
object MultiPart {
518
val Any = ContentType("multipart", "*")
519
val Mixed = ContentType("multipart", "mixed")
520
val Alternative = ContentType("multipart", "alternative")
521
val Related = ContentType("multipart", "related")
522
val FormData = ContentType("multipart", "form-data")
523
val Signed = ContentType("multipart", "signed")
524
val Encrypted = ContentType("multipart", "encrypted")
525
val ByteRanges = ContentType("multipart", "byteranges")
526
}
527
528
object Text {
529
val Any = ContentType("text", "*")
530
val Plain = ContentType("text", "plain")
531
val CSS = ContentType("text", "css")
532
val CSV = ContentType("text", "csv")
533
val Html = ContentType("text", "html")
534
val JavaScript = ContentType("text", "javascript")
535
val VCard = ContentType("text", "vcard")
536
val Xml = ContentType("text", "xml")
537
val EventStream = ContentType("text", "event-stream")
538
}
539
540
object Video {
541
val Any = ContentType("video", "*")
542
val MPEG = ContentType("video", "mpeg")
543
val MP4 = ContentType("video", "mp4")
544
val OGG = ContentType("video", "ogg")
545
val QuickTime = ContentType("video", "quicktime")
546
}
547
}
548
}
549
550
// Parameter types
551
data class HeaderValueParam(
552
val name: String,
553
val value: String
554
)
555
556
// Parameters interface
557
interface Parameters {
558
operator fun get(name: String): String?
559
fun getAll(name: String): List<String>?
560
fun contains(name: String): Boolean
561
fun contains(name: String, value: String): Boolean
562
fun names(): Set<String>
563
fun isEmpty(): Boolean
564
fun entries(): Set<Map.Entry<String, List<String>>>
565
}
566
567
class ParametersBuilder {
568
fun append(name: String, value: String)
569
fun appendAll(name: String, values: Iterable<String>)
570
fun appendAll(parameters: Parameters)
571
fun appendMissing(parameters: Parameters)
572
fun set(name: String, value: String)
573
fun remove(name: String): Boolean
574
fun removeKeysWithNoEntries()
575
fun clear()
576
fun build(): Parameters
577
}
578
```