Cleanup (minor optimization; deprecated API; typos; formatting)

GitOrigin-RevId: 01b2043d1259943e78df74590c02f0c30384c2f5
This commit is contained in:
Roman Shevchenko
2024-11-26 13:46:44 +01:00
committed by intellij-monorepo-bot
parent c9cf9ef185
commit bc1bf10d19
15 changed files with 283 additions and 342 deletions

View File

@@ -1,10 +1,11 @@
# suppress inspection "GrazieInspection"
checkbox.tooltip.can.t.be.enabled.for.default.port=Can\u2019t be enabled for default port (port number >= 63342). Please change it.
dialog.button.copy.authorization.url.to.clipboard=Copy Authorization URL to Clipboard
dialog.message.page=Page ''{0}'' requested without authorization, \nyou can copy URL and open it in browser to trust it.
dialog.title.diff.service=Diff Service
notification.content.built.in.web.server.is.deactivated=Built-in web server is deactivated, to activate, please use Open in Browser
notification.content.cannot.start.built.in.http.server.on.custom.port=Cannot start built-in HTTP server on custom port {0}. Please ensure that port is free (or check your firewall settings) and restart {1}
notification.content.cannot.start.internal.http.server.and.ask.for.restart.0=Cannot start internal HTTP server. Git integration, JavaScript debugger and LiveEdit may operate with errors. Please check your firewall settings and restart {0}.
notification.content.cannot.start.built.in.http.server.on.custom.port=Cannot start built-in HTTP server on custom port {0}. \
Please ensure that port is free (or check your firewall settings) and restart {1}
notification.content.cannot.start.internal.http.server.and.ask.for.restart.0=Cannot start internal HTTP server. \
Git integration, JavaScript debugger, and LiveEdit may operate with errors. \
Please check your firewall settings and restart {0}.
setting.builtin.server.category.label=Built-in Server
setting.value.builtin.server.port.label=&Port\:
@@ -14,4 +15,4 @@ setting.value.builtin.server.allow.unsigned.requests=&Allow unsigned requests
reload.on.save.got.it.title=Reloading in Browser on Save
reload.on.save.got.it.content=The page will be reloaded automatically in the browser when the changes are saved.
reload.on.save.preview.got.it.title=Reloading Page Preview on Save
reload.on.save.preview.got.it.content=To see the updated preview of the page, save the changes.
reload.on.save.preview.got.it.content=To see the updated preview of the page, save the changes.

View File

@@ -35,9 +35,7 @@ f:org.jetbrains.builtInWebServer.BuiltInWebBrowserUrlProviderKt
- sf:getBuiltInServerUrls(com.intellij.openapi.vfs.VirtualFile,com.intellij.openapi.project.Project,java.lang.String):java.util.List
- sf:getBuiltInServerUrls(com.intellij.openapi.vfs.VirtualFile,com.intellij.openapi.project.Project,java.lang.String,Z):java.util.List
- sf:getBuiltInServerUrls(com.intellij.openapi.vfs.VirtualFile,com.intellij.openapi.project.Project,java.lang.String,Z,com.intellij.ide.browsers.ReloadMode):java.util.List
- sf:getBuiltInServerUrls(org.jetbrains.builtInWebServer.PathInfo,com.intellij.openapi.project.Project,java.lang.String,Z,com.intellij.ide.browsers.ReloadMode):com.intellij.util.SmartList
- bs:getBuiltInServerUrls$default(com.intellij.openapi.vfs.VirtualFile,com.intellij.openapi.project.Project,java.lang.String,Z,com.intellij.ide.browsers.ReloadMode,I,java.lang.Object):java.util.List
- bs:getBuiltInServerUrls$default(org.jetbrains.builtInWebServer.PathInfo,com.intellij.openapi.project.Project,java.lang.String,Z,com.intellij.ide.browsers.ReloadMode,I,java.lang.Object):com.intellij.util.SmartList
f:org.jetbrains.builtInWebServer.BuiltInWebServerAuth
- <init>():V
- f:acquireToken():java.lang.String
@@ -95,9 +93,8 @@ f:org.jetbrains.builtInWebServer.StaticFileHandlerKt
a:org.jetbrains.builtInWebServer.WebServerFileHandler
- <init>():V
- getPageFileExtensions():java.util.List
- pf:getRequestPath(java.lang.CharSequence,java.lang.String):java.lang.String
- a:process(org.jetbrains.builtInWebServer.PathInfo,java.lang.CharSequence,com.intellij.openapi.project.Project,io.netty.handler.codec.http.FullHttpRequest,io.netty.channel.Channel,java.lang.String,io.netty.handler.codec.http.HttpHeaders):Z
f:org.jetbrains.builtInWebServer.WebServerFileHandlerKt
- sf:getRequestPath(java.lang.CharSequence,java.lang.String):java.lang.String
org.jetbrains.builtInWebServer.WebServerPathHandler
- a:process(java.lang.String,com.intellij.openapi.project.Project,io.netty.handler.codec.http.FullHttpRequest,io.netty.channel.ChannelHandlerContext,java.lang.String,io.netty.handler.codec.http.HttpHeaders,Z):Z
f:org.jetbrains.builtInWebServer.WebServerPathToFileManager

View File

@@ -1,8 +1,8 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.builtInWebServer
import com.intellij.ide.browsers.*
import com.intellij.openapi.components.service
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
@@ -21,68 +21,57 @@ open class BuiltInWebBrowserUrlProvider : WebBrowserUrlProvider(), DumbAware {
return true
}
// we must use base language because we serve file - not part of file, but the whole file
// handlebars, for example, contains HTML and HBS psi trees, so, regardless of context, we should not handle such file
// we must use base language because we serve file - not part of file, but the entire Handlebars file, for example,
// contains HTML and HBS trees, so regardless of context, we should not handle such a file
return request.isPhysicalFile() && isFileOfMyLanguage(request.file)
}
protected open fun isFileOfMyLanguage(psiFile: PsiFile): Boolean = WebBrowserXmlService.getInstance().isHtmlOrXmlFile(psiFile)
override fun getUrl(request: OpenInBrowserRequest, file: VirtualFile): Url? {
if (file is HttpVirtualFile) {
return Urls.newFromVirtualFile(file)
}
else {
return getBuiltInServerUrls(file, request.project, null, request.isAppendAccessToken, request.reloadMode).firstOrNull()
}
}
override fun getUrl(request: OpenInBrowserRequest, file: VirtualFile): Url? =
if (file is HttpVirtualFile) Urls.newFromVirtualFile(file)
else getBuiltInServerUrls(file, request.project, null, request.isAppendAccessToken, request.reloadMode).firstOrNull()
}
@JvmOverloads
fun getBuiltInServerUrls(file: VirtualFile,
project: Project,
currentAuthority: String?,
appendAccessToken: Boolean = true,
reloadMode: ReloadMode? = null): List<Url> {
fun getBuiltInServerUrls(
file: VirtualFile,
project: Project,
currentAuthority: String?,
appendAccessToken: Boolean = true,
reloadMode: ReloadMode? = null,
): List<Url> {
if (currentAuthority != null && !compareAuthority(currentAuthority)) {
return emptyList()
}
val info = WebServerPathToFileManager.getInstance(project).getPathInfo(file) ?: return emptyList()
return getBuiltInServerUrls(info, project, currentAuthority, appendAccessToken, reloadMode)
}
fun getBuiltInServerUrls(info: PathInfo,
project: Project,
currentAuthority: String? = null,
appendAccessToken: Boolean = true,
preferredReloadMode: ReloadMode? = null): SmartList<Url> {
val effectiveBuiltInServerPort = BuiltInServerOptions.getInstance().effectiveBuiltInServerPort
val effectivePort = BuiltInServerOptions.getInstance().effectiveBuiltInServerPort
val path = info.path
val authority = currentAuthority ?: "localhost:$effectiveBuiltInServerPort"
val reloadMode = preferredReloadMode ?: WebBrowserManager.getInstance().webServerReloadMode
val authority = currentAuthority ?: "localhost:${effectivePort}"
@Suppress("MISSING_DEPENDENCY_SUPERCLASS_IN_TYPE_ARGUMENT")
val reloadMode = reloadMode ?: WebBrowserManager.getInstance().webServerReloadMode
val appendReloadOnSave = reloadMode != ReloadMode.DISABLED
val queryBuilder = StringBuilder()
if (appendAccessToken || appendReloadOnSave) queryBuilder.append('?')
if (appendAccessToken) queryBuilder.append(TOKEN_PARAM_NAME).append('=').append(acquireToken())
if (appendAccessToken) queryBuilder.append(TOKEN_PARAM_NAME).append('=').append(service<BuiltInWebServerAuth>().acquireToken())
if (appendAccessToken && appendReloadOnSave) queryBuilder.append('&')
if (appendReloadOnSave) queryBuilder.append(WebServerPageConnectionService.RELOAD_URL_PARAM).append('=').append(reloadMode.name)
val query = queryBuilder.toString()
val urls = SmartList(Urls.newHttpUrl(authority, "/${project.name}/$path", query))
val urls = SmartList(Urls.newHttpUrl(authority, "/${project.name}/${path}", query))
val path2 = info.rootLessPathIfPossible
if (path2 != null) {
urls.add(Urls.newHttpUrl(authority, '/' + project.name + '/' + path2, query))
urls += Urls.newHttpUrl(authority, "/${project.name}/${path2}", query)
}
val defaultPort = BuiltInServerManager.getInstance().port
if (currentAuthority == null && defaultPort != effectiveBuiltInServerPort) {
if (currentAuthority == null && defaultPort != effectivePort) {
val defaultAuthority = "localhost:$defaultPort"
urls.add(Urls.newHttpUrl(defaultAuthority, "/${project.name}/$path", query))
urls += Urls.newHttpUrl(defaultAuthority, "/${project.name}/${path}", query)
if (path2 != null) {
urls.add(Urls.newHttpUrl(defaultAuthority, "/${project.name}/$path2", query))
urls += Urls.newHttpUrl(defaultAuthority, "/${project.name}/${path2}", query)
}
}
@@ -94,7 +83,7 @@ fun compareAuthority(currentAuthority: String?): Boolean {
return false
}
val portIndex = currentAuthority!!.indexOf(':')
val portIndex = currentAuthority.indexOf(':')
if (portIndex < 0) {
return false
}
@@ -106,4 +95,4 @@ fun compareAuthority(currentAuthority: String?): Boolean {
val port = StringUtil.parseInt(currentAuthority.substring(portIndex + 1), -1)
return port == BuiltInServerOptions.getInstance().effectiveBuiltInServerPort || port == BuiltInServerManager.getInstance().port
}
}

View File

@@ -309,11 +309,11 @@ internal fun isOwnHostName(host: String): Boolean {
}
val localHostName = InetAddress.getLocalHost().hostName
// WEB-8889
// develar.local is own host name: develar. equals to "develar.labs.intellij.net" (canonical host name)
return localHostName.equals(host, ignoreCase = true) || (host.endsWith(".local") && localHostName.regionMatches(0, host, 0, host.length - ".local".length, true))
// WEB-8889: "host.local" is an own host name; "host." equals to "host.domain" (canonical host name)
return localHostName.equals(host, ignoreCase = true) ||
host.endsWith(".local") && localHostName.regionMatches(0, host, 0, host.length - ".local".length, ignoreCase = true)
}
catch (ignored: IOException) {
catch (_: IOException) {
return false
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.builtInWebServer
import com.intellij.openapi.project.Project
@@ -11,20 +11,19 @@ abstract class WebServerFileHandler {
get() = emptyList()
/**
* canonicalRequestPath contains index file name (if not specified in the request)
* `canonicalRequestPath` contains index file name (if not specified in the request)
*/
abstract fun process(pathInfo: PathInfo,
canonicalPath: CharSequence,
project: Project,
request: FullHttpRequest,
channel: Channel,
projectNameIfNotCustomHost: String?,
extraHeaders: HttpHeaders): Boolean
}
abstract fun process(
pathInfo: PathInfo,
canonicalPath: CharSequence,
project: Project,
request: FullHttpRequest,
channel: Channel,
projectNameIfNotCustomHost: String?,
extraHeaders: HttpHeaders,
): Boolean
fun getRequestPath(canonicalPath: CharSequence, projectNameIfNotCustomHost: String?): String {
return when (projectNameIfNotCustomHost) {
null -> "/$canonicalPath"
else -> "/$projectNameIfNotCustomHost/$canonicalPath"
}
}
protected fun getRequestPath(canonicalPath: CharSequence, projectNameIfNotCustomHost: String?): String =
if (projectNameIfNotCustomHost == null) "/${canonicalPath}"
else "/${projectNameIfNotCustomHost}/${canonicalPath}"
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.builtInWebServer
import com.github.benmanes.caffeine.cache.CacheLoader
@@ -32,16 +32,14 @@ private const val cacheSize: Long = 4096 * 4
@Service(Service.Level.PROJECT)
class WebServerPathToFileManager(private val project: Project) {
internal val pathToInfoCache = Caffeine.newBuilder().maximumSize(cacheSize).expireAfterAccess(10, TimeUnit.MINUTES).build<String, PathInfo>()
internal val pathToExistShortTermCache = Caffeine.newBuilder().maximumSize(cacheSize).expireAfterAccess(5, TimeUnit.SECONDS).build<String, Boolean>()
// time to expire should be greater than pathToFileCache
private val virtualFileToPathInfo = Caffeine.newBuilder().maximumSize(cacheSize).expireAfterAccess(11, TimeUnit.MINUTES).build<VirtualFile, PathInfo>()
internal val pathToExistShortTermCache = Caffeine.newBuilder().maximumSize(cacheSize).expireAfterAccess(5, TimeUnit.SECONDS).build<String, Boolean>()
/**
* https://youtrack.jetbrains.com/issue/WEB-25900
*
* Compute suitable roots for oldest parent (web/foo/my/file.dart -> oldest is web and we compute all suitable roots for it in advance) to avoid linear search
* (i.e. to avoid two queries for root if files web/foo and web/bar requested if root doesn't have web dir)
* Compute suitable roots for the topmost parent (like `web` in `web/foo/my/file.dart`) to avoid linear search -
* i.e., to avoid two queries for root when `web/foo` and `web/bar` requested and root doesn't have `web` dir (WEB-25900).
*/
internal val parentToSuitableRoot = Caffeine
.newBuilder()
@@ -50,12 +48,11 @@ class WebServerPathToFileManager(private val project: Project) {
val suitableRoots = SmartList<SuitableRoot>()
var moduleQualifier: String? = null
val modules = runReadAction { ModuleManager.getInstance(project).modules }
for (rootProvider in RootProvider.values()) {
for (rootProvider in RootProvider.entries) {
for (module in modules) {
if (module.isDisposed) {
continue
}
for (root in rootProvider.getRoots(module.rootManager)) {
if (root.findChild(path) != null) {
if (moduleQualifier == null) {
@@ -94,15 +91,14 @@ class WebServerPathToFileManager(private val project: Project) {
clearCache()
}
})
project.messageBus.connect().subscribe(AdditionalLibraryRootsListener.TOPIC,
AdditionalLibraryRootsListener { _, _, _, _ ->
clearCache()
})
project.messageBus.connect().subscribe(AdditionalLibraryRootsListener.TOPIC, AdditionalLibraryRootsListener { _, _, _, _ ->
clearCache()
})
}
companion object {
@JvmStatic
fun getInstance(project: Project) = project.service<WebServerPathToFileManager>()
fun getInstance(project: Project): WebServerPathToFileManager = project.service<WebServerPathToFileManager>()
}
private fun clearCache() {
@@ -152,7 +148,9 @@ class WebServerPathToFileManager(private val project: Project) {
}
internal fun doFindByRelativePath(path: String, pathQuery: PathQuery): PathInfo? {
val result = WebServerRootsProvider.EP_NAME.extensionList.asSequence().map { it.resolve(path, project, pathQuery) }.find { it != null } ?: return null
val result = WebServerRootsProvider.EP_NAME.extensionList.asSequence()
.map { it.resolve(path, project, pathQuery) }
.find { it != null } ?: return null
result.file?.let {
virtualFileToPathInfo.put(it, result)
}
@@ -191,4 +189,4 @@ private val EMPTY_PATH_RESOLVER = object : FileResolver {
}
}
internal val defaultPathQuery = PathQuery()
internal val defaultPathQuery = PathQuery()

View File

@@ -6,6 +6,7 @@ import com.intellij.notification.NotificationType
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ApplicationNamesInfo
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.util.Disposer
import com.intellij.util.SystemProperties
@@ -17,9 +18,9 @@ import io.netty.bootstrap.ServerBootstrap
import kotlinx.coroutines.*
import kotlinx.coroutines.future.asCompletableFuture
import org.jetbrains.builtInWebServer.BuiltInServerOptions
import org.jetbrains.builtInWebServer.BuiltInWebServerAuth
import org.jetbrains.builtInWebServer.TOKEN_HEADER_NAME
import org.jetbrains.builtInWebServer.TOKEN_PARAM_NAME
import org.jetbrains.builtInWebServer.acquireToken
import org.jetbrains.io.BuiltInServer
import org.jetbrains.io.NettyUtil
import org.jetbrains.io.SubServer
@@ -29,13 +30,8 @@ import java.net.NetworkInterface
import java.net.URLConnection
import java.util.*
private const val PORTS_COUNT = 20
private const val PROPERTY_RPC_PORT = "rpc.port"
private const val PROPERTY_DISABLED = "idea.builtin.server.disabled"
private val LOG = logger<BuiltInServerManager>()
class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : BuiltInServerManager() {
private val authService = service<BuiltInWebServerAuth>()
private var serverStartFuture: Job? = null
private var server: BuiltInServer? = null
private var portOverride: Int? = null
@@ -57,6 +53,12 @@ class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : Bui
override fun createClientBootstrap(): Bootstrap = NettyUtil.nioClientBootstrap(server!!.childEventLoopGroup)
companion object {
private const val PORTS_COUNT = 20
private const val PROPERTY_RPC_PORT = "rpc.port"
private const val PROPERTY_DISABLED = "idea.builtin.server.disabled"
private val LOG = logger<BuiltInServerManager>()
internal const val NOTIFICATION_GROUP = "Built-in Server"
@JvmStatic
@@ -84,7 +86,7 @@ class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : Bui
inetAddress.isAnyLocalAddress ||
options.builtInServerAvailableExternally && idePort != port && NetworkInterface.getByInetAddress(inetAddress) != null
}
catch (e: IOException) {
catch (_: IOException) {
return false
}
}
@@ -94,10 +96,10 @@ class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : Bui
override fun waitForStart(): BuiltInServerManager {
val app = ApplicationManager.getApplication()
LOG.assertTrue(app.isUnitTestMode ||
app.isHeadlessEnvironment ||
!app.isDispatchThread,
"Should not wait for built-in server on EDT")
LOG.assertTrue(
app.isUnitTestMode || app.isHeadlessEnvironment || !app.isDispatchThread,
"Should not wait for built-in server on EDT"
)
var future: Job?
synchronized(this) {
@@ -121,7 +123,7 @@ class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : Bui
server = BuiltInServer.start(firstPort = getDefaultPort(), portsCount = PORTS_COUNT, tryAnyPort = true)
bindCustomPorts(server!!)
}
catch (e: CancellationException) {
catch (_: CancellationException) {
return
}
catch (e: Throwable) {
@@ -136,16 +138,12 @@ class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : Bui
Disposer.register(ApplicationManager.getApplication(), server!!)
}
override fun isOnBuiltInWebServer(url: Url?): Boolean {
return url != null && !url.authority.isNullOrEmpty() && isOnBuiltInWebServerByAuthority(url.authority!!)
}
override fun isOnBuiltInWebServer(url: Url?): Boolean =
url != null && !url.authority.isNullOrEmpty() && isOnBuiltInWebServerByAuthority(url.authority!!)
override fun addAuthToken(url: Url): Url {
return when {
// the built-in server URL contains a query only if a token is specified
url.parameters != null -> url
else -> Urls.newUrl(url.scheme!!, url.authority!!, url.path, Collections.singletonMap(TOKEN_PARAM_NAME, acquireToken()))
}
override fun addAuthToken(url: Url): Url = when {
url.parameters != null -> url // the built-in server URL contains a query only if a token is specified
else -> Urls.newUrl(url.scheme!!, url.authority!!, url.path, Collections.singletonMap(TOKEN_PARAM_NAME, authService.acquireToken()))
}
override fun overridePort(port: Int?) {
@@ -155,24 +153,21 @@ class BuiltInServerManagerImpl(private val coroutineScope: CoroutineScope) : Bui
}
override fun configureRequestToWebServer(connection: URLConnection) {
connection.setRequestProperty(TOKEN_HEADER_NAME, acquireToken())
}
}
// the default port will be occupied by the main IDE instance - define the custom default to avoid searching for a free port
private fun getDefaultPort(): Int {
return SystemProperties.getIntProperty(
PROPERTY_RPC_PORT,
if (ApplicationManager.getApplication().isUnitTestMode) 64463 else BuiltInServerOptions.DEFAULT_PORT,
)
}
private fun bindCustomPorts(server: BuiltInServer) {
if (ApplicationManager.getApplication().isUnitTestMode) {
return
connection.setRequestProperty(TOKEN_HEADER_NAME, authService.acquireToken())
}
CustomPortServerManager.EP_NAME.forEachExtensionSafe { customPortServerManager ->
SubServer(customPortServerManager, server).bind(customPortServerManager.port)
// the default port will be occupied by the main IDE instance - define the custom default to avoid searching for a free port
private fun getDefaultPort(): Int =
System.getProperty(PROPERTY_RPC_PORT)?.toIntOrNull()
?: if (ApplicationManager.getApplication().isUnitTestMode) 64463 else BuiltInServerOptions.DEFAULT_PORT
private fun bindCustomPorts(server: BuiltInServer) {
if (ApplicationManager.getApplication().isUnitTestMode) {
return
}
CustomPortServerManager.EP_NAME.forEachExtensionSafe { customPortServerManager ->
SubServer(customPortServerManager, server).bind(customPortServerManager.port)
}
}
}

View File

@@ -5,6 +5,7 @@ import com.github.benmanes.caffeine.cache.CacheLoader
import com.github.benmanes.caffeine.cache.Caffeine
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.Strictness
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import com.google.gson.stream.MalformedJsonException
@@ -13,6 +14,7 @@ import com.intellij.ide.impl.ProjectUtil.showYesNoDialog
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
@@ -35,10 +37,7 @@ import io.netty.buffer.Unpooled
import io.netty.channel.Channel
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.http.*
import org.jetbrains.annotations.NonNls
import org.jetbrains.builtInWebServer.BuiltInWebServerAuth
import org.jetbrains.ide.RestService.Companion.createJsonReader
import org.jetbrains.ide.RestService.Companion.createJsonWriter
import org.jetbrains.io.*
import java.awt.Window
import java.io.IOException
@@ -52,11 +51,11 @@ import java.util.concurrent.atomic.AtomicInteger
/**
* Document your service using [apiDoc](http://apidocjs.com).
* To extract a big example from source code, consider adding a *.coffee file near the sources
* (or Python/Ruby, but CoffeeScript is recommended because it's plugin is lightweight).
* To extract a big example from source code, consider adding a `*.coffee` file near the sources
* (or Python/Ruby, but CoffeeScript is recommended because its plugin is lightweight).
* See [AboutHttpService] for example.
*
* Don't create [JsonReader]/[JsonWriter] directly, use only provided [createJsonReader] and [createJsonWriter] methods
* Don't create [JsonReader]/[JsonWriter] directly, use only provided [RestService.createJsonReader] and [RestService.createJsonWriter] methods
* (to ensure that you handle in/out according to REST API guidelines).
*
* @see <a href="http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api">Best Practices for Designing a Pragmatic REST API</a>.
@@ -64,9 +63,9 @@ import java.util.concurrent.atomic.AtomicInteger
abstract class RestService : HttpRequestHandler() {
companion object {
@JvmField
val LOG = logger<RestService>()
val LOG: Logger = logger<RestService>()
const val PREFIX = "api"
const val PREFIX: String = "api"
@JvmStatic
fun activateLastFocusedFrame() {
@@ -74,23 +73,18 @@ abstract class RestService : HttpRequestHandler() {
}
@JvmStatic
fun createJsonReader(request: FullHttpRequest): JsonReader {
val reader = JsonReader(ByteBufInputStream(request.content()).reader())
reader.isLenient = true
return reader
}
fun createJsonReader(request: FullHttpRequest): JsonReader =
JsonReader(ByteBufInputStream(request.content()).reader())
.apply { strictness = Strictness.LENIENT }
@JvmStatic
fun createJsonWriter(out: OutputStream): JsonWriter {
val writer = JsonWriter(out.writer())
writer.setIndent(" ")
return writer
}
fun createJsonWriter(out: OutputStream): JsonWriter =
JsonWriter(out.writer())
.apply { setIndent(" ") }
@JvmStatic
fun getLastFocusedOrOpenedProject(): Project? {
return IdeFocusManager.getGlobalInstance().lastFocusedFrame?.project ?: ProjectManager.getInstance().openProjects.firstOrNull()
}
fun getLastFocusedOrOpenedProject(): Project? =
IdeFocusManager.getGlobalInstance().lastFocusedFrame?.project ?: ProjectManager.getInstance().openProjects.firstOrNull()
@JvmStatic
fun sendOk(request: FullHttpRequest, context: ChannelHandlerContext) {
@@ -117,14 +111,12 @@ abstract class RestService : HttpRequestHandler() {
@Suppress("SameParameterValue")
@JvmStatic
fun getStringParameter(name: String, urlDecoder: QueryStringDecoder): String? {
return urlDecoder.parameters()[name]?.lastOrNull()
}
fun getStringParameter(name: String, urlDecoder: QueryStringDecoder): String? =
urlDecoder.parameters()[name]?.lastOrNull()
@JvmStatic
fun getIntParameter(name: String, urlDecoder: QueryStringDecoder): Int {
return StringUtilRt.parseInt(getStringParameter(name, urlDecoder).nullize(nullizeSpaces = true), -1)
}
fun getIntParameter(name: String, urlDecoder: QueryStringDecoder): Int =
StringUtilRt.parseInt(getStringParameter(name, urlDecoder).nullize(nullizeSpaces = true), -1)
@JvmOverloads
@JvmStatic
@@ -135,7 +127,7 @@ abstract class RestService : HttpRequestHandler() {
return value.toBoolean()
}
fun parameterMissedErrorMessage(name: String) = "Parameter \"$name\" is not specified"
fun parameterMissedErrorMessage(name: String): String = "Parameter \"$name\" is not specified"
}
protected val gson: Gson by lazy {
@@ -153,6 +145,7 @@ abstract class RestService : HttpRequestHandler() {
.maximumSize(1024)
.expireAfterWrite(1, TimeUnit.DAYS)
.build<Pair<String, String>, Boolean>()
private val hostLocks = CollectionFactory.createConcurrentWeakKeyWeakValueMap<String, Any>()
private var isBlockUnknownHosts = false
@@ -203,17 +196,13 @@ abstract class RestService : HttpRequestHandler() {
return false
}
protected open fun isMethodSupported(method: HttpMethod): Boolean {
return method === HttpMethod.GET
}
protected open fun isMethodSupported(method: HttpMethod): Boolean = method === HttpMethod.GET
/**
* If the requests per minute counter exceeds this value, the exception [HttpResponseStatus.TOO_MANY_REQUESTS] will be sent.
* @return The value of "ide.rest.api.requests.per.minute" Registry key or '30', if the key does not exist.
*/
protected open fun getMaxRequestsPerMinute(): Int {
return Registry.intValue("ide.rest.api.requests.per.minute", 30)
}
protected open fun getMaxRequestsPerMinute(): Int = Registry.intValue("ide.rest.api.requests.per.minute", 30)
override fun process(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Boolean {
try {
@@ -251,10 +240,12 @@ abstract class RestService : HttpRequestHandler() {
return true
}
private fun HttpResponseStatus.sendError(channel: Channel,
request: HttpRequest,
description: String? = null,
extraHeaders: HttpHeaders? = null) {
private fun HttpResponseStatus.sendError(
channel: Channel,
request: HttpRequest,
description: String? = null,
extraHeaders: HttpHeaders? = null,
) {
if (reportErrorsAsPlainText) {
sendPlainText(channel, request, description, extraHeaders)
}
@@ -264,22 +255,20 @@ abstract class RestService : HttpRequestHandler() {
}
@Throws(InterruptedException::class, InvocationTargetException::class)
protected open fun isHostTrusted(request: FullHttpRequest, urlDecoder: QueryStringDecoder): Boolean {
protected open fun isHostTrusted(request: FullHttpRequest, urlDecoder: QueryStringDecoder): Boolean =
@Suppress("DEPRECATION")
return isHostTrusted(request)
}
isHostTrusted(request)
/**
* Used to set individual API access rate limits.
*/
@Throws(InterruptedException::class, InvocationTargetException::class)
protected open fun getRequesterId(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Any {
return (context.channel().remoteAddress() as InetSocketAddress).address
}
protected open fun getRequesterId(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Any =
(context.channel().remoteAddress() as InetSocketAddress).address
@Deprecated("Use {@link #isHostTrusted(FullHttpRequest, QueryStringDecoder)}")
@Throws(InterruptedException::class, InvocationTargetException::class)
// e.g. upsource trust to configured host
// e.g., Upsource trust to configured host
protected open fun isHostTrusted(request: FullHttpRequest): Boolean {
if (service<BuiltInWebServerAuth>().isRequestSigned(request) || isOriginAllowed(request) == OriginCheckResult.ALLOW) {
return true
@@ -294,7 +283,7 @@ abstract class RestService : HttpRequestHandler() {
host.nullize() to scheme
}
}
catch (ignored: URISyntaxException) {
catch (_: URISyntaxException) {
return false
}
@@ -310,8 +299,8 @@ abstract class RestService : HttpRequestHandler() {
}
}
}
else {
if (isBlockUnknownHosts) return false
else if (isBlockUnknownHosts) {
return false
}
var isTrusted = false
@@ -326,10 +315,8 @@ abstract class RestService : HttpRequestHandler() {
if (host != null) {
trustedOrigins.put(host to scheme, isTrusted)
}
else {
if (!isTrusted) {
isBlockUnknownHosts = showYesNoDialog(IdeBundle.message("warning.use.rest.api.block.unknown.hosts"), "title.use.rest.api")
}
else if (!isTrusted) {
isBlockUnknownHosts = showYesNoDialog(IdeBundle.message("warning.use.rest.api.block.unknown.hosts"), "title.use.rest.api")
}
}, ModalityState.any())
return isTrusted
@@ -341,7 +328,7 @@ abstract class RestService : HttpRequestHandler() {
val originHost = try {
if (origin == null) null else URI(origin).takeIf { it.scheme == "https" }?.host.nullize()
}
catch (ignored: URISyntaxException) {
catch (_: URISyntaxException) {
return false
}
@@ -357,19 +344,13 @@ abstract class RestService : HttpRequestHandler() {
}
/**
* Return error or send response using [sendOk], [send]
* Return error or send response using [RestService.sendOk], [RestService.send]
*/
@Throws(IOException::class)
@NonNls
abstract fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String?
}
fun HttpResponseStatus.orInSafeMode(safeStatus: HttpResponseStatus): HttpResponseStatus {
if (Registry.`is`("ide.http.server.response.actual.status", true) ||
ApplicationManager.getApplication()?.isUnitTestMode == true) {
return this
}
else {
return safeStatus
}
}
fun HttpResponseStatus.orInSafeMode(safeStatus: HttpResponseStatus): HttpResponseStatus = when {
Registry.`is`("ide.http.server.response.actual.status", false) || ApplicationManager.getApplication()?.isUnitTestMode == true -> this
else -> safeStatus
}

View File

@@ -33,6 +33,18 @@ class BuiltInServer private constructor(
get() = !channelRegistrar.isEmpty
companion object {
private val selectorProvider: SelectorProvider? by lazy {
try {
val aClass = ClassLoader.getSystemClassLoader().loadClass("sun.nio.ch.DefaultSelectorProvider")
MethodHandles.lookup()
.findStatic(aClass, "create", MethodType.methodType(SelectorProvider::class.java))
.invokeExact() as SelectorProvider
}
catch (_: Throwable) {
null
}
}
init {
// IDEA-120811
if (java.lang.Boolean.parseBoolean(System.getProperty("io.netty.random.id", "true"))) {
@@ -54,6 +66,12 @@ class BuiltInServer private constructor(
})
}
private fun setSystemPropertyIfNotConfigured(name: String, @Suppress("SameParameterValue") value: String) {
if (System.getProperty(name) == null) {
System.setProperty(name, value)
}
}
suspend fun start(firstPort: Int, portsCount: Int, tryAnyPort: Boolean, handler: (() -> ChannelHandler)? = null): BuiltInServer {
val provider = selectorProvider ?: SelectorProvider.provider()
val factory = BuiltInServerThreadFactory()
@@ -61,19 +79,8 @@ class BuiltInServer private constructor(
val channelRegistrar = ChannelRegistrar()
val bootstrap = createServerBootstrap(eventLoopGroup, eventLoopGroup)
configureChildHandler(bootstrap, channelRegistrar, handler)
val port = bind(
firstPort = firstPort,
portsCount = portsCount,
tryAnyPort = tryAnyPort,
bootstrap = bootstrap,
registrar = channelRegistrar,
)
return BuiltInServer(
eventLoopGroup = eventLoopGroup,
childEventLoopGroup = eventLoopGroup,
port = port,
channelRegistrar = channelRegistrar,
)
val port = bind(firstPort, portsCount, tryAnyPort, bootstrap, channelRegistrar)
return BuiltInServer(eventLoopGroup, eventLoopGroup, port, channelRegistrar)
}
fun configureChildHandler(
@@ -92,6 +99,62 @@ class BuiltInServer private constructor(
fun replaceDefaultHandler(context: ChannelHandlerContext, channelHandler: ChannelHandler) {
context.pipeline().replace(DelegatingHttpRequestHandler::class.java, "replacedDefaultHandler", channelHandler)
}
private suspend fun bind(
firstPort: Int,
portsCount: Int,
tryAnyPort: Boolean,
bootstrap: ServerBootstrap,
registrar: ChannelRegistrar,
): Int {
val address = InetAddress.getByName("127.0.0.1")
val maxPort = (firstPort + portsCount) - 1
for (port in firstPort..maxPort) {
// some antiviral software detects viruses by the fact of accessing these ports, so we should not touch them to appear innocent
if (port == 6953 || port == 6969 || port == 6970) {
continue
}
val future = asDeferred(bootstrap.bind(address, port)).await()
if (future.isSuccess) {
registrar.setServerChannel(future.channel(), true)
return port
}
else if (!tryAnyPort && port == maxPort) {
throw future.cause()
}
}
logger<BuiltInServer>().info("Cannot bind to our default range, so, try to bind to any free port")
val future = asDeferred(bootstrap.bind(address, 0)).await()
if (future.isSuccess) {
registrar.setServerChannel(future.channel(), true)
return (future.channel().localAddress() as InetSocketAddress).port
}
throw future.cause()
}
private fun asDeferred(channelFuture: ChannelFuture): CompletableDeferred<ChannelFuture> {
val deferred = CompletableDeferred<ChannelFuture>()
channelFuture.addListener(object : GenericFutureListener<ChannelFuture> {
override fun operationComplete(future: ChannelFuture) {
try {
channelFuture.removeListener(this)
}
finally {
deferred.complete(future)
}
}
})
return deferred
}
private fun createServerBootstrap(parentEventLoopGroup: EventLoopGroup, childEventLoopGroup: EventLoopGroup): ServerBootstrap =
ServerBootstrap()
.group(parentEventLoopGroup, childEventLoopGroup)
.channel(NioServerSocketChannel::class.java)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
}
fun createServerBootstrap(): ServerBootstrap = createServerBootstrap(eventLoopGroup, childEventLoopGroup)
@@ -102,83 +165,8 @@ class BuiltInServer private constructor(
}
}
private suspend fun bind(
firstPort: Int,
portsCount: Int,
tryAnyPort: Boolean,
bootstrap: ServerBootstrap,
registrar: ChannelRegistrar,
): Int {
val address = InetAddress.getByName("127.0.0.1")
val maxPort = (firstPort + portsCount) - 1
for (port in firstPort..maxPort) {
// some antiviral software detects viruses by the fact of accessing these ports, so we should not touch them to appear innocent
if (port == 6953 || port == 6969 || port == 6970) {
continue
}
val future = asDeferred(bootstrap.bind(address, port)).await()
if (future.isSuccess) {
registrar.setServerChannel(future.channel(), true)
return port
}
else if (!tryAnyPort && port == maxPort) {
throw future.cause()
}
}
logger<BuiltInServer>().info("Cannot bind to our default range, so, try to bind to any free port")
val future = asDeferred(bootstrap.bind(address, 0)).await()
if (future.isSuccess) {
registrar.setServerChannel(future.channel(), true)
return (future.channel().localAddress() as InetSocketAddress).port
}
throw future.cause()
}
private fun asDeferred(channelFuture: ChannelFuture): CompletableDeferred<ChannelFuture> {
val deferred = CompletableDeferred<ChannelFuture>()
channelFuture.addListener(object : GenericFutureListener<ChannelFuture> {
override fun operationComplete(future: ChannelFuture) {
try {
channelFuture.removeListener(this)
}
finally {
deferred.complete(future)
}
}
})
return deferred
}
private class BuiltInServerThreadFactory : ThreadFactory {
private val counter = AtomicInteger()
override fun newThread(r: Runnable): Thread = FastThreadLocalThread(r, "Netty Builtin Server ${counter.incrementAndGet()}")
}
private fun createServerBootstrap(parentEventLoopGroup: EventLoopGroup, childEventLoopGroup: EventLoopGroup): ServerBootstrap {
return ServerBootstrap()
.group(parentEventLoopGroup, childEventLoopGroup)
.channel(NioServerSocketChannel::class.java)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
}
private fun setSystemPropertyIfNotConfigured(name: String, @Suppress("SameParameterValue") value: String) {
if (System.getProperty(name) == null) {
System.setProperty(name, value)
}
}
private val selectorProvider: SelectorProvider? by lazy {
try {
val aClass = ClassLoader.getSystemClassLoader().loadClass("sun.nio.ch.DefaultSelectorProvider")
MethodHandles.lookup()
.findStatic(aClass, "create", MethodType.methodType(SelectorProvider::class.java))
.invokeExact() as SelectorProvider
}
catch (e: Throwable) {
null
}
}

View File

@@ -34,24 +34,23 @@ internal class DelegatingHttpRequestHandler() : SimpleChannelInboundHandlerAdapt
isSupported(request) && isAccessible(request) && process(urlDecoder, request, context)
val prevHandlerAttribute = context.channel().attr(PREV_HANDLER)
val connectedHandler = prevHandlerAttribute.get()?.get()
if (connectedHandler != null) {
if (connectedHandler.checkAndProcess()) {
val prevHandler = prevHandlerAttribute.get()?.get()
if (prevHandler != null) {
if (prevHandler.checkAndProcess()) {
return true
}
// prev cached connectedHandler is not suitable for this request, so, let's find it again
// the cached handler is not suitable for this request, so let's find it again
prevHandlerAttribute.set(null)
}
return HttpRequestHandler.EP_NAME.findFirstSafe { handler ->
if (handler.checkAndProcess()) {
prevHandlerAttribute.set(WeakReference(handler))
true
}
else {
false
}
} != null
val handler = HttpRequestHandler.EP_NAME.findFirstSafe { it.checkAndProcess() }
if (handler != null) {
prevHandlerAttribute.set(WeakReference(handler))
return true
}
return false
}
override fun exceptionCaught(context: ChannelHandlerContext, cause: Throwable) {

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.ui;
import com.intellij.openapi.application.ApplicationManager;
@@ -15,20 +15,17 @@ public interface ProductIcons {
}
/**
* Returns a node icon to represent projects which may be opened by the IDE (e.g. in file choosers).
* Returns a node icon to represent projects which may be opened by the IDE (e.g., in a file tree).
*/
@NotNull
Icon getProjectNodeIcon();
@NotNull Icon getProjectNodeIcon();
/**
* Returns an action icon to represent a project directory.
*/
@NotNull
Icon getProjectIcon();
@NotNull Icon getProjectIcon();
/**
* Returns icon containing logo of this IDE.
* Returns the logo of this IDE.
*/
@NotNull
Icon getProductIcon();
@NotNull Icon getProductIcon();
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.ide;
import com.intellij.openapi.extensions.ExtensionPointName;
@@ -15,9 +15,9 @@ public abstract class BinaryRequestHandler {
public static final ExtensionPointName<BinaryRequestHandler> EP_NAME = new ExtensionPointName<>("org.jetbrains.binaryRequestHandler");
/**
* uuidgen on Mac OS X could be used to generate UUID
* Use any online UUID generator (e.g., <a href="https://www.uuidgenerator.net">this one</a>), or {@code uuidgen} on macOS.
*/
public abstract @NotNull UUID getId();
public abstract @NotNull ChannelHandler getInboundHandler(@NotNull ChannelHandlerContext context);
}
}

View File

@@ -1,6 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("ReplaceGetOrSet")
package org.jetbrains.ide
import com.intellij.openapi.extensions.ExtensionPointName
@@ -20,17 +18,18 @@ import java.io.IOException
import java.util.*
/**
* See [Remote Communication](https://youtrack.jetbrains.com/articles/IDEA-A-63/Remote-Communication)
* See [Remote Communication](https://youtrack.jetbrains.com/articles/IDEA-A-63/Remote-Communication).
* Your handler will be instantiated on the first user request.
*/
abstract class HttpRequestHandler {
enum class OriginCheckResult {
ALLOW, FORBID,
// any origin is allowed but user confirmation is required
ALLOW,
FORBID,
/** Any origin is allowed but user confirmation is required */
ASK_CONFIRMATION
}
companion object {
// Your handler will be instantiated on the first user request
val EP_NAME: ExtensionPointName<HttpRequestHandler> = ExtensionPointName("com.intellij.httpRequestHandler")
@JvmStatic
@@ -40,7 +39,7 @@ abstract class HttpRequestHandler {
return true
}
else {
val c = uri.get(prefix.length + 1)
val c = uri[prefix.length + 1]
return c == '/' || c == '?'
}
}
@@ -49,7 +48,7 @@ abstract class HttpRequestHandler {
}
/**
* Write request from a browser without Origin will always be blocked regardless of your implementation.
* Write request from a browser without `Origin` will always be blocked regardless of your implementation.
*/
open fun isAccessible(request: HttpRequest): Boolean {
val hostName = getHostName(request)
@@ -58,9 +57,8 @@ abstract class HttpRequestHandler {
return hostName != null && isOriginAllowed(request) != OriginCheckResult.FORBID && isLocalHost(hostName)
}
protected open fun isOriginAllowed(request: HttpRequest): OriginCheckResult {
return if (request.isLocalOrigin()) OriginCheckResult.ALLOW else OriginCheckResult.FORBID
}
protected open fun isOriginAllowed(request: HttpRequest): OriginCheckResult =
if (request.isLocalOrigin()) OriginCheckResult.ALLOW else OriginCheckResult.FORBID
/**
* Note that changes of [request] object in methods that overrides [isSupported] are highly undesirable. The same mutable [request] object
@@ -68,12 +66,11 @@ abstract class HttpRequestHandler {
* If one [isSupported] method changes [request] then other services in the chain are affected by this change and might function
* improperly.
*/
open fun isSupported(request: FullHttpRequest): Boolean {
return request.method() === HttpMethod.GET || request.method() === HttpMethod.HEAD
}
open fun isSupported(request: FullHttpRequest): Boolean =
request.method() === HttpMethod.GET || request.method() === HttpMethod.HEAD
/**
* @return true if processed successfully, false to pass processing to other handlers.
* @return `true` if processed successfully, `false` to pass processing to other handlers.
*/
@Throws(IOException::class)
abstract fun process(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Boolean
@@ -82,7 +79,7 @@ abstract class HttpRequestHandler {
val response = DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
response.headers().set(HttpHeaderNames.CONTENT_TYPE, FileResponses.getContentType(name))
response.addCommonHeaders()
response.headers().set(HttpHeaderNames.CACHE_CONTROL, "private, must-revalidate") //NON-NLS
response.headers().set(HttpHeaderNames.CACHE_CONTROL, "private, must-revalidate")
response.headers().set(HttpHeaderNames.LAST_MODIFIED, Date(Calendar.getInstance().timeInMillis))
response.headers().add(extraHeaders)
@@ -105,4 +102,4 @@ abstract class HttpRequestHandler {
}
return true
}
}
}

View File

@@ -31,9 +31,8 @@ fun response(contentType: String?, content: ByteBuf?): FullHttpResponse {
return response
}
fun response(content: CharSequence, charset: Charset = CharsetUtil.US_ASCII): FullHttpResponse {
return DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(content, charset))
}
fun response(content: CharSequence, charset: Charset = CharsetUtil.US_ASCII): FullHttpResponse =
DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(content, charset))
fun responseStatus(status: HttpResponseStatus, keepAlive: Boolean, channel: Channel) {
val response = DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status)
@@ -48,8 +47,8 @@ fun responseStatus(status: HttpResponseStatus, keepAlive: Boolean, channel: Chan
}
fun HttpResponse.addNoCache(): HttpResponse {
headers().add(HttpHeaderNames.CACHE_CONTROL, "no-cache, no-store, must-revalidate, max-age=0")//NON-NLS
headers().add(HttpHeaderNames.PRAGMA, "no-cache")//NON-NLS
headers().add(HttpHeaderNames.CACHE_CONTROL, "no-cache, no-store, must-revalidate, max-age=0")
headers().add(HttpHeaderNames.PRAGMA, "no-cache")
return this
}
@@ -101,11 +100,11 @@ fun HttpResponse.addCommonHeaders() {
headers().set(HttpHeaderNames.X_FRAME_OPTIONS, "SameOrigin")
}
@Suppress("SpellCheckingInspection")
headers().set("X-Content-Type-Options", "nosniff")//NON-NLS
headers().set("x-xss-protection", "1; mode=block")//NON-NLS
headers().set("X-Content-Type-Options", "nosniff")
headers().set("x-xss-protection", "1; mode=block")
if (status() < HttpResponseStatus.MULTIPLE_CHOICES) {
headers().set(HttpHeaderNames.ACCEPT_RANGES, "bytes")//NON-NLS
headers().set(HttpHeaderNames.ACCEPT_RANGES, "bytes")
}
}
@@ -124,7 +123,8 @@ fun HttpResponse.send(channel: Channel, close: Boolean) {
}
}
fun HttpResponseStatus.response(request: HttpRequest? = null, description: String? = null): HttpResponse = createStatusResponse(this, request, description)
fun HttpResponseStatus.response(request: HttpRequest? = null, description: String? = null): HttpResponse =
createStatusResponse(this, request, description)
@JvmOverloads
fun HttpResponseStatus.send(channel: Channel, request: HttpRequest? = null, description: String? = null, extraHeaders: HttpHeaders? = null) {
@@ -149,8 +149,7 @@ fun createStatusResponse(
val message = responseStatus.toString()
@NlsSafe
val builder = StringBuilder()
val builder = @NlsSafe StringBuilder()
if (usePlainText) {
builder.append(message)
if (description != null) {
@@ -169,4 +168,4 @@ fun createStatusResponse(
val response = DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseStatus, content)
response.headers().set(HttpHeaderNames.CONTENT_TYPE, if (usePlainText) HttpHeaderValues.TEXT_PLAIN else HttpHeaderValues.TEXT_HTML)
return response
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.ide
import com.intellij.codeWithMe.ClientId
@@ -25,8 +25,6 @@ import javax.swing.SwingUtilities
import kotlin.io.path.exists
import kotlin.math.max
private val LINE_AND_COLUMN = Pattern.compile("^(.*?)(?::(\\d+))?(?::(\\d+))?$")
/**
* @api {get} /file Open file
* @apiName file
@@ -35,7 +33,7 @@ private val LINE_AND_COLUMN = Pattern.compile("^(.*?)(?::(\\d+))?(?::(\\d+))?$")
* @apiParam {String} file The path of the file. Relative (to project base dir, VCS root, module source or content root) or absolute.
* @apiParam {Integer} [line] The line number of the file (1-based).
* @apiParam {Integer} [column] The column number of the file (1-based).
* @apiParam {Boolean} [focused=true] Whether to focus project window.
* @apiParam {Boolean} [focused=true] Whether to focus a project window.
*
* @apiExample {curl} Absolute path
* curl http://localhost:63342/api/file//absolute/path/to/file.kt
@@ -49,7 +47,10 @@ private val LINE_AND_COLUMN = Pattern.compile("^(.*?)(?::(\\d+))?(?::(\\d+))?$")
* @apiExample {curl} Query parameters
* curl http://localhost:63342/api/file?file=path/to/file.kt&line=100&column=34
*/
@Suppress("KDocUnresolvedReference")
internal class OpenFileHttpService : RestService() {
private val LINE_AND_COLUMN = Pattern.compile("^(.*?)(?::(\\d+))?(?::(\\d+))?$")
override fun getServiceName() = "file"
override fun isMethodSupported(method: HttpMethod) = method === HttpMethod.GET || method === HttpMethod.POST
@@ -60,16 +61,16 @@ internal class OpenFileHttpService : RestService() {
val keepAlive = HttpUtil.isKeepAlive(request)
val channel = context.channel()
val apiRequest: OpenFileRequest
if (request.method() === HttpMethod.POST) {
apiRequest = gson.fromJson(createJsonReader(request), OpenFileRequest::class.java)
val apiRequest = if (request.method() === HttpMethod.POST) {
gson.fromJson(createJsonReader(request), OpenFileRequest::class.java)
}
else {
apiRequest = OpenFileRequest()
apiRequest.file = getStringParameter("file", urlDecoder).takeIf { !it.isNullOrBlank() }
apiRequest.line = getIntParameter("line", urlDecoder)
apiRequest.column = getIntParameter("column", urlDecoder)
apiRequest.focused = getBooleanParameter("focused", urlDecoder, true)
OpenFileRequest().apply {
file = getStringParameter("file", urlDecoder).takeIf { !it.isNullOrBlank() }
line = getIntParameter("line", urlDecoder)
column = getIntParameter("column", urlDecoder)
focused = getBooleanParameter("focused", urlDecoder, true)
}
}
val prefixLength = 1 + PREFIX.length + 1 + getServiceName().length + 1
@@ -157,7 +158,7 @@ internal class OpenFileHttpService : RestService() {
val clientId = ClientId.ownerId
val task = Runnable {
ClientId.withClientId(clientId) {
val effectiveProject = project ?: RestService.getLastFocusedOrOpenedProject() ?: ProjectManager.getInstance().defaultProject
val effectiveProject = project ?: getLastFocusedOrOpenedProject() ?: ProjectManager.getInstance().defaultProject
// OpenFileDescriptor line and column number are 0-based.
OpenFileDescriptor(effectiveProject, file, max(request.line - 1, 0), max(request.column - 1, 0)).navigate(true)
if (request.focused) {