Post Urlrequest Doesn't Work in Swift 4

URLRequest POST doesn't work in Swift 5. There may be a problem with the server?

Nothing wrong here.

  • Status 200
  • code doesn’t test for error but I bet it’s nil
  • Print of response is what you should expect
  • Print JSON prints a json object.

You should be happy. Or What would you expect?

HTTP Request in Swift with POST method

The key is that you want to:

  • set the httpMethod to POST;
  • optionally, set the Content-Type header, to specify how the request body was encoded, in case server might accept different types of requests;
  • optionally, set the Accept header, to request how the response body should be encoded, in case the server might generate different types of responses; and
  • set the httpBody to be properly encoded for the specific Content-Type; e.g. if application/x-www-form-urlencoded request, we need to percent-encode the body of the request.

E.g., in Swift 3 and later you can:

let url = URL(string: "https://httpbin.org/post")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpMethod = "POST"
let parameters: [String: Any] = [
"id": 13,
"name": "Jack & Jill"
]
request.httpBody = parameters.percentEncoded()

let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard
let data = data,
let response = response as? HTTPURLResponse,
error == nil
else { // check for fundamental networking error
print("error", error ?? URLError(.badServerResponse))
return
}

guard (200 ... 299) ~= response.statusCode else { // check for http errors
print("statusCode should be 2xx, but is \(response.statusCode)")
print("response = \(response)")
return
}

// do whatever you want with the `data`, e.g.:

do {
let responseObject = try JSONDecoder().decode(ResponseObject<Foo>.self, from: data)
print(responseObject)
} catch {
print(error) // parsing error

if let responseString = String(data: data, encoding: .utf8) {
print("responseString = \(responseString)")
} else {
print("unable to parse response as string")
}
}
}

task.resume()

Where the following extensions facilitate the percent-encoding request body, converting a Swift Dictionary to a application/x-www-form-urlencoded formatted Data:

extension Dictionary {
func percentEncoded() -> Data? {
map { key, value in
let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
return escapedKey + "=" + escapedValue
}
.joined(separator: "&")
.data(using: .utf8)
}
}

extension CharacterSet {
static let urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="

var allowed: CharacterSet = .urlQueryAllowed
allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
return allowed
}()
}

And the following Decodable model objects facilitate the parsing of the application/json response using JSONDecoder:

// sample Decodable objects for https://httpbin.org

struct ResponseObject<T: Decodable>: Decodable {
let form: T // often the top level key is `data`, but in the case of https://httpbin.org, it echos the submission under the key `form`
}

struct Foo: Decodable {
let id: String
let name: String
}

This checks for both fundamental networking errors as well as high-level HTTP errors. This also properly percent escapes the parameters of the query.

Note, I used a name of Jack & Jill, to illustrate the proper x-www-form-urlencoded result of name=Jack%20%26%20Jill, which is “percent encoded” (i.e. the space is replaced with %20 and the & in the value is replaced with %26).


See previous revision of this answer for Swift 2 rendition.

Swift URL Session and URL Request not working

Your code is structured incorrectly.

URLSession creates tasks that are run asynchronously. You set up a task, and either pass in a completion block, or set up a delegate.

The task.resume() call returns immediately, long before the network download is complete.

Once the task is complete, the system calls your completion handler (or your delegate, if you use the delegate style).

Beware that URLSessions' completion handlers and delegate calls are done on a background thread. If you do any UIKit calls in response to a task completing, you need to do it on the main thread.

As @keithbhunter says in his comment, you need to put the call to your completion handler inside the completion handler for your task. It's probably safest if you wrap that whole completion handler call in a call to the main thread:

func startConnection(completion: (NSArray, Int) -> Void) {
let url = URL(string: "http://www.example.com/path")
var request : URLRequest = URLRequest(url: url!)
request.httpMethod = "POST"
let postString = "a=\(Int(teamInput.text!)!)"
request.httpBody = postString.data(using: .utf8)

let dataTask = URLSession.shared.dataTask(with: request) {
data,response,error in
print("anything")
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
self.teamResult = jsonResult
print(jsonResult)
//Use GCD to invoke the completion handler on the main thread
DispatchQueue.main.async() {
completion(NSArray(object: teamResult), Int(teamInput.text!)!)
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
dataTask.resume()
}

Note that your force-unwrapping of teamInput.text is very fragile, and will crash if teamInput.text is nil, or if it can't be converted to an Int. You'd be much better off to write your completion handler to take optionals for both the data and the int value you get back from teamInput.text:

func startConnection(completion: (NSArray?, Int?) -> Void) {

and call it passing in an optional:

let value: Int? = teamInput.text != nil ? Int(teamInput.text!) : nil
completion(NSArray(object: teamResult), value)

http Post says its working but it isn't posting on localhost

If you don't see logs in your server, may be caused by URLSession's cache policy.

Try to set the cachePolicy property of your URLRequest to .reloadIgnoringLocalAndRemoteCacheData or .reloadIgnoringCacheData and see if that works:

let url = URL(string: "http://192.168.1.318:8080")! /*localhost*/
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
// or
request.cachePolicy = .reloadIgnoringCacheData

Swift POST Request not working

I figured it out, I had to add in this in my swift file:

request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")

(iOS / Swift) Simple Post Request with Key-Value Failed, But Succeeded using Postman

Your content type is application/x-www-form-urlencoded, but you're setting the body as JSON instead of URL-encoded parameters. The way your HTTP body looks with the way you have it now is something like:

{"uname":"21120113120038","pass":"123456"}

...but you want it to look like this:

uname=21120113120038&pass=123456

So, replace this line:

guard let httpBody = try? JSONSerialization.data(withJSONObject: parameter, options: []) else {return}

...with something like this:

guard let httpBody = parameter.map({
[$0.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? "",
$1.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? ""].joined(separator: "=")
}).joined(separator: "&").data(using: .utf8) else { return }

(However, I think that's a bit messy, so here are some extensions to clean it up somewhat:)

extension String {
var urlEncodedQueryKeyOrValue: String {
return addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? ""
}
}

extension Dictionary where Key == String, Value == String {
var wwwFormURLEncodedString: String {
return map { "\($0.urlEncodedQueryKeyOrValue)=\($1.urlEncodedQueryKeyOrValue)" }.joined(separator: "&")
}
}

And then you'd use it like this:

[...]
guard let httpBody = parameter.wwwFormURLEncodedString.data(using: .utf8) else { return }
request.httpBody = httpBody
[...]

Swift Alamofire post request doesn't work,

Try removing the '-> Bool' and the 'return resp' from your function to give Alamofire a chance to respond.

How to make HTTP request in Swift?

You can use URL, URLRequest and URLSession or NSURLConnection as you'd normally do in Objective-C. Note that for iOS 7.0 and later, URLSession is preferred.

Using URLSession

Initialize a URL object and a URLSessionDataTask from URLSession. Then run the task with resume().

let url = URL(string: "http://www.stackoverflow.com")!

let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
}

task.resume()

Using NSURLConnection

First, initialize a URL and a URLRequest:

let url = URL(string: "http://www.stackoverflow.com")!
var request = URLRequest(url: url)
request.httpMethod = "POST"

Then, you can load the request asynchronously with:

NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue.main) {(response, data, error) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
}

Or you can initialize an NSURLConnection:

let connection = NSURLConnection(request: request, delegate:nil, startImmediately: true)

Just make sure to set your delegate to something other than nil and use the delegate methods to work with the response and data received.

For more detail, check the documentation for the NSURLConnectionDataDelegate protocol

Testing on an Xcode playground

If you want to try this code on a Xcode playground, add import PlaygroundSupport to your playground, as well as the following call:

PlaygroundPage.current.needsIndefiniteExecution = true

This will allow you to use asynchronous code in playgrounds.



Related Topics



Leave a reply



Submit