Search code examples
iosswiftgetnsurlrequest

Swift GET request with parameters


I'm very new to swift, so I will probably have a lot of faults in my code but what I'm trying to achieve is send a GET request to a localhost server with paramters. More so I'm trying to achieve it given my function take two parameters baseURL:string,params:NSDictionary. I am not sure how to combine those two into the actual URLRequest ? Here is what I have tried so far

    func sendRequest(url:String,params:NSDictionary){
       let urls: NSURL! = NSURL(string:url)
       var request = NSMutableURLRequest(URL:urls)
       request.HTTPMethod = "GET"
       var data:NSData! =  NSKeyedArchiver.archivedDataWithRootObject(params)
       request.HTTPBody = data
       println(request)
       var session = NSURLSession.sharedSession()
       var task = session.dataTaskWithRequest(request, completionHandler:loadedData)
       task.resume()

    }

}

func loadedData(data:NSData!,response:NSURLResponse!,err:NSError!){
    if(err != nil){
        println(err?.description)
    }else{
        var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
        println(jsonResult)

    }

}

Solution

  • When building a GET request, there is no body to the request, but rather everything goes on the URL. To build a URL (and properly percent escaping it), you can also use URLComponents.

    var components = URLComponents(string: "https://www.google.com/search/")!
    
    components.queryItems = [
        URLQueryItem(name: "q", value: "War & Peace")
    ]
    
    guard let url = components.url else {
        throw URLError(.badURL)
    }
    

    The only trick is that most web services need + character percent escaped (because they'll interpret that as a space character as dictated by the application/x-www-form-urlencoded specification). But URLComponents will not percent escape it. Apple contends that + is a valid character in a query and therefore shouldn't be escaped. Technically, they are correct, that it is allowed in a query of a URI, but it has a special meaning in application/x-www-form-urlencoded requests and really should not be passed unescaped.

    When I presented this issue to Apple support, they advised to manually percent escaping the + characters:

    components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
    

    This is an inelegant work-around, but it works, and is what Apple advises if your queries may include a + character and you have a server that interprets them as spaces.

    Anyway, combining that with your sendRequest routine, you might end up with something like:

    enum WebServiceError: Error {
        case invalidResponse(Data, URLResponse)
        case statusCode(Int, Data)
    }
    
    func object<T: Decodable>(from baseUrl: URL, parameters: [String: String]? = nil) async throws -> T {
        guard var components = URLComponents(url: baseUrl, resolvingAgainstBaseURL: false) else {
            throw URLError(.badURL)
        }
    
        components.queryItems = parameters?.map { (key, value) in
            URLQueryItem(name: key, value: value)
        }
        components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
    
        guard let url = components.url else {
            throw URLError(.badURL)
        }
    
        let (data, response) = try await URLSession.shared.data(from: url)
    
        guard let statusCode = (response as? HTTPURLResponse)?.statusCode else {  // is there HTTP response
            throw WebServiceError.invalidResponse(data, response)
        }
    
        guard 200 ..< 300 ~= statusCode else {                                    // is statusCode 2XX
            throw WebServiceError.statusCode(statusCode, data)
        }
    
        return try JSONDecoder().decode(T.self, from: data)
    }
    

    And you'd call it like:

    do {
        let foo: Foo = try await object(from: baseUrl)
        // do something with `foo` here
    } catch WebServiceError.statusCode(404, _) {        // if you want, you can catch individual status codes here
        // handle not found error here
    } catch {
        // handle other errors here
    }
    

    Clearly, there are lots of permutations on the idea, but hopefully this illustrates the basic idea of how to percent encode the parameters into the URL of a GET request.


    See previous revisions of this answer for Swift 2, manual percent escaping renditions, and non-Swift concurrency renditions of the above.