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

Kotlin Multiplatform中iOS端Ktor HttpClient处理307临时重定向问题

问题

我正在开发一个Kotlin Multiplatform应用,使用Ktor HttpClient发起网络请求。API调用在Android上表现正常(会自动从307临时重定向到200),但在iOS上处理307 Temporary Redirect响应时出现问题,请求失败并抛出ClientRequestException,状态码为401 Unauthorized。

相关代码

公共API类部分:

class LuluApiImpl(engine: HttpClientEngine) {
    private val client = HttpClient(engine) {
        expectSuccess = true
        followRedirects = true

        install(ContentNegotiation) {
            json(
                Json {
                    isLenient = true
                    ignoreUnknownKeys = true
                    prettyPrint = true
                },
            )
        }

        Logging {
            logger = object : Logger {
                override fun log(message: String) {
                    log.v { message }
                }
            }
            level = LogLevel.ALL
        }

        install(HttpTimeout) {
            val timeout = 60000L
            connectTimeoutMillis = timeout
            requestTimeoutMillis = timeout
            socketTimeoutMillis = timeout
        }
    }
}

请求发起代码:

override suspend fun getProfileData(): ApiResponse<Profile> {
    val response = client.get {
        call("(ommited)/profile")
    }.body<ApiResponse<Profile>>()
    return response
}

private fun HttpRequestBuilder.call(path: String) {
    url {
        takeFrom("(ommited)")
        encodedPath += path
    }
    headers {
        credentialsStorageManager.currentAccessToken?.let { bearerAuth(it) }
    }
}

Darwin引擎注入:

single { Darwin.create() }

问题细节

在iOS上,服务器返回307 Temporary Redirect时,Ktor客户端未正确跟随重定向,反而抛出401错误的ClientRequestException。通过ProxyMan观察到了307重定向,但Ktor客户端在iOS端处理异常。已启用followRedirects = true但无效果,添加ResponseObserver记录显示:Android上先收到307再收到200,iOS上仅收到401。

解决方案

针对iOS端Ktor处理307重定向的问题,可以尝试以下几种方案:

1. 手动处理重定向,避免依赖引擎自动跟随

关闭自动跟随重定向,手动拦截307响应并重新发起请求,同时确保Authorization头被正确携带:

private val client = HttpClient(engine) {
    followRedirects = false // 关闭自动跟随
    expectSuccess = true

    // 其他配置...

    HttpResponseValidator {
        handleResponseException { exception ->
            val clientException = exception as? ClientRequestException ?: return@handleResponseException
            val response = clientException.response

            // 拦截307临时重定向
            if (response.status == HttpStatusCode.TemporaryRedirect) {
                val redirectUrl = response.headers[HttpHeaders.Location] ?: throw exception
                // 重新发起请求,携带原请求的Authorization头
                val newResponse = client.get(redirectUrl) {
                    headers {
                        credentialsStorageManager.currentAccessToken?.let { bearerAuth(it) }
                    }
                }
                // 返回重新请求的响应,替代原异常
                return@handleResponseException newResponse
            }
        }
    }
}

2. 配置Darwin引擎的重定向策略

Darwin引擎(基于NSURLSession)可能需要额外配置重定向行为,在创建引擎时添加自定义NSURLSessionConfiguration(代码放在iosMain源集):

single {
    val config = NSURLSessionConfiguration.defaultSessionConfiguration()
    // 允许跟随所有类型的重定向
    config.httpShouldHandleCookies = true
    config.allowsCellularAccess = true
    // 自定义重定向处理,确保头信息被传递
    val session = NSURLSession.sessionWithConfiguration(config, object : NSURLSessionTaskDelegate() {
        override fun URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest: NSURLRequest, completionHandler: (NSURLRequest?) -> Unit) {
            // 复制原请求的Authorization头到重定向请求
            val mutableRequest = newRequest.mutableCopy() as NSMutableURLRequest
            task.originalRequest?.allHTTPHeaderFields?.let { headers ->
                headers["Authorization"]?.let { mutableRequest.setValue(it, forHTTPHeaderField = "Authorization") }
            }
            completionHandler(mutableRequest)
        }
    }, null)
    Darwin.create(sessionConfiguration = config, urlSession = session)
}

3. 检查服务器重定向响应的Location头

确保服务器返回的307响应中Location头是完整的URL(包含协议、域名),而非相对路径。部分情况下,Darwin引擎对相对路径的重定向处理存在问题,需要服务器返回绝对路径。

4. 临时禁用expectSuccess调试

暂时将expectSuccess = false,获取完整的响应链,确认重定向过程中是否有其他异常:

private val client = HttpClient(engine) {
    expectSuccess = false // 临时关闭,便于调试
    followRedirects = true
    // 其他配置...
}

发起请求时手动处理状态码:

override suspend fun getProfileData(): ApiResponse<Profile> {
    val response = client.get {
        call("(ommited)/profile")
    }
    if (response.status.isSuccess()) {
        return response.body<ApiResponse<Profile>>()
    } else if (response.status == HttpStatusCode.TemporaryRedirect) {
        val redirectUrl = response.headers[HttpHeaders.Location] ?: throw Exception("Missing redirect URL")
        val redirectResponse = client.get(redirectUrl) {
            headers {
                credentialsStorageManager.currentAccessToken?.let { bearerAuth(it) }
            }
        }
        return redirectResponse.body<ApiResponse<Profile>>()
    } else {
        throw ClientRequestException(response, "Request failed")
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 20:07:41