Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class MultiPartFormDataTest : ClientLoader() {
}
else -> fail("Unexpected part type: ${part::class.simpleName}")
}
part.dispose()
part.release()
Comment thread
bjhham marked this conversation as resolved.
}

assertTrue(textFound, "Text part not found")
Expand Down
15 changes: 11 additions & 4 deletions ktor-http/api/ktor-http.api
Original file line number Diff line number Diff line change
Expand Up @@ -1514,12 +1514,13 @@ public final class io/ktor/http/content/OutputStreamContent : io/ktor/http/conte
}

public abstract class io/ktor/http/content/PartData {
public synthetic fun <init> (Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getContentDisposition ()Lio/ktor/http/ContentDisposition;
public final fun getContentType ()Lio/ktor/http/ContentType;
public final fun getDispose ()Lkotlin/jvm/functions/Function0;
public final fun getHeaders ()Lio/ktor/http/Headers;
public final fun getName ()Ljava/lang/String;
public final fun getRelease ()Lkotlin/jvm/functions/Function1;
}

public final class io/ktor/http/content/PartData$BinaryChannelItem : io/ktor/http/content/PartData {
Expand All @@ -1528,18 +1529,24 @@ public final class io/ktor/http/content/PartData$BinaryChannelItem : io/ktor/htt
}

public final class io/ktor/http/content/PartData$BinaryItem : io/ktor/http/content/PartData {
public fun <init> (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;)V
public fun <init> (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getProvider ()Lkotlin/jvm/functions/Function0;
}

public final class io/ktor/http/content/PartData$FileItem : io/ktor/http/content/PartData {
public fun <init> (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;)V
public fun <init> (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getOriginalFileName ()Ljava/lang/String;
public final fun getProvider ()Lkotlin/jvm/functions/Function0;
}

public final class io/ktor/http/content/PartData$FormItem : io/ktor/http/content/PartData {
public fun <init> (Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;)V
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;)V
public fun <init> (Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lio/ktor/http/Headers;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
Comment thread
coderabbitai[bot] marked this conversation as resolved.
public final fun getValue ()Ljava/lang/String;
}

Expand Down
5 changes: 5 additions & 0 deletions ktor-http/api/ktor-http.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,8 @@ sealed class io.ktor.http.content/PartData { // io.ktor.http.content/PartData|nu
final fun <get-headers>(): io.ktor.http/Headers // io.ktor.http.content/PartData.headers.<get-headers>|<get-headers>(){}[0]
final val name // io.ktor.http.content/PartData.name|{}name[0]
final fun <get-name>(): kotlin/String? // io.ktor.http.content/PartData.name.<get-name>|<get-name>(){}[0]
final val release // io.ktor.http.content/PartData.release|{}release[0]
final fun <get-release>(): kotlin.coroutines/SuspendFunction0<kotlin/Unit> // io.ktor.http.content/PartData.release.<get-release>|<get-release>(){}[0]

final class BinaryChannelItem : io.ktor.http.content/PartData { // io.ktor.http.content/PartData.BinaryChannelItem|null[0]
constructor <init>(kotlin/Function0<io.ktor.utils.io/ByteReadChannel>, io.ktor.http/Headers) // io.ktor.http.content/PartData.BinaryChannelItem.<init>|<init>(kotlin.Function0<io.ktor.utils.io.ByteReadChannel>;io.ktor.http.Headers){}[0]
Expand All @@ -1424,13 +1426,15 @@ sealed class io.ktor.http.content/PartData { // io.ktor.http.content/PartData|nu

final class BinaryItem : io.ktor.http.content/PartData { // io.ktor.http.content/PartData.BinaryItem|null[0]
constructor <init>(kotlin/Function0<kotlinx.io/Source>, kotlin/Function0<kotlin/Unit>, io.ktor.http/Headers) // io.ktor.http.content/PartData.BinaryItem.<init>|<init>(kotlin.Function0<kotlinx.io.Source>;kotlin.Function0<kotlin.Unit>;io.ktor.http.Headers){}[0]
constructor <init>(kotlin/Function0<kotlinx.io/Source>, kotlin/Function0<kotlin/Unit>, io.ktor.http/Headers, kotlin.coroutines/SuspendFunction0<kotlin/Unit> = ...) // io.ktor.http.content/PartData.BinaryItem.<init>|<init>(kotlin.Function0<kotlinx.io.Source>;kotlin.Function0<kotlin.Unit>;io.ktor.http.Headers;kotlin.coroutines.SuspendFunction0<kotlin.Unit>){}[0]

final val provider // io.ktor.http.content/PartData.BinaryItem.provider|{}provider[0]
final fun <get-provider>(): kotlin/Function0<kotlinx.io/Source> // io.ktor.http.content/PartData.BinaryItem.provider.<get-provider>|<get-provider>(){}[0]
}

final class FileItem : io.ktor.http.content/PartData { // io.ktor.http.content/PartData.FileItem|null[0]
constructor <init>(kotlin/Function0<io.ktor.utils.io/ByteReadChannel>, kotlin/Function0<kotlin/Unit>, io.ktor.http/Headers) // io.ktor.http.content/PartData.FileItem.<init>|<init>(kotlin.Function0<io.ktor.utils.io.ByteReadChannel>;kotlin.Function0<kotlin.Unit>;io.ktor.http.Headers){}[0]
constructor <init>(kotlin/Function0<io.ktor.utils.io/ByteReadChannel>, kotlin/Function0<kotlin/Unit>, io.ktor.http/Headers, kotlin.coroutines/SuspendFunction0<kotlin/Unit> = ...) // io.ktor.http.content/PartData.FileItem.<init>|<init>(kotlin.Function0<io.ktor.utils.io.ByteReadChannel>;kotlin.Function0<kotlin.Unit>;io.ktor.http.Headers;kotlin.coroutines.SuspendFunction0<kotlin.Unit>){}[0]

final val originalFileName // io.ktor.http.content/PartData.FileItem.originalFileName|{}originalFileName[0]
final fun <get-originalFileName>(): kotlin/String? // io.ktor.http.content/PartData.FileItem.originalFileName.<get-originalFileName>|<get-originalFileName>(){}[0]
Expand All @@ -1440,6 +1444,7 @@ sealed class io.ktor.http.content/PartData { // io.ktor.http.content/PartData|nu

final class FormItem : io.ktor.http.content/PartData { // io.ktor.http.content/PartData.FormItem|null[0]
constructor <init>(kotlin/String, kotlin/Function0<kotlin/Unit>, io.ktor.http/Headers) // io.ktor.http.content/PartData.FormItem.<init>|<init>(kotlin.String;kotlin.Function0<kotlin.Unit>;io.ktor.http.Headers){}[0]
constructor <init>(kotlin/String, kotlin/Function0<kotlin/Unit>, io.ktor.http/Headers, kotlin.coroutines/SuspendFunction0<kotlin/Unit> = ...) // io.ktor.http.content/PartData.FormItem.<init>|<init>(kotlin.String;kotlin.Function0<kotlin.Unit>;io.ktor.http.Headers;kotlin.coroutines.SuspendFunction0<kotlin.Unit>){}[0]
Comment thread
bjhham marked this conversation as resolved.

final val value // io.ktor.http.content/PartData.FormItem.value|{}value[0]
final fun <get-value>(): kotlin/String // io.ktor.http.content/PartData.FormItem.value.<get-value>|<get-value>(){}[0]
Expand Down
52 changes: 43 additions & 9 deletions ktor-http/common/src/io/ktor/http/content/Multipart.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package io.ktor.http.content

import io.ktor.http.*
import io.ktor.http.content.PartData.*
import io.ktor.http.content.PartData.BinaryItem
import io.ktor.http.content.PartData.FileItem
import io.ktor.utils.io.*
import io.ktor.utils.io.core.*
import kotlinx.coroutines.flow.*
Expand All @@ -18,16 +20,32 @@ import kotlinx.coroutines.flow.*
* @property dispose to be invoked when this part is no longer needed
* @property headers of this part, could be inaccurate on some engines
*/
public sealed class PartData(public val dispose: () -> Unit, public val headers: Headers) {
public sealed class PartData(
@Deprecated("Use release instead", level = DeprecationLevel.WARNING)
public val dispose: () -> Unit,
public val headers: Headers,
public val release: suspend () -> Unit,
) {
/**
* Represents a multipart form item.
*
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.http.content.PartData.FormItem)
*
* @property value of this field
*/
public class FormItem(public val value: String, dispose: () -> Unit, partHeaders: Headers) :
PartData(dispose, partHeaders)
public class FormItem(
public val value: String,
dispose: () -> Unit,
partHeaders: Headers,
release: suspend () -> Unit = {},
) : PartData(dispose, partHeaders, release) {
@Deprecated("Binary compatability", level = DeprecationLevel.HIDDEN)
public constructor(
value: String,
dispose: () -> Unit,
partHeaders: Headers
) : this(value, dispose, partHeaders, {})
}

/**
* Represents a file item.
Expand All @@ -40,8 +58,16 @@ public sealed class PartData(public val dispose: () -> Unit, public val headers:
public class FileItem(
public val provider: () -> ByteReadChannel,
dispose: () -> Unit,
partHeaders: Headers
) : PartData(dispose, partHeaders) {
partHeaders: Headers,
release: suspend () -> Unit = {},
) : PartData(dispose, partHeaders, release) {
@Deprecated("Binary compatability", level = DeprecationLevel.HIDDEN)
public constructor(
provider: () -> ByteReadChannel,
dispose: () -> Unit,
partHeaders: Headers
) : this(provider, dispose, partHeaders, {})

/**
* Original file name if present
*
Expand All @@ -61,8 +87,16 @@ public sealed class PartData(public val dispose: () -> Unit, public val headers:
public class BinaryItem(
public val provider: () -> Input,
dispose: () -> Unit,
partHeaders: Headers
) : PartData(dispose, partHeaders)
partHeaders: Headers,
release: suspend () -> Unit = {},
) : PartData(dispose, partHeaders, release) {
@Deprecated("Binary compatability", level = DeprecationLevel.HIDDEN)
public constructor(
provider: () -> Input,
dispose: () -> Unit,
partHeaders: Headers
) : this(provider, dispose, partHeaders, {})
}

/**
* Represents a binary part with a provider that supplies [ByteReadChannel].
Expand All @@ -73,8 +107,8 @@ public sealed class PartData(public val dispose: () -> Unit, public val headers:
*/
public class BinaryChannelItem(
public val provider: () -> ByteReadChannel,
partHeaders: Headers
) : PartData({}, partHeaders)
partHeaders: Headers,
) : PartData({}, partHeaders, {})

/**
* Parsed `Content-Disposition` header or `null` if missing.
Expand Down
4 changes: 4 additions & 0 deletions ktor-http/ktor-http-cio/api/ktor-http-cio.api
Original file line number Diff line number Diff line change
Expand Up @@ -88,25 +88,29 @@ public final class io/ktor/http/cio/HttpParserKt {

public abstract class io/ktor/http/cio/MultipartEvent {
public abstract fun release ()V
public abstract fun releaseSuspend (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class io/ktor/http/cio/MultipartEvent$Epilogue : io/ktor/http/cio/MultipartEvent {
public fun <init> (Lkotlinx/io/Source;)V
public final fun getBody ()Lkotlinx/io/Source;
public fun release ()V
public fun releaseSuspend (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class io/ktor/http/cio/MultipartEvent$MultipartPart : io/ktor/http/cio/MultipartEvent {
public fun <init> (Lkotlinx/coroutines/Deferred;Lio/ktor/utils/io/ByteReadChannel;)V
public final fun getBody ()Lio/ktor/utils/io/ByteReadChannel;
public final fun getHeaders ()Lkotlinx/coroutines/Deferred;
public fun release ()V
public fun releaseSuspend (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class io/ktor/http/cio/MultipartEvent$Preamble : io/ktor/http/cio/MultipartEvent {
public fun <init> (Lkotlinx/io/Source;)V
public final fun getBody ()Lkotlinx/io/Source;
public fun release ()V
public fun releaseSuspend (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class io/ktor/http/cio/MultipartKt {
Expand Down
4 changes: 4 additions & 0 deletions ktor-http/ktor-http-cio/api/ktor-http-cio.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ final class io.ktor.http.cio/Response : io.ktor.http.cio/HttpMessage { // io.kto

sealed class io.ktor.http.cio/MultipartEvent { // io.ktor.http.cio/MultipartEvent|null[0]
abstract fun release() // io.ktor.http.cio/MultipartEvent.release|release(){}[0]
abstract suspend fun releaseSuspend() // io.ktor.http.cio/MultipartEvent.releaseSuspend|releaseSuspend(){}[0]

final class Epilogue : io.ktor.http.cio/MultipartEvent { // io.ktor.http.cio/MultipartEvent.Epilogue|null[0]
constructor <init>(kotlinx.io/Source) // io.ktor.http.cio/MultipartEvent.Epilogue.<init>|<init>(kotlinx.io.Source){}[0]
Expand All @@ -144,6 +145,7 @@ sealed class io.ktor.http.cio/MultipartEvent { // io.ktor.http.cio/MultipartEven
final fun <get-body>(): kotlinx.io/Source // io.ktor.http.cio/MultipartEvent.Epilogue.body.<get-body>|<get-body>(){}[0]

final fun release() // io.ktor.http.cio/MultipartEvent.Epilogue.release|release(){}[0]
final suspend fun releaseSuspend() // io.ktor.http.cio/MultipartEvent.Epilogue.releaseSuspend|releaseSuspend(){}[0]
}

final class MultipartPart : io.ktor.http.cio/MultipartEvent { // io.ktor.http.cio/MultipartEvent.MultipartPart|null[0]
Expand All @@ -155,6 +157,7 @@ sealed class io.ktor.http.cio/MultipartEvent { // io.ktor.http.cio/MultipartEven
final fun <get-headers>(): kotlinx.coroutines/Deferred<io.ktor.http.cio/HttpHeadersMap> // io.ktor.http.cio/MultipartEvent.MultipartPart.headers.<get-headers>|<get-headers>(){}[0]

final fun release() // io.ktor.http.cio/MultipartEvent.MultipartPart.release|release(){}[0]
final suspend fun releaseSuspend() // io.ktor.http.cio/MultipartEvent.MultipartPart.releaseSuspend|releaseSuspend(){}[0]
}

final class Preamble : io.ktor.http.cio/MultipartEvent { // io.ktor.http.cio/MultipartEvent.Preamble|null[0]
Expand All @@ -164,6 +167,7 @@ sealed class io.ktor.http.cio/MultipartEvent { // io.ktor.http.cio/MultipartEven
final fun <get-body>(): kotlinx.io/Source // io.ktor.http.cio/MultipartEvent.Preamble.body.<get-body>|<get-body>(){}[0]

final fun release() // io.ktor.http.cio/MultipartEvent.Preamble.release|release(){}[0]
final suspend fun releaseSuspend() // io.ktor.http.cio/MultipartEvent.Preamble.releaseSuspend|releaseSuspend(){}[0]
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class CIOMultipartDataBase(
parseMultipart(channel, contentType, contentLength, formFieldLimit)

override suspend fun readPart(): PartData? {
previousPart?.dispose?.invoke()
previousPart?.release()
Comment thread
coderabbitai[bot] marked this conversation as resolved.

while (true) {
val event = events.tryReceive().getOrNull() ?: break
Expand Down Expand Up @@ -61,12 +61,12 @@ public class CIOMultipartDataBase(
when (event) {
is MultipartEvent.MultipartPart -> partToData(event)
else -> {
event.release()
event.releaseSuspend()
null
}
}
} catch (cause: Throwable) {
event.release()
event.releaseSuspend()
throw cause
}
}
Expand All @@ -81,14 +81,15 @@ public class CIOMultipartDataBase(
if (filename == null) {
val packet = body.readRemaining()
packet.use {
return PartData.FormItem(it.readText(), { part.release() }, CIOHeaders(headers))
return PartData.FormItem(it.readText(), part::release, CIOHeaders(headers), part::releaseSuspend)
}
}

return PartData.FileItem(
{ part.body },
{ part.release() },
CIOHeaders(headers)
part::release,
CIOHeaders(headers),
part::releaseSuspend
)
}
}
25 changes: 24 additions & 1 deletion ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/Multipart.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import kotlinx.io.EOFException
import kotlinx.io.IOException
import kotlinx.io.Source
import kotlinx.io.bytestring.ByteString
import kotlin.coroutines.cancellation.CancellationException

/**
* Represents a multipart content starting event. Every part need to be completely consumed or released via [release]
Expand All @@ -31,8 +32,16 @@ public sealed class MultipartEvent {
*
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.http.cio.MultipartEvent.release)
*/
@Deprecated("Use releaseSuspend instead", level = DeprecationLevel.WARNING)
public abstract fun release()

/**
* Release underlying data/packet.
*
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.http.cio.MultipartEvent.releaseSuspend)
*/
public abstract suspend fun releaseSuspend()

/**
* Represents a multipart content preamble. A multipart stream could have at most one preamble.
*
Expand All @@ -46,6 +55,9 @@ public sealed class MultipartEvent {
override fun release() {
body.close()
}
override suspend fun releaseSuspend() {
body.close()
}
}

/**
Expand All @@ -63,6 +75,7 @@ public sealed class MultipartEvent {
public val headers: Deferred<HttpHeadersMap>,
public val body: ByteReadChannel
) : MultipartEvent() {
@Deprecated("Use releaseSuspend instead", level = DeprecationLevel.WARNING)
@OptIn(ExperimentalCoroutinesApi::class)
override fun release() {
headers.invokeOnCompletion { t ->
Expand All @@ -73,6 +86,13 @@ public sealed class MultipartEvent {

body.discardBlocking()
}
override suspend fun releaseSuspend() {
try {
headers.await().release()
} finally {
body.discard()
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/**
Expand All @@ -88,6 +108,9 @@ public sealed class MultipartEvent {
override fun release() {
body.close()
}
override suspend fun releaseSuspend() {
body.close()
}
}
}

Expand Down Expand Up @@ -229,7 +252,7 @@ private fun CoroutineScope.parseMultipart(
headersMap = parsePartHeadersImpl(countedInput)
if (!headers.complete(headersMap)) {
headersMap.release()
throw kotlin.coroutines.cancellation.CancellationException(
throw CancellationException(
"Multipart processing has been cancelled"
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public fun ApplicationReceivePipeline.installDefaultTransformations() {
}
}

part.dispose()
part.release()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ internal fun buildMultipart(

append("--$boundary--\r\n")
} finally {
parts.forEach { it.dispose() }
parts.forEach { it.release() }
}
}.channel

Expand Down
Loading
Loading