0
# Content Framework
1
2
Outgoing content types for representing different data sources and formats in HTTP responses, with support for multipart data, versioning, and caching.
3
4
## Capabilities
5
6
### OutgoingContent Base Class
7
8
Abstract base class for all outgoing HTTP content with common properties and extensibility.
9
10
```kotlin { .api }
11
/**
12
* Base class for all outgoing HTTP content
13
*/
14
abstract class OutgoingContent {
15
open val contentType: ContentType?
16
open val contentLength: Long?
17
open val status: HttpStatusCode?
18
open val headers: Headers?
19
20
/**
21
* Get content property by key
22
* @param key Property key
23
* @return Property value or null
24
*/
25
fun <T : Any> getProperty(key: AttributeKey<T>): T?
26
27
/**
28
* Set content property
29
* @param key Property key
30
* @param value Property value
31
*/
32
fun <T : Any> setProperty(key: AttributeKey<T>, value: T?)
33
34
/**
35
* Get trailer headers (sent after content)
36
* @return Trailer headers or null
37
*/
38
open fun trailers(): Headers?
39
}
40
41
/**
42
* Check if content is empty
43
* @return true if content has no body
44
*/
45
fun OutgoingContent.isEmpty(): Boolean
46
```
47
48
### ByteArrayContent
49
50
Content type for byte array data.
51
52
```kotlin { .api }
53
/**
54
* Content from byte array
55
*/
56
abstract class OutgoingContent.ByteArrayContent : OutgoingContent() {
57
/**
58
* Get content as byte array
59
* @return Content bytes
60
*/
61
abstract fun bytes(): ByteArray
62
}
63
64
/**
65
* Concrete implementation for byte array content
66
*/
67
class ByteArrayContent(
68
private val bytes: ByteArray,
69
override val contentType: ContentType?,
70
override val status: HttpStatusCode? = null
71
) : OutgoingContent.ByteArrayContent() {
72
override fun bytes(): ByteArray = bytes
73
override val contentLength: Long? = bytes.size.toLong()
74
}
75
```
76
77
### TextContent
78
79
Content type for text data with automatic encoding.
80
81
```kotlin { .api }
82
/**
83
* Content from text string
84
*/
85
class TextContent(
86
val text: String,
87
override val contentType: ContentType,
88
override val status: HttpStatusCode? = null
89
) : OutgoingContent.ByteArrayContent() {
90
91
override fun bytes(): ByteArray
92
override val contentLength: Long?
93
}
94
```
95
96
### ReadChannelContent
97
98
Content type for streaming data from ByteReadChannel.
99
100
```kotlin { .api }
101
/**
102
* Content from ByteReadChannel
103
*/
104
abstract class OutgoingContent.ReadChannelContent : OutgoingContent() {
105
/**
106
* Get read channel for content
107
* @return ByteReadChannel with content
108
*/
109
abstract fun readFrom(): ByteReadChannel
110
111
/**
112
* Get read channel for content range
113
* @param range Byte range to read
114
* @return ByteReadChannel with range content
115
*/
116
open fun readFrom(range: LongRange): ByteReadChannel = readFrom()
117
}
118
```
119
120
### WriteChannelContent
121
122
Content type for data written to ByteWriteChannel.
123
124
```kotlin { .api }
125
/**
126
* Content written to ByteWriteChannel
127
*/
128
abstract class OutgoingContent.WriteChannelContent : OutgoingContent() {
129
/**
130
* Write content to channel
131
* @param channel Channel to write to
132
*/
133
abstract suspend fun writeTo(channel: ByteWriteChannel)
134
}
135
136
/**
137
* Content written via lambda function
138
*/
139
class ChannelWriterContent(
140
private val body: suspend ByteWriteChannel.() -> Unit,
141
override val contentType: ContentType?,
142
override val status: HttpStatusCode? = null,
143
override val contentLength: Long? = null
144
) : OutgoingContent.WriteChannelContent() {
145
146
override suspend fun writeTo(channel: ByteWriteChannel) {
147
channel.body()
148
}
149
}
150
```
151
152
### Additional Content Types
153
154
Specialized content types for different output methods.
155
156
```kotlin { .api }
157
/**
158
* Content written to OutputStream
159
*/
160
class OutputStreamContent(
161
private val body: suspend OutputStream.() -> Unit,
162
override val contentType: ContentType?,
163
override val status: HttpStatusCode? = null,
164
override val contentLength: Long? = null
165
) : OutgoingContent.WriteChannelContent()
166
167
/**
168
* Content written to Writer
169
*/
170
class WriterContent(
171
private val body: suspend Writer.() -> Unit,
172
override val contentType: ContentType?,
173
override val status: HttpStatusCode? = null,
174
override val contentLength: Long? = null
175
) : OutgoingContent.WriteChannelContent()
176
177
/**
178
* Content from URI/URL
179
*/
180
class URIFileContent(
181
val uri: URI,
182
override val contentType: ContentType?,
183
override val contentLength: Long? = null
184
) : OutgoingContent.ReadChannelContent() {
185
186
constructor(url: URL, contentType: ContentType?)
187
188
override fun readFrom(): ByteReadChannel
189
}
190
```
191
192
### NoContent and Special Types
193
194
Special content types for empty responses and protocol upgrades.
195
196
```kotlin { .api }
197
/**
198
* Empty content (no body)
199
*/
200
abstract class OutgoingContent.NoContent : OutgoingContent()
201
202
/**
203
* Protocol upgrade content (e.g., WebSocket)
204
*/
205
abstract class OutgoingContent.ProtocolUpgrade : OutgoingContent() {
206
override val status: HttpStatusCode? = HttpStatusCode.SwitchingProtocols
207
208
/**
209
* Handle protocol upgrade
210
* @param input Input channel
211
* @param output Output channel
212
* @param engineContext Engine coroutine context
213
* @param userContext User coroutine context
214
*/
215
abstract suspend fun upgrade(
216
input: ByteReadChannel,
217
output: ByteWriteChannel,
218
engineContext: CoroutineContext,
219
userContext: CoroutineContext
220
)
221
}
222
223
/**
224
* Null body marker
225
*/
226
object NullBody
227
```
228
229
### Content Wrapper
230
231
Base class for wrapping and modifying existing content.
232
233
```kotlin { .api }
234
/**
235
* Content wrapper for modifying existing content
236
*/
237
abstract class OutgoingContent.ContentWrapper(
238
private val delegate: OutgoingContent
239
) : OutgoingContent() {
240
241
/**
242
* Create copy with different delegate
243
* @param delegate New delegate content
244
* @return New ContentWrapper instance
245
*/
246
abstract fun copy(delegate: OutgoingContent): OutgoingContent.ContentWrapper
247
248
/**
249
* Get wrapped content
250
* @return Delegate content
251
*/
252
fun delegate(): OutgoingContent = delegate
253
254
// Default implementations delegate to wrapped content
255
override val contentType: ContentType? get() = delegate.contentType
256
override val contentLength: Long? get() = delegate.contentLength
257
override val status: HttpStatusCode? get() = delegate.status
258
override val headers: Headers? get() = delegate.headers
259
override fun <T : Any> getProperty(key: AttributeKey<T>): T? = delegate.getProperty(key)
260
override fun <T : Any> setProperty(key: AttributeKey<T>, value: T?) = delegate.setProperty(key, value)
261
}
262
```
263
264
### Multipart Data
265
266
Interface and types for handling multipart content.
267
268
```kotlin { .api }
269
/**
270
* Interface for multipart data reading
271
*/
272
interface MultiPartData {
273
/**
274
* Read next part from multipart data
275
* @return Next PartData or null if no more parts
276
*/
277
suspend fun readPart(): PartData?
278
279
companion object {
280
val Empty: MultiPartData
281
}
282
}
283
284
/**
285
* Base class for multipart parts
286
*/
287
abstract class PartData(
288
private val dispose: () -> Unit,
289
val headers: Headers
290
) {
291
val contentDisposition: ContentDisposition?
292
val contentType: ContentType?
293
val name: String?
294
295
/**
296
* Dispose part resources
297
*/
298
fun dispose() = dispose.invoke()
299
}
300
301
/**
302
* Form field part
303
*/
304
class PartData.FormItem(
305
val value: String,
306
dispose: () -> Unit,
307
headers: Headers
308
) : PartData(dispose, headers)
309
310
/**
311
* File upload part
312
*/
313
class PartData.FileItem(
314
val provider: () -> InputStream,
315
dispose: () -> Unit,
316
headers: Headers
317
) : PartData(dispose, headers) {
318
val originalFileName: String?
319
val streamProvider: () -> InputStream // JVM-specific
320
}
321
322
/**
323
* Binary data part
324
*/
325
class PartData.BinaryItem(
326
val provider: () -> ByteArray,
327
dispose: () -> Unit,
328
headers: Headers
329
) : PartData(dispose, headers)
330
331
/**
332
* Binary channel part
333
*/
334
class PartData.BinaryChannelItem(
335
val provider: () -> ByteReadChannel,
336
dispose: () -> Unit,
337
headers: Headers
338
) : PartData(dispose, headers)
339
```
340
341
### Multipart Utilities
342
343
Utility functions for working with multipart data.
344
345
```kotlin { .api }
346
/**
347
* Convert multipart data to Flow
348
* @return Flow of PartData
349
*/
350
fun MultiPartData.asFlow(): Flow<PartData>
351
352
/**
353
* Read all parts into list
354
* @return List of all PartData
355
*/
356
suspend fun MultiPartData.readAllParts(): List<PartData>
357
358
/**
359
* Process each part with action
360
* @param action Suspend function to process each part
361
*/
362
suspend fun MultiPartData.forEachPart(action: suspend (PartData) -> Unit)
363
```
364
365
### Content Versioning
366
367
System for content versioning with ETags and Last-Modified headers.
368
369
```kotlin { .api }
370
/**
371
* Base interface for content versioning
372
*/
373
interface Version {
374
/**
375
* Check version against request headers
376
* @param headers Request headers
377
* @return Version check result
378
*/
379
fun check(headers: Headers): VersionCheckResult
380
381
/**
382
* Append version headers to response
383
* @param builder Headers builder
384
*/
385
fun appendHeadersTo(builder: HeadersBuilder)
386
}
387
388
/**
389
* ETag-based versioning
390
*/
391
class EntityTagVersion(
392
val etag: String,
393
val weak: Boolean = false
394
) : Version {
395
396
/**
397
* Check if this ETag matches another
398
* @param other Other ETag version
399
* @return true if matches
400
*/
401
fun match(other: EntityTagVersion): Boolean
402
403
/**
404
* Check if this ETag matches any in list
405
* @param etags List of ETag versions
406
* @return Version check result
407
*/
408
fun match(etags: List<EntityTagVersion>): VersionCheckResult
409
410
/**
411
* Check if this ETag matches none in list
412
* @param etags List of ETag versions
413
* @return Version check result
414
*/
415
fun noneMatch(etags: List<EntityTagVersion>): VersionCheckResult
416
417
companion object {
418
val STAR: EntityTagVersion
419
420
/**
421
* Parse ETag header value
422
* @param value ETag header value
423
* @return List of EntityTagVersion instances
424
*/
425
fun parse(value: String): List<EntityTagVersion>
426
427
/**
428
* Parse single ETag value
429
* @param value Single ETag value
430
* @return EntityTagVersion instance
431
*/
432
fun parseSingle(value: String): EntityTagVersion
433
}
434
}
435
436
/**
437
* Last-Modified based versioning
438
*/
439
class LastModifiedVersion(
440
val lastModified: GMTDate
441
) : Version {
442
443
/**
444
* Check if modified since dates
445
* @param dates List of dates to check
446
* @return true if modified
447
*/
448
fun ifModifiedSince(dates: List<GMTDate>): Boolean
449
450
/**
451
* Check if unmodified since dates
452
* @param dates List of dates to check
453
* @return true if unmodified
454
*/
455
fun ifUnmodifiedSince(dates: List<GMTDate>): Boolean
456
}
457
458
/**
459
* Version check result
460
*/
461
enum class VersionCheckResult(val statusCode: HttpStatusCode) {
462
OK(HttpStatusCode.OK),
463
NOT_MODIFIED(HttpStatusCode.NotModified),
464
PRECONDITION_FAILED(HttpStatusCode.PreconditionFailed)
465
}
466
467
/**
468
* Create EntityTagVersion from string
469
* @param etag ETag value
470
* @return EntityTagVersion instance
471
*/
472
fun EntityTagVersion(etag: String): EntityTagVersion
473
474
/**
475
* Create LastModifiedVersion from Date
476
* @param date Last modified date
477
* @return LastModifiedVersion instance
478
*/
479
fun LastModifiedVersion(date: Date): LastModifiedVersion
480
```
481
482
### Caching Options
483
484
System for content caching configuration.
485
486
```kotlin { .api }
487
/**
488
* Content caching configuration
489
*/
490
data class CachingOptions(
491
val cacheControl: CacheControl? = null,
492
val expires: GMTDate? = null
493
)
494
495
/**
496
* Get caching options from content
497
* @return CachingOptions or null
498
*/
499
fun OutgoingContent.getCaching(): CachingOptions?
500
501
/**
502
* Set caching options on content
503
* @param options Caching options to set
504
*/
505
fun OutgoingContent.setCaching(options: CachingOptions)
506
507
/**
508
* Get versions list from content
509
* @return List of Version instances
510
*/
511
fun OutgoingContent.getVersions(): List<Version>
512
513
/**
514
* Set versions on content
515
* @param versions List of Version instances
516
*/
517
fun OutgoingContent.setVersions(versions: List<Version>)
518
```
519
520
### Content Compression
521
522
Utilities for content compression.
523
524
```kotlin { .api }
525
/**
526
* Create compressed version of content
527
* @param encoder Content encoder to use
528
* @param coroutineContext Coroutine context for compression
529
* @return Compressed OutgoingContent
530
*/
531
fun OutgoingContent.compressed(
532
encoder: ContentEncoder,
533
coroutineContext: CoroutineContext = EmptyCoroutineContext
534
): OutgoingContent
535
```
536
537
**Usage Examples:**
538
539
```kotlin
540
import io.ktor.http.content.*
541
import io.ktor.http.*
542
543
// Create different content types
544
val textContent = TextContent("Hello World", ContentType.Text.Plain)
545
val jsonContent = TextContent("""{"message": "hello"}""", ContentType.Application.Json)
546
val binaryContent = ByteArrayContent(byteArrayOf(1, 2, 3), ContentType.Application.OctetStream)
547
548
// Create streaming content
549
val channelContent = ChannelWriterContent(
550
body = { writeStringUtf8("Hello from channel") },
551
contentType = ContentType.Text.Plain
552
)
553
554
// Work with multipart data
555
suspend fun processMultipart(multipart: MultiPartData) {
556
multipart.forEachPart { part ->
557
when (part) {
558
is PartData.FormItem -> println("Form field: ${part.name} = ${part.value}")
559
is PartData.FileItem -> println("File: ${part.originalFileName}")
560
is PartData.BinaryItem -> println("Binary data: ${part.provider().size} bytes")
561
}
562
part.dispose()
563
}
564
}
565
566
// Content versioning
567
val etag = EntityTagVersion("abc123")
568
val lastModified = LastModifiedVersion(GMTDate())
569
570
textContent.setVersions(listOf(etag, lastModified))
571
572
// Caching
573
val caching = CachingOptions(
574
cacheControl = CacheControl.MaxAge(3600),
575
expires = GMTDate(System.currentTimeMillis() + 3600000)
576
)
577
textContent.setCaching(caching)
578
579
// Check content properties
580
val isEmpty = textContent.isEmpty() // false
581
val versions = textContent.getVersions()
582
val cachingOptions = textContent.getCaching()
583
```