Alamofire处理错误响应时重复读取响应体引发JSON序列化失败问题排查
你的问题核心是拦截器错误地对非401状态码的请求触发了重试逻辑,导致Alamofire重复读取响应数据流,把两次相同的JSON内容拼接在一起,最终触发了"Garbage at end"的JSON解码错误。
问题分析
从错误日志里能明显看到响应体被重复输出了两次:{"message": "User with same email already exists"} {"message": "User with same email already exists"},这显然不是服务器返回的原始内容——因为请求被不必要地重试后,响应数据流只能被读取一次,第二次读取时会出现数据拼接异常,最终变成无效的JSON格式。
你的拦截器应该是在遇到所有错误响应时都执行了重试,而非仅针对401未授权场景,这就导致像400这类业务错误也被重复处理,引发了解码失败。
解决方案
1. 限制拦截器仅对401状态码触发重试
修改你的RequestInterceptor实现,确保只有当响应状态码为401时,才执行刷新token和重试逻辑,其他错误直接跳过:
class AuthInterceptor: RequestInterceptor { func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { // 先判断是否为HTTP响应错误 guard let httpResponse = request.task?.response as? HTTPURLResponse else { completion(.doNotRetry) return } // 仅在401未授权时处理重试逻辑 if httpResponse.statusCode == 401 { // 执行你的JWT刷新逻辑 self.refreshJWTToken { success in if success { // 刷新成功,重试请求 completion(.retry) } else { // 刷新失败,终止重试并返回错误 completion(.doNotRetryWithError(error)) } } } else { // 非401错误,直接终止重试 completion(.doNotRetry) } } // 你的token刷新方法示例 private func refreshJWTToken(completion: @escaping (Bool) -> Void) { // 这里实现你的token刷新逻辑,比如调用后端刷新接口 // ... } }
2. 调整请求的validate规则(可选)
你当前的validate(statusCode: [200, 401])会把401以外的状态码标记为错误,这本身没问题,但要确保拦截器不对这些错误触发重试。如果业务需要处理其他状态码的响应,可以考虑移除validate,或者在响应回调中手动判断状态码进行处理。
3. 检查拦截器的adapt方法
确保你的拦截器在adapt方法中没有做额外的重复请求处理,adapt仅用于请求发送前的参数/头信息适配,重试逻辑应该只放在retry方法中。
为什么.responseString只看到一次内容?
这是因为responseString的处理逻辑和responseDecodable不同,当重试触发时,第二次的响应数据可能没有被完整捕获,或者responseString在处理时自动忽略了异常的数据流,但本质问题还是不必要的重试导致的。
总结
通过限制拦截器仅对401状态码执行重试,就能避免非授权错误被重复处理,从根源上解决响应体重复拼接导致的JSON解码失败问题。
内容的提问来源于stack exchange,提问作者Peter Hlavatík

