diff --git a/.idea/libraries/ktor_server_jetty.xml b/.idea/libraries/ktor_server_jetty.xml new file mode 100644 index 000000000000..5ae8a4cf89ab --- /dev/null +++ b/.idea/libraries/ktor_server_jetty.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityLibraryLicenses.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityLibraryLicenses.groovy index d49cffe0b368..d0e729b79bd4 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityLibraryLicenses.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityLibraryLicenses.groovy @@ -440,6 +440,11 @@ class CommunityLibraryLicenses { license: "Apache 2.0", licenseUrl: "http://www.apache.org/licenses/LICENSE-2.0", url: "https://github.com/ktorio/ktor"), + new LibraryLicense(name: "ktor-server-jetty", + libraryName: "ktor-server-jetty", + license: "Apache 2.0", + licenseUrl: "http://www.apache.org/licenses/LICENSE-2.0", + url: "https://github.com/ktorio/ktor"), new LibraryLicense(name: "ktor-websockets", libraryName: "ktor-websockets", license: "Apache 2.0", diff --git a/plugins/space/intellij.space.iml b/plugins/space/intellij.space.iml index 12075c0c4365..340cf24b9b1d 100644 --- a/plugins/space/intellij.space.iml +++ b/plugins/space/intellij.space.iml @@ -107,6 +107,7 @@ + diff --git a/plugins/space/src/main/kotlin/com/intellij/space/auth/SpaceOAuthRedirectHandler.kt b/plugins/space/src/main/kotlin/com/intellij/space/auth/SpaceOAuthRedirectHandler.kt index c5b1ae45d6cc..6cf40169b247 100644 --- a/plugins/space/src/main/kotlin/com/intellij/space/auth/SpaceOAuthRedirectHandler.kt +++ b/plugins/space/src/main/kotlin/com/intellij/space/auth/SpaceOAuthRedirectHandler.kt @@ -1,14 +1,19 @@ package com.intellij.space.auth +import com.intellij.openapi.application.ApplicationManager import com.intellij.util.concurrency.AppExecutorUtil -import io.ktor.network.selector.ActorSelectorManager -import io.ktor.network.sockets.ServerSocket -import io.ktor.network.sockets.aSocket -import io.ktor.network.sockets.openReadChannel -import io.ktor.network.sockets.openWriteChannel -import io.ktor.utils.io.readUTF8Line -import io.ktor.utils.io.writeStringUtf8 -import kotlinx.coroutines.* +import io.ktor.application.call +import io.ktor.http.ContentType +import io.ktor.request.uri +import io.ktor.response.respondText +import io.ktor.routing.get +import io.ktor.routing.routing +import io.ktor.server.engine.embeddedServer +import io.ktor.server.jetty.Jetty +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Runnable +import kotlinx.coroutines.asCoroutineDispatcher import libraries.coroutines.extra.Lifetime import libraries.coroutines.extra.launch import java.io.IOException @@ -16,51 +21,51 @@ import java.io.IOException private val ioDispatcher = AppExecutorUtil.getAppExecutorService().asCoroutineDispatcher() internal suspend fun startRedirectHandling(lifetime: Lifetime, ports: Collection): SpaceRedirectHandlingInfo? { - val redirectUrl = CompletableDeferred() - val freePort = occupyAvailablePort(lifetime, ports) { serverSocket -> - val socket = serverSocket.accept() - launch(lifetime, ioDispatcher) { - val response = "" - val output = socket.openWriteChannel(autoFlush = true) - output.writeStringUtf8( - """ - HTTP/1.1 200 OK - Content-Type: text/html - Content-Length: ${response.length} - - $response - """.trimIndent() - ) - } - - val input = socket.openReadChannel() - val line = input.readUTF8Line()!!.substringAfter("/").substringBefore(" ") - redirectUrl.complete(line) - } ?: return null - return SpaceRedirectHandlingInfo(freePort, redirectUrl) -} - -private suspend fun occupyAvailablePort(lifetime: Lifetime, ports: Collection, withFreePort: suspend (ServerSocket) -> Unit): Int? { - val reservedPort = CompletableDeferred() + val info = CompletableDeferred() launch(lifetime, ioDispatcher) { for (port in ports.distinct().shuffled()) { try { - aSocket(ActorSelectorManager(ioDispatcher)).tcp().bind(port = port).use { socket -> - reservedPort.complete(port) - withFreePort(socket) - } + val redirectUrl = startAuthServerAsync(lifetime, port) + info.complete(SpaceRedirectHandlingInfo(port, redirectUrl)) break } catch (e: IOException) { - if (reservedPort.isCompleted) { - throw e - } // check next port } } - reservedPort.complete(null) + info.complete(null) } - return reservedPort.await() + return info.await() +} + +private fun startAuthServerAsync(lifetime: Lifetime, port: Int): Deferred { + val redirectUrl = CompletableDeferred() + val server = embeddedServer(Jetty, port = port, host = "localhost") { + routing { + get("/auth") { + call.respondText( + "", + contentType = ContentType.Text.Html + ) + redirectUrl.complete(call.request.uri) + } + } + } + server.start(wait = false) + + fun stopServer() { + ApplicationManager.getApplication().executeOnPooledThread(Runnable { + server.stop(1000, 1000) + }) + } + lifetime.addOrCallImmediately { + stopServer() + } + redirectUrl.invokeOnCompletion { + stopServer() + } + + return redirectUrl } internal data class SpaceRedirectHandlingInfo(val port: Int, val redirectUrl: Deferred) \ No newline at end of file