NSURLSession取消dataTask有时会出现错误消息任务已完成并发生错误-代码:-999

时间:2018-10-19 09:10:44

标签: ios swift nsurlsession

我正在上传大小为URLRequest.httpBody 6MB png Images-> Base64-> jsonData 。我在默认配置下使用静态NSURLSession,并在urlsession上使用dataTask上传。有时将其成功上传到服务器,有时不成功,并且出现错误,并且在服务器端没有任何输出。我没有进行并行通话。我们正在使用SSL固定并正确处理身份验证挑战,因此没有SSL身份验证错误。 我们正在使用的iOS设备11.3和XCode 10。

任务<58BF437E-7388-4AE4-B676-2485A57CB0CD>。<10>完成,错误-代码:-999

private lazy var configuration: URLSessionConfiguration = {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = TimeInterval(120)
        configuration.timeoutIntervalForResource = TimeInterval(120)
        return configuration
    }()
private lazy var urlSession = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)

func invokeService(methodName : String, collectionName: String, queryDictionary: [String:AnyObject]! = nil, httpMethod: String! = nil) {
        // Set up the URL request
        let baseUrl: String = WebServiceConstants.hostUrl.qaUrl + "/\(collectionName)" + "/\(methodName)"
       // let baseUrl: String = WebServiceConstants.hostUrl.demoUrl + "/\(collectionName)" + "/\(methodName)"
      // let baseUrl: String = WebServiceConstants.hostUrl.prod_Url + "/\(collectionName)" + "/\(methodName)"


        guard let url = URL(string: baseUrl) else {
            return
        }
        var urlRequest = URLRequest(url: url)
        // set up the session
//        let configuration = URLSessionConfiguration.default
//        configuration.timeoutIntervalForRequest = TimeInterval(120)
//        configuration.timeoutIntervalForResource = TimeInterval(120)
//
//        let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
        urlRequest.httpMethod = httpMethod
        do {
            urlRequest.httpBody = try JSONSerialization.data(withJSONObject: queryDictionary, options: []) // pass dictionary to nsdata object and set it as request body
        }
        catch _ {
        }

            urlRequest.setValue(WebServiceConstants.HTTPStrings.contentTypeJSON, forHTTPHeaderField: WebServiceConstants.HTTPStrings.contentTypeHeader)


       // print(queryDictionary)
        if AppController.sharedInstance.isAlreadyLogin() && (KeychainWrapper.standard.string(forKey: Constants.UserDefaultKeys.authorizationHeaderValue) != nil) {
            let authorizationHeaderValue = WebServiceConstants.HTTPStrings.authorizationHeaderValue + KeychainWrapper.standard.string(forKey: Constants.UserDefaultKeys.authorizationHeaderValue)!
            urlRequest.setValue(authorizationHeaderValue, forHTTPHeaderField: WebServiceConstants.HTTPStrings.authorizationHeader)
        }

        let _ = urlSession.dataTask(with: urlRequest, completionHandler: { [unowned self]
            (data, response, error)   in

            //print(response)
            if error != nil {

                if error?._code == NSURLErrorTimedOut {
                   // print(error?.localizedDescription)
                    let userInfo = [
                        NSLocalizedDescriptionKey: BWLocalizer.sharedInstance.localizedStringForKey(key:"App_Timeout_Message")
                    ]
                    let errorTemp = NSError(domain:"", code:-1001, userInfo:userInfo)
                    self.delegate?.didFailWithError(errorObject: errorTemp)
                } else if error?._code == NSURLErrorNotConnectedToInternet {
                    let userInfo = [
                        NSLocalizedDescriptionKey: BWLocalizer.sharedInstance.localizedStringForKey(key:"Internet_Not_Available")
                    ]
                    let errorTemp = NSError(domain:"", code:-1001, userInfo:userInfo)
                    self.delegate?.didFailWithError(errorObject: errorTemp)
                }
               else  if error?._code == NSURLErrorCancelled {
                    // canceled
                  print("Request is cancelled") // Control reaches here on Finished with Error code = -999
                     self.delegate?.didFailWithError(errorObject: error!)

                }
                else {
                    self.delegate?.didFailWithError(errorObject: error!)
                }

            } else {

                do {

                    if let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any]
                    {
                        self.delegate?.didReceiveResponse(responseObject: json as AnyObject)
                        //Implement your logic
                        print(json)
                    }

                } catch {
                    self.delegate?.didFailWithError(errorObject: error)

                }
            }

        }).resume()


    }

*添加了SSL证书固定代码*

extension WebserviceHandler : URLSessionDelegate {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        guard let trust = challenge.protectionSpace.serverTrust else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        let credential = URLCredential(trust: trust)
        let pinner = setupCertificatePinner() // adding CertificateHash

        if (!pinner.validateCertificateTrustChain(trust)) {
            challenge.sender?.cancel(challenge)

        }

        if (pinner.validateTrustPublicKeys(trust)) {
            completionHandler(.useCredential, credential)

      }
            else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            let popUp = UIAlertController(title: "", message: BWLocalizer.sharedInstance.localizedStringForKey(key:"Certificate_Pining_Fail_Message"), preferredStyle: UIAlertController.Style.alert)
            popUp.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: {alertAction in popUp.dismiss(animated: true, completion: nil)}))
            popUp.addAction(UIAlertAction(title: BWLocalizer.sharedInstance.localizedStringForKey(key:"Action_Go"), style: UIAlertAction.Style.default, handler: { action in
                let urlToOpenAppStorepage = URL(string: WebServiceConstants.hostUrl.appstore_url)
                let objApp = UIApplication.shared
                objApp.openURL(urlToOpenAppStorepage!)

            }))
            UIApplication.shared.keyWindow?.rootViewController?.present(popUp, animated: true, completion: nil)
        }
   }

}

func setupCertificatePinner() -> CertificatePinner {
//      let baseUrl: String = WebServiceConstants.hostUrl.dev_URL

        let baseUrl: String = WebServiceConstants.hostUrl.qaUrl
//      let baseUrl: String = WebServiceConstants.hostUrl.demoUrl
         // let baseUrl: String = WebServiceConstants.hostUrl.prod_Url
        let pinner = CertificatePinner(baseUrl)

        /*

         You will see something like this:

         being challanged! for www.google.co.nz
         hash order is usually most specific to least, so the first one is your domain, the last is the root CA
         Production
         hash: 8U/k3RvTcMVSafJeS9NGpY4KDFdTLwpQ/GUc+lmPH/s=
         hash: 4vkhpuZeIkPQ+6k0lXGi7ywkVNV55LhVgU0GaWWMOdk=
         hash: jZzMXbxSnIsuAiEBqDZulZ/wCrrpW9bRLMZ6QYxs0Gk=
         hash: uthKDtpuYHgn+wRsokVptSysyqBzmr4RP86mOC703bg=
         you might need to change the has below to be the second one in the list for the code to pass

         QA
         hash: LX6ZGwP3Uii+KCZxDxDWlDWijvNI6K/t2906cUzKYM4=
         hash: 4vkhpuZeIkPQ+6k0lXGi7ywkVNV55LhVgU0GaWWMOdk=
         hash: jZzMXbxSnIsuAiEBqDZulZ/wCrrpW9bRLMZ6QYxs0Gk=
        */

        pinner.debugMode = true
        pinner.addCertificateHash(WebServiceConstants.HTTPStrings.hashKey)

        return pinner
    }

使用固定库:https://github.com/nicwise/certificatepinner

1 个答案:

答案 0 :(得分:1)

我在该代码中看到了几个错误。

  • 如果您在经过身份验证的代理后面,则身份验证处理程序的第一行将导致失败。

    如果服务器需要任何类型的HTTP身份验证密码或OAuth凭据,也会失败。

    在其他许多情况下也会失败。

    除非该请求实际上在某种程度上是不好的,否则切勿取消身份验证请求。如果可以的话,取消身份验证请求还会阻止操作系统为您透明地处理它。唯一应该取消身份验证请求的情况是,如果您检查证书或其他内容,则该证书实际上无效。否则,在请求外部处理时,通常应该信任操作系统默认情况下可以做的正确的事情。

    因此,除非身份验证方法是服务器信任,否则请使用默认处理。

  • 该代码根本不检查身份验证方法。出于上述所有原因,除非身份验证方法是服务器信任,否则您不应该进行 any 检查。否则,请使用默认处理。总是。

  • 下一个if语句有两个问题:

    • 它为身份验证请求提供了新的状态而没有返回。这意味着您之后可以调用完成处理程序,这可能导致崩溃和其他不良行为。

    • 它正在质询发送方上调用旨在影响行为的方法。这就是您过去使用NSURLConnection进行操作的方式,但是您应该从不调用挑战发件人上的任何方法(除非可能,否则请查看它是否是您自己创建的对象正在将自定义NSURLProtocol类与NSURLSession一起使用,因为它可能导致各种问题,甚至崩溃。 (请参见NSURLAuthenticationChallenge's -sender method文档中的大警告,或者对此警告之前的两段内容。)

  • 我也不完全确定我是否信任固定代码。如果信任链中的任何密钥都是受信任密钥,则看起来像过去了,而通常,锁定要求信任链中的 last (叶)密钥是受信任密钥。

  • 该固定代码中的安全建议也令人怀疑。您可能不应该固定在证书上,而应该固定在证书内的密钥上。固定叶子证书的密钥是完全合适的,并且实际上是唯一合适的选择,因为它通常是唯一真正受您直接控制的密钥。如果您重新发行证书,那没什么大不了的,因为除非您的密钥已经以某种方式被盗用,否则您应该使用与以前相同的密钥重新发行。

    或者,您可以根据需要添加信任层,但这需要运行自己的自定义根目录(这需要更改validateCertificateTrustChain方法以在验证信任链的同时添加自定义根证书)或说服CA向您出售可以签署其他证书的证书,价格为$$$$。这些选项似乎都不实用。

    这些问题使我有点担心整个图书馆,但是我没有时间对其进行审核。您可能应该四处询问,看看是否有人对有关库进行了彻底的审核,因为编写编写与TLS密钥一起使用的代码时很容易犯错误,而且我不希望看到您以后遇到安全问题

修复上面列出的所有错误之后,如果仍然有问题,请返回并使用更新的代码提出另一个问题。另外,还请询问有问题的代码固定项目来修复其代码片段,因为它们似乎包含相同的错误。 :-)谢谢。