You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Ktor框架的Android代理服务器HTTPS请求异常问题

Ktor Android代理服务器HTTPS隧道连接失败问题解决

问题现象

  • HTTP请求处理正常,HTTPS请求通过代理时触发错误,curl返回SEC_E_INVALID_TOKEN(令牌无效)错误
  • 测试用curl命令:
curl -v -x http://aaa:sss@192.168.0.100:2222 https://www.google.com

完整日志:

*   Trying 192.168.0.100:2222...
* Connected to 192.168.0.100 (192.168.0.100) port 2222 (#0)
* allocate connect buffer
* Establish HTTP proxy tunnel to www.google.com:443
* Proxy auth using Basic with user 'aaa'
> CONNECT www.google.com:443 HTTP/1.1
> Host: www.google.com:443
> Proxy-Authorization: Basic 
> User-Agent: curl/7.83.1
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 OK
< Content-Type: application/octet-stream
<
* Proxy replied 200 to CONNECT request
* CONNECT phase completed
* schannel: disabled automatic use of client certificate
* ALPN: offers http/1.1
* schannel: next InitializeSecurityContext failed: SEC_E_INVALID_TOKEN (0x80090308) - The token supplied to the function is invalid
* Closing connection 0
curl: (35) schannel: next InitializeSecurityContext failed: SEC_E_INVALID_TOKEN (0x80090308) - The token supplied to the function is invalid

相关代码片段

代理连接处理逻辑

val tcpSocketBuilder = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp()
val server: Socket?
try {
    // 连接目标服务器
    server = tcpSocketBuilder.connect(call.request.host(), call.request.port())
} catch (e: Exception) {
    Log.v(TAG,"jothi Failed to connect to ${call.request.host()}:${call.request.port()}\n\t${e.printStackTrace()}")
    return
}

Log.v(TAG,"jothi Connected to ${call.request.host()}:${call.request.port()}")
val successConnectionString =
    "HTTP/1.1 200 OK\r\nServer-test: https-proxy\r\n\r\n"

call.respondBytesWriter(status = HttpStatusCode.OK){successConnectionString}
Log.v(TAG,"jothi response send ")

val serverReader = server.openReadChannel()
val serverWriter = server.openWriteChannel()

delay(20)

val readChannel: ByteReadChannel = call.receiveChannel()
val size = readChannel.availableForRead
val byteArray: ByteArray = ByteArray(size)
readChannel.readFully(byteArray,0,size)
Log.v(TAG,"jothi channel is closed for read  " +readChannel.isClosedForRead)
Log.v(TAG,"jothi read size " +size)

TLS证书配置代码

val pass = "testpass" // keystore密码
val alias = "certificateAlias" // 证书别名
val filedir = applicationContext.getExternalFilesDir(null) // App专属文件目录

val destfolder = File(filedir, "jothi")
if (!destfolder.exists()) {
    if (!destfolder.mkdirs()) {
        Log.v(TAG, "jothi Directory not created")
    }
}
val keyStoreFile = File(filedir,"keystore.Jks")

// 生成带证书的keystore
val keystore = buildKeyStore {
    certificate(alias) {
        hash = HashAlgorithm.SHA256
        sign = SignatureAlgorithm.ECDSA
        keySizeInBits = 256
        password = pass
    }
}
keystore.saveToFile(keyStoreFile, pass)

核心问题分析

  1. HTTPS隧道转发逻辑缺失:当前代码返回200 OK后,仅尝试读取一次客户端数据就终止,没有建立客户端与目标服务器之间的双向持续数据转发通道,导致客户端TLS握手时无数据通路,触发令牌无效错误。
  2. 证书配置冗余:HTTPS代理隧道是TCP透明转发,客户端会直接与目标服务器(如google.com)完成TLS握手,不需要代理服务器提供证书。当前的自签名证书仅用于代理自身的HTTPS服务,与隧道逻辑无关。
  3. 通道处理方式错误:使用delay等待和单次读取的方式不可靠,会导致通道提前关闭,无法处理后续的TLS握手数据流。

解决方案

1. 修复双向数据转发逻辑

返回200 OK后,通过协程启动两个方向的数据流转发,持续处理客户端与目标服务器之间的数据传输:

// 返回隧道建立成功响应
call.respondBytesWriter(status = HttpStatusCode.OK) {
    writeFully(successConnectionString.toByteArray(Charsets.UTF_8))
}

// 启动协程处理双向转发
launch(Dispatchers.IO) {
    // 客户端 -> 目标服务器
    runCatching {
        call.receiveChannel().copyTo(serverWriter)
    }.onFailure {
        Log.v(TAG, "Client to server forward failed: ${it.message}")
    }.finally {
        serverWriter.close()
        call.response.close()
    }
}

launch(Dispatchers.IO) {
    // 目标服务器 -> 客户端
    runCatching {
        serverReader.copyTo(call.responseChannel())
    }.onFailure {
        Log.v(TAG, "Server to client forward failed: ${it.message}")
    }.finally {
        serverReader.close()
        call.response.close()
    }
}

2. 移除隧道无关的TLS配置

如果仅需要实现HTTPS代理隧道功能,可移除当前的JKS证书生成代码(保留该代码仅当你需要代理服务器自身提供HTTPS服务时)。

3. 移除不可靠的延迟与单次读取逻辑

删除delay(20)和单次读取客户端数据的代码,改用copyTo方法自动处理通道的持续读写,直到任意一端关闭连接。

验证方式

重新运行代理服务器后,再次执行原curl测试命令,若握手成功,将正常返回目标网站的HTTPS响应内容。

内容的提问来源于stack exchange,提问作者user1123931

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 05:37:31