Search code examples
iospostswiftnsurlconnection

Perform POST request in iOS Swift


I am trying perform a POST request and the request does not go through. I have looked through Perform POST request in Swift already but it does not contain what I'm looking for.

func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
    var request = NSMutableURLRequest(URL: NSURL(string: "https://us1.lacunaexpanse.com"))
    println("request url https://us1.lacunaexpanse.com")
    var session = NSURLSession.sharedSession()
    request.HTTPMethod = "POST"

    let apikey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    println("apikey",apikey)

    let username = "username"
    let password = "password"

    var login = Array(["username", "password", "apikey"])

    let jsonDictionary = ["2.0", "jsonrpc", "1", "id", "login", "method", "login", "params"]
    println("jsonDictionary",jsonDictionary)

    var writeError: NSError?

    let jsonData = NSJSONSerialization.dataWithJSONObject(jsonDictionary, options: NSJSONWritingOptions(), error: NSErrorPointer())

    var resultAsString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)

    resultAsString = resultAsString.stringByAppendingString("empire")

    let url = NSURL.URLWithString("string")
    println("url",url)

    var request2 = NSMutableURLRequest()
    println("Post url =%@",url)

    request2 = NSMutableURLRequest(URL:url)

    request2.HTTPMethod = "POST"

    var connection = NSURLConnection(request: request, delegate: self, startImmediately: false)

    return true

Solution

  • There are a whole bunch of tactical issues here:

    1. You're creating URLSession, but then issuing NSURLConnection request. Just use URLSession.

    2. Your “request dictionary” isn't a dictionary, but rather an array. For example, to issue the JSON-RPC request, the proper format of the dictionary is:

      let requestDictionary: [String: Any] = [
          "jsonrpc" : "2.0",
          "id"      : 1,
          "method"  : "login",
          "params"  : ["myuserid", "mypassword", "mykey"]
      ]
      
    3. Minor issue, but you are using a lot of variables (via var) where a constant (via let) would be fine. In the spirit of Swift’s safety, use let wherever possible.

    4. According to the Lacuna Expanse API, your URL should be including the module name.

      So, for example if doing POST requests in the "Empire" module, the URL is:

      let url = URL(string: "https://us1.lacunaexpanse.com/empire")!
      
    5. You're likely to be doing a lot of requests, so I'd suggest putting the bulk of that in a single function that you can call again and again, without repeating code all over the place. Perhaps a function like the following that takes the following parameters:

      • module (e.g. “empire” vs “alliance”);

      • method (e.g. “login” vs “fetch_captcha”);

      • the parameters appropriate for that request (e.g. for “login”, that would be the “name”, “password”, and the “api_key”); and

      • closure that will be called when the asynchronous request finishes.

      This function then prepares the JSON-RPC request and calls the closure when the request finishes:

      @discardableResult
      func submitLacunaRequest(module: String, method: String, parameters: Any, completion: @escaping (Result<[String: Any], Error>) -> Void) -> URLSessionTask? {
          let session = URLSession.shared
          let url = URL(string: "https://us1.lacunaexpanse.com")!
              .appendingPathComponent(module)
          var request = URLRequest(url: url)
          request.httpMethod = "POST"
          request.setValue("application/json-rpc", forHTTPHeaderField: "Content-Type")
      
          let requestDictionary: [String: Any] = [
              "jsonrpc": "2.0",
              "id"     : 1,
              "method" : method,
              "params" : parameters
          ]
      
          request.httpBody = try! JSONSerialization.data(withJSONObject: requestDictionary)
      
          let task = session.dataTask(with: request) { data, response, error in
      
              // handle fundamental network errors (e.g. no connectivity)
      
              guard error == nil, let data = data else {
                  completion(.failure(error ?? URLError(.badServerResponse)))
                  return
              }
      
              // check that http status code was 200
      
              guard
                  let httpResponse = response as? HTTPURLResponse,
                  200 ..< 300 ~= httpResponse.statusCode
              else {
                  completion(.failure(URLError(.badServerResponse)))
                  return
              }
      
              // parse the JSON response
      
              do {
                  guard let responseObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
                      throw URLError(.badServerResponse)
                  }
                  completion(.success(responseObject))
              } catch let parseError {
                  completion(.failure(parseError))
              }
          }
          task.resume()
      
          return task
      }
      

      This does all of the necessary wrapping of the method and parameters within a JSON-RPC request. Then, all you need to do to call that method is something like so:

      submitLacunaRequest(module: "empire", method: "login", parameters: ["myuserid", "mypassword", "mykey"]) { result in
      
          switch result {
          case .failure(let error):
              print("error = \(error)")
      
          case .success(let value):
              if let errorDictionary = value["error"] as? [String: Any] {
                  print("error logging in (bad userid/password?): \(errorDictionary)")
              } else if let resultDictionary = value["result"] as? [String: Any] {
                  print("successfully logged in, refer to resultDictionary for details: \(resultDictionary)")
              } else {
                  print("we should never get here")
                  print("responseObject = \(value)")
              }
          }
      }
      

      For a request that requires a dictionary, such as "create", just go ahead and supply the dictionary:

      submitLacunaRequest(module:"empire", method: "create", parameters: [
          "name"      : "user",
          "password"  : "password",
          "password1" : "password",
          "captcha_guid" : "305...dd-....-....-....-e3706...73c0",
          "captcha_solution" : "42",
          "email" : "test@gmail.com"
      ]) { result in
          switch result {
          case .failure(let error):
              print("error = \(error)")
      
          case .success(let value):
              print("responseObject = \(responseObject)")
          }
      }
      

    Clearly, in these above, I am just doing minimal error handling, so you could beef this up, but your question was about issuing POST request, and hopefully the above illustrates how that is done.

    For Swift 2 version, see previous revision of this answer.