基于通用网络层解码401状态码的服务器响应方案
如何让Swift通用网络层HTTPClient获取401响应中的自定义错误信息
问题背景
我从网上获取了一段Swift通用网络层HTTPClient代码,当前遇到的问题是:服务器返回401状态码时,会附带如下JSON响应:
{ "message": "Username or password is incorrect", "success": false }
但现有HTTPClient仅会抛出NetworkError.unauthorized,无法获取响应里的message字段。需要修改load函数来获取这部分自定义错误信息。
解决方案思路
通用网络层不应耦合业务特定错误(比如LoginError),合理的做法是让NetworkError携带服务器返回的通用错误响应模型,将业务逻辑的处理交给上层调用方。
具体实现步骤
1. 定义通用错误响应模型
先创建可解析服务器错误响应的模型:
struct ErrorResponse: Decodable { let message: String let success: Bool }
2. 更新NetworkError枚举,添加关联值携带错误响应
修改原有NetworkError,让unauthorized及其他需要的错误类型可以关联ErrorResponse,同时补充本地化描述:
enum NetworkError: Error { case invalidURL case invalidResponse case unauthorized(ErrorResponse?) case serverError(Int, ErrorResponse?) case decodingError(Error) case unknown } extension NetworkError: LocalizedError { var errorDescription: String? { switch self { case .invalidURL: return NSLocalizedString("无效的请求地址", comment: "") case .invalidResponse: return NSLocalizedString("服务器返回无效响应", comment: "") case .unauthorized(let response): return response?.message ?? NSLocalizedString("未授权访问", comment: "") case .serverError(let code, let response): return response?.message ?? NSLocalizedString("服务器错误,状态码:\(code)", comment: "") case .decodingError(let error): return NSLocalizedString("数据解析失败:\(error.localizedDescription)", comment: "") case .unknown: return NSLocalizedString("未知网络错误", comment: "") } } }
3. 修改HTTPClient的load函数,解析401响应并关联到错误中
在状态码处理逻辑里,针对401情况尝试解析服务器返回的ErrorResponse,再抛出携带该响应的NetworkError.unauthorized:
class HTTPClient { func load<T: Decodable>(_ request: URLRequest) async throws -> T { guard let url = request.url else { throw NetworkError.invalidURL } let (data, response) = try await URLSession.shared.data(from: url) guard let httpResponse = response as? HTTPURLResponse else { throw NetworkError.invalidResponse } switch httpResponse.statusCode { case 200...299: do { return try JSONDecoder().decode(T.self, from: data) } catch { throw NetworkError.decodingError(error) } case 401: // 尝试解析错误响应,解析失败则传nil let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: data) throw NetworkError.unauthorized(errorResponse) case 400...599: // 其他服务器错误统一处理 let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: data) throw NetworkError.serverError(httpResponse.statusCode, errorResponse) default: throw NetworkError.unknown } } }
4. 上层业务方(如AuthenticationController)处理具体逻辑
在登录等业务场景中,捕获NetworkError.unauthorized后,取出message做业务处理:
class AuthenticationController { private let httpClient = HTTPClient() func login(username: String, password: String) async { guard let url = URL(string: "your-login-api-url") else { return } var request = URLRequest(url: url) // 构造请求方法、参数等... do { let loginResult = try await httpClient.load(LoginResponse.self, request) // 登录成功逻辑 } catch let error as NetworkError { switch error { case .unauthorized(let response): let errorMessage = response?.message ?? "登录失败,请检查账号密码" // 展示错误提示给用户 print(errorMessage) default: // 处理其他网络错误 print(error.localizedDescription) } } catch { print("未知错误:\(error.localizedDescription)") } } }
为什么不选择抛出业务特定错误?
第一次尝试中直接抛出LoginError的问题在于,HTTPClient是通用网络层,需要适配所有网络请求场景(如获取用户信息、提交订单等),如果在网络层抛出业务特定错误,会导致网络层与业务逻辑强耦合,违背单一职责原则。而让NetworkError携带通用错误响应,既能保留错误的具体信息,又能让上层业务方根据自身需求处理,更符合架构设计的合理性。
内容的提问来源于stack exchange,提问作者john doe
相关产品推荐
相关产品推荐

