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

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)

Related

NSURLSession POST Request API

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.

Swift 3 - issue to create easy request method POST (URLRequestVonvertible)

I am developing an application with Swift 3.0 and IOS 10 in Xcode 8.3.2. But I have a problem when I try to retrieve the JSON from this APIRest (http://schematic-ipsum.herokuapp.com/). What is the problem? Or how you would make the call. If you need more information regarding the code, tell me, but basically, I just want to make a call to that page to recover the biography.
enter image description here
My code is this:
import AlamofireDomain
import Alamofire
import ObjectMapper
class AuthorDAO : SimpleDAO {
func getBiography(_ parameters: Dictionary<String, Int>,
callbackFuncionOK: #escaping
(PropertiesAuthorModel)->(),
callbackFunctionERROR: #escaping (Int,NSError)->()) {
let ulr = NSURL( string: "http://schematic-ipsum.herokuapp.com/" as String)
let request = NSMutableURLRequest(url: ulr! as URL)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Constent-Type")
let data = try! JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions.prettyPrinted)
let json = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
if let json = json {
print(json)
}
request.httpBody = json!.data(using: String.Encoding.utf8.rawValue)
Alamofire.request(request as URLRequest)
.responseJSON { response in
if response.result.isSuccess{
if let status = response.response?.statusCode {
switch(status){
//MARK: CONTROL ON DIFERENTS KINDS OF RESPONSES ON SERVER
case 200:
if let value = response.result.value {
let biographyResponse = Mapper<PropertiesAuthorModel>().map(JSONObject: value)
callbackFuncionOK(biographyResponse!)
}
//case 400: {} ..., case 404: {} ...
default:
break
}
}
}
//MARK: ERROR ON SERVER
else {
var statusCode = -1
if let _response = response.response {
statusCode = _response.statusCode
}
var nsError: NSError = NSError(domain: Constants.UNKNOWN_HTTP_ERROR_MSG,
code: Constants.UNKNOWN_HTTP_ERROR_ID,
userInfo: nil)
if let _error = response.result.error {
nsError = _error as NSError
}
callbackFunctionERROR(statusCode,nsError)
}
}
}
And the error is: "Error Domain=Alamofire.AFError Code=4 "JSON could not be serialized because of error: The data couldn’t be read because it isn’t in the correct format." 400"
The problem is that it does not enter into the "if response.result.isSuccess {" because the result of the response is "nil" and goes directly to the "else". Showing the error that I have commented. Can it be because I have to send the JSON format in the httpBody request? How can I do it?
Your question should be more clear. "What is the problem?" isn't possible to answer without more information.
To issue HTTP request, take a look at Alamofire, an HTTP networking library written in Swift: https://github.com/Alamofire/Alamofire
Alamofire.request("https://httpbin.org/get").responseJSON { response in
print(response.request) // original URL request
print(response.response) // HTTP URL response
print(response.data) // server data
print(response.result) // result of response serialization
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}

Swift 3 reusable post method in helper

I am working on swift 3 application and want to build login system using REST API. First I wanted a way to post data to server (PHP + MYSQL) with parameters so I found this post.
HTTP Request in Swift with POST method
Now I wanted place this code in a method as helper so I can utilise this method from anywhere in app. Hence followed this way:
Where to put reusable functions in IOS Swift?
Current code is as follow:
import Foundation
class Helper {
static func postData(resource: String, params: [String: String]) -> [String:String] {
var request = URLRequest(url: URL(string: "http://localsite.dev/api/\(resource)")!)
request.httpMethod = "POST"
var qryString: String = "?key=abcd"
for (paramKey, paramVal) in params {
qryString = qryString.appending("&\(paramKey)=\(paramVal)")
}
request.httpBody = qryString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("Error")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("Error on HTTP")
return
}
let responseString = String(data: data, encoding: .utf8)
print("success and here is returned data \(responseString)")
}
task.resume()
return ["data" : "some data"]
}
}
Call this using
let loginDetails: [String: String] = ["email": emailTxt.text!, "pass": passTxt.text!]
Helper.postData(resource: "login", params: loginDetails)
In above method rather then printing data I want to return data as per below 4 conditions.
1.If error in request data then I want to return as
[“status”: false, “message”: “Something wrong with request”]
2.If error in HTTP request
[“status”: false, “message”: “Resource not found”]
3.If login fail
[“status”: false, “message”: “Wrong login details”]
4.If login success
[“status”: true, “message”: “Login success”]
If you want to use a third party library for handling HTTP request, I strongly recommend Alamofire.
When I wanna handle HTTP requests I usually create a singleton class:
class HttpRequestHelper {
static let shared = HttpRequestHelper()
func post(to url: URL, params: [String: String], headers: [String: String], completion: (Bool, String) -> ()){
//Make the http request
//if u got a successful response
// parse it to JSON and return it via completion handle
completion(true, message)
//if response is not successful
completion(false, message)
}
}
And you can use it everywhere:
class AnotherClass: UIViewController {
HttpRequestHelper.shared.post(to: url, params: [:], header: [:],
completion:{
success, message in
print(success)
print(message)
})
}
To the POST method more reusable and not just specific to an endpoint, I usually make the completion handler params as Bool, JSON. And then I handle the JSON response from wherever I call the method.
Oh and I use SwiftyJson to parse json, it's the simplest.

Data in HTTPBody with a PUT method fails, while it works with a POST?

first of all i would like to say i got the exact same problem as the following question: How to add data to HTTPBody with PUT method in NSURLSession?. But it wasn't answered so i made my own question.
We have written a node API for a school assignment. We've tested the whole API. (The chances of being something wrong there are slim.)
After that i went working on a iOS client to CRUD users.
Making a user is going perfectly, but whenever i try to edit a user something strange happens. The data on the server arrives as undefined.
I use the following code to save a user:
func saveUser(user: User, completionHandler: (String?, User?) -> Void) {
let url = NSURL(string: "https://pokeapi9001.herokuapp.com/api/users/")
let request = NSMutableURLRequest(URL:url!)
request.HTTPMethod = "POST"
let postString = "email=\(user.email)&password=\(user.password!)&role=\(user.role)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error: \(error)")
}
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary else {
throw JSONError.ConversionFailed
}
//do specific things
} catch let error as JSONError {
completionHandler(error.rawValue, nil)
} catch let error as NSError {
completionHandler(error.debugDescription, nil)
}
}
task.resume()
}
keep in mind, this is working perfectly (don't know if it is intended to be used like this)
To edit a user i use the following code:
func editUser(user: User, completionHandler: (String?, User?) -> Void) {
let url = NSURL(string: "https://pokeapi9001.herokuapp.com/api/users/\(user.id!)")
let request = NSMutableURLRequest(URL:url!)
request.HTTPMethod = "PUT"
let postString = "email=\(user.email)&password=\(user.password!)&role=\(user.role)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error: \(error)")
}
do {
guard let data = data else {
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary else {
throw JSONError.ConversionFailed
}
//do specific things
} catch let error as JSONError {
completionHandler(error.rawValue, nil)
} catch let error as NSError {
completionHandler(error.debugDescription, nil)
}
}
task.resume()
}
(The original code is a bit longer but i removed parts that had nothing to do with the actual posting of the data)
I have really no idea what i'm doing wrong, could be something small and stupid. Please help.
edit after input from #fiks
To be clear, the problem i am having is that I fill the "postString" the same way in the editUser method as I do in the saveUser method.(At least I think I do)
However in the saveUser method the postString seems to be correctly passed through to the API (it creates a new user with the given values).
The editUser method does not pass the values through.
If I put a console log on the server it shows all values are "undefined".
To test if the postString was correct on the iOS part I printed both strings out. Both of them outputted email=user#test.com&password=test&role=admin
From what I see in the postman request, you are sending a x-www-form-urlencoded request.
You have to specify it in the code. See example: POST request using application/x-www-form-urlencoded
Regarding Charles: since you are using https, you have to enable proxy for the host. More info here: https://www.charlesproxy.com/documentation/proxying/ssl-proxying/

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