HTTP Status NIL using both URLSession and AlamoFire. Error Code=303 and NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask

846 views Asked by At

What's Expected:

  • Response with status code 200 and a JSON body with auth token etc.

What's Happening:

  • A device will (randomly?) no longer make the login call to auth0 successfully.
  • HTTP status code, response data will all be nil values. -Other devices running the same app branch, same wifi network are able to make the call with no issue. Then after some time, generally 1-2 days the problem phone will no longer have any trouble making the call and will continue on as before the issue started.
  • Throws error Code 303 (CFNetworkErrors.cfErrorHTTPParseFailure) in logs. Apple docs say it's a failure to parse the response from the server. I believe it's because the response data is null.

App Version:

  • This issue has shown up so far on iOS devices running 14.6 or higher.

Testing:

  • Used both an AlamoFire based and URLSession based network call and both can have the issue. The example code below is using the URLSession instead of the AlamoFire, but switching does not seem to have an effect on stopping devices from being able to develop the issue.
  • Changing from WiFi to Celluar internet has no effect, changing to another WiFi network has no effect either.
  • Deleting the App and reinstalling has no effect.
  • Because I have not figured out what triggers the issue I have only got my hands on a device with the issue a few times before it resolved itself and there was nothing to test.
  • I have managed to save some logs (print statements and some Firebase Analytics console messages). The segments I think are useful are pasted below.

Network call Code:

func makeNetworkCall(url: String, method: HTTPMethod = .get, parameters: Parameters? = nil, headers: HTTPHeaders? = nil, timeout: Double, completion: @escaping (DefaultURLRequestReturnModel) -> Void ) {
        
        logger.debug("makingCall with: url= _\(url)_ method= _\(method)_, param= _\(parameters)_, headers= _\(headers)_")
        
        var urlRequest: URLRequest?
        var hasReturned: Bool = false
        
        DispatchQueue.main.asyncAfter(deadline: .now() + timeout, execute: {
            if !hasReturned {
                completion(DefaultURLRequestReturnModel(isSuccess: false, statusCode: ErrorCodes.timeoutError.rawValue, action: .failure, dataResponse: nil))
            }
        })
        
        do {
            urlRequest = try URLRequest(url: url, method: method, headers: headers)
            
            if let parameters = parameters {
                urlRequest?.httpBody = try JSONSerialization.data(withJSONObject: parameters)
            }
        } catch {
            completion(DefaultURLRequestReturnModel(isSuccess: false, statusCode: ErrorCodes.encodeError.rawValue, action: .failure, dataResponse: nil))
            return
        }
        
        // Unwrap the optional urlRequest to confirm its != nil
        guard var safeURLRequest = urlRequest else {
            
            completion(DefaultURLRequestReturnModel(isSuccess: false, statusCode: ErrorCodes.encodeError.rawValue, action: .failure, dataResponse: nil))
            return
        }
        
        if safeURLRequest.httpBody != nil {
            safeURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }
        
        let session = URLSession.shared

        logger.debug("before session.dataTask for url \(safeURLRequest.url)")
        let task = session.dataTask(with: safeURLRequest, completionHandler: { data, response, error in
            
            logger.debug("url session completion for safeURLRequest.url _\(safeURLRequest.url)_, response= _\(response)_, error= _\(error.debugDescription)_, data= _\(data)_")
            
            if let httpResponse = response as? HTTPURLResponse {
                
                logger.debug("url session completion for URL \(safeURLRequest.url): status \(httpResponse.statusCode)")
                
                switch httpResponse.statusCode {
                    case 200, 204:
                        let defaultDataResponse = DefaultDataResponse(request: safeURLRequest, response: httpResponse, data: data, error: error)
                        completion(DefaultURLRequestReturnModel(isSuccess: true, statusCode: httpResponse.statusCode, action: httpResponse.statusCode == 200 ? .success : .successEmpty, dataResponse: defaultDataResponse))
                    default:
                        logger.debug("ON ERROR")
                        completion(DefaultURLRequestReturnModel(isSuccess: false, statusCode: httpResponse.statusCode, action: httpResponse.statusCode == 401 ? .authError : .failure, dataResponse: nil))
                }
            } else {
                // This is Trigged in the error logs
                logger.debug("url session completion for URL \(safeURLRequest.url): response = \(response) __ can't be cast to HTTPURLResponse")
                if NetworkManager.isConnectedToInternet() { logger.networkNilEvent() }
                completion(DefaultURLRequestReturnModel(isSuccess: false, statusCode: ErrorCodes.noResponseError.rawValue, action: .failure, dataResponse: nil))
            }
            
            hasReturned = true
            
        })

        task.resume()
        
    }

Console Logs From A Problem Device:

debug: url session completion for safeURLRequest.url Optional(https://cpht.auth0.com/oauth/ro), response= nil, error= _Optional(Error Domain=kCFErrorDomainCFNetwork Code=303 "(null)" UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask .<9>, _kCFStreamErrorDomainKey=4, NSErrorPeerAddressKey=<CFData 0x28375a080 [0x1f9151860]>{length = 16, capacity = 16, bytes = 0x100201bb6810b8f80000000000000000}, _kCFStreamErrorCodeKey=-2205, NSURLErrorRelatedURLSessionTaskErrorKey=( "LocalDataTask .<9>" )}), data= nil debug: url session completion for URL Optional(https://cpht.auth0.com/oauth/ro): response = nil __ can't be cast to HTTPURLResponse

Firebase Analytics Logs From A Problem Device:

  • I have replaced the IP Addresses from something like 100.0.0.0:443 to IP:443 in the following logs.
  • App name has been changed to MyAppName

2021-08-12 16:59:51.615734-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88 2021-08-12 16:59:51.615828-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88 2021-08-12 16:59:51.624933-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88 2021-08-12 16:59:51.625051-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88 2021-08-12 16:59:51.625570-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88 2021-08-12 16:59:51.625716-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_adaptive_read_handler [C14 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for read_timeout failed 2021-08-12 16:59:51.625773-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_adaptive_write_handler [C14 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for write_timeout failed 2021-08-12 16:59:51.625828-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_keepalive_handler [C14 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for keepalive failed 2021-08-12 16:59:51.626028-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88 2021-08-12 16:59:51.626859-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 10909f7c8 2021-08-12 16:59:51.627061-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_adaptive_read_handler [C13.2.1 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for read_timeout failed 2021-08-12 16:59:51.627118-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_adaptive_write_handler [C13.2.1 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for write_timeout failed 2021-08-12 16:59:51.627257-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_keepalive_handler [C13.2.1 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for keepalive failed 2021-08-12 16:59:51.641425-0500 MyAppName[4586:1909925] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88 2021-08-12 16:59:51.647736-0500 MyAppName[4586:1909925] Task .<9> HTTP load failed, 529/0 bytes (error code: 303 [4:-2205]) 2021-08-12 16:59:51.648181-0500 MyAppName[4586:1909923] Task .<9> finished with error [303] Error Domain=kCFErrorDomainCFNetwork Code=303 "(null)" UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask .<9>, _kCFStreamErrorDomainKey=4, NSErrorPeerAddressKey=<CFData 0x28375a080 [0x1f9151860]>{length = 16, capacity = 16, bytes = 0x100201bb6810b8f80000000000000000}, _kCFStreamErrorCodeKey=-2205, _NSURLErrorRelatedURLSessionTaskErrorKey=( "LocalDataTask .<9>" )} 2021-08-12 16:59:51.648326-0500 MyAppName[4586:1909925] [connection] nw_endpoint_handler_unregister_context [C13.2.1 IP:443 failed channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] Cannot unregister after flow table is released 2021-08-12 16:59:51.648393-0500 MyAppName[4586:1909925] [h3connection] 0x1099d1a18 13 closed with peer error 258 2021-08-12 16:59:51.648500-0500 MyAppName[4586:1909925] [connection] nw_endpoint_handler_add_write_request [C13.2.1 IP:443 failed channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] Cannot send after flow table is released 2021-08-12 16:59:51.648625-0500 MyAppName[4586:1909925] [connection] nw_write_request_report [C13] Send failed with error "Socket is not connected"

Thank you for any of your ideas on fixing this issue!

1

There are 1 answers

2
Ditscheridou On

We had the same issuie that you described. In our case, one of our custom request headers exceeded the max header size limit of the webserver. Nginx has a max header size around 8kb and will return a 413 error to the client if you exceed this limit. for whatever reason, the ios app will not parse this response and throw a 303 error. After we increased the http2_max_header_size to 32kb, it worked.