Skip to content

Commit

Permalink
KTOR-7801 and KTOR-7802 Document streamProvider deprecation and updat…
Browse files Browse the repository at this point in the history
…e the file upload example (#571)
  • Loading branch information
vnikolova authored Jan 9, 2025
1 parent c93c189 commit b47adbb
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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 -> {}
Expand Down
128 changes: 94 additions & 34 deletions topics/migrating-3.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,26 @@ 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`.

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 |
|-------------------------------------------------------|--------------------------------------|
Expand Down Expand Up @@ -79,7 +79,7 @@ fun defaultServer(module: Application.() -> Unit) =
)
```

{validate="false" noinject}
{validate="false"}

```kotlin
import io.ktor.server.application.*
Expand Down Expand Up @@ -183,10 +183,9 @@ fun main(args: Array<String>) {

#### 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).
Expand All @@ -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"}

Expand Down Expand Up @@ -250,7 +251,8 @@ class ApplicationTest {
}
}
```
{validate="false" noinject}

{validate="false"}

```kotlin
import com.example.plugins.*
Expand All @@ -275,7 +277,8 @@ class ApplicationTest {
}

```
{validate="false" noinject}

{validate="false"}


</compare>
Expand All @@ -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
Expand All @@ -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.*`.

Expand Down Expand Up @@ -382,28 +385,26 @@ extension:

<compare first-title="2.x.x" second-title="3.0.x">

<code-block lang="kotlin" show-white-spaces="true">
<![CDATA[
import java.time.Duration
```kotlin
import java.time.Duration

install(WebSockets) {
install(WebSockets) {
pingPeriod = Duration.ofSeconds(15)
timeout = Duration.ofSeconds(15)
//..
}
]]>
</code-block>
}
```

<code-block lang="kotlin" show-white-spaces="true">
<![CDATA[
```kotlin
import kotlin.time.Duration.Companion.seconds

install(WebSockets) {
pingPeriod = 15.seconds
timeout = 15.seconds
//..
}]]>
</code-block>
pingPeriod = 15.seconds
timeout = 15.seconds
//..
}
```

</compare>

You can use similar Kotlin duration extensions (`minutes`, `hours`, etc.) as needed for other duration configurations.
Expand All @@ -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`.

<compare first-title="2.x.x" second-title="3.0.x">

```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())
}
// ...
}
}
}
}
```

</compare>

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
Expand Down Expand Up @@ -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
Expand Down
54 changes: 49 additions & 5 deletions topics/server-requests.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[//]: # (title: Handling requests)

<show-structure for="chapter" depth="2"/>
<show-structure for="chapter" depth="3"/>

<link-summary>Learn how to handle incoming requests inside route handlers.</link-summary>

Expand Down Expand Up @@ -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).

0 comments on commit b47adbb

Please sign in to comment.