Обработка временного перенаправления 307 в Ktor HttpClient на iOS с помощью Kotlin Multiplatform

Я разрабатываю многоплатформенное приложение 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.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
148
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Размещаю здесь свое решение на случай, если кто-то еще столкнется с той же проблемой.

Я инициализировал своего клиента следующим образом:

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 гарантирует, что все заголовки исходного запроса будут добавлены к новому запросу после перенаправления, решая проблему, упомянутую в этом вопросе о переполнении стека.

Другие вопросы по теме