diff --git a/codeSnippets/snippets/upload-file/src/main/kotlin/uploadfile/UploadFile.kt b/codeSnippets/snippets/upload-file/src/main/kotlin/uploadfile/UploadFile.kt index 65e5a3e91..e7b569047 100644 --- a/codeSnippets/snippets/upload-file/src/main/kotlin/uploadfile/UploadFile.kt +++ b/codeSnippets/snippets/upload-file/src/main/kotlin/uploadfile/UploadFile.kt @@ -5,8 +5,8 @@ import io.ktor.http.content.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* +import io.ktor.util.cio.* import io.ktor.utils.io.* -import kotlinx.io.readByteArray import java.io.File fun Application.main() { @@ -25,8 +25,8 @@ fun Application.main() { is PartData.FileItem -> { fileName = part.originalFileName as String - val fileBytes = part.provider().readRemaining().readByteArray() - File("uploads/$fileName").writeBytes(fileBytes) + val file = File("uploads/$fileName") + part.provider().copyAndClose(file.writeChannel()) } else -> {} diff --git a/topics/migrating-3.md b/topics/migrating-3.md index 4662e72d1..4480caa5f 100644 --- a/topics/migrating-3.md +++ b/topics/migrating-3.md @@ -20,7 +20,7 @@ This restructuring comes with the following set of breaking changes: - [`ApplicationEngineEnvironmentBuilder` and `applicationEngineEnvironment` classes are renamed](#renamed-classes). - [`start()` and `stop()` methods are removed from `ApplicationEngineEnvironment`](#ApplicationEnvironment). -- [`commandLineEnvironment()` is removed.](#CommandLineConfig). +- [`commandLineEnvironment()` is removed](#CommandLineConfig). - [Introduction of `ServerConfigBuilder`](#ServerConfigBuilder). - [`embeddedServer()` returns`EmbeddedServer`](#EmbeddedServer) instead of `ApplicationEngine`. @@ -28,18 +28,18 @@ These changes will impact existing code that relies on the previous model. #### Renamed classes {id="renamed-classes"} -| Package | 2.x.x | 3.0.x | -|---------------------------|---------------------------------------|---------------------------------| +| Package | 2.x.x | 3.0.x | +|----------------------------|---------------------------------------|---------------------------------| | `io.ktor:ktor-server-core` | `ApplicationEngineEnvironmentBuilder` | `ApplicationEnvironmentBuilder` | | `io.ktor:ktor-server-core` | `applicationEngineEnvironment` | `applicationEnvironment` | #### `start()` and `stop()` methods are removed from `ApplicationEngineEnvironment` {id="ApplicationEnvironment"} -With the merge of `ApplicationEngineEnvironment` -to [`ApplicationEnvironment`](https://api.ktor.io/ktor-server/ktor-server-core/io.ktor.server.application/-application-environment/index.html), +With the merge of `ApplicationEngineEnvironment` to [ +`ApplicationEnvironment`](https://api.ktor.io/ktor-server/ktor-server-core/io.ktor.server.application/-application-environment/index.html), the `start()` and `stop()` methods are now -only accessible -through [`ApplicationEngine`](https://api.ktor.io/ktor-server/ktor-server-core/io.ktor.server.engine/-application-engine/index.html). +only accessible through [ +`ApplicationEngine`](https://api.ktor.io/ktor-server/ktor-server-core/io.ktor.server.engine/-application-engine/index.html). | 2.x.x | 3.0.x | |-------------------------------------------------------|--------------------------------------| @@ -79,7 +79,7 @@ fun defaultServer(module: Application.() -> Unit) = ) ``` -{validate="false" noinject} +{validate="false"} ```kotlin import io.ktor.server.application.* @@ -183,10 +183,9 @@ fun main(args: Array) { #### Introduction of `EmbeddedServer` {id="EmbeddedServer"} -The -class [`EmbeddedServer`](https://api.ktor.io/ktor-server/ktor-server-core/io.ktor.server.engine/-embedded-server/index.html) -is introduced and used to replace `ApplicationEngine` as a return type of the `embeddedServer()` -function. +The class +[`EmbeddedServer`](https://api.ktor.io/ktor-server/ktor-server-core/io.ktor.server.engine/-embedded-server/index.html) +is introduced and used to replace `ApplicationEngine` as a return type of the `embeddedServer()` function. For more details about the model change, see [issue KTOR-3857 on YouTrack](https://youtrack.jetbrains.com/issue/KTOR-3857/Environment-Engine-Application-Design). @@ -207,11 +206,13 @@ In the test below, the `handleRequest` function is replaced with the `client.get ```kotlin ``` + {src="https://raw.githubusercontent.com/ktorio/ktor-documentation/refs/heads/2.3.12/codeSnippets/snippets/engine-main/src/test/kotlin/EngineMainTest.kt" include-lines="18-26"} ```kotlin ``` + {src="https://raw.githubusercontent.com/ktorio/ktor-documentation/refs/heads/2.3.12/codeSnippets/snippets/engine-main/src/test/kotlin/EngineMainTest.kt" include-lines="11-16"} @@ -250,7 +251,8 @@ class ApplicationTest { } } ``` -{validate="false" noinject} + +{validate="false"} ```kotlin import com.example.plugins.* @@ -275,7 +277,8 @@ class ApplicationTest { } ``` -{validate="false" noinject} + +{validate="false"} @@ -287,20 +290,21 @@ file for your test. ```kotlin ``` + {src="snippets/auth-oauth-google/src/test/kotlin/ApplicationTest.kt" include-lines="17-21,51"} For more information on configuring the test application, see the [](server-testing.md) section. ### `CallLogging` plugin package has been renamed -The [`CallLogging`](https://api.ktor.io/ktor-server/ktor-server-plugins/ktor-server-call-logging/io.ktor.server.plugins.calllogging/index.html) +The [ +`CallLogging`](https://api.ktor.io/ktor-server/ktor-server-plugins/ktor-server-call-logging/io.ktor.server.plugins.calllogging/index.html) plugin package has been renamed due to a typo. | 2.x.x | 3.0.x | |-------------------------------------|--------------------------------------| | `io.ktor.server.plugins.callloging` | `io.ktor.server.plugins.calllogging` | - ### `ktor-server-host-common` module has been removed Due to `Application` requiring knowledge of `ApplicationEngine`, the contents of `ktor-server-host-common` module have @@ -318,9 +322,8 @@ the [Resources plugin](server-resources.md) instead. This requires the following * Replace the `io.ktor:ktor-server-locations` artifact with `io.ktor:ktor-server-resources`. -* The `Resources` plugin depends on the Kotlin - serialization plugin. To add the serialization plugin, see the - [kotlinx.serialization setup](https://github.com/Kotlin/kotlinx.serialization#setup). +* The `Resources` plugin depends on the Kotlin serialization plugin. To add the serialization plugin, see the +* [kotlinx.serialization setup](https://github.com/Kotlin/kotlinx.serialization#setup). * Update the plugin import from `io.ktor.server.locations.*` to `io.ktor.server.resources.*`. @@ -382,28 +385,26 @@ extension: - - - +} +``` - - - + pingPeriod = 15.seconds + timeout = 15.seconds + //.. +} +``` + You can use similar Kotlin duration extensions (`minutes`, `hours`, etc.) as needed for other duration configurations. @@ -430,6 +431,66 @@ To migrate, ensure `.bind()` is only called within a coroutine or suspending fun For more information on working with sockets, see the [Sockets documentation](server-sockets.md). +### `PartData.FileItem.streamProvider()` is deprecated + +In previous versions of Ktor, the `.streamProvider()` function in `PartData.FileItem` was used to access a file +item's content as an `InputStream`. Starting with Ktor 3.0.0, this function has been deprecated. + +To migrate your application, replace `.streamProvider()` with the +[`.provider()`](https://api.ktor.io/ktor-http/io.ktor.http.content/-part-data/-file-item/provider.html) +function. The `.provider()` function returns a `ByteReadChannel`, which is a coroutine-friendly, non-blocking +abstraction for reading streams of bytes incrementally. +You can then stream data directly from the channel to the file output, using the +[`.copyTo()`](https://api.ktor.io/ktor-io/io.ktor.utils.io/copy-to.html) or +[`.copyAndClose()`](https://api.ktor.io/ktor-io/io.ktor.utils.io/copy-and-close.html) +methods provided by `ByteReadChannel`. + +In the example the `.copyAndClose()` method transfers data from the `ByteReadChannel` +to a file's `WritableByteChannel`. + + + +```kotlin +fun Application.main() { + routing { + post("/upload") { + val multipart = call.receiveMultipart() + multipart.forEachPart { partData -> + if (partData is PartData.FileItem) { + var fileName = partData.originalFileName as String + val file = File("uploads/$fileName") + file.writeBytes(partData.streamProvider().readBytes()) + } + // ... + } + } + } +} +``` + +```kotlin +fun Application.main() { + routing { + post("/upload") { + val multipart = call.receiveMultipart() + multipart.forEachPart { partData -> + if (partData is PartData.FileItem) { + var fileName = partData.originalFileName as String + val file = File("uploads/$fileName") + partData.provider().copyAndClose(file.writeChannel()) + } + // ... + } + } + } +} +``` + + + +For the full example and more information on working with multipart form data, +see [Request handling of multipart form data](server-requests.md#form_data). + ### Session encryption method update The encryption method offered by the `Sessions` plugin has been updated to enhance @@ -459,7 +520,6 @@ install(Sessions) { For more information on session encryption in Ktor, see [](server-sessions.md#sign_encrypt_session). - ## Ktor Client ### Renaming of `HttpResponse`'s `content` property diff --git a/topics/server-requests.md b/topics/server-requests.md index abda68acc..3b3e8383e 100644 --- a/topics/server-requests.md +++ b/topics/server-requests.md @@ -1,6 +1,6 @@ [//]: # (title: Handling requests) - + Learn how to handle incoming requests inside route handlers. @@ -114,19 +114,63 @@ You can obtain parameter values in code as follows: You can find the full example here: [post-form-parameters](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/post-form-parameters). - ### Multipart form data {id="form_data"} -If you need to receive a file sent as a part of a multipart request, call the [receiveMultipart](https://api.ktor.io/ktor-server/ktor-server-core/io.ktor.server.request/receive-multipart.html) function and then loop over each part as required. In the example below, `PartData.FileItem` is used to receive a file as a byte stream. + +To receive a file sent as a part of a multipart request, call +the [.receiveMultipart()](https://api.ktor.io/ktor-server/ktor-server-core/io.ktor.server.request/receive-multipart.html) +function and then loop over each part as required. + +Multipart request data is processed sequentially, so you can't directly access a specific part of it. Additionally, +these requests can contain different types of parts, such as form fields, files, or binary data, which need to +be handled differently. + +The example demonstrates how to receive a file and save it to file system: + +```kotlin +``` + +{src="snippets/upload-file/src/main/kotlin/uploadfile/UploadFile.kt" include-lines="3-40"} + +#### Form fields + +`PartData.FormItem` represents a form field, which values can be accessed through the `value` property: + +```kotlin +``` + +{src="snippets/upload-file/src/main/kotlin/uploadfile/UploadFile.kt" include-lines="21-24,33"} + +#### File uploads + +`PartData.FileItem` represents a file item. You can handle file uploads as byte streams: + ```kotlin ``` -{src="snippets/upload-file/src/main/kotlin/uploadfile/UploadFile.kt" include-lines="3-38"} -Learn how to run this sample from [upload-file](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/upload-file). +{src="snippets/upload-file/src/main/kotlin/uploadfile/UploadFile.kt" include-lines="21,26-30,33"} + +The [`.provider()`](https://api.ktor.io/ktor-http/io.ktor.http.content/-part-data/-file-item/provider.html) +function returns a `ByteReadChannel`, which allows you to read data incrementally. +Using the `.copyAndClose()` function, you then write the file content to the specified destination +while ensuring proper resource cleanup. To determine the uploaded file size, you can get the `Content-Length` [header value](#request_information) inside the `post` handler: + ```kotlin post("/upload") { val contentLength = call.request.header(HttpHeaders.ContentLength) // ... } ``` + +#### Resource cleanup + +Once the form processing is complete, each part is disposed of using the `.dispose()` function to free resources. + +```kotlin +``` + +{src="snippets/upload-file/src/main/kotlin/uploadfile/UploadFile.kt" include-lines="34"} + +To learn how to run this sample, see +[upload-file](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/upload-file). \ No newline at end of file