diff --git a/ktor-client/ktor-client-android/jvm/src/io/ktor/client/engine/android/AndroidClientEngine.kt b/ktor-client/ktor-client-android/jvm/src/io/ktor/client/engine/android/AndroidClientEngine.kt index 9f4462c32..5719643eb 100644 --- a/ktor-client/ktor-client-android/jvm/src/io/ktor/client/engine/android/AndroidClientEngine.kt +++ b/ktor-client/ktor-client-android/jvm/src/io/ktor/client/engine/android/AndroidClientEngine.kt @@ -61,7 +61,7 @@ class AndroidClientEngine(override val config: AndroidEngineConfig) : HttpClient config.requestConfig(this) if (outgoingContent !is OutgoingContent.NoContent) { - if (data.method in listOf(HttpMethod.Get, HttpMethod.Head)) throw RequestInvalidException( + if (data.method in listOf(HttpMethod.Get, HttpMethod.Head)) error( "Request of type ${data.method} couldn't send a body with the [Android] engine." ) @@ -123,6 +123,3 @@ internal fun HttpURLConnection.content(callScope: CoroutineContext): ByteReadCha } catch (_: IOException) { errorStream?.buffered() }?.toByteReadChannel(context = callScope, pool = KtorDefaultPool) ?: ByteReadChannel.Empty - -@Suppress("KDocMissingDocumentation") -internal class RequestInvalidException(override val message: String) : IllegalStateException() diff --git a/ktor-client/ktor-client-cio/jvm/src/io/ktor/client/engine/cio/CIOEngine.kt b/ktor-client/ktor-client-cio/jvm/src/io/ktor/client/engine/cio/CIOEngine.kt index 619d505d4..36e539518 100644 --- a/ktor-client/ktor-client-cio/jvm/src/io/ktor/client/engine/cio/CIOEngine.kt +++ b/ktor-client/ktor-client-cio/jvm/src/io/ktor/client/engine/cio/CIOEngine.kt @@ -92,7 +92,7 @@ internal class CIOEngine(override val config: CIOEngineConfig) : HttpClientEngin @Suppress("KDocMissingDocumentation") @Deprecated("Use ClientEngineClosedException instead", replaceWith = ReplaceWith("ClientEngineClosedException")) -class ClientClosedException(override val cause: Throwable? = null) : IllegalStateException("Client already closed") +class ClientClosedException(cause: Throwable? = null) : IllegalStateException("Client already closed", cause) private fun ConcurrentHashMap.computeIfAbsentWeak(key: K, block: (K) -> V): V { get(key)?.let { return it } diff --git a/ktor-client/ktor-client-cio/jvm/src/io/ktor/client/engine/cio/ConnectionFactory.kt b/ktor-client/ktor-client-cio/jvm/src/io/ktor/client/engine/cio/ConnectionFactory.kt index af343f28e..87079864e 100644 --- a/ktor-client/ktor-client-cio/jvm/src/io/ktor/client/engine/cio/ConnectionFactory.kt +++ b/ktor-client/ktor-client-cio/jvm/src/io/ktor/client/engine/cio/ConnectionFactory.kt @@ -4,7 +4,6 @@ package io.ktor.client.engine.cio -import io.ktor.util.cio.* import io.ktor.network.selector.* import io.ktor.network.sockets.* import io.ktor.network.sockets.Socket diff --git a/ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpWebsocketSession.kt b/ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpWebsocketSession.kt index c35d88bd9..39619109d 100644 --- a/ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpWebsocketSession.kt +++ b/ktor-client/ktor-client-okhttp/jvm/src/io/ktor/client/engine/okhttp/OkHttpWebsocketSession.kt @@ -111,4 +111,10 @@ internal class OkHttpWebsocketSession( } @Suppress("KDocMissingDocumentation") -class UnsupportedFrameTypeException(frame: Frame) : IllegalArgumentException("Unsupported frame type: $frame") +class UnsupportedFrameTypeException( + private val frame: Frame +) : IllegalArgumentException("Unsupported frame type: $frame"), CopyableThrowable { + override fun createCopy(): UnsupportedFrameTypeException? = UnsupportedFrameTypeException(frame).also { + it.initCause(this) + } +} diff --git a/ktor-features/ktor-auth/jvm/src/io/ktor/auth/OAuth2.kt b/ktor-features/ktor-auth/jvm/src/io/ktor/auth/OAuth2.kt index ae4ee7c19..e33901509 100644 --- a/ktor-features/ktor-auth/jvm/src/io/ktor/auth/OAuth2.kt +++ b/ktor-features/ktor-auth/jvm/src/io/ktor/auth/OAuth2.kt @@ -6,22 +6,18 @@ package io.ktor.auth import io.ktor.application.* import io.ktor.client.* -import io.ktor.client.call.* import io.ktor.client.request.* -import io.ktor.client.response.* import io.ktor.client.statement.* -import io.ktor.client.statement.HttpResponse -import io.ktor.http.content.* import io.ktor.http.* import io.ktor.http.auth.* -import io.ktor.util.pipeline.* +import io.ktor.http.content.* import io.ktor.response.* import io.ktor.util.* +import io.ktor.util.pipeline.* import kotlinx.coroutines.* import org.json.simple.* import org.slf4j.* import java.io.* -import java.lang.Exception import java.net.* private val Logger: Logger = LoggerFactory.getLogger("io.ktor.auth.oauth") @@ -364,22 +360,33 @@ sealed class OAuth2Exception(message: String, val errorCode: String?) : Exceptio * decoded but the response doesn't contain error code nor access token */ @KtorExperimentalAPI - class MissingAccessToken : - OAuth2Exception("OAuth2 server response is OK neither error nor access token provided", null) + class MissingAccessToken : OAuth2Exception( + "OAuth2 server response is OK neither error nor access token provided", null + ) /** * Throw when an OAuth2 server replied with error "unsupported_grant_type" * @param grantType that was passed to the server */ @KtorExperimentalAPI - class UnsupportedGrantType(val grantType: String) : - OAuth2Exception("OAuth2 server doesn't support grant type $grantType", "unsupported_grant_type") + class UnsupportedGrantType(val grantType: String) : OAuth2Exception( + "OAuth2 server doesn't support grant type $grantType", "unsupported_grant_type" + ), CopyableThrowable { + override fun createCopy(): UnsupportedGrantType = UnsupportedGrantType(grantType).also { + it.initCause(this) + } + } /** * OAuth2 server responded with an error code [errorCode] * @param errorCode the OAuth2 server replied with */ @KtorExperimentalAPI - class UnknownException(message: String, errorCode: String) : - OAuth2Exception("$message (error code = $errorCode)", errorCode) + class UnknownException( + private val details: String, errorCode: String + ) : OAuth2Exception("$details (error code = $errorCode)", errorCode), CopyableThrowable { + override fun createCopy(): UnknownException = UnknownException(details, errorCode!!).also { + it.initCause(this) + } + } } diff --git a/ktor-features/ktor-gson/jvm/src/io/ktor/gson/GsonSupport.kt b/ktor-features/ktor-gson/jvm/src/io/ktor/gson/GsonSupport.kt index 6a859b6af..2df0e27bc 100644 --- a/ktor-features/ktor-gson/jvm/src/io/ktor/gson/GsonSupport.kt +++ b/ktor-features/ktor-gson/jvm/src/io/ktor/gson/GsonSupport.kt @@ -10,10 +10,11 @@ import io.ktor.features.* import io.ktor.features.ContentTransformationException import io.ktor.http.* import io.ktor.http.content.* -import io.ktor.util.pipeline.* import io.ktor.request.* +import io.ktor.util.pipeline.* import io.ktor.utils.io.* import io.ktor.utils.io.jvm.javaio.* +import kotlinx.coroutines.* import kotlin.reflect.* import kotlin.reflect.jvm.* @@ -56,8 +57,15 @@ fun ContentNegotiation.Configuration.gson( register(contentType, converter) } -internal class ExcludedTypeGsonException(val type: KClass<*>) : - Exception("Type ${type.jvmName} is excluded so couldn't be used in receive") +internal class ExcludedTypeGsonException( + val type: KClass<*> +) : Exception("Type ${type.jvmName} is excluded so couldn't be used in receive"), + CopyableThrowable { + + override fun createCopy(): ExcludedTypeGsonException? = ExcludedTypeGsonException(type).also { + it.initCause(this) + } +} internal class UnsupportedNullValuesException : ContentTransformationException("Receiving null values is not supported") diff --git a/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/websocket/WebSocketReader.kt b/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/websocket/WebSocketReader.kt index 048cad03b..c8026adef 100644 --- a/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/websocket/WebSocketReader.kt +++ b/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/websocket/WebSocketReader.kt @@ -111,9 +111,14 @@ class WebSocketReader( * Raised when the frame is bigger than allowed in a current websocket session * @param frameSize size of received or posted frame that is too big */ - class FrameTooBigException(val frameSize: Long) : Exception() { + class FrameTooBigException(val frameSize: Long) : Exception(), CopyableThrowable { + override val message: String get() = "Frame is too big: $frameSize" + + override fun createCopy(): FrameTooBigException = FrameTooBigException(frameSize).also { + it.initCause(this) + } } private enum class State { diff --git a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt index 725ceeffe..a550c65df 100644 --- a/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt +++ b/ktor-network/ktor-network-tls/jvm/src/io/ktor/network/tls/TLSConfigBuilder.kt @@ -4,6 +4,7 @@ package io.ktor.network.tls +import kotlinx.coroutines.* import java.security.* import java.security.cert.* import javax.net.ssl.* @@ -99,8 +100,14 @@ fun TLSConfigBuilder.addKeyStore(store: KeyStore, password: CharArray) { * Throws if failed to find [PrivateKey] for any alias in [KeyStore]. */ class NoPrivateKeyException( - alias: String, store: KeyStore -) : IllegalStateException("Failed to find private key for alias $alias. Please check your key store: $store") + private val alias: String, private val store: KeyStore +) : IllegalStateException("Failed to find private key for alias $alias. Please check your key store: $store"), + CopyableThrowable { + + override fun createCopy(): NoPrivateKeyException? = NoPrivateKeyException(alias, store).also { + it.initCause(this) + } +} private fun findTrustManager(): X509TrustManager { val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())!! diff --git a/ktor-server/ktor-server-core/jvm/src/io/ktor/application/ApplicationFeature.kt b/ktor-server/ktor-server-core/jvm/src/io/ktor/application/ApplicationFeature.kt index 103e68b56..c23037a26 100644 --- a/ktor-server/ktor-server-core/jvm/src/io/ktor/application/ApplicationFeature.kt +++ b/ktor-server/ktor-server-core/jvm/src/io/ktor/application/ApplicationFeature.kt @@ -6,6 +6,7 @@ package io.ktor.application import io.ktor.util.pipeline.* import io.ktor.util.* +import kotlinx.coroutines.* import io.ktor.utils.io.core.* /** @@ -116,6 +117,12 @@ class DuplicateApplicationFeatureException(message: String) : Exception(message) * Thrown when Application Feature has been attempted to be accessed but has not been installed before * @param key application feature's attribute key */ -class MissingApplicationFeatureException(val key: AttributeKey<*>) : IllegalStateException() { +class MissingApplicationFeatureException( + val key: AttributeKey<*> +) : IllegalStateException(), CopyableThrowable { override val message: String get() = "Application feature ${key.name} is not installed" + + override fun createCopy(): MissingApplicationFeatureException? = MissingApplicationFeatureException(key).also { + it.initCause(this) + } } diff --git a/ktor-server/ktor-server-core/jvm/src/io/ktor/features/CallId.kt b/ktor-server/ktor-server-core/jvm/src/io/ktor/features/CallId.kt index 64c939577..425ef65ca 100644 --- a/ktor-server/ktor-server-core/jvm/src/io/ktor/features/CallId.kt +++ b/ktor-server/ktor-server-core/jvm/src/io/ktor/features/CallId.kt @@ -9,6 +9,7 @@ import io.ktor.http.* import io.ktor.response.* import io.ktor.util.* import io.ktor.util.pipeline.* +import kotlinx.coroutines.* import org.slf4j.* import kotlin.random.* import kotlin.reflect.jvm.* @@ -29,7 +30,13 @@ typealias CallIdVerifier = (String) -> Boolean * An exception that could be thrown to reject a call due to illegal call id * @param illegalCallId that caused rejection */ -class RejectedCallIdException(val illegalCallId: String) : IllegalArgumentException() +class RejectedCallIdException( + val illegalCallId: String +) : IllegalArgumentException(), CopyableThrowable { + override fun createCopy(): RejectedCallIdException? = RejectedCallIdException(illegalCallId).also { + it.initCause(this) + } +} /** * A call id that is retrieved or generated by [CallId] feature or `null` (this is possible if there is no @@ -64,9 +71,9 @@ val ApplicationCall.callId: String? get() = attributes.getOrNull(CallId.callIdKe * [CallId] feature will be installed to [CallId.phase] into [ApplicationCallPipeline]. */ class CallId private constructor( - private val providers: Array, - private val repliers: Array<(call: ApplicationCall, callId: String) -> Unit>, - private val verifier: CallIdVerifier + private val providers: Array, + private val repliers: Array<(call: ApplicationCall, callId: String) -> Unit>, + private val verifier: CallIdVerifier ) { /** * [CallId] feature's configuration @@ -186,9 +193,9 @@ class CallId private constructor( val configuration = Configuration().apply(configure) val instance = CallId( - providers = (configuration.retrievers + configuration.generators).toTypedArray(), - repliers = configuration.responseInterceptors.toTypedArray(), - verifier = configuration.verifier + providers = (configuration.retrievers + configuration.generators).toTypedArray(), + repliers = configuration.responseInterceptors.toTypedArray(), + verifier = configuration.verifier ) pipeline.insertPhaseBefore(ApplicationCallPipeline.Setup, phase) @@ -216,7 +223,8 @@ class CallId private constructor( break } } catch (rejection: RejectedCallIdException) { - logger.warn("Illegal call id retrieved or generated that is rejected by call id verifier:" + + logger.warn( + "Illegal call id retrieved or generated that is rejected by call id verifier:" + " (url-encoded) " + rejection.illegalCallId.encodeURLParameter() ) diff --git a/ktor-server/ktor-server-core/jvm/src/io/ktor/features/DoubleReceive.kt b/ktor-server/ktor-server-core/jvm/src/io/ktor/features/DoubleReceive.kt index 699c19c93..3469998d8 100644 --- a/ktor-server/ktor-server-core/jvm/src/io/ktor/features/DoubleReceive.kt +++ b/ktor-server/ktor-server-core/jvm/src/io/ktor/features/DoubleReceive.kt @@ -132,8 +132,9 @@ sealed class CachedTransformationResult(val type: KType) { * receive attempt is simply replaying the previous exception cause. */ @KtorExperimentalAPI -class RequestReceiveAlreadyFailedException internal constructor(cause: Throwable) : - Exception("Request body consumption was failed", cause, false, true) +class RequestReceiveAlreadyFailedException internal constructor( + cause: Throwable +) : Exception("Request body consumption was failed", cause, false, true) private val LastReceiveCachedResult = AttributeKey>("LastReceiveRequest") diff --git a/ktor-server/ktor-server-core/jvm/src/io/ktor/features/Errors.kt b/ktor-server/ktor-server-core/jvm/src/io/ktor/features/Errors.kt index 58dfa44ff..53fd98ade 100644 --- a/ktor-server/ktor-server-core/jvm/src/io/ktor/features/Errors.kt +++ b/ktor-server/ktor-server-core/jvm/src/io/ktor/features/Errors.kt @@ -6,6 +6,7 @@ package io.ktor.features import io.ktor.http.* import io.ktor.util.* +import kotlinx.coroutines.* import kotlin.reflect.* import kotlin.reflect.full.* @@ -31,8 +32,15 @@ class NotFoundException(message: String? = "Resource not found") : Exception(mes * @property parameterName of missing request parameter */ @KtorExperimentalAPI -class MissingRequestParameterException(val parameterName: String) : - BadRequestException("Request parameter $parameterName is missing") +class MissingRequestParameterException( + val parameterName: String +) : BadRequestException("Request parameter $parameterName is missing"), + CopyableThrowable { + + override fun createCopy(): MissingRequestParameterException = MissingRequestParameterException(parameterName).also { + it.initCause(this) + } +} /** * This exception is thrown when a required parameter with name [parameterName] couldn't be converted to the [type] @@ -41,7 +49,14 @@ class MissingRequestParameterException(val parameterName: String) : */ @KtorExperimentalAPI class ParameterConversionException(val parameterName: String, val type: String, cause: Throwable? = null) : - BadRequestException("Request parameter $parameterName couldn't be parsed/converted to $type", cause) + BadRequestException("Request parameter $parameterName couldn't be parsed/converted to $type", cause), + CopyableThrowable { + + override fun createCopy(): ParameterConversionException = + ParameterConversionException(parameterName, type, this).also { + it.initCause(this) + } +} /** * Thrown when content cannot be transformed to the desired type. @@ -51,16 +66,30 @@ class ParameterConversionException(val parameterName: String, val type: String, @KtorExperimentalAPI abstract class ContentTransformationException(message: String) : Exception(message) -internal class CannotTransformContentToTypeException(type: KType) : - ContentTransformationException("Cannot transform this request's content to $type") { +internal class CannotTransformContentToTypeException( + private val type: KType +) : ContentTransformationException("Cannot transform this request's content to $type"), + CopyableThrowable { @Suppress("unused") @Deprecated("Use KType instead", level = DeprecationLevel.HIDDEN) constructor(type: KClass<*>) : this(type.starProjectedType) + + override fun createCopy(): CannotTransformContentToTypeException? = + CannotTransformContentToTypeException(type).also { + it.initCause(this) + } } /** * Thrown when there is no conversion for a content type configured. * HTTP status 415 Unsupported Media Type will be replied when this exception is thrown and not caught. */ -class UnsupportedMediaTypeException(contentType: ContentType) : - ContentTransformationException("Content type $contentType is not supported") +class UnsupportedMediaTypeException( + private val contentType: ContentType +) : ContentTransformationException("Content type $contentType is not supported"), + CopyableThrowable { + + override fun createCopy(): UnsupportedMediaTypeException? = UnsupportedMediaTypeException(contentType).also { + it.initCause(this) + } +} diff --git a/ktor-server/ktor-server-host-common/jvm/src/io/ktor/server/engine/BaseApplicationResponse.kt b/ktor-server/ktor-server-host-common/jvm/src/io/ktor/server/engine/BaseApplicationResponse.kt index bda89c99b..cc41a029b 100644 --- a/ktor-server/ktor-server-host-common/jvm/src/io/ktor/server/engine/BaseApplicationResponse.kt +++ b/ktor-server/ktor-server-host-common/jvm/src/io/ktor/server/engine/BaseApplicationResponse.kt @@ -5,15 +5,15 @@ package io.ktor.server.engine import io.ktor.application.* -import io.ktor.util.cio.* -import io.ktor.http.content.* import io.ktor.features.* import io.ktor.http.* +import io.ktor.http.content.* import io.ktor.response.* import io.ktor.util.* -import kotlinx.coroutines.* +import io.ktor.util.cio.* import io.ktor.utils.io.* import io.ktor.utils.io.pool.* +import kotlinx.coroutines.* import java.nio.* /** @@ -70,7 +70,8 @@ abstract class BaseApplicationResponse(override val call: ApplicationCall) : App } !transferEncodingSet -> { when (content) { - is OutgoingContent.ProtocolUpgrade -> { } + is OutgoingContent.ProtocolUpgrade -> { + } is OutgoingContent.NoContent -> headers.append(HttpHeaders.ContentLength, "0", safeOnly = false) else -> headers.append(HttpHeaders.TransferEncoding, "chunked", safeOnly = false) } @@ -100,7 +101,7 @@ abstract class BaseApplicationResponse(override val call: ApplicationCall) : App return respondUpgrade(content) } - // ByteArrayContent is most efficient + // ByteArrayContent is most efficient is OutgoingContent.ByteArrayContent -> { // First call user code to acquire bytes, because it could fail val bytes = content.bytes() @@ -109,7 +110,7 @@ abstract class BaseApplicationResponse(override val call: ApplicationCall) : App return respondFromBytes(bytes) } - // WriteChannelContent is more efficient than ReadChannelContent + // WriteChannelContent is more efficient than ReadChannelContent is OutgoingContent.WriteChannelContent -> { // First set headers commitHeaders(content) @@ -117,7 +118,7 @@ abstract class BaseApplicationResponse(override val call: ApplicationCall) : App return respondWriteChannelContent(content) } - // Pipe is least efficient + // Pipe is least efficient is OutgoingContent.ReadChannelContent -> { // First call user code to acquire read channel, because it could fail val readChannel = content.readFrom() @@ -126,7 +127,7 @@ abstract class BaseApplicationResponse(override val call: ApplicationCall) : App return respondFromChannel(readChannel) } - // Do nothing, but maintain `when` exhaustiveness + // Do nothing, but maintain `when` exhaustiveness is OutgoingContent.NoContent -> { commitHeaders(content) return respondNoContent(content) @@ -215,11 +216,13 @@ abstract class BaseApplicationResponse(override val call: ApplicationCall) : App /** * ByteBuffer pool */ - @Deprecated("Avoid specifying pools or use KtorDefaultPool instead.", + @Deprecated( + "Avoid specifying pools or use KtorDefaultPool instead.", ReplaceWith("KtorDefaultPool", "io.ktor.util.cio.KtorDefaultPool"), level = DeprecationLevel.ERROR ) - protected open val bufferPool: ObjectPool get() = KtorDefaultPool + protected open val bufferPool: ObjectPool + get() = KtorDefaultPool /** * Set underlying engine's response status @@ -239,21 +242,39 @@ abstract class BaseApplicationResponse(override val call: ApplicationCall) : App * [OutgoingContent] is trying to set some header that is not allowed for this content type. * For example, only upgrade content can set `Upgrade` header. */ - class InvalidHeaderForContent(name: String, content: String) : IllegalStateException("Header $name is not allowed for $content") + class InvalidHeaderForContent( + private val name: String, private val content: String + ) : IllegalStateException("Header $name is not allowed for $content"), + CopyableThrowable { + override fun createCopy(): InvalidHeaderForContent? = InvalidHeaderForContent(name, content).also { + it.initCause(this) + } + + } /** * Content's actual body size doesn't match the provided one in `Content-Length` header */ - class BodyLengthIsTooSmall(expected: Long, actual: Long) : IllegalStateException( - "Body.size is too small. Body: $actual, Content-Length: $expected" - ) + class BodyLengthIsTooSmall( + private val expected: Long, private val actual: Long + ) : IllegalStateException("Body.size is too small. Body: $actual, Content-Length: $expected"), + CopyableThrowable { + override fun createCopy(): BodyLengthIsTooSmall? = BodyLengthIsTooSmall(expected, actual).also { + it.initCause(this) + } + } /** * Content's actual body size doesn't match the provided one in `Content-Length` header */ - class BodyLengthIsTooLong(expected: Long) : IllegalStateException( - "Body.size is too long. Expected $expected" - ) + class BodyLengthIsTooLong(private val expected: Long) : IllegalStateException( + "Body.size is too long. Expected $expected" + ), CopyableThrowable { + override fun createCopy(): BodyLengthIsTooLong? = BodyLengthIsTooLong(expected).also { + it.initCause(this) + } + + } companion object { /** diff --git a/ktor-server/ktor-server-netty/jvm/src/io/ktor/server/netty/http2/NettyHttp2Handler.kt b/ktor-server/ktor-server-netty/jvm/src/io/ktor/server/netty/http2/NettyHttp2Handler.kt index ba549b8d9..dd89ce833 100644 --- a/ktor-server/ktor-server-netty/jvm/src/io/ktor/server/netty/http2/NettyHttp2Handler.kt +++ b/ktor-server/ktor-server-netty/jvm/src/io/ktor/server/netty/http2/NettyHttp2Handler.kt @@ -157,9 +157,15 @@ internal class NettyHttp2Handler( return superclass.findIdField() } - private class Http2ClosedChannelException(val errorCode: Long) : ClosedChannelException() { + private class Http2ClosedChannelException( + val errorCode: Long + ) : ClosedChannelException(), CopyableThrowable { override val message: String get() = "Got close frame with code $errorCode" + + override fun createCopy(): Http2ClosedChannelException? = Http2ClosedChannelException(errorCode).also { + it.initCause(this) + } } companion object {