or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client-configuration.mdcontent-handling.mdengine-architecture.mdevents-monitoring.mdhttp-statement.mdindex.mdplugin-system.mdrequest-building.mdresponse-handling.mdwebsocket-support.md

content-handling.mddocs/

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

```