0
# Form Data and File Uploads
1
2
Comprehensive form submission and file upload functionality including URL-encoded forms, multipart forms, and binary data handling.
3
4
## Capabilities
5
6
### Form Submission Functions
7
8
Submit forms with URL-encoded and multipart data formats.
9
10
```kotlin { .api }
11
/**
12
* Submit URL-encoded form data
13
* @param url Target URL
14
* @param formParameters Form fields as Parameters
15
* @param encodeInQuery Whether to encode parameters in query string (GET)
16
* @returns HttpResponse
17
*/
18
suspend fun HttpClient.submitForm(
19
url: String,
20
formParameters: Parameters = Parameters.Empty,
21
encodeInQuery: Boolean = false
22
): HttpResponse
23
24
/**
25
* Submit multipart form data with binary support
26
* @param url Target URL
27
* @param formData List of form parts including files
28
* @returns HttpResponse
29
*/
30
suspend fun HttpClient.submitFormWithBinaryData(
31
url: String,
32
formData: List<PartData>
33
): HttpResponse
34
```
35
36
**Usage Examples:**
37
38
```kotlin
39
val client = HttpClient()
40
41
// Submit simple form
42
val response = client.submitForm(
43
url = "https://example.com/login",
44
formParameters = Parameters.build {
45
append("username", "john")
46
append("password", "secret")
47
append("remember", "true")
48
}
49
)
50
51
// Submit form as GET request with query parameters
52
val searchResponse = client.submitForm(
53
url = "https://example.com/search",
54
formParameters = Parameters.build {
55
append("q", "kotlin")
56
append("type", "code")
57
},
58
encodeInQuery = true
59
)
60
61
// Submit multipart form with file
62
val uploadResponse = client.submitFormWithBinaryData(
63
url = "https://example.com/upload",
64
formData = formData {
65
append("description", "Profile photo")
66
append("category", "image")
67
append("file", File("photo.jpg").readBytes(), Headers.build {
68
append(HttpHeaders.ContentType, "image/jpeg")
69
append(HttpHeaders.ContentDisposition, """form-data; name="file"; filename="photo.jpg"""")
70
})
71
}
72
)
73
```
74
75
### Form Data Content Classes
76
77
Content classes for different form data types.
78
79
```kotlin { .api }
80
/**
81
* URL-encoded form data content
82
*/
83
class FormDataContent(
84
val formData: Parameters
85
) : OutgoingContent.ByteArrayContent() {
86
override val contentType: ContentType
87
override val contentLength: Long?
88
override fun bytes(): ByteArray
89
}
90
91
/**
92
* Multipart form data content
93
*/
94
class MultiPartFormDataContent(
95
val parts: List<PartData>,
96
val boundary: String = generateBoundary()
97
) : OutgoingContent.WriteChannelContent() {
98
override val contentType: ContentType
99
override suspend fun writeTo(channel: ByteWriteChannel)
100
}
101
```
102
103
**Usage Examples:**
104
105
```kotlin
106
// Create form data content manually
107
val formContent = FormDataContent(Parameters.build {
108
append("name", "John Doe")
109
append("email", "john@example.com")
110
})
111
112
val response = client.post("https://example.com/users") {
113
setBody(formContent)
114
}
115
116
// Create multipart content manually
117
val multipartContent = MultiPartFormDataContent(
118
parts = listOf(
119
PartData.FormItem("John Doe", dispose = {}, partHeaders = Headers.build {
120
append(HttpHeaders.ContentDisposition, """form-data; name="name"""")
121
}),
122
PartData.FileItem(
123
provider = { File("document.pdf").readChannel() },
124
dispose = {},
125
partHeaders = Headers.build {
126
append(HttpHeaders.ContentType, "application/pdf")
127
append(HttpHeaders.ContentDisposition, """form-data; name="file"; filename="document.pdf"""")
128
}
129
)
130
)
131
)
132
133
val uploadResponse = client.post("https://example.com/upload") {
134
setBody(multipartContent)
135
}
136
```
137
138
### Form Data Builder
139
140
DSL for building form data with type safety and convenience methods.
141
142
```kotlin { .api }
143
/**
144
* Build form data using DSL
145
* @param block Form building block
146
* @returns List of PartData for multipart submission
147
*/
148
fun formData(block: FormBuilder.() -> Unit): List<PartData>
149
150
/**
151
* Form builder DSL class
152
*/
153
class FormBuilder {
154
/**
155
* Append text field
156
* @param key Field name
157
* @param value Field value
158
* @param headers Optional headers for this part
159
*/
160
fun append(key: String, value: String, headers: Headers = Headers.Empty)
161
162
/**
163
* Append binary data
164
* @param key Field name
165
* @param value Binary data
166
* @param headers Headers including content type and disposition
167
*/
168
fun append(key: String, value: ByteArray, headers: Headers = Headers.Empty)
169
170
/**
171
* Append file input stream
172
* @param key Field name
173
* @param value Input provider function
174
* @param headers Headers including content type and disposition
175
*/
176
fun append(key: String, value: () -> ByteReadChannel, headers: Headers = Headers.Empty)
177
178
/**
179
* Append channel content
180
* @param key Field name
181
* @param value Channel with content
182
* @param headers Headers including content type and disposition
183
*/
184
fun appendInput(key: String, value: ByteReadChannel, headers: Headers = Headers.Empty)
185
186
/**
187
* Append Input object (unified input abstraction)
188
* @param key Field name
189
* @param value Input object
190
* @param headers Headers including content type and disposition
191
*/
192
fun appendInput(key: String, value: Input, headers: Headers = Headers.Empty)
193
}
194
```
195
196
**Usage Examples:**
197
198
```kotlin
199
// Build complex form data
200
val formData = formData {
201
// Text fields
202
append("name", "John Doe")
203
append("email", "john@example.com")
204
append("age", "30")
205
206
// File from bytes
207
val imageBytes = File("profile.jpg").readBytes()
208
append("profile_image", imageBytes, Headers.build {
209
append(HttpHeaders.ContentType, "image/jpeg")
210
append(HttpHeaders.ContentDisposition, """form-data; name="profile_image"; filename="profile.jpg"""")
211
})
212
213
// File from stream provider
214
append("document", { File("report.pdf").readChannel() }, Headers.build {
215
append(HttpHeaders.ContentType, "application/pdf")
216
append(HttpHeaders.ContentDisposition, """form-data; name="document"; filename="report.pdf"""")
217
})
218
219
// Input stream
220
val dataStream = ByteReadChannel("large data content")
221
appendInput("data_file", dataStream, Headers.build {
222
append(HttpHeaders.ContentType, "text/plain")
223
append(HttpHeaders.ContentDisposition, """form-data; name="data_file"; filename="data.txt"""")
224
})
225
}
226
227
val response = client.submitFormWithBinaryData("https://example.com/upload", formData)
228
```
229
230
### PartData Sealed Class
231
232
Sealed class hierarchy for different types of form parts.
233
234
```kotlin { .api }
235
/**
236
* Base class for form parts
237
*/
238
sealed class PartData {
239
/** Headers for this part */
240
abstract val headers: Headers
241
242
/** Cleanup function */
243
abstract val dispose: () -> Unit
244
245
/**
246
* Text form field
247
*/
248
data class FormItem(
249
val value: String,
250
override val dispose: () -> Unit,
251
override val headers: Headers = Headers.Empty
252
) : PartData()
253
254
/**
255
* File form field with streaming content
256
*/
257
data class FileItem(
258
val provider: () -> ByteReadChannel,
259
override val dispose: () -> Unit,
260
override val headers: Headers = Headers.Empty
261
) : PartData()
262
263
/**
264
* Binary data form field
265
*/
266
data class BinaryItem(
267
val provider: () -> ByteArray,
268
override val dispose: () -> Unit,
269
override val headers: Headers = Headers.Empty
270
) : PartData()
271
272
/**
273
* Channel-based form field
274
*/
275
data class BinaryChannelItem(
276
val provider: () -> ByteReadChannel,
277
override val dispose: () -> Unit,
278
override val headers: Headers = Headers.Empty
279
) : PartData()
280
}
281
```
282
283
### Parameters Builder
284
285
Builder for URL-encoded form parameters.
286
287
```kotlin { .api }
288
/**
289
* Parameters collection interface
290
*/
291
interface Parameters : StringValues {
292
companion object {
293
val Empty: Parameters
294
295
/**
296
* Build parameters using DSL
297
* @param block Parameter building block
298
* @returns Parameters collection
299
*/
300
fun build(block: ParametersBuilder.() -> Unit): Parameters
301
}
302
}
303
304
/**
305
* Parameters builder class
306
*/
307
class ParametersBuilder : StringValuesBuilder() {
308
/**
309
* Append parameter value
310
* @param name Parameter name
311
* @param value Parameter value
312
*/
313
override fun append(name: String, value: String)
314
315
/**
316
* Append all parameters from another collection
317
* @param parameters Source parameters
318
*/
319
fun appendAll(parameters: Parameters)
320
321
/**
322
* Append parameter with multiple values
323
* @param name Parameter name
324
* @param values Parameter values
325
*/
326
fun appendAll(name: String, values: Iterable<String>)
327
328
/**
329
* Set parameter value (replaces existing)
330
* @param name Parameter name
331
* @param value Parameter value
332
*/
333
override fun set(name: String, value: String)
334
335
/**
336
* Build final Parameters collection
337
* @returns Immutable Parameters
338
*/
339
override fun build(): Parameters
340
}
341
```
342
343
**Usage Examples:**
344
345
```kotlin
346
// Build parameters for form submission
347
val params = Parameters.build {
348
append("username", "john")
349
append("email", "john@example.com")
350
append("interests", "kotlin")
351
append("interests", "programming") // Multiple values for same key
352
353
// Conditional parameters
354
if (rememberMe) {
355
append("remember", "true")
356
}
357
358
// Append from map
359
val additionalParams = mapOf("source" to "mobile", "version" to "1.0")
360
additionalParams.forEach { (key, value) ->
361
append(key, value)
362
}
363
}
364
365
val response = client.submitForm("https://example.com/register", params)
366
367
// Access parameter values
368
val username = params["username"] // Single value
369
val interests = params.getAll("interests") // All values for key
370
val allNames = params.names() // All parameter names
371
```
372
373
### Content-Type and Headers
374
375
Utilities for setting proper content types and headers for form data.
376
377
```kotlin { .api }
378
/**
379
* Generate multipart boundary string
380
* @returns Random boundary string
381
*/
382
fun generateBoundary(): String
383
384
/**
385
* Create Content-Disposition header for form field
386
* @param name Field name
387
* @param filename Optional filename
388
* @returns Content-Disposition header value
389
*/
390
fun formDataContentDisposition(name: String, filename: String? = null): String
391
392
/**
393
* Common content types for file uploads
394
*/
395
object ContentTypes {
396
val MultipartFormData: ContentType
397
val ApplicationFormUrlEncoded: ContentType
398
val ApplicationOctetStream: ContentType
399
val TextPlain: ContentType
400
val ImageJpeg: ContentType
401
val ImagePng: ContentType
402
val ApplicationPdf: ContentType
403
}
404
```
405
406
**Usage Examples:**
407
408
```kotlin
409
// Custom headers for file upload
410
val headers = Headers.build {
411
append(HttpHeaders.ContentType, "image/png")
412
append(HttpHeaders.ContentDisposition, formDataContentDisposition("avatar", "profile.png"))
413
append("X-Upload-Source", "mobile-app")
414
}
415
416
val formData = formData {
417
append("user_id", "123")
418
append("avatar", File("profile.png").readBytes(), headers)
419
}
420
421
// Manual content type handling
422
val response = client.post("https://example.com/upload") {
423
setBody(MultiPartFormDataContent(formData))
424
// Content-Type with boundary is set automatically
425
}
426
```
427
428
## Types
429
430
### Form Data Types
431
432
```kotlin { .api }
433
/**
434
* String values collection interface
435
*/
436
interface StringValues {
437
val caseInsensitiveName: Boolean
438
439
fun get(name: String): String?
440
fun getAll(name: String): List<String>?
441
fun names(): Set<String>
442
fun isEmpty(): Boolean
443
fun entries(): Set<Map.Entry<String, List<String>>>
444
fun forEach(body: (String, List<String>) -> Unit)
445
446
operator fun contains(name: String): Boolean
447
operator fun contains(name: String, value: String): Boolean
448
}
449
450
/**
451
* Headers collection
452
*/
453
interface Headers : StringValues {
454
companion object {
455
val Empty: Headers
456
457
fun build(block: HeadersBuilder.() -> Unit): Headers
458
}
459
}
460
461
/**
462
* HTTP header names constants
463
*/
464
object HttpHeaders {
465
const val ContentType = "Content-Type"
466
const val ContentLength = "Content-Length"
467
const val ContentDisposition = "Content-Disposition"
468
const val ContentEncoding = "Content-Encoding"
469
const val Authorization = "Authorization"
470
const val UserAgent = "User-Agent"
471
const val Accept = "Accept"
472
const val AcceptEncoding = "Accept-Encoding"
473
const val AcceptLanguage = "Accept-Language"
474
const val CacheControl = "Cache-Control"
475
const val Connection = "Connection"
476
const val Cookie = "Cookie"
477
const val SetCookie = "Set-Cookie"
478
}
479
```
480
481
### File and Input Types
482
483
```kotlin { .api }
484
/**
485
* Input abstraction for different data sources
486
*/
487
interface Input : Closeable {
488
suspend fun readPacket(size: Int): ByteReadPacket
489
suspend fun readBuffer(size: Int): Buffer
490
suspend fun discard(max: Long): Long
491
suspend fun close()
492
}
493
494
/**
495
* Channel for reading bytes
496
*/
497
interface ByteReadChannel {
498
val availableForRead: Int
499
val isClosedForRead: Boolean
500
val isClosedForWrite: Boolean
501
val totalBytesRead: Long
502
503
suspend fun readByte(): Byte
504
suspend fun readPacket(size: Int): ByteReadPacket
505
suspend fun readRemaining(limit: Long = Long.MAX_VALUE): ByteReadPacket
506
suspend fun readBuffer(size: Int): Buffer
507
suspend fun discard(max: Long = Long.MAX_VALUE): Long
508
suspend fun cancel(cause: Throwable?)
509
}
510
511
/**
512
* Channel for writing bytes
513
*/
514
interface ByteWriteChannel {
515
val availableForWrite: Int
516
val isClosedForWrite: Boolean
517
val totalBytesWritten: Long
518
519
suspend fun writeByte(b: Byte)
520
suspend fun writePacket(packet: ByteReadPacket)
521
suspend fun writeBuffer(buffer: Buffer): Int
522
suspend fun flush()
523
suspend fun close(cause: Throwable?)
524
}
525
```