or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

built-in-plugins.mdengine-configuration.mdform-data-content.mdhttp-client.mdindex.mdplugin-system.mdrequest-building.mdresponse-handling.md

form-data-content.mddocs/

0

# Form Data and Content Handling

1

2

Content handling for form data, multipart uploads, file uploads, and various content types with proper encoding and streaming support for HTTP client requests.

3

4

## Capabilities

5

6

### Form Data Submission

7

8

Standard HTML form submission with URL-encoded and multipart formats.

9

10

```kotlin { .api }

11

/**

12

* Submit HTML form with URL-encoded parameters

13

* @param url Target URL for form submission

14

* @param formParameters Form parameters to submit

15

* @param encodeInQuery Whether to encode parameters in query string (GET-style)

16

* @param block Additional request configuration

17

* @return HTTP response

18

*/

19

suspend fun HttpClient.submitForm(

20

url: String,

21

formParameters: Parameters = Parameters.Empty,

22

encodeInQuery: Boolean = false,

23

block: HttpRequestBuilder.() -> Unit = {}

24

): HttpResponse

25

26

/**

27

* Submit form with binary data using multipart/form-data

28

* @param url Target URL for form submission

29

* @param formData List of form parts including files

30

* @param block Additional request configuration

31

* @return HTTP response

32

*/

33

suspend fun HttpClient.submitFormWithBinaryData(

34

url: String,

35

formData: List<PartData>,

36

block: HttpRequestBuilder.() -> Unit = {}

37

): HttpResponse

38

```

39

40

**Usage Examples:**

41

42

```kotlin

43

import io.ktor.client.*

44

import io.ktor.client.request.*

45

import io.ktor.client.request.forms.*

46

import io.ktor.http.*

47

48

val client = HttpClient()

49

50

// Simple form submission

51

val formResponse = client.submitForm(

52

url = "https://example.com/submit",

53

formParameters = Parameters.build {

54

append("username", "john_doe")

55

append("password", "secret123")

56

append("remember", "true")

57

}

58

)

59

60

// Form submission with query encoding (GET-style)

61

val getFormResponse = client.submitForm(

62

url = "https://example.com/search",

63

formParameters = Parameters.build {

64

append("q", "kotlin http client")

65

append("page", "1")

66

append("limit", "10")

67

},

68

encodeInQuery = true

69

)

70

71

// Multipart form with file upload

72

val uploadResponse = client.submitFormWithBinaryData(

73

url = "https://example.com/upload",

74

formData = formData {

75

append("description", "File upload example")

76

append("category", "documents")

77

append("file", File("document.pdf").readBytes(), Headers.build {

78

append(HttpHeaders.ContentType, "application/pdf")

79

append(HttpHeaders.ContentDisposition, "filename=\"document.pdf\"")

80

})

81

}

82

)

83

```

84

85

### FormDataContent

86

87

URL-encoded form data content for standard form submissions.

88

89

```kotlin { .api }

90

/**

91

* Content class for URL-encoded form data (application/x-www-form-urlencoded)

92

*/

93

class FormDataContent(

94

private val formData: Parameters

95

) : OutgoingContent.ByteArrayContent() {

96

override val contentType: ContentType = ContentType.Application.FormUrlEncoded

97

override val contentLength: Long?

98

override fun bytes(): ByteArray

99

}

100

```

101

102

**Usage Examples:**

103

104

```kotlin

105

import io.ktor.client.*

106

import io.ktor.client.request.*

107

import io.ktor.client.request.forms.*

108

import io.ktor.http.*

109

110

val client = HttpClient()

111

112

// Using FormDataContent directly

113

val formData = Parameters.build {

114

append("email", "user@example.com")

115

append("name", "John Smith")

116

append("age", "30")

117

append("interests", "kotlin")

118

append("interests", "programming")

119

}

120

121

val response = client.post("https://api.example.com/users") {

122

setBody(FormDataContent(formData))

123

}

124

125

// Equivalent to submitForm but with more control

126

val controlledResponse = client.post("https://api.example.com/login") {

127

headers {

128

append("X-Client-Version", "1.0")

129

}

130

setBody(FormDataContent(Parameters.build {

131

append("username", "user")

132

append("password", "pass")

133

append("device_id", "mobile-123")

134

}))

135

}

136

```

137

138

### MultiPartFormDataContent

139

140

Multipart form data for file uploads and mixed content types.

141

142

```kotlin { .api }

143

/**

144

* Content class for multipart/form-data submissions

145

*/

146

class MultiPartFormDataContent(

147

private val formData: List<PartData>,

148

override val contentType: ContentType = ContentType.MultiPart.FormData.withParameter("boundary", boundary),

149

override val contentLength: Long? = null

150

) : OutgoingContent.WriteChannelContent() {

151

override suspend fun writeTo(channel: ByteWriteChannel)

152

}

153

154

/**

155

* Multipart form data builder

156

*/

157

class MultiPartFormDataContent.Builder {

158

/** Add text part */

159

fun append(key: String, value: String)

160

161

/** Add text part with headers */

162

fun append(key: String, value: String, headers: Headers)

163

164

/** Add binary part */

165

fun append(key: String, data: ByteArray, headers: Headers = Headers.Empty)

166

167

/** Add input provider part */

168

fun append(key: String, provider: InputProvider, headers: Headers = Headers.Empty)

169

170

/** Add channel provider part */

171

fun append(key: String, channelProvider: ChannelProvider, headers: Headers = Headers.Empty)

172

173

/** Build the multipart content */

174

fun build(): MultiPartFormDataContent

175

}

176

177

/**

178

* DSL for building multipart form data

179

*/

180

fun formData(block: MultiPartFormDataContent.Builder.() -> Unit): List<PartData>

181

```

182

183

**Usage Examples:**

184

185

```kotlin

186

import io.ktor.client.*

187

import io.ktor.client.request.*

188

import io.ktor.client.request.forms.*

189

import io.ktor.http.*

190

import java.io.File

191

192

val client = HttpClient()

193

194

// File upload with additional form fields

195

val uploadResponse = client.post("https://api.example.com/upload") {

196

setBody(MultiPartFormDataContent(

197

formData {

198

// Text fields

199

append("title", "My Document")

200

append("description", "Important document for project")

201

append("category", "reports")

202

203

// File upload

204

append("document", File("report.pdf").readBytes(), Headers.build {

205

append(HttpHeaders.ContentType, "application/pdf")

206

append(HttpHeaders.ContentDisposition, "filename=\"report.pdf\"")

207

})

208

209

// Image upload

210

append("thumbnail", File("thumb.jpg").readBytes(), Headers.build {

211

append(HttpHeaders.ContentType, "image/jpeg")

212

append(HttpHeaders.ContentDisposition, "filename=\"thumbnail.jpg\"")

213

})

214

}

215

))

216

}

217

218

// Multiple file upload

219

val multiFileResponse = client.post("https://api.example.com/batch-upload") {

220

setBody(MultiPartFormDataContent(

221

formData {

222

append("batch_id", "batch-001")

223

append("user_id", "12345")

224

225

// Multiple files

226

listOf("file1.txt", "file2.txt", "file3.txt").forEach { filename ->

227

append("files", File(filename).readBytes(), Headers.build {

228

append(HttpHeaders.ContentType, ContentType.Text.Plain.toString())

229

append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")

230

})

231

}

232

}

233

))

234

}

235

236

// Mixed content types

237

val mixedResponse = client.post("https://api.example.com/mixed") {

238

setBody(MultiPartFormDataContent(

239

formData {

240

// JSON data

241

append("metadata", """{"version": "1.0", "author": "John"}""", Headers.build {

242

append(HttpHeaders.ContentType, ContentType.Application.Json.toString())

243

})

244

245

// XML data

246

append("config", "<config><enabled>true</enabled></config>", Headers.build {

247

append(HttpHeaders.ContentType, ContentType.Application.Xml.toString())

248

})

249

250

// Binary data

251

append("data", byteArrayOf(0x89, 0x50, 0x4E, 0x47), Headers.build {

252

append(HttpHeaders.ContentType, ContentType.Application.OctetStream.toString())

253

})

254

}

255

))

256

}

257

```

258

259

### PartData Types

260

261

Individual part representations for multipart content.

262

263

```kotlin { .api }

264

/**

265

* Represents a single part in multipart content

266

*/

267

sealed class PartData {

268

/** Part headers */

269

abstract val headers: Headers

270

271

/** Dispose part resources */

272

abstract fun dispose()

273

}

274

275

/**

276

* Text/string part data

277

*/

278

class PartData.FormItem(

279

val value: String,

280

override val headers: Headers

281

) : PartData()

282

283

/**

284

* Binary part data

285

*/

286

class PartData.BinaryItem(

287

val provider: () -> Input,

288

override val headers: Headers,

289

val contentLength: Long? = null

290

) : PartData()

291

292

/**

293

* File part data

294

*/

295

class PartData.FileItem(

296

val provider: () -> Input,

297

override val headers: Headers,

298

val contentLength: Long? = null

299

) : PartData()

300

301

/**

302

* Channel-based part data for streaming

303

*/

304

class PartData.BinaryChannelItem(

305

val provider: () -> ByteReadChannel,

306

override val headers: Headers,

307

val contentLength: Long? = null

308

) : PartData()

309

```

310

311

**Usage Examples:**

312

313

```kotlin

314

import io.ktor.client.*

315

import io.ktor.client.request.*

316

import io.ktor.client.request.forms.*

317

import io.ktor.http.*

318

import io.ktor.utils.io.*

319

320

val client = HttpClient()

321

322

// Custom part data creation

323

val customParts = listOf(

324

// Text part

325

PartData.FormItem(

326

value = "Custom form value",

327

headers = Headers.build {

328

append(HttpHeaders.ContentDisposition, "form-data; name=\"custom_field\"")

329

}

330

),

331

332

// Binary part

333

PartData.BinaryItem(

334

provider = { File("data.bin").inputStream().asInput() },

335

headers = Headers.build {

336

append(HttpHeaders.ContentDisposition, "form-data; name=\"binary_data\"; filename=\"data.bin\"")

337

append(HttpHeaders.ContentType, ContentType.Application.OctetStream.toString())

338

},

339

contentLength = File("data.bin").length()

340

),

341

342

// Streaming part

343

PartData.BinaryChannelItem(

344

provider = {

345

// Create a channel with streaming data

346

produce {

347

repeat(1000) {

348

channel.writeStringUtf8("Line $it\n")

349

}

350

}

351

},

352

headers = Headers.build {

353

append(HttpHeaders.ContentDisposition, "form-data; name=\"stream_data\"; filename=\"stream.txt\"")

354

append(HttpHeaders.ContentType, ContentType.Text.Plain.toString())

355

}

356

)

357

)

358

359

val customResponse = client.post("https://api.example.com/custom-upload") {

360

setBody(MultiPartFormDataContent(customParts))

361

}

362

```

363

364

### Parameters Builder

365

366

Builder for creating URL parameters and form data.

367

368

```kotlin { .api }

369

/**

370

* Builder for creating Parameters collections

371

*/

372

class ParametersBuilder(size: Int = 8) : StringValuesBuilder {

373

/** Append single parameter value */

374

fun append(name: String, value: String)

375

376

/** Append all values from StringValues */

377

fun appendAll(stringValues: StringValues)

378

379

/** Append all values for specific name */

380

fun appendAll(name: String, values: Iterable<String>)

381

382

/** Append missing values from StringValues */

383

fun appendMissing(stringValues: StringValues)

384

385

/** Append missing values for specific name */

386

fun appendMissing(name: String, values: Iterable<String>)

387

388

/** Set single parameter value (replace existing) */

389

fun set(name: String, value: String)

390

391

/** Set all values from StringValues */

392

fun setAll(stringValues: StringValues)

393

394

/** Set all values for specific name */

395

fun setAll(name: String, values: Iterable<String>)

396

397

/** Remove parameter by name */

398

fun remove(name: String): Boolean

399

400

/** Remove parameters with no values */

401

fun removeKeysWithNoEntries()

402

403

/** Clear all parameters */

404

fun clear()

405

406

/** Build final Parameters instance */

407

fun build(): Parameters

408

}

409

410

/**

411

* Parameters collection interface

412

*/

413

interface Parameters : StringValues {

414

companion object {

415

/** Empty parameters instance */

416

val Empty: Parameters

417

418

/** Build parameters using DSL */

419

inline fun build(builder: ParametersBuilder.() -> Unit): Parameters

420

}

421

}

422

```

423

424

**Usage Examples:**

425

426

```kotlin

427

import io.ktor.client.*

428

import io.ktor.client.request.*

429

import io.ktor.client.request.forms.*

430

import io.ktor.http.*

431

432

// Building parameters

433

val params = Parameters.build {

434

append("query", "kotlin")

435

append("sort", "relevance")

436

append("filter", "language:kotlin")

437

append("filter", "type:repository")

438

439

// Conditional parameters

440

if (includeArchived) {

441

append("archived", "true")

442

}

443

444

// From existing parameters

445

appendAll(existingParams)

446

447

// Set (replace) parameter

448

set("per_page", "50")

449

}

450

451

// Complex parameter building

452

val searchParams = ParametersBuilder().apply {

453

append("q", searchQuery)

454

append("page", currentPage.toString())

455

append("size", pageSize.toString())

456

457

// Multiple values

458

selectedCategories.forEach { category ->

459

append("category", category)

460

}

461

462

// Conditional parameters

463

if (sortBy.isNotEmpty()) {

464

append("sort", sortBy)

465

append("order", sortOrder)

466

}

467

468

// Date range

469

startDate?.let { append("start_date", it.toString()) }

470

endDate?.let { append("end_date", it.toString()) }

471

}.build()

472

473

// Use in request

474

val searchResponse = client.get("https://api.example.com/search") {

475

url {

476

parameters.appendAll(searchParams)

477

}

478

}

479

480

// Use in form submission

481

val formResponse = client.submitForm(

482

url = "https://example.com/search",

483

formParameters = searchParams

484

)

485

```

486

487

### Content Types and Utilities

488

489

Content type utilities for form and multipart handling.

490

491

```kotlin { .api }

492

/**

493

* Common content types for form data

494

*/

495

object ContentType {

496

object Application {

497

val FormUrlEncoded = ContentType("application", "x-www-form-urlencoded")

498

val Json = ContentType("application", "json")

499

val Xml = ContentType("application", "xml")

500

val OctetStream = ContentType("application", "octet-stream")

501

val Pdf = ContentType("application", "pdf")

502

}

503

504

object Text {

505

val Plain = ContentType("text", "plain")

506

val Html = ContentType("text", "html")

507

val Css = ContentType("text", "css")

508

val JavaScript = ContentType("text", "javascript")

509

}

510

511

object Image {

512

val Jpeg = ContentType("image", "jpeg")

513

val Png = ContentType("image", "png")

514

val Gif = ContentType("image", "gif")

515

val WebP = ContentType("image", "webp")

516

}

517

518

object MultiPart {

519

val FormData = ContentType("multipart", "form-data")

520

val Mixed = ContentType("multipart", "mixed")

521

val Alternative = ContentType("multipart", "alternative")

522

}

523

}

524

525

/**

526

* Content disposition utilities

527

*/

528

object ContentDisposition {

529

fun attachment(filename: String? = null): String

530

fun inline(filename: String? = null): String

531

fun formData(name: String, filename: String? = null): String

532

}

533

```

534

535

**Usage Examples:**

536

537

```kotlin

538

import io.ktor.client.*

539

import io.ktor.client.request.*

540

import io.ktor.client.request.forms.*

541

import io.ktor.http.*

542

543

// File upload with proper content types

544

val fileUploadResponse = client.post("https://api.example.com/files") {

545

setBody(MultiPartFormDataContent(

546

formData {

547

// Document

548

append("document", pdfBytes, Headers.build {

549

append(HttpHeaders.ContentType, ContentType.Application.Pdf.toString())

550

append(HttpHeaders.ContentDisposition, ContentDisposition.formData("document", "report.pdf"))

551

})

552

553

// Image

554

append("image", imageBytes, Headers.build {

555

append(HttpHeaders.ContentType, ContentType.Image.Jpeg.toString())

556

append(HttpHeaders.ContentDisposition, ContentDisposition.formData("image", "photo.jpg"))

557

})

558

559

// JSON metadata

560

append("metadata", jsonString, Headers.build {

561

append(HttpHeaders.ContentType, ContentType.Application.Json.toString())

562

append(HttpHeaders.ContentDisposition, ContentDisposition.formData("metadata"))

563

})

564

}

565

))

566

}

567

```

568

569

## Types

570

571

### Form Data Types

572

573

```kotlin { .api }

574

interface StringValues {

575

operator fun get(name: String): String?

576

fun getAll(name: String): List<String>?

577

fun contains(name: String): Boolean

578

fun contains(name: String, value: String): Boolean

579

fun names(): Set<String>

580

fun isEmpty(): Boolean

581

fun entries(): Set<Map.Entry<String, List<String>>>

582

fun toMap(): Map<String, List<String>>

583

}

584

585

interface StringValuesBuilder {

586

fun get(name: String): String?

587

fun getAll(name: String): List<String>?

588

fun contains(name: String): Boolean

589

fun names(): Set<String>

590

fun isEmpty(): Boolean

591

fun entries(): Set<Map.Entry<String, List<String>>>

592

fun append(name: String, value: String)

593

fun appendAll(stringValues: StringValues)

594

fun appendAll(name: String, values: Iterable<String>)

595

fun appendMissing(stringValues: StringValues)

596

fun appendMissing(name: String, values: Iterable<String>)

597

fun set(name: String, value: String)

598

fun setAll(stringValues: StringValues)

599

fun setAll(name: String, values: Iterable<String>)

600

fun remove(name: String): Boolean

601

fun removeKeysWithNoEntries()

602

fun clear()

603

fun build(): StringValues

604

}

605

606

typealias InputProvider = () -> Input

607

typealias ChannelProvider = () -> ByteReadChannel

608

609

class Input : Closeable {

610

fun readAvailable(buffer: ByteArray): Int

611

fun readFully(buffer: ByteArray)

612

override fun close()

613

}

614

```