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