kotlin 代码片段

31 阅读5分钟
conn.outputStream.use {
    it.write(body.toByteArray())
}

这三行代码的作用是将请求体数据写入HTTP连接的输出流中:

  • conn.outputStream.use { ... }: 获取 conn 的输出流并使用 use 函数进行资源管理,确保流在使用后能被正确关闭

  • it.write(body.toByteArray()): 将传入的 [body] 字符串转换为字节数组,并通过输出流写入到HTTP请求中

  val responseBody = if (conn.responseCode in 100..399) {
      conn.inputStream.use { it.readBytes() }.toString(Charsets.UTF_8)
  } else {
      conn.errorStream.use { it.readBytes() }.toString(Charsets.UTF_8)
  }
  return Response(conn.responseCode, conn.headerFields, responseBody)

这段代码用于读取HTTP响应的内容并返回结果:

  • val responseBody = if (conn.responseCode in 100..399) { ... } else { ... }: 根据HTTP状态码判断使用哪个输入流来读取响应数据

    • 状态码在100-399之间表示成功响应,使用 conn.inputStream
    • 状态码在400-599之间表示错误响应,使用 conn.errorStream
  • conn.inputStream.use { it.readBytes() }.toString(Charsets.UTF_8):

    • 使用 use 函数安全地打开输入流并读取所有字节数据
    • 将字节数组转换为UTF-8编码的字符串
  • return Response(conn.responseCode, conn.headerFields, responseBody): 构造并返回包含状态码、响应头和响应体的 [Response] 对象

这种方式能够正确处理HTTP的成功和错误响应情况。

private val client: CloseableHttpAsyncClient = HttpAsyncClients.customHttp2()
    .setTlsStrategy(
        ClientTlsStrategyBuilder.create()
            .setSslContext(SSLContexts.custom().loadTrustMaterial(TrustSelfSignedStrategy()).build())
            .setTlsVersions(TLS.V_1_3, TLS.V_1_2)
            .build()
    )
    .setRetryStrategy(
        DefaultHttpRequestRetryStrategy(5, TimeValue.ofMilliseconds(50))
    )
    .evictIdleConnections(TimeValue.ofSeconds(10))
    .setIOReactorConfig(
        IOReactorConfig.custom()
            .setSoTimeout(30, TimeUnit.SECONDS)
            .setSelectInterval(TimeValue.ofMilliseconds(50))
            .setTcpNoDelay(true)
            .build()
    )
    .setDefaultRequestConfig(
        RequestConfig.custom()
            .setConnectionKeepAlive(TimeValue.ofMilliseconds(50))
            .setHardCancellationEnabled(true)
            .setConnectionRequestTimeout(50, TimeUnit.MILLISECONDS)
            .setResponseTimeout(30, TimeUnit.SECONDS)
            .build()
    )
    .setDefaultHeaders(
        listOf<Header>(
            BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"),
            BasicHeader(HttpHeaders.CACHE_CONTROL, "no-cache"),
            BasicHeader(HttpHeaders.REFERER, "no-referrer"),
        )
    )
    .build()

这段代码是在初始化和配置一个异步HTTP客户端,具体配置如下:

  • HttpAsyncClients.customHttp2(): 创建支持HTTP/2协议的异步HTTP客户端

  • TLS安全配置:

    • 使用 ClientTlsStrategyBuilder 配置TLS策略
    • 设置SSL上下文,通过 TrustSelfSignedStrategy 信任自签名证书
    • 限制TLS版本为 TLS.V_1_3TLS.V_1_2
  • 重试策略:

    • setRetryStrategy 设置默认重试策略,最多重试5次,间隔50毫秒
  • 连接管理:

    • evictIdleConnections 设置空闲连接清理,10秒后清理
  • IO反应器配置:

    • setSoTimeout 设置Socket超时为30秒
    • setSelectInterval 设置选择间隔为50毫秒
    • setTcpNoDelay 启用TCP无延迟
  • 请求配置:

    • 连接保持活跃时间50毫秒
    • 启用强制取消功能
    • 连接请求超时50毫秒
    • 响应超时30秒
  • 默认请求头:

    • 设置 Content-Typeapplication/json
    • 设置缓存控制为 no-cache
    • 设置 Refererno-referrer

这个配置创建了一个高性能、安全且具备重试机制的异步HTTP客户端。

init {
    client.start()
}

fun ping(uri: URI) {
    client.execute(SimpleHttpRequest.create(Method.GET, uri), null).get(5, TimeUnit.SECONDS)
}

[ping] 函数是 [AsyncConnection] 类中的一个方法,用于测试与指定 URI 的连接是否正常。

具体解释如下:

  • 方法签名: fun ping(uri: URI)

    • 接收一个 URI 类型的参数,表示要测试连接的目标地址
  • 核心实现:

    client.execute(SimpleHttpRequest.create(Method.GET, uri), null).get(5, TimeUnit.SECONDS)
    
    • 使用已初始化的 HTTP 客户端 [client] 发送 GET 请求
    • 通过 SimpleHttpRequest.create(Method.GET, uri) 创建一个简单的 GET 请求
    • 调用 client.execute() 执行该请求
    • 使用 .get(5, TimeUnit.SECONDS) 同步等待响应,超时时间为 5 秒
  • 作用:

    • 验证与目标服务器的连接状态
    • 如果在 5 秒内收到响应,则表示连接正常
    • 如果超时或出现异常,则说明连接存在问题

这个方法主要用于健康检查或连接状态验证,确保服务可用性。

private fun send(
    requestProducer: AsyncRequestProducer,
    uri: URI,
    stat: UsageStatistic,
    dataReceiveEnded: (String) -> Unit,
    dataReceived: (String, String) -> Unit,
    errorDataReceived: (JsonObject) -> Unit,
    failedDataReceiveEnded: (Throwable?) -> Unit = {},
    requestId: String = ""
): CompletableFuture<Future<*>> {
    var canceled = false;
    return CompletableFuture.supplyAsync {
        return@supplyAsync client.execute(
            requestProducer,
            object : AbstractBinResponseConsumer<String>() {
                private var bufferStr = ""
                private var isStreaming = false
                override fun releaseResources() {
                }

                override fun capacityIncrement(): Int {
                    return 128
                }

                override fun informationResponse(
                    response: HttpResponse?,
                    context: org.apache.hc.core5.http.protocol.HttpContext?
                ) {
                }

                override fun data(src: ByteBuffer?, endOfStream: Boolean) {
                    src ?: return
                    if(canceled) return;

                    val part = Charset.forName("UTF-8").decode(src)
                    bufferStr += part
                    if (part.startsWith(STREAMING_PREFIX)) {
                        isStreaming = true
                        try {
                            val data = Gson().fromJson(bufferStr, JsonObject::class.java)
                            errorDataReceived(data)
                            return
                        } catch (_: JsonSyntaxException) {
                        }
                        val (dataPieces, maybeLeftOverBuffer) = lookForCompletedDataInStreamingBuf(bufferStr)
                        dataPieces.forEach { dataReceived(it, requestId) }
                        if (maybeLeftOverBuffer == null) {
                            return
                        } else {
                            bufferStr = maybeLeftOverBuffer
                        }
                    }
                    if (!isStreaming && endOfStream) {
                        if (bufferStr.isNotEmpty()) {
                            dataReceived(bufferStr, requestId)
                        }
                    }

                }

                override fun start(response: HttpResponse?, contentType: ContentType?) {
                }

                override fun buildResult(): String {
                    return bufferStr
                }

                override fun failed(cause: Exception?) {}
            },
            object : FutureCallback<String> {
                override fun completed(result: String) {
                    dataReceiveEnded(result)
                }

                override fun failed(ex: java.lang.Exception?) {
                    if (ex !is SMCExceptions)
                        UsageStats?.addStatistic(false, stat, uri.toString(), ex.toString())
                    if (ex is java.net.SocketException ||
                        ex is java.net.UnknownHostException) {
                        InferenceGlobalContext.status = ConnectionStatus.DISCONNECTED
                    }
                    failedDataReceiveEnded(ex)
                }

                override fun cancelled() {
                    // TODO: figure out how to stop the stream fro the lsp
                    canceled = true
                    dataReceiveEnded("Canceled")
                }
            }
        )
    }
}

ApplicationManager.getApplication().invokeLater {} 是 IntelliJ Platform 提供的一个重要机制,用于在事件调度线程(EDT)上执行代码。

让我详细解释一下它的作用:

  1. 线程安全: IntelliJ 平台的 UI 操作必须在特定的事件调度线程上执行。invokeLater 确保代码块在正确的线程上运行,避免线程安全问题。

  2. 异步执行: 当前代码可能在后台线程运行(如网络请求、文件操作等),但需要更新 UI 时,就需要使用 invokeLater 将操作推迟到 UI 线程执行。

  3. 非阻塞: 与 invokeAndWait 不同,invokeLater 是非阻塞的,不会等待 UI 操作完成就继续执行后续代码。

例如:

// 在后台线程处理一些逻辑
someBackgroundProcessing()

// 当需要更新 UI 时,使用 invokeLater
ApplicationManager.getApplication().invokeLater {
    // 更新 UI 组件
    myTextField.text = "更新的内容"
    myPanel.repaint()
}

// 这行代码会立即执行,不需要等待上面的 UI 更新完成
continueWithOtherWork()

ApplicationManager.getApplication().invokeLater {} 是专属于 IntelliJ Platform(JetBrains 系列 IDE 的平台)的 API,不能在 Android 应用开发中使用。

在 Android 开发中,如果你需要在 UI 线程上执行代码,应该使用以下方法之一:

  1. Activity.runOnUiThread():
activity.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // 更新 UI 的代码
        textView.setText("新文本");
    }
});
  1. View.post():
view.post(new Runnable() {
    @Override
    public void run() {
        // 更新 UI 的代码
        view.setVisibility(View.VISIBLE);
    }
});
  1. Handler:
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(new Runnable() {
    @Override
    public void run() {
        // 更新 UI 的代码
    }
});
  1. 在 Kotlin 中使用协程:
lifecycleScope.launch(Dispatchers.Main) {
    // 更新 UI 的代码
    textView.text = "新文本"
}

这些是 Android 中标准的 UI 线程操作方式,与 IntelliJ Platform 的 API 不同。每种方法都有其适用场景:

  • Activity.runOnUiThread(): 当你持有 Activity 实例时使用
  • View.post(): 当你需要在特定 View 上执行操作时使用
  • Handler: 更底层的线程间通信方式
  • 协程: 现代 Android 开发推荐的方式,更简洁易读