Коммит e8de451d создал по автору Leonid Stashevsky's avatar Leonid Stashevsky
Просмотр файлов

Make client websocket feature common

владелец 6cc32ea3
build build
.gradle .gradle
.gradletasknamecache
.idea/* .idea/*
!.idea/runConfigurations !.idea/runConfigurations
!.idea/runConfigurations/* !.idea/runConfigurations/*
......
...@@ -42,7 +42,10 @@ def projectNeedsPlatform(project, platform) { ...@@ -42,7 +42,10 @@ def projectNeedsPlatform(project, platform) {
def hasDarwin = files.any { it.name == "darwin" } def hasDarwin = files.any { it.name == "darwin" }
if (hasPosix && hasDarwin) return false if (hasPosix && hasDarwin) return false
if (!hasDarwin && platform == "darwin") return false
if (hasPosix && platform == "darwin") return false
if (hasDarwin && platform == "posix") return false
if (!hasPosix && !hasDarwin && platform == "darwin") return false
return files.any { it.name == "common" || it.name == platform } return files.any { it.name == "common" || it.name == platform }
} }
......
...@@ -3,7 +3,7 @@ kotlin.code.style=official ...@@ -3,7 +3,7 @@ kotlin.code.style=official
# config # config
version=1.2.0-SNAPSHOT version=1.2.0-SNAPSHOT
kotlin.incremental.js=true kotlin.incremental.js=false
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true
# gradle # gradle
......
...@@ -90,6 +90,9 @@ prepareMocha.doLast { ...@@ -90,6 +90,9 @@ prepareMocha.doLast {
<script src="$libraryPath/ktor-client.js"></script> <script src="$libraryPath/ktor-client.js"></script>
<script src="$libraryPath/ktor-client-json.js"></script> <script src="$libraryPath/ktor-client-json.js"></script>
<script src="$libraryPath/ktor-client-auth-basic.js"></script> <script src="$libraryPath/ktor-client-auth-basic.js"></script>
<script src="$libraryPath/ktor-client-tests-dispatcher.js"></script>
<script src="$libraryPath/ktor-client-tests.js"></script>
<script src="$libraryPath/ktor-client-websocket.js"></script>
<script src="$compileTestKotlinJs.outputFile"></script> <script src="$compileTestKotlinJs.outputFile"></script>
<script>mocha.run();</script> <script>mocha.run();</script>
</body> </body>
......
...@@ -33,9 +33,18 @@ kotlin { ...@@ -33,9 +33,18 @@ kotlin {
configure([iosArm32Main, iosArm64Main, iosX64Main, macosX64Main, linuxX64Main, mingwX64Main]) { configure([iosArm32Main, iosArm64Main, iosX64Main, macosX64Main, linuxX64Main, mingwX64Main]) {
dependsOn posixMain dependsOn posixMain
} }
configure([iosArm32Test, iosArm64Test, iosX64Test, macosX64Test, linuxX64Test, mingwX64Test]) { configure([iosArm32Test, iosArm64Test, iosX64Test, macosX64Test, linuxX64Test, mingwX64Test]) {
dependsOn posixTest dependsOn posixTest
} }
iosArm32Test.dependsOn iosArm32Main
iosArm64Test.dependsOn iosArm64Main
iosX64Test.dependsOn iosX64Main
linuxX64Test.dependsOn linuxX64Main
macosX64Test.dependsOn macosX64Main
iosX64Test.dependsOn iosX64Main
mingwX64Test.dependsOn mingwX64Main
} }
} }
} }
......
kotlin.sourceSets.commonMain.dependencies { kotlin.sourceSets.commonMain.dependencies {
api project(':ktor-client:ktor-client-core') api project(':ktor-client:ktor-client-core')
} }
...@@ -5,6 +5,7 @@ kotlin.sourceSets { ...@@ -5,6 +5,7 @@ kotlin.sourceSets {
api project(':ktor-client:ktor-client-core') api project(':ktor-client:ktor-client-core')
api project(':ktor-http:ktor-http-cio') api project(':ktor-http:ktor-http-cio')
api project(':ktor-network:ktor-network-tls') api project(':ktor-network:ktor-network-tls')
api project(':ktor-client:ktor-client-features:ktor-client-websocket')
} }
jvmTest.dependencies { jvmTest.dependencies {
api project(':ktor-client:ktor-client-tests') api project(':ktor-client:ktor-client-tests')
......
...@@ -2,7 +2,9 @@ package io.ktor.client.engine.cio ...@@ -2,7 +2,9 @@ package io.ktor.client.engine.cio
import io.ktor.client.call.* import io.ktor.client.call.*
import io.ktor.client.engine.* import io.ktor.client.engine.*
import io.ktor.client.features.websocket.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.response.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.network.selector.* import io.ktor.network.selector.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
...@@ -11,7 +13,7 @@ import java.io.* ...@@ -11,7 +13,7 @@ import java.io.*
import java.util.concurrent.* import java.util.concurrent.*
import java.util.concurrent.atomic.* import java.util.concurrent.atomic.*
internal class CIOEngine(override val config: CIOEngineConfig) : HttpClientJvmEngine("ktor-cio") { internal class CIOEngine(override val config: CIOEngineConfig) : HttpClientJvmEngine("ktor-cio"), WebSocketEngine {
private val endpoints = ConcurrentHashMap<String, Endpoint>() private val endpoints = ConcurrentHashMap<String, Endpoint>()
@UseExperimental(InternalCoroutinesApi::class) @UseExperimental(InternalCoroutinesApi::class)
...@@ -29,7 +31,12 @@ internal class CIOEngine(override val config: CIOEngineConfig) : HttpClientJvmEn ...@@ -29,7 +31,12 @@ internal class CIOEngine(override val config: CIOEngineConfig) : HttpClientJvmEn
return@withContext HttpEngineCall(request, response) return@withContext HttpEngineCall(request, response)
} }
private suspend fun executeRequest(request: DefaultHttpRequest): CIOHttpResponse { override suspend fun execute(request: HttpRequest): WebSocketResponse {
val response = executeRequest(request)
return response as WebSocketResponse
}
private suspend fun executeRequest(request: HttpRequest): HttpResponse {
while (true) { while (true) {
if (closed.get()) throw ClientClosedException() if (closed.get()) throw ClientClosedException()
......
...@@ -11,9 +11,10 @@ import kotlin.coroutines.* ...@@ -11,9 +11,10 @@ import kotlin.coroutines.*
internal class CIOHttpResponse( internal class CIOHttpResponse(
request: HttpRequest, request: HttpRequest,
override val headers: Headers,
override val requestTime: GMTDate, override val requestTime: GMTDate,
override val content: ByteReadChannel, override val content: ByteReadChannel,
private val response: Response, response: Response,
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
) : HttpResponse { ) : HttpResponse {
...@@ -21,14 +22,7 @@ internal class CIOHttpResponse( ...@@ -21,14 +22,7 @@ internal class CIOHttpResponse(
override val status: HttpStatusCode = HttpStatusCode(response.status, response.statusText.toString()) override val status: HttpStatusCode = HttpStatusCode(response.status, response.statusText.toString())
override val version: HttpProtocolVersion = HttpProtocolVersion.HTTP_1_1 override val version: HttpProtocolVersion = HttpProtocolVersion.parse(response.version)
override val headers: Headers = Headers.build {
val origin = CIOHeaders(response.headers)
origin.names().forEach {
appendAll(it, origin.getAll(it))
}
}
override val responseTime: GMTDate = GMTDate() override val responseTime: GMTDate = GMTDate()
......
...@@ -6,17 +6,20 @@ import io.ktor.network.sockets.* ...@@ -6,17 +6,20 @@ import io.ktor.network.sockets.*
import io.ktor.network.sockets.Socket import io.ktor.network.sockets.Socket
import java.net.* import java.net.*
internal class ConnectionFactory(private val selector: SelectorManager, maxConnectionsCount: Int) { internal class ConnectionFactory(
private val selector: SelectorManager,
maxConnectionsCount: Int
) {
private val semaphore = Semaphore(maxConnectionsCount) private val semaphore = Semaphore(maxConnectionsCount)
suspend fun connect(address: InetSocketAddress): Socket { suspend fun connect(address: InetSocketAddress): Socket {
semaphore.enter() semaphore.enter()
return try { return try {
aSocket(selector).tcpNoDelay().tcp().connect(address) aSocket(selector).tcpNoDelay().tcp().connect(address)
} catch (t: Throwable) { } catch (cause: Throwable) {
// a failure or cancellation // a failure or cancellation
semaphore.leave() semaphore.leave()
throw t throw cause
} }
} }
......
...@@ -76,6 +76,11 @@ internal class ConnectionPipeline( ...@@ -76,6 +76,11 @@ internal class ConnectionPipeline(
val transferEncoding = rawResponse.headers[HttpHeaders.TransferEncoding] val transferEncoding = rawResponse.headers[HttpHeaders.TransferEncoding]
val chunked = transferEncoding == "chunked" val chunked = transferEncoding == "chunked"
val connectionType = ConnectionOptions.parse(rawResponse.headers[HttpHeaders.Connection]) val connectionType = ConnectionOptions.parse(rawResponse.headers[HttpHeaders.Connection])
val headers = CIOHeaders(rawResponse.headers)
callContext[Job]?.invokeOnCompletion {
rawResponse.release()
}
shouldClose = (connectionType == ConnectionOptions.Close) shouldClose = (connectionType == ConnectionOptions.Close)
...@@ -90,7 +95,7 @@ internal class ConnectionPipeline( ...@@ -90,7 +95,7 @@ internal class ConnectionPipeline(
} else ByteReadChannel.Empty } else ByteReadChannel.Empty
val response = CIOHttpResponse( val response = CIOHttpResponse(
task.request, requestTime, task.request, headers, requestTime,
body, body,
rawResponse, rawResponse,
coroutineContext = callContext coroutineContext = callContext
...@@ -99,19 +104,13 @@ internal class ConnectionPipeline( ...@@ -99,19 +104,13 @@ internal class ConnectionPipeline(
task.response.complete(response) task.response.complete(response)
responseChannel?.use { responseChannel?.use {
try { parseHttpBody(
parseHttpBody( contentLength,
contentLength, transferEncoding,
transferEncoding, connectionType,
connectionType, networkInput,
networkInput, this
this )
)
} finally {
callContext[Job]?.invokeOnCompletion {
rawResponse.release()
}
}
} }
skipTask?.join() skipTask?.join()
......
package io.ktor.client.engine.cio package io.ktor.client.engine.cio
import io.ktor.client.features.websocket.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.response.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.http.cio.* import io.ktor.http.cio.*
import io.ktor.http.cio.websocket.*
import io.ktor.network.sockets.* import io.ktor.network.sockets.*
import io.ktor.network.sockets.Socket import io.ktor.network.sockets.Socket
import io.ktor.network.tls.* import io.ktor.network.tls.*
import io.ktor.util.* import io.ktor.util.*
import io.ktor.util.date.* import io.ktor.util.date.*
import kotlinx.atomicfu.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.* import kotlinx.coroutines.channels.*
import kotlinx.coroutines.io.* import kotlinx.coroutines.io.*
import java.io.* import java.io.*
import java.net.* import java.net.*
import java.util.concurrent.atomic.*
import kotlin.coroutines.* import kotlin.coroutines.*
internal class Endpoint( internal class Endpoint(
...@@ -25,14 +28,13 @@ internal class Endpoint( ...@@ -25,14 +28,13 @@ internal class Endpoint(
override val coroutineContext: CoroutineContext, override val coroutineContext: CoroutineContext,
private val onDone: () -> Unit private val onDone: () -> Unit
) : CoroutineScope, Closeable { ) : CoroutineScope, Closeable {
private val address = InetSocketAddress(host, port)
private val connections: AtomicInt = atomic(0)
private val tasks: Channel<RequestTask> = Channel(Channel.UNLIMITED) private val tasks: Channel<RequestTask> = Channel(Channel.UNLIMITED)
private val deliveryPoint: Channel<RequestTask> = Channel() private val deliveryPoint: Channel<RequestTask> = Channel()
private val maxEndpointIdleTime = 2 * config.endpoint.connectTimeout
@Volatile private val maxEndpointIdleTime: Long = 2 * config.endpoint.connectTimeout
private var connectionsHolder: Int = 0
private val address = InetSocketAddress(host, port)
private val postman = launch(start = CoroutineStart.LAZY) { private val postman = launch(start = CoroutineStart.LAZY) {
try { try {
...@@ -64,8 +66,8 @@ internal class Endpoint( ...@@ -64,8 +66,8 @@ internal class Endpoint(
} }
} }
suspend fun execute(request: DefaultHttpRequest, callContext: CoroutineContext): CIOHttpResponse { suspend fun execute(request: HttpRequest, callContext: CoroutineContext): HttpResponse {
val result = CompletableDeferred<CIOHttpResponse>(parent = callContext[Job]) val result = CompletableDeferred<HttpResponse>(parent = callContext[Job])
val task = RequestTask(request, result, callContext) val task = RequestTask(request, result, callContext)
tasks.offer(task) tasks.offer(task)
return result.await() return result.await()
...@@ -74,7 +76,7 @@ internal class Endpoint( ...@@ -74,7 +76,7 @@ internal class Endpoint(
private suspend fun makePipelineRequest(task: RequestTask) { private suspend fun makePipelineRequest(task: RequestTask) {
if (deliveryPoint.offer(task)) return if (deliveryPoint.offer(task)) return
val connections = Connections.get(this@Endpoint) val connections = connections.value
if (connections < config.endpoint.maxConnectionsPerRoute) { if (connections < config.endpoint.maxConnectionsPerRoute) {
try { try {
createPipeline() createPipeline()
...@@ -114,18 +116,20 @@ internal class Endpoint( ...@@ -114,18 +116,20 @@ internal class Endpoint(
val contentLength = rawResponse.headers[HttpHeaders.ContentLength]?.toString()?.toLong() ?: -1L val contentLength = rawResponse.headers[HttpHeaders.ContentLength]?.toString()?.toLong() ?: -1L
val transferEncoding = rawResponse.headers[HttpHeaders.TransferEncoding] val transferEncoding = rawResponse.headers[HttpHeaders.TransferEncoding]
val connectionType = ConnectionOptions.parse(rawResponse.headers[HttpHeaders.Connection]) val connectionType = ConnectionOptions.parse(rawResponse.headers[HttpHeaders.Connection])
val headers = CIOHeaders(rawResponse.headers)
val body = when { callContext[Job]!!.invokeOnCompletion {
status == HttpStatusCode.SwitchingProtocols.value -> { rawResponse.headers.release()
val content = request.content as? ClientUpgradeContent }
?: error("Invalid content type: UpgradeContent required")
if (status == HttpStatusCode.SwitchingProtocols.value) {
val session = RawWebSocket(input, output, masking = true, coroutineContext = callContext)
response.complete(WebSocketResponse(callContext, requestTime, session))
return@launch
}
launch {
content.pipeTo(output)
}.invokeOnCompletion(::closeConnection)
input val body = when {
}
request.method == HttpMethod.Head -> { request.method == HttpMethod.Head -> {
closeConnection() closeConnection()
ByteReadChannel.Empty ByteReadChannel.Empty
...@@ -140,12 +144,12 @@ internal class Endpoint( ...@@ -140,12 +144,12 @@ internal class Endpoint(
} }
} }
response.complete( val result = CIOHttpResponse(
CIOHttpResponse( request, headers, requestTime, body, rawResponse,
request, requestTime, body, rawResponse, coroutineContext = callContext
coroutineContext = callContext
)
) )
response.complete(result)
} catch (cause: Throwable) { } catch (cause: Throwable) {
response.completeExceptionally(cause) response.completeExceptionally(cause)
} }
...@@ -168,7 +172,7 @@ internal class Endpoint( ...@@ -168,7 +172,7 @@ internal class Endpoint(
val retryAttempts = config.endpoint.connectRetryAttempts val retryAttempts = config.endpoint.connectRetryAttempts
val connectTimeout = config.endpoint.connectTimeout val connectTimeout = config.endpoint.connectTimeout
Connections.incrementAndGet(this) connections.incrementAndGet()
try { try {
repeat(retryAttempts) { repeat(retryAttempts) {
...@@ -187,23 +191,28 @@ internal class Endpoint( ...@@ -187,23 +191,28 @@ internal class Endpoint(
address.hostName address.hostName
) )
} }
} catch (t: Throwable) { } catch (cause: Throwable) {
try {
connection.close()
} catch (_: Throwable) {
}
connectionFactory.release() connectionFactory.release()
throw t throw cause
} }
} }
} catch (cause: Throwable) { } catch (cause: Throwable) {
Connections.decrementAndGet(this) connections.decrementAndGet()
throw cause throw cause
} }
Connections.decrementAndGet(this) connections.decrementAndGet()
throw ConnectException() throw ConnectException()
} }
private fun releaseConnection() { private fun releaseConnection() {
connectionFactory.release() connectionFactory.release()
Connections.decrementAndGet(this) connections.decrementAndGet()
} }
override fun close() { override fun close() {
...@@ -213,12 +222,8 @@ internal class Endpoint( ...@@ -213,12 +222,8 @@ internal class Endpoint(
init { init {
postman.start() postman.start()
} }
companion object {
private val Connections = AtomicIntegerFieldUpdater
.newUpdater(Endpoint::class.java, Endpoint::connectionsHolder.name)
}
} }
@KtorExperimentalAPI @KtorExperimentalAPI
@Suppress("KDocMissingDocumentation")
class ConnectException : Exception("Connect timed out or retry attempts exceeded") class ConnectException : Exception("Connect timed out or retry attempts exceeded")
package io.ktor.client.engine.cio package io.ktor.client.engine.cio
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.response.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.util.date.* import io.ktor.util.date.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlin.coroutines.* import kotlin.coroutines.*
internal data class RequestTask( internal data class RequestTask(
val request: DefaultHttpRequest, val request: HttpRequest,
val response: CompletableDeferred<CIOHttpResponse>, val response: CompletableDeferred<HttpResponse>,
val context: CoroutineContext val context: CoroutineContext
) )
......
...@@ -9,7 +9,7 @@ import io.ktor.http.content.* ...@@ -9,7 +9,7 @@ import io.ktor.http.content.*
import kotlinx.coroutines.io.* import kotlinx.coroutines.io.*
import kotlin.coroutines.* import kotlin.coroutines.*
internal suspend fun DefaultHttpRequest.write(output: ByteWriteChannel, callContext: CoroutineContext) { internal suspend fun HttpRequest.write(output: ByteWriteChannel, callContext: CoroutineContext) {
val builder = RequestResponseBuilder() val builder = RequestResponseBuilder()
val contentLength = headers[HttpHeaders.ContentLength] ?: content.contentLength?.toString() val contentLength = headers[HttpHeaders.ContentLength] ?: content.contentLength?.toString()
......
package io.ktor.client.features.websocket
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.http.*
suspend fun HttpClient.webSocketRawSession(
method: HttpMethod = HttpMethod.Get, host: String = "localhost", port: Int = DEFAULT_PORT, path: String = "/",
block: HttpRequestBuilder.() -> Unit = {}
): ClientWebSocketSession = request {
this.method = method
url("ws", host, port, path)
block()
}
suspend fun HttpClient.webSocketRaw(
method: HttpMethod = HttpMethod.Get, host: String = "localhost", port: Int = DEFAULT_PORT, path: String = "/",
request: HttpRequestBuilder.() -> Unit = {}, block: suspend ClientWebSocketSession.() -> Unit
): Unit {
val session = webSocketRawSession(method, host, port, path) {
url.protocol = URLProtocol.WS
url.port = port
request()
}
try {
session.block()
} catch (cause: Throwable) {
session.close(cause)
} finally {
session.close()
}
}
suspend fun HttpClient.wsRaw(
method: HttpMethod = HttpMethod.Get, host: String = "localhost", port: Int = DEFAULT_PORT, path: String = "/",
request: HttpRequestBuilder.() -> Unit = {}, block: suspend ClientWebSocketSession.() -> Unit
): Unit = webSocketRaw(method, host, port, path, request, block)
suspend fun HttpClient.wssRaw(
method: HttpMethod = HttpMethod.Get, host: String = "localhost", port: Int = DEFAULT_PORT, path: String = "/",
request: HttpRequestBuilder.() -> Unit = {}, block: suspend ClientWebSocketSession.() -> Unit
): Unit = webSocketRaw(method, host, port, path, request = {
url.protocol = URLProtocol.WSS
url.port = port
request()
}, block = block)
...@@ -52,14 +52,16 @@ fun HttpClient( ...@@ -52,14 +52,16 @@ fun HttpClient(
* Asynchronous client to perform HTTP requests. * Asynchronous client to perform HTTP requests.
* *
* This is a generic implementation that uses a specific engine [HttpClientEngine]. * This is a generic implementation that uses a specific engine [HttpClientEngine].
* @property engine: [HttpClientEngine] for executing requests.
*/ */
class HttpClient( class HttpClient(
private val engine: HttpClientEngine, @InternalAPI val engine: HttpClientEngine,
private val userConfig: HttpClientConfig<out HttpClientEngineConfig> = HttpClientConfig() private val userConfig: HttpClientConfig<out HttpClientEngineConfig> = HttpClientConfig()
) : CoroutineScope, Closeable { ) : CoroutineScope, Closeable {
private val closed = atomic(false) private val closed = atomic(false)
override val coroutineContext: CoroutineContext get() = engine.coroutineContext override val coroutineContext: CoroutineContext get() = engine.coroutineContext
/** /**
* Pipeline used for processing all the requests sent by this client. * Pipeline used for processing all the requests sent by this client.
*/ */
......
...@@ -4,6 +4,7 @@ import io.ktor.client.* ...@@ -4,6 +4,7 @@ import io.ktor.client.*
import io.ktor.client.features.* import io.ktor.client.features.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.response.* import io.ktor.client.response.*
import io.ktor.util.*
import kotlinx.atomicfu.* import kotlinx.atomicfu.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.io.core.* import kotlinx.io.core.*
...@@ -13,9 +14,9 @@ import kotlin.reflect.* ...@@ -13,9 +14,9 @@ import kotlin.reflect.*
/** /**
* A class that represents a single pair of [request] and [response] for a specific [HttpClient]. * A class that represents a single pair of [request] and [response] for a specific [HttpClient].
* *
* [client] - client that executed the call. * @property client: client that executed the call.
*/ */
class HttpClientCall internal constructor( open class HttpClientCall constructor(
val client: HttpClient val client: HttpClient
) : CoroutineScope, Closeable { ) : CoroutineScope, Closeable {
private val received = atomic(false) private val received = atomic(false)
...@@ -23,7 +24,12 @@ class HttpClientCall internal constructor( ...@@ -23,7 +24,12 @@ class HttpClientCall internal constructor(
override val coroutineContext: CoroutineContext get() = response.coroutineContext override val coroutineContext: CoroutineContext get() = response.coroutineContext
/** /**
* Represents the [request] sent by the client. * Typed [Attributes] associated to this call serving as a lightweight container.
*/
val attributes: Attributes get() = request.attributes
/**
* Represents the [request] sent by the client
*/ */
lateinit var request: HttpRequest lateinit var request: HttpRequest
internal set internal set
...@@ -70,6 +76,12 @@ class HttpClientCall internal constructor( ...@@ -70,6 +76,12 @@ class HttpClientCall internal constructor(
} }
} }
/**
* Raw http call produced by engine.
*
* @property request - executed http request.
* @property response - raw http response
*/
data class HttpEngineCall(val request: HttpRequest, val response: HttpResponse) data class HttpEngineCall(val request: HttpRequest, val response: HttpResponse)
/** /**
...@@ -98,6 +110,7 @@ suspend inline fun <reified T> HttpResponse.receive(): T = call.receive(typeInfo ...@@ -98,6 +110,7 @@ suspend inline fun <reified T> HttpResponse.receive(): T = call.receive(typeInfo
/** /**
* Exception representing that the response payload has already been received. * Exception representing that the response payload has already been received.
*/ */
@Suppress("KDocMissingDocumentation")
class DoubleReceiveException(call: HttpClientCall) : IllegalStateException() { class DoubleReceiveException(call: HttpClientCall) : IllegalStateException() {
override val message: String = "Response already received: $call" override val message: String = "Response already received: $call"
} }
...@@ -106,6 +119,7 @@ class DoubleReceiveException(call: HttpClientCall) : IllegalStateException() { ...@@ -106,6 +119,7 @@ class DoubleReceiveException(call: HttpClientCall) : IllegalStateException() {
* Exception representing fail of the response pipeline * Exception representing fail of the response pipeline
* [cause] contains origin pipeline exception * [cause] contains origin pipeline exception
*/ */
@Suppress("KDocMissingDocumentation")
class ReceivePipelineException( class ReceivePipelineException(
val request: HttpClientCall, val request: HttpClientCall,
val info: TypeInfo, val info: TypeInfo,
...@@ -116,6 +130,7 @@ class ReceivePipelineException( ...@@ -116,6 +130,7 @@ class ReceivePipelineException(
* Exception representing the no transformation was found. * Exception representing the no transformation was found.
* It includes the received type and the expected type as part of the message. * It includes the received type and the expected type as part of the message.
*/ */
@Suppress("KDocMissingDocumentation")
class NoTransformationFoundException(from: KClass<*>, to: KClass<*>) : UnsupportedOperationException() { class NoTransformationFoundException(from: KClass<*>, to: KClass<*>) : UnsupportedOperationException() {
override val message: String? = "No transformation found: $from -> $to" override val message: String? = "No transformation found: $from -> $to"
} }
...@@ -125,4 +140,5 @@ class NoTransformationFoundException(from: KClass<*>, to: KClass<*>) : Unsupport ...@@ -125,4 +140,5 @@ class NoTransformationFoundException(from: KClass<*>, to: KClass<*>) : Unsupport
ReplaceWith("NoTransformationFoundException"), ReplaceWith("NoTransformationFoundException"),
DeprecationLevel.ERROR DeprecationLevel.ERROR
) )
@Suppress("KDocMissingDocumentation")
typealias NoTransformationFound = NoTransformationFoundException typealias NoTransformationFound = NoTransformationFoundException
...@@ -9,6 +9,10 @@ import io.ktor.http.content.* ...@@ -9,6 +9,10 @@ import io.ktor.http.content.*
class UnsupportedContentTypeException(content: OutgoingContent) : class UnsupportedContentTypeException(content: OutgoingContent) :
IllegalStateException("Failed to write body: ${content::class}") IllegalStateException("Failed to write body: ${content::class}")
class UnsupportedUpgradeProtocolException(
url: Url
) : IllegalArgumentException("Unsupported upgrade protocol exception: $url")
/** /**
* Constructs a [HttpClientCall] from this [HttpClient] and * Constructs a [HttpClientCall] from this [HttpClient] and
* with the specified HTTP request [builder]. * with the specified HTTP request [builder].
......
...@@ -61,7 +61,7 @@ class HttpSend( ...@@ -61,7 +61,7 @@ class HttpSend(
do { do {
callChanged = false callChanged = false
passInterceptors@for (interceptor in feature.interceptors) { passInterceptors@ for (interceptor in feature.interceptors) {
val transformed = interceptor(sender, currentCall) val transformed = interceptor(sender, currentCall)
if (transformed === currentCall) continue@passInterceptors if (transformed === currentCall) continue@passInterceptors
...@@ -77,7 +77,7 @@ class HttpSend( ...@@ -77,7 +77,7 @@ class HttpSend(
} }
private class DefaultSender(private val maxSendCount: Int, private val client: HttpClient) : Sender { private class DefaultSender(private val maxSendCount: Int, private val client: HttpClient) : Sender {
private var sentCount = 0 private var sentCount: Int = 0
override suspend fun execute(requestBuilder: HttpRequestBuilder): HttpClientCall { override suspend fun execute(requestBuilder: HttpRequestBuilder): HttpClientCall {
if (sentCount >= maxSendCount) throw SendCountExceedException("Max send count $maxSendCount exceeded") if (sentCount >= maxSendCount) throw SendCountExceedException("Max send count $maxSendCount exceeded")
......
...@@ -5,7 +5,7 @@ import io.ktor.http.* ...@@ -5,7 +5,7 @@ import io.ktor.http.*
import kotlinx.coroutines.io.* import kotlinx.coroutines.io.*
abstract class ClientUpgradeContent : OutgoingContent.NoContent() { abstract class ClientUpgradeContent : OutgoingContent.NoContent() {
private val content: ByteChannel = ByteChannel() private val content: ByteChannel by lazy { ByteChannel() }
val output: ByteWriteChannel get() = content val output: ByteWriteChannel get() = content
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать