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

