Swift Alamofire:如何获取HTTP响应状态码

126

我想获取HTTP响应状态码(例如400、401、403、503等)以便处理请求失败(最好也能处理成功的情况)。在这段代码中,我正在使用HTTP基本身份验证进行用户认证,并希望能够在用户输入错误密码时向用户发送身份验证失败的消息。

Alamofire.request(.GET, "https://host.com/a/path").authenticate(user: "user", password: "typo")
    .responseString { (req, res, data, error) in
        if error != nil {
            println("STRING Error:: error:\(error)")
            println("  req:\(req)")
            println("  res:\(res)")
            println("  data:\(data)")
            return
        }
        println("SUCCESS for String")
}
    .responseJSON { (req, res, data, error) in
        if error != nil {
            println("JSON Error:: error:\(error)")
            println("  req:\(req)")
            println("  res:\(res)")
            println("  data:\(data)")
            return
        }
        println("SUCCESS for JSON")
}

不幸的是,产生的错误似乎并未表明实际接收到了HTTP状态码409:

STRING Error:: error:Optional(Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo=0x7f9beb8efce0 {NSErrorFailingURLKey=https://host.com/a/path, NSLocalizedDescription=cancelled, NSErrorFailingURLStringKey=https://host.com/a/path})
  req:<NSMutableURLRequest: 0x7f9beb89d5e0> { URL: https://host.com/a/path }
  res:nil
  data:Optional("")
JSON Error:: error:Optional(Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo=0x7f9beb8efce0 {NSErrorFailingURLKey=https://host.com/a/path, NSLocalizedDescription=cancelled, NSErrorFailingURLStringKey=https://host.com/a/path})
  req:<NSMutableURLRequest: 0x7f9beb89d5e0> { URL: https://host.com/a/path }
  res:nil
  data:nil

另外,如果发生错误,能够检索HTTP正文内容会很好,因为我的服务器端将在那里放置错误的文本描述。

问题
是否可以在非2xx响应时检索状态代码?
是否可以在2xx响应中检索特定的状态代码?
是否可以在非2xx响应中检索HTTP正文内容?

谢谢!


1
如果您未经身份验证,则按设计会收到-999。不确定为什么会这样,也不知道如何解决... 您解决了吗? - Guido Hendriks
12个回答

213

适用于 Swift 3.x / Swift 4.0 / Swift 5.0 用户,且使用 Alamofire >= 4.0 / Alamofire >= 5.0 版本。


response.response?.statusCode

更详细的示例:

Alamofire.request(urlString)
        .responseString { response in
            print("Success: \(response.result.isSuccess)")
            print("Response String: \(response.result.value)")

            var statusCode = response.response?.statusCode
            if let error = response.result.error as? AFError {  
                statusCode = error._code // statusCode private                 
                switch error {
                case .invalidURL(let url):
                    print("Invalid URL: \(url) - \(error.localizedDescription)")
                case .parameterEncodingFailed(let reason):
                    print("Parameter encoding failed: \(error.localizedDescription)")
                    print("Failure Reason: \(reason)")
                case .multipartEncodingFailed(let reason):
                    print("Multipart encoding failed: \(error.localizedDescription)")
                    print("Failure Reason: \(reason)")
                case .responseValidationFailed(let reason):
                    print("Response validation failed: \(error.localizedDescription)")
                    print("Failure Reason: \(reason)")

                    switch reason {
                    case .dataFileNil, .dataFileReadFailed:
                        print("Downloaded file could not be read")
                    case .missingContentType(let acceptableContentTypes):
                        print("Content Type Missing: \(acceptableContentTypes)")
                    case .unacceptableContentType(let acceptableContentTypes, let responseContentType):
                        print("Response content type: \(responseContentType) was unacceptable: \(acceptableContentTypes)")
                    case .unacceptableStatusCode(let code):
                        print("Response status code was unacceptable: \(code)")
                        statusCode = code
                    }
                case .responseSerializationFailed(let reason):
                    print("Response serialization failed: \(error.localizedDescription)")
                    print("Failure Reason: \(reason)")
                    // statusCode = 3840 ???? maybe..
                default:break
                }

                print("Underlying error: \(error.underlyingError)")
            } else if let error = response.result.error as? URLError {
                print("URLError occurred: \(error)")
            } else {
                print("Unknown error: \(response.result.error)")
            }

            print(statusCode) // the status code
    } 

(Alamofire 4包含全新的错误系统,详情请查看这里)

对于使用Alamofire >= 3.0Swift 2.x用户

Alamofire.request(.GET, urlString)
      .responseString { response in
             print("Success: \(response.result.isSuccess)")
             print("Response String: \(response.result.value)")
             if let alamoError = response.result.error {
               let alamoCode = alamoError.code
               let statusCode = (response.response?.statusCode)!
             } else { //no errors
               let statusCode = (response.response?.statusCode)! //example : 200
             }
}

response.result.error 可以给你一个 Alamofire 错误,例如 StatusCodeValidationFailedFAILURE: Error Domain=com.alamofire.error Code=-6003。如果你真的想要获取 HTTP 响应错误,最好使用 response.response?.statusCode - Kostiantyn Koval
@KostiantynKoval 我同意你的观点,你已经做了适当的澄清。我更新了我的答案以便更加清晰明了。 - Alessandro Ornano
对我来说运行正常... {let statusCode = response.response!.statusCode} - M Hamayun zeb

56
在下面带有参数response的完成处理程序中,我发现HTTP状态代码在response.response.statusCode中。
Alamofire.request(.POST, urlString, parameters: parameters)
            .responseJSON(completionHandler: {response in
                switch(response.result) {
                case .Success(let JSON):
                    // Yeah! Hand response
                case .Failure(let error):
                   let message : String
                   if let httpStatusCode = response.response?.statusCode {
                      switch(httpStatusCode) {
                      case 400:
                          message = "Username or password not provided."
                      case 401:
                          message = "Incorrect password for user '\(name)'."
                       ...
                      }
                   } else {
                      message = error.localizedDescription
                   }
                   // display alert with error message
                 }

嗨,statusCode 200会归类为失败吗?我的请求在服务器端被正确处理,但响应却被归类为失败。 - perwyl
1
@perwyl 我认为200不是一个HTTP错误:请参见https://dev59.com/n14c5IYBdhLWcg3wc5_r - wcochran
2
@perwyl 状态码200表示成功,如果您的服务器返回200但响应指示错误(即某个名为issuccess = false的属性),则必须在前端代码中处理。 - Parama Dharmika

25
    Alamofire
        .request(.GET, "REQUEST_URL", parameters: parms, headers: headers)
        .validate(statusCode: 200..<300)
        .responseJSON{ response in

            switch response.result{
            case .Success:
                if let JSON = response.result.value
                {
                }
            case .Failure(let error):
    }

这促进了API的最佳实践。 - CESCO
尝试为您的请求实现路由器。;) - Atef

12

使用Alamofire获取状态码的最佳方法

Alamofire.request(URL).responseJSON {
  response in
let status = response.response?.statusCode print("状态码 \(status)")
}

6

或者使用模式匹配

if let error = response.result.error as? AFError {
   if case .responseValidationFailed(.unacceptableStatusCode(let code)) = error {
       print(code)
   }
}

运行得非常顺利。 - alasker
1
当仅传递错误而非响应至视图时,此操作非常有用。 - Carson Holzheimer

5
您可以检查以下alamofire状态码处理程序的代码
    let request = URLRequest(url: URL(string:"url string")!)    
    Alamofire.request(request).validate(statusCode: 200..<300).responseJSON { (response) in
        switch response.result {
        case .success(let data as [String:Any]):
            completion(true,data)
        case .failure(let err):
            print(err.localizedDescription)
            completion(false,err)
        default:
            completion(false,nil)
        }
    }

如果状态码无效,则会进入switch case中的失败情况。

4

在你的 responseJSON 完成回调函数中,你可以从响应对象中获取状态码,该对象的类型为 NSHTTPURLResponse?:

if let response = res {
    var statusCode = response.statusCode
}

无论状态码是否在错误范围内都可以使用此方法。如需更多信息,请参见NSHTTPURLResponse文档
对于您的另一个问题,您可以使用responseString函数来获取原始响应内容。您可以将其添加到responseJSON中,两者都将被调用。
.responseJson { (req, res, json, error) in
   // existing code
}
.responseString { (_, _, body, _) in
   // body is a String? containing the response body
}

3

我需要知道如何获取实际的错误代码。

我继承了别人的项目,需要从他们以前为Alamofire设置的 .catch 子句中获取错误代码:

} .catch { (error) in

    guard let error = error as? AFError else { return }
    guard let statusCode = error.responseCode else { return }

    print("Alamofire statusCode num is: ", statusCode)
}

或者如果你需要从response值中获取它,请参考@mbryzinski的答案

Alamofire ... { (response) in

    guard let error = response.result.error as? AFError else { return }
    guard let statusCode = error.responseCode else { return }

    print("Alamofire statusCode num is: ", statusCode)
})

3
您的错误提示表明操作因某种原因被取消。我需要更多细节才能了解原因。但我认为更大的问题可能是,由于您的端点 https://host.com/a/path 是虚假的,没有真实的服务器响应报告,因此您看到的是 nil
如果您访问可以提供正确响应的有效端点,则应该使用 Sam 提到的技术,在 res 表单中看到 NSURLHTTPResponse 对象的非空值,该对象具有诸如 statusCode 等属性。
另外,只是为了明确起见,error 的类型为 NSError。它告诉您网络请求失败的原因。服务器端的失败状态码实际上是响应的一部分。
希望这有助于回答您的主要问题。

好的,你说得对,我太专注于底部的问题列表了。 - Sam
1
实际上,它收到了一个401未经授权的代码,因为我有意发送了错误的密码来模拟用户输入错误的密码,以便我可以捕捉到该情况并向用户提供反馈。这不是我正在使用的实际URL,但我正在使用一个合法的URL,当我发送正确的密码时会成功。我的原始帖子中的控制台输出是从命中真实URL(除了URL是假的)的实际输出,并且您可以看到'res'对象为 nil,因此这个答案没有帮助,很抱歉。 - GregT
谢谢澄清。那么这里唯一可疑的就是-999错误了。我没有遇到过这种情况,但文档表明请求被取消了(所以甚至收到响应的问题都不重要了)。你有尝试打印响应对象来查看与授权无关的其他类型的错误吗?只是好奇。 - rainypixels
啊啊啊,我以为 error 是指那些超出我们在 validate() 中提供的状态码范围的响应。谢谢!!! - Gerald

2
    AF.request(url, method: .get).responseDecodable(of: Weather.self) { response in
        switch response.result {
        case .success(let data):
            print(data)
            var statusCode = response.response?.statusCode
            
            if statusCode == 200 {
                print(response)
            }
        case .failure(let error):
            print(error)
        }
    }

response.response?.statusCode 已经在其他几个答案中提到过。 - Eric Aya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接