Я разрабатываю многоплатформенное приложение Kotlin и использую Ktor HttpClient для выполнения сетевых запросов. Хотя вызовы API работают должным образом на Android, обработка ответов 307 Temporary Redirect на iOS вызывает проблемы. Вызов API завершается успешно на Android (с автоматическим перенаправлением с 307 на 200), но на iOS он завершается с ошибкой 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) }
}
}
Я внедряю двигатель Дарвина, используя Koin, вот так
single { Darwin.create() }
В iOS, когда сервер отвечает временным перенаправлением 307, клиент Ktor выдает исключение ClientRequestException с ошибкой 401 вместо того, чтобы следовать перенаправлению. Я заметил перенаправление 307 в ProxyMan, но похоже, что оно неправильно обрабатывается клиентом Ktor на iOS.
Я включил FollowRedirects в конфигурации клиента, но, похоже, это не влияет на поведение на iOS.
Как я могу правильно обрабатывать перенаправления 307 в Ktor на iOS, гарантируя, что клиент последует перенаправлению и успешно выполнит запрос?
Редактировать: Я попытался добавить ResponseObserver для регистрации получаемого ответа. Те же результаты: в Android я получаю 307, а затем 200. На iOS я получаю только 401.





Размещаю здесь свое решение на случай, если кто-то еще столкнется с той же проблемой.
Я инициализировал своего клиента следующим образом:
val client: HttpClient
get() = HttpClient(Darwin.create {
val delegate = KtorNSURLSessionDelegate()
val customDelegate = CustomDelegate(delegate)
val sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration
val session = NSURLSession.sessionWithConfiguration(sessionConfiguration, customDelegate, null)
usePreconfiguredSession(session = session, delegate = delegate)
})
В этой настройке usePreconfiguredSession используется для предоставления специального делегата для NSURLSession.
Пользовательская реализация делегата:
class CustomDelegate(private val ktorNSURLSessionDelegate: KtorNSURLSessionDelegate) : NSObject(),
NSURLSessionDataDelegateProtocol {
override fun URLSession(
session: NSURLSession,
task: NSURLSessionTask,
didCompleteWithError: NSError?
) {
ktorNSURLSessionDelegate.URLSession(session, task, didCompleteWithError)
}
override fun URLSession(
session: NSURLSession,
dataTask: NSURLSessionDataTask,
didReceiveData: NSData
) {
ktorNSURLSessionDelegate.URLSession(session, dataTask, didReceiveData)
}
override fun URLSession(
session: NSURLSession,
task: NSURLSessionTask,
willPerformHTTPRedirection: NSHTTPURLResponse,
newRequest: NSURLRequest,
completionHandler: (NSURLRequest?) -> Unit
) {
if (newRequest.URL == null) {
completionHandler(null)
return
}
val nextRequest = newRequest.URL?.let { NSMutableURLRequest(it) }
listOf("Authorization", "X-Platform", "X-Version", "X-Locale").forEach { header ->
task.originalRequest?.valueForHTTPHeaderField(header)?.let {
nextRequest?.addValue(it, forHTTPHeaderField = header)
}
}
nextRequest?.HTTPBody = task.originalRequest?.HTTPBody
completionHandler(nextRequest)
}
Этот класс CustomDelegate гарантирует, что все заголовки исходного запроса будут добавлены к новому запросу после перенаправления, решая проблему, упомянутую в этом вопросе о переполнении стека.