Getting statusCode other than 200...299 in HTTPURLResponse of URLSession

1.2k Views Asked by At

the following is my APIManager code, I'm using it in all my apps. But sometimes, the guard statement fails in connectToServer function, which means the statusCode of HTTPURLResponse other than 200...299 and the thing here is even after getting statusCode other than 200...299 my record got inserted into DB. I don't know what happens.

I thought that the cause of this behavior is from ServerURL, because I'm using a dev server with IP address http://00.000.0.000/ without security. Once I moved it to domain as https://XXX.XXXXXXXXXX.XXXXX/ it is working fine. Can you help me to figure out this?

And also will it supports for asynchronous calls?

import UIKit

struct APIResponse : Decodable {
    let status : Bool
    let message : String
    let extra: String?
}

internal let BASE_URL = "http://00.000.0.00/app/v0_1/api/" // Example server URL

enum APIPath: String {
    
    case registration = "registration"
    case login = "login"
    case getProfile = "get_profile"
    
    func directURL() -> URL? {
        let urlPath = BASE_URL + self.rawValue
        return URL(string: urlPath)
    }
    
    func extendedURL(using parameters: [String: Any]) -> URL? {
        let extendedPath = parameters.map { $0.key + "=" + "\($0.value)" }.joined(separator: "&")
        let urlPath = BASE_URL + self.rawValue + "?" + extendedPath
        return URL(string: urlPath)
     }
    
}

enum APIMethod: String {
    case get = "GET"
    case put = "PUT"
    case post = "POST"
    case patch = "PATCH"
    case delete = "DELETE"
}

enum APIHeaders {
    case user
    case app
    
    var authorization: [String:String] {
        let acceptLanguage = UserDefaults.standard.value(forKey: UDKeys.appleLanguage) as? String ?? ""
        if self == .user {
            let token = UserDefaults.standard.value(forKey: UDKeys.userToken) as? String ?? ""
            return ["Content-Type": "application/json", "Accept": "application/json", "Accept-Language": acceptLanguage, "Token" : token]
        }
        return ["Content-Type": "application/json", "Accept": "application/json", "Accept-Language": acceptLanguage]
    }
}

struct APIRequest {
    
    var url: URL?
    var method: String
    var parameters: Data?
    var headers: [String:String]
    
    init(path: APIPath, method: APIMethod, headers: APIHeaders) {
        self.url = path.directURL()
        self.method = method.rawValue
        self.headers = headers.authorization
    }
    
    init(path: APIPath, parameters: [String: Any], method: APIMethod, headers: APIHeaders) {
        self.url = path.extendedURL(using: parameters)
        self.method = method.rawValue
        self.headers = headers.authorization
    }
    
    init(path: APIPath, method: APIMethod, body: [String:Any], headers: APIHeaders) {
        self.url = path.directURL()
        self.method = method.rawValue
        self.parameters = try? JSONSerialization.data(withJSONObject: body, options: .sortedKeys)
        self.headers = headers.authorization
    }
    
    init<Encode: Encodable>(path: APIPath, method: APIMethod, body: Encode, headers: APIHeaders) {
        self.url = path.directURL()
        self.method = method.rawValue
        self.parameters = try? JSONEncoder().encode(body)
        self.headers = headers.authorization
    }
    
}

struct APIError: Error {
    let reason: String
    let code: String?
    init(reason: String, code: String? = nil) {
        self.reason = reason
        self.code = code
    }
}


struct APIDispatcher {
    
    static let instance = APIDispatcher()
    private init() {}
        
    func dispatch<Decode: Decodable>(request: APIRequest, response: Decode.Type, result: @escaping (Result<Decode, APIError>) -> ()) {
        DispatchQueue(label: "queue", attributes: .concurrent).async {
            self.connectToServer(with: request) { (resultant) in
                switch resultant {
                case .success(let data):
                    do {
                        let decoded = try JSONDecoder().decode(response, from: data)
                        DispatchQueue.main.async {
                            result(.success(decoded))
                        }
                    } catch let decodedError {
                        print("[Decoded Error]: ", decodedError)
                        do {
                            let apiResponse = try JSONDecoder().decode(APIResponse.self, from: data)
                            let apiError = APIError(reason: apiResponse.message, code: apiResponse.extra)
                            DispatchQueue.main.async {
                                result(.failure(apiError))
                            }
                        } catch {
                            let apiError = APIError(reason: decodedError.localizedDescription)
                            DispatchQueue.main.async {
                                result(.failure(apiError))
                            }
                        }
                    }
                case .failure(let error):
                    DispatchQueue.main.async {
                        result(.failure(error))
                    }
                }
            }
        }
    }
    
    func dispatch(request: APIRequest, result: @escaping (Result<Dictionary<String,Any>, APIError>) -> ()) {
        DispatchQueue(label: "queue", attributes: .concurrent).async {
            self.connectToServer(with: request) { (resultant) in
                switch resultant {
                case .success(let data):
                    do {
                        let serialized = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! Dictionary<String,Any>
                        DispatchQueue.main.async {
                            result(.success(serialized))
                        }
                    } catch {
                        let error = APIError(reason: error.localizedDescription)
                        DispatchQueue.main.async {
                            result(.failure(error))
                        }
                    }
                case .failure(let error):
                    DispatchQueue.main.async {
                        result(.failure(error))
                    }
                }
            }
        }
    }
    
    private func connectToServer(with request: APIRequest, result: @escaping (Result<Data, APIError>) -> ()) {
        
        guard let url = request.url else {
            let error = APIError(reason: "Invalid URL")
            result(.failure(error))
            return
        }
                
        var urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30)
        urlRequest.httpMethod = request.method
        urlRequest.httpBody = request.parameters
        urlRequest.allHTTPHeaderFields = request.headers
        
        print(urlRequest)
        
        let urlSessionConfiguration = URLSessionConfiguration.default
        urlSessionConfiguration.waitsForConnectivity = false
        urlSessionConfiguration.timeoutIntervalForRequest = 30
        urlSessionConfiguration.timeoutIntervalForResource = 60
        
        let urlSession = URLSession(configuration: urlSessionConfiguration)
        urlSession.dataTask(with: urlRequest) { (data, response, error) in
            if let error = error {
                let error = APIError(reason: error.localizedDescription)
                result(.failure(error))
                return
            }
            guard let httpResponse = response as? HTTPURLResponse,
                  (200...299).contains(httpResponse.statusCode) else {
                let error = APIError(reason: "Server Error")
                result(.failure(error))
                return
            }
            if let data = data {
                result(.success(data))
            }
        }.resume()
        
    }
    
}

Note: BASE_URL and APIResponse might be vary according to project.

I'm using it as


func login() {
        self.startLoading()
        let body = ["mobile_number": phoneNumberTF.text!, "password" : passwordTF.text!, "uuid" : UIDevice.current.identifierForVendor!.uuidString]
        let apiRequest = APIRequest(path: .login, method: .post, body: body, headers: .app)
        APIDispatcher.instance.dispatch(request: apiRequest) { result in
            self.stopLoading()
            switch result {
            case .success(let response):
                break
            case .failure(let error):
                break
            }
        }
    }


EDIT: My bad I asked completely reverse on statsCode now I modified it.

0

There are 0 best solutions below