NSURLSession POST Request API - ios

I am trying to upload the image as multipart form-data, but i am getting the error that status code 404 from server. Can anyone point out the mistake i am doing in this?
Server accepts "file" as key of the image that we are uploading.
This is what i am tried so far...
public func UPLOADING(url: String,parameters: Dictionary<String,AnyObject>?,filename:String,image:UIImage, success:((NSDictionary) -> Void)!, failed:((NSDictionary) -> Void)!, errord:((NSError) -> Void)!) {
let TWITTERFON_FORM_BOUNDARY:String = "AaB03x"
let url = NSURL(string: url)!
let request:NSMutableURLRequest = NSMutableURLRequest(url: url as URL, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringCacheData, timeoutInterval: 10)
let MPboundary:String = "--\(TWITTERFON_FORM_BOUNDARY)"
let endMPboundary:String = "\(MPboundary)--"
//convert UIImage to NSData
let data:NSData = UIImagePNGRepresentation(image)! as NSData
let body:NSMutableString = NSMutableString();
// with other params
if parameters != nil {
for (key, value) in parameters! {
body.appendFormat("\(MPboundary)\r\n" as NSString)
body.appendFormat("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n" as NSString)
body.appendFormat("\(value)\r\n" as NSString)
}
}
// set upload image, name is the key of image
body.appendFormat("%#\r\n",MPboundary)
body.appendFormat("Content-Disposition: form-data; file=\"\(filename)\"\"\r\n" as NSString)
body.appendFormat("Content-Type: image/png\r\n\r\n")
let end:String = "\r\n\(endMPboundary)"
let myRequestData:NSMutableData = NSMutableData();
myRequestData.append(body.data(using: String.Encoding.utf8.rawValue)!)
myRequestData.append(data as Data)
myRequestData.append(end.data(using: String.Encoding.utf8)!)
let content:String = "multipart/form-data; boundary=\(TWITTERFON_FORM_BOUNDARY)"
request.setValue(content, forHTTPHeaderField: "Content-Type")
request.setValue("\(myRequestData.length)", forHTTPHeaderField: "Content-Length")
request.httpBody = myRequestData as Data
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {
data, response, error in
if error != nil {
print(error!)
errord(error! as NSError)
return
}
print(response ?? "")
do {
let responseObject:[String:Any]? = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String:Any]
if let responseDictionary = responseObject as NSDictionary? {
success(responseDictionary)
} else {
}
} catch let error as NSError {
print(error.localizedDescription)
}
})
task.resume()
}
In viewDidload,
let image = UIImage(named: "ed1.png",
in: Bundle(for: type(of: self)),
compatibleWith: nil)
UPLOADING(url: "url-of-site", parameters: nil, filename: "ed1.png", image: image!, success: { (sucDict) in
print(sucDict)
}, failed: { (failedDict) in
print(failedDict)
}) { (error) in
print(error.description)
}

I see several issues, mostly minor:
Your Content-Disposition: line is missing the 'name=file;' bit. This is one major reason why it isn't working.
Your boundary is way too short (by at least twenty characters or so; the upper bound is 70 characters, and you should use a good percentage of them). A short boundary runs a real risk of appearing in the actual data you're trying to send.
You should really be using NSData for constructing the entire body, rather than using a mutable string up until the last part.
Ideally, as you do so, you should check whether the boundary appears in any of the values, and if so, generate a new boundary and start over, until you succeed.
The Content-Type field should really be present on for every part unless the value is guaranteed to be 7-bit ASCII, because the Content-Type provides the character encoding (UTF-8, in this case). Otherwise, weird things may happen.
You should probably have a trailing CRLF after the end boundary, just for readability when debugging, though that shouldn't cause a failure.
Finally, if you're uploading to a public service like Twitter, you may need to add some sort of API key, either in the URL or in a request header, and the API might issue an error if that key is missing.
But none of those reasonably explain a 404 error. That should only be issued if the path to the script itself is wrong. This might be caused by something subtle, such as iOS preferring to hit servers via their IPv6 address over IPv4, and the IPv6 side of your web server might be misconfigured. Or it might be the wrong URL. Either way, the only way to debug that is to dig into your server logs and see what file it was trying to read, then figure out why that file doesn't exist.

Related

Parameters are not passed on Http post request?

Hai I am trying to pass some parameters of string in Http post request. I have created a dictionary and then converted that dictionary to data and set as httpBody.But when I looked on our server nothing has been passd I mean parameters are empty.Why? What mistake i am doing?Please help me to find out.Thanks in advance.
func receiptValidation(productId:String,requestFrom:String)
{
let SUBSCRIPTION_SECRET = "mySecretKey"
let defaults = UserDefaults.standard
let receiptPath = Bundle.main.appStoreReceiptURL?.path
if FileManager.default.fileExists(atPath: receiptPath!){
var receiptData:NSData?
do {
receiptData = try NSData(contentsOf: Bundle.main.appStoreReceiptURL!, options: NSData.ReadingOptions.alwaysMapped)
}
catch{
print("ERROR: " + error.localizedDescription)
}
//let receiptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
let base64encodedReceipt = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithCarriageReturn)
print(base64encodedReceipt!)
let requestDictionary = ["receipt-data":base64encodedReceipt!,"password":SUBSCRIPTION_SECRET]
guard JSONSerialization.isValidJSONObject(requestDictionary) else { print("requestDictionary is not valid JSON"); return }
do {
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
let requestDataString=String(describing: requestData)
let URLForApplication:String = String(format:"%#/api/validate-receipt-data",opcodeDetails["apiProxyBaseUrl"]!) // this works but as noted above it's best to use your own trusted server
SwiftyBeaver.info("URLForApplication Path:\n\(URLForApplication)")
let url:URL! = URL.init(string: URLForApplication)
var request = URLRequest.init(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let configure = URLSessionConfiguration.background(withIdentifier: Bundle.main.bundleIdentifier!)
session1=URLSession(configuration: .default, delegate: applicationDelegate.application, delegateQueue: OperationQueue.main)
var postString =
["receiptData":requestDataString,
"deviceType":"IOS",
"subscriberId":encodeString(normalString: defaults.array(forKey: "userDetails")?.first as! String),
"password":encodeString(normalString: defaults.array(forKey: "userDetails")?.last as! String),
"productId":encodeString(normalString: productId ),
"code":opcodeDetails["opCode"]!]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: postString, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task = session1?.dataTask(with: request) { (data, response, error) in
if let data = data , error == nil {
do {
let appReceiptJSON = try JSONSerialization.jsonObject(with: data)
print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
// if you are using your server this will be a json representation of whatever your server provided
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
} else {
print("the upload task returned an error: \(error)")
}
}
task?.resume()
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
}
}
and what error i am getting is Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
You don’t say, but I’m assuming you’re getting this error where you print “json serialization failed with error”. If so, CZ54 is right, that your response is obviously not JSON. So, where you print that error, also print the header and body to see what the server actually returned, if anything:
print("response header:", response ?? "No response")
print("response body:", String(data: data, using: .utf8) ?? "No body")
The response header will include the status code (which should be something in the 200...299 range). If it’s not in that range, the status code will tell you the broad nature of the problem.
And regarding the response body, Sometimes (esp in development environments) if the server choked on something, it may return a HTML page outlining the nature of the problem (though, admittedly, in other cases, it only outputs the fact that there was an error, but not the details, and you’ll need to go into the server error logs to figure out what went wrong).
Looking at the specifics of the response, like above, is is your first step. Or you can achieve this by running the app on a simulator, and watching the request and the response in a tool like Charles or Wireshark. Once you get them up and running, these are great tools for inspecting requests and responses.
The next question is why the server generated the response that it did. As a general rule, while these sorts of problems can be a result of some server mistake, the more likely scenario is that the request wasn’t formed correctly and the server didn’t know how to handle it. Looking at the response (or looking at your server’s error logs) often provides good clues. But there’s no way anyone can help you on the basis of the information provided.

How to send an image via a POST request in Swift 4?

func createPost(username: String, user_id: String, description: String, image: UIImage, completion: #escaping (Bool) -> ()) {
var request = URLRequest(url: URL(string: Globals.root+"/amazing/image/post/here")!)
request.httpMethod = "POST"
//request.httpBody = .data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
if let data = data {
do {
let aaData = try JSONSerialization.jsonObject(with: data, options: [])
if let ppData = aaData as? [String:Any] {
let u = ppData["username"] as! Bool
if u == true {
completion(true)
}
}
} catch let error as NSError {
print(error.localizedDescription)
completion(false)
}
}
}
task.resume()
}
I've been searching and searching for a valid answer and I haven't found any that pertains to Swift 4. I want to be able to send an image to my RESTful API without encoding it. How can one do that? Thanks in advance. : )
First off, given the fact you're sending username, and stuff along with image, it must be a multipart form (or some ugly BASE64 encoded JSON property).
Typically speaking, clean way to do this is upload image first via binary body post (as the image is the sole thing in transfer and is faster too), obtain the Id and reference it as a field.
Failing this, it's quite hard, how about using Moya + AlamoFire?
https://github.com/Moya/Moya/issues/598
An image is a binary object, and this not valid HTTP unless you either encode it via multipart form or wrap it JSON safe Base-64 (assuming you are using JSON)

Returning data from URLSession and saving in property variable

I try to get some data from server using URLSession.shared.dataTask.
It works fine, but I can't save result like a class variable.
Many answers recommend to use completion Handler, but it doesn't help for my task.
Here is my testing code:
class PostForData {
func forData(completion: #escaping (String) -> ()) {
if let url = URL(string: "http://odnakrov.info/MyWebService/api/test.php") {
var request = URLRequest(url: url)
request.httpMethod = "POST"
let postString : String = "json={\"Ivan Bolgov\":\"050-062-0769\"}"
print(postString)
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
let json = String(data: data!, encoding: String.Encoding.utf8)!
completion(json)
}
task.resume()
}
}
}
class ViewController: UIViewController {
var str:String?
override func viewDidLoad() {
super.viewDidLoad()
let pfd = PostForData()
pfd.forData { jsonString in
print(jsonString)
DispatchQueue.main.async {
self.str = jsonString
}
}
print(str ?? "not init yet")
}
}
This closure is #escaping (i.e. it's asynchronously called later), so you have to put it inside the closure:
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var str: String?
override func viewDidLoad() {
super.viewDidLoad()
let pfd = PostForData()
pfd.performRequest { jsonString, error in
guard let jsonString = jsonString, error == nil else {
print(error ?? "Unknown error")
return
}
// use jsonString inside this closure ...
DispatchQueue.main.async {
self.str = jsonString
self.label.text = jsonString
}
}
// ... but not after it, because the above runs asynchronously (i.e. later)
}
}
Note, I changed your closure to return String? and Error? so that the the view controller can know whether an error occurred or not (and if it cares, it can see what sort of error happened).
Note, I renamed your forData to be performRequest. Generally you'd use even more meaningful names than that, but method names (in Swift 3 and later) should generally contain a verb that indicates what's being done.
class PostForData {
func performRequest(completion: #escaping (String?, Error?) -> Void) {
// don't try to build JSON manually; use `JSONSerialization` or `JSONEncoder` to build it
let dictionary = [
"name": "Ivan Bolgov",
"ss": "050-062-0769"
]
let jsonData = try! JSONEncoder().encode(dictionary)
// It's a bit weird to incorporate JSON in `x-www-form-urlencoded` request, but OK, I'll do that.
// But make sure to percent escape it.
let jsonString = String(data: jsonData, encoding: .utf8)!
.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
let body = "json=" + jsonString
let url = URL(string: "http://odnakrov.info/MyWebService/api/test.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = body.data(using: .utf8)
// It's not required, but it's good practice to set `Content-Type` (to specify what you're sending)
// and `Accept` (to specify what you're expecting) headers.
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
// now perform the prepared request
let task = URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data, error == nil else {
completion(nil, error)
return
}
let responseString = String(data: data, encoding: .utf8)
completion(responseString, nil)
}
task.resume()
}
}
There are also some modifications to that routine, specifically:
Don't ever use ! forced unwrapping when processing server responses. You have no control over whether the request succeeds or fails, and the forced unwrapping operator will crash your app. You should gracefully unwrap these optionals with guard let or if let patterns.
It's exceedingly unusual to use json=... pattern where the ... is the JSON string. One can infer from that you're preparing a application/x-www-form-urlencoded request, and using $_POST or $_REQUEST to get the value associated with the json key. Usually you'd either do true JSON request, or you'd do application/x-www-form-urlencoded request, but not both. But to do both in one request is doubling the amount of work in both the client and server code. The above code follows the pattern in your original code snippet, but I'd suggest using one or the other, but not both.
Personally, I wouldn't have performRequest return the JSON string. I'd suggest that it actually perform the parsing of the JSON. But, again, I left this as it was in your code snippet.
I notice that you used JSON in the form of "Ivan Bolgov": "050-062-0769". I would recommend not using "values" as the key of a JSON. The keys should be constants that are defined in advantage. So, for example, above I used "name": "Ivan Bolgov" and "ss": "050-062-0769", where the server knows to look for keys called name and ss. Do whatever you want here, but your original JSON request seems to conflate keys (which are generally known in advance) and values (what values are associated with those keys).
If you're going to do x-www-form-urlencoded request, you must percent encode the value supplied, like I have above. Notably, characters like the space characters, are not allowed in these sorts of requests, so you have to percent encode them. Needless to say, if you did a proper JSON request, none of this silliness would be required.
But note that, when percent encoding, don't be tempted to use the default .urlQueryAllowed character set as it will allow certain characters to pass unescaped. So I define a .urlQueryValueAllowed, which removes certain characters from the .urlQueryAllowed character set (adapted from a pattern employed in Alamofire):
extension CharacterSet {
/// Returns the character set for characters allowed in the individual parameters within a query URL component.
///
/// The query component of a URL is the component immediately following a question mark (?).
/// For example, in the URL `http://www.example.com/index.php?key1=value1#jumpLink`, the query
/// component is `key1=value1`. The individual parameters of that query would be the key `key1`
/// and its associated value `value1`.
///
/// According to RFC 3986, the set of unreserved characters includes
///
/// `ALPHA / DIGIT / "-" / "." / "_" / "~"`
///
/// In section 3.4 of the RFC, it further recommends adding `/` and `?` to the list of unescaped characters
/// for the sake of compatibility with some erroneous implementations, so this routine also allows those
/// to pass unescaped.
static var 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
}()
}
I would suggest changing your PHP to accept a JSON request, e.g.:
<?php
// read the raw post data
$handle = fopen("php://input", "rb");
$raw_post_data = '';
while (!feof($handle)) {
$raw_post_data .= fread($handle, 8192);
}
fclose($handle);
// decode the JSON into an associative array
$request = json_decode($raw_post_data, true);
// you can now access the associative array how ever you want
if ($request['foo'] == 'bar') {
$response['success'] = true;
$response['value'] = 'baz';
} else {
$response['success'] = false;
}
// I don't know what else you might want to do with `$request`, so I'll just throw
// the whole request as a value in my response with the key of `request`:
$raw_response = json_encode($response);
// specify headers
header("Content-Type: application/json");
header("Content-Length: " . strlen($raw_response));
// output response
echo $raw_response;
?>
Then you can simplify the building of the request, eliminating the need for all of that percent-encoding that we have to do with x-www-form-urlencoded requests:
class PostForData {
func performRequest(completion: #escaping (String?, Error?) -> Void) {
// Build the json body
let dictionary = [
"name": "Ivan Bolgov",
"ss": "050-062-0769"
]
let data = try! JSONEncoder().encode(dictionary)
// build the request
let url = URL(string: "http://odnakrov.info/MyWebService/api/test.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = data
// It's not required, but it's good practice to set `Content-Type` (to specify what you're sending)
// and `Accept` (to specify what you're expecting) headers.
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
// now perform the prepared request
let task = URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data, error == nil else {
completion(nil, error)
return
}
let responseString = String(data: data, encoding: .utf8)
completion(responseString, nil)
}
task.resume()
}
}

Swift POST request sends an empty body

Here's the code:
func makePOSTCall(endpoint: String, languageName: String) {
guard let url = URL(string: endpoint) else {
print("Could not create URL.")
return
}
let requestLang: [String: Any] = ["name": languageName]
let requestBody = try? JSONSerialization.data(withJSONObject: requestLang)
var urlRequest = URLRequest(url: url)
urlRequest.httpBody = requestBody
urlRequest.httpMethod = "POST"
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) {
data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
print(responseJSON)
}
}
task.resume()
}
This sends a {"name": "Go"} JSON dictionary to Flask. Flask is supposed to append the language name to an array and return the full array in the response. Now, this works when I send the request manually, so it's not Flask's error. But when I send the above from iOS, I get request.json == None in the flask console. Clearly, I'm sending an empty body, but I shouldn't be. Any idea where I went wrong?
I call the function as
#IBAction func pressedMakePOSTCall(_ sender: UIButton) {
makePOSTCall(endpoint: "http://127.0.0.1:5000/lang", languageName: "Go")
}
I tried adding a trailing slash, just get a 404 in the console. The only question similar to mine that I've found is this: How to make HTTP Post request with JSON body in Swift and my code is basically identical.
#weissja19 was correct, I needed to set content type to application/json. Adding
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
fixed the error. Now the code works as I expected.
P.S. I couldn't catch it because I use the app Paw for testing, which sets content type automatically.
You might want to do it manually:
urlRequest.httpBody = "name=\(languageName)".data(using: .utf8)
Use JSONSerialization will make your POST body like {"name":"abc"} which might not be supported by your server

How to get userID from JSON response while JSON text did not start with array or object and option to allow fragments not set

I got json response from server like this:
"{\"userID\":\"dkjagfhaghdalgalg\"}"
I try to get that userID with this:
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
(data, response, error) -> Void in
if let unwrappedData = data {
do {
let userIDDictionary:NSDictionary = try NSJSONSerialization.JSONObjectWithData(unwrappedData, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
print("userIDDictionary:\(userIDDictionary)")
//let userID:String = userIDDictionary["userID"] as! String
//print("userID:\(userID)")
print("data:\(data)")
print("response:\(response)")
print("error:\(error)")
} catch {
print("Failed to get userID: \(error)")
}
}
}
but the response is
Failed to get userID: Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set. UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}".
How to get userID with json response like that?
update: I try to get with anyobject but still did not get that json string to change to dictionary.
let bodyStr = "test={ \"email\" : \"\(username)\", \"password\" : \"\(password)\" }"
let myURL = NSURL(string: Constant.getSignInEmail())!
let request = NSMutableURLRequest(URL: myURL)
request.HTTPMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.HTTPBody = bodyStr.dataUsingEncoding(NSUTF8StringEncoding)!
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
(data, response, error) -> Void in
if let unwrappedData = data {
do {
let json:AnyObject! = try NSJSONSerialization.JSONObjectWithData(unwrappedData, options: NSJSONReadingOptions.MutableContainers) as! AnyObject
print("json:\(json)")
//let userID:String = userIDDictionary["userID"] as! String
//print("userID:\(userID)")
} catch {
print("Failed to get userID: \(error)")
}
}
}
Try with try with NSJSONReadingOptions.AllowFragments in json reading options
I think this is a case of confusion between the data that you are receiving and the way they are displayed. Either on your side, or on the server side. Try to tell us exactly what the server is sending, byte for byte.
What you have got there is a string containing JSON. Not JSON, but a string containing JSON. Which is not the same. Just like a bottle of beer is not made of beer, but of glass.
If this is indeed what you are receiving, then you should first kick the guys writing the server code. If that doesn't help, then read what the "fragment" option does; this will give you a string, then you extract the bytes, and throw the bytes into a JSON parser.
Two way you can resolve.
Check your Webservice format and correct it as {"key":"value","key":"value"}
or else you have to Convert NSData to NSString.
Using String(data:, encoding:.utf8)
then format the string file with reduction '\'
then again convert it to NSData type then Call JSONSerialization.
Actually this is NSASCIIStringEncoding.
For help, I created a program.
Please just copy/paste and run it. You will find your answer.
import Foundation
let string = "{\"userID\":\"dkjagfhaghdalgalg\"}"
let unwrappedData: NSData = string.dataUsingEncoding(NSASCIIStringEncoding)!
do {
let userIDDictionary:NSDictionary = try NSJSONSerialization.JSONObjectWithData(unwrappedData, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
let userid = userIDDictionary.valueForKey("userID")
print("userid:\(userid!)")
} catch {
print("Failed to get userID: \(error)")
}

Resources