HTTP status 415 when using Alamofire multipart upload - swift

I'm trying to get an image uploaded to a RESTful web api and I'm getting a HTTP status 415 (unsupported media type). The strange thing is I did have it working the other day and i'm not to sure what i've changed to make it stop accepting the POST request.
My swift code:
func uploadImage(image: UIImage, userId: String, completion: () -> Void) {
parameters["userId"] = userId
let imageData = UIImageJPEGRepresentation(image, 70)
let urlRequest = urlRequestWithComponents(myUrl, parameters: parameters, imageData: imageData!)
Alamofire.upload(urlRequest.0, data: urlRequest.1)
.responseJSON { (response) in
print(response)
if let result = response.result.value as? Dictionary<String, String>{
print("have a result from uploading!")
print(result)
if let result = result["success"] {
if (result == "true") {
completion()
}
}
}
}
}
func urlRequestWithComponents(urlString:String, parameters:Dictionary<String, String>, imageData:NSData) -> (URLRequestConvertible, NSData) {
// create url request to send
let mutableURLRequest = NSMutableURLRequest(URL: NSURL(string: urlString)!)
mutableURLRequest.HTTPMethod = Alamofire.Method.POST.rawValue
let boundaryConstant = "myRandomBoundary12345";
let contentType = "multipart/form-data"
mutableURLRequest.setValue(contentType, forHTTPHeaderField: "Content-Type")
// create upload data to send
let uploadData = NSMutableData()
// add image
uploadData.appendData("\r\n--\(boundaryConstant)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
uploadData.appendData("Content-Disposition: form-data; name=\"file\"; filename=\"file.png\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
uploadData.appendData("Content-Type: image/png\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
uploadData.appendData(imageData)
// add parameters
for (key, value) in parameters {
uploadData.appendData("\r\n--\(boundaryConstant)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
uploadData.appendData("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n\(value)".dataUsingEncoding(NSUTF8StringEncoding)!)
}
uploadData.appendData("\r\n--\(boundaryConstant)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
// return URLRequestConvertible and NSData
return (Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: nil).0, uploadData)
}
And here is what my Jersey Rest service accepts for that url:
#POST
#Path("/uploadImage")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.MULTIPART_FORM_DATA)
public String uploadImage(FormDataMultiPart form) throws IOException
{
//do stuff
}
Its driving me crazy trying to figure out what is going wrong, any help is muchly appreciated!

Related

Swift https POST request for the PlantNet API

Currently I'm trying to program an https post request for the Plant Net API (https://my.plantnet.org/account/doc#) in Swift. I want to have one to five images inside my https post request and each image has as parameter organ (in my case this is always "leaf"). When I try to run my application, I get the error code 400 "Invalid multipart payload format". I'm not sure how I can solve this problem.
//
// PlantRecognition.swift
// Happy Plant
//
// Insert several images of the same plant (maximum of 5) and get the result list
//
import Foundation
import UIKit
class PlantRecognition {
//List with images of the plant to be recognized
var imageList: [UIImage] = []
//Api key for the plant net access
var API_KEY: String = "MY_API_KEY"
var plantNetURL: String = ""
init() {
//Set plant net url after class initialization
plantNetURL = "https://my-api.plantnet.org/v2/identify/all?api-key{\(API_KEY)}"
}
func recognizeImages(images: [UIImage]) {
//Try to create the api endpoint url
guard let apiEndpoint = URL(string: "https://my-api.plantnet.org/v2/identify/all?api-key=\(API_KEY)") else {print("Error creating api endpoint"); return }
//Convert the UIImages to jpeg
let imagesJPG = convertImagesToJPEG(images: images)
//Create url request
var request = URLRequest(url: apiEndpoint)
//Set the http method to post (local images)
request.httpMethod = "POST"
//Create boundary which marks the start/end of the request body
let boundary = "Boundary-\(UUID().uuidString)"
//Set value for our specific http header field
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
//Set the mime type to jpg (we will convert our UIImages to jpg)
let mimeType = "image/jpg"
//Create http body
var body = Data()
let boundaryPrefix = " — \(boundary)\r\n"
//Add the parameter to the body (Currently just leaf recognition is possible)
body.append("--\(boundaryPrefix)\r\n")
body.append("Content-Disposition: form-data; name=\"organs\r\n\r\n")
body.append("leaf\r\n")
//Add the images to the body
var filename = String()
for (index,data) in imagesJPG.enumerated(){
filename = "image\(index)"
body.append(boundaryPrefix)
body.append("Content-Disposition: form-data; name=\"image[]\"; filename=\"\(filename)\"\r\n")
body.append("Content-Type: \(mimeType)\r\n\r\n")
body.append(data)
body.append("\r\n")
}
body.append("--\(boundary)--\r\n")
//Post the request
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}
}.resume()
}
func convertImagesToJPEG(images: [UIImage]) -> [Data] {
var dataArray = [Data]()
for i in images {
if let data = i.jpegData(compressionQuality: 1.0) {
dataArray.append(data)
}
}
return dataArray
}
}
extension Data {
mutating func append(_ string: String) {
if let data = string.data(using: .utf8) {
append(data)
}
}
}

Cannot upload zip file with multipart and URLSession Swift

I am experiencing a really strange issue with uploading zip file to s3... Here is my sample code and all details:
private func uploadZipFile(_ photos:URL,_ step:String,_ presignedUrlModel: PresignedUrlModel) {
var uploadingBackgroundTask = UIBackgroundTaskIdentifier(rawValue: 3)
uploadingBackgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(uploadingBackgroundTask)
uploadingBackgroundTask = .invalid
})
let success: () -> Void = {
DispatchQueue.main.async {
UIApplication.shared.endBackgroundTask(uploadingBackgroundTask)
uploadingBackgroundTask = .invalid
}
}
let failure: (ErrorResponse) -> Void = { error in
DispatchQueue.main.async {
UIApplication.shared.endBackgroundTask(uploadingBackgroundTask)
uploadingBackgroundTask = .invalid
}
}
let boundary = "Boundary-\(UUID().uuidString)"
let uploadingRequest = URL(string: presignedUrlModel.url)!
var request = APIhelper().getMultipartRequestBody(_type: "POST", _url: uploadingRequest, boundary: boundary)
let body = self.formBackgroundPhotoData(presignedUrlModel, step, photos, boundary)
request.httpBody = body
self.session.configuration.shouldUseExtendedBackgroundIdleMode = true
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
guard let data = data else {
return
}
if let httpResponse = response as? HTTPURLResponse {
if(httpResponse.statusCode==200)
{
success()
}
else if(httpResponse.statusCode>=400 && httpResponse.statusCode<500)
{
//getting 403 errorr
let error = ErrorResponse(message:
self.errorFormatingClass.getLocalizedErrorMessage(identifier: self.errorFormatingClass.ERROR)
,
identifier: self.errorFormatingClass.ERROR)
failure(error)
}
}
}
)
task.resume()
}
I always getting 403 response:AccessDeniedInvalid according to Policy: Policy Condition failed: ["eq", "$Content-Type", "application/zip"]
​
Here is how I form data:
private func formBackgroundPhotoData(_ presignedUrlModel: PresignedUrlModel,_ step: String,_ zip: URL,_ boundary: String) -> Data {
var body = Data()
var key = "key"
var value = presignedUrlModel.key
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
key = "AWSAccessKeyId"
value = presignedUrlModel.AWSAccessKeyId
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
key = "signature"
value = presignedUrlModel.signature
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
key = "policy"
value = presignedUrlModel.policy
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
let filename = zip.lastPathComponent
guard let data = try? Data(contentsOf: zip) else {
#if DEBUG
debugPrint("backgroundupload", "dataisnull")
#endif
return Data()
}
let mimetype = mimeTypeForPath(url: zip)
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n")
body.append("Content-Type: \(mimetype)\r\n\r\n")
body.append(data)
body.append("\r\n")
body.append("--\(boundary)--\r\n")
return body
}
Here is multipart request:
public func getMultipartRequestBody(_type: String, _url: URL, boundary: String) ->URLRequest
{
var request = URLRequest(url: _url)
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.timeoutInterval = 300
request.httpMethod = _type
return request
}
Now, I know this tons of code, and perhaps nobody will take a look... All I am asking: please provide an example for uploading zip or images to AWS S3, without adding S3 framework for uploading files..
Our whole team does not know what to do :(

How to convert this to a POST call with a JSON serialized Object

I have tried Alamofire, I have tried it with all my heart. It just does not work for me. I finally got http GET working, but I need to get http POST working. Our POST API's take a Request object that has all the necessary data in it. I have requested the backend developers to provide me with a KEY-VALUE pair JSON (no embedded objects/arrays) so that I can use a Dictionary in my swift code convert that to json and send the request. All I need is now to convert the below code to POST.
My earlier questions that did not help much.
NSInvalidArgumentException Invalid type in JSON write DemographicsPojo
Swift 3.0, Alamofire 4.0 Extra argument 'method' in call
I have given up on Alamofire. I want to use Foundation classes. Simple basic and fundamental way of life :).
func callWebService(url : String) {
// Show MBProgressHUD Here
var config :URLSessionConfiguration!
var urlSession :URLSession!
config = URLSessionConfiguration.default
urlSession = URLSession(configuration: config)
// MARK:- HeaderField
let HTTPHeaderField_ContentType = "Content-Type"
// MARK:- ContentType
let ContentType_ApplicationJson = "application/json"
//MARK: HTTPMethod
let HTTPMethod_Get = "GET"
let callURL = URL.init(string: url)
var request = URLRequest.init(url: callURL!)
request.timeoutInterval = 60.0 // TimeoutInterval in Second
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
request.addValue(ContentType_ApplicationJson, forHTTPHeaderField: HTTPHeaderField_ContentType)
request.httpMethod = HTTPMethod_Get
let dataTask = urlSession.dataTask(with: request) { (data,response,error) in
if error != nil{
print("Error **")
return
}
do {
let resultJson = try JSONSerialization.jsonObject(with: data!, options: []) as? [String:AnyObject]
print("Result",resultJson!)
} catch {
print("Error -> \(error)")
}
}
dataTask.resume()
print("..In Background..")
}
Just pass JSON string and the API URL to this function. Complete code for POST.
func POST(api:String,jsonString:String,completionHandler:#escaping (_ success:Bool,_ response:String?,_ httpResponseStatusCode:Int?) -> Void) {
let url = URL(string: api)
var request: URLRequest = URLRequest(url: url!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField:"Content-Type")
request.timeoutInterval = 60.0
//additional headers
if let token = Helper.readAccessToken() {
request.setValue("\(token)", forHTTPHeaderField: "Authorization")
}
let jsonData = jsonString.data(using: String.Encoding.utf8, allowLossyConversion: true)
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) {
(data: Data?, response: URLResponse?, error: Error?) -> Void in
var responseCode = 0
if let httpResponse = response as? HTTPURLResponse {
responseCode = httpResponse.statusCode
print("responseCode \(httpResponse.statusCode)")
}
if error != nil {
completionHandler(false, error?.localizedDescription,nil)
} else {
let responseString = String(data: data!, encoding: .utf8)
completionHandler(true, responseString, responseCode)
}
}
task.resume()
}

set body in NSMutableURLRequest doesn´t work

Header:
let header = ["Content-Type" : "application/x-www-form-urlencoded", "Authorization" : "Basic " + self.basicAuth];
Body:
var body : [String : AnyObject] = [:];
let body = ["grant_type" : "client_credentials", "scope" : "MessageSender"];
The Request and Serialization:
private func makeHTTPPostRequest(path: String, header: [String : String], body: [String: AnyObject], onCompletion: #escaping ServiceResponse) {
let request = NSMutableURLRequest(url: NSURL(string: path)! as URL)
// Set the method to POST
request.httpMethod = "POST"
do {
// Set the POST body for the request
let jsonBody = try JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
request.httpBody = jsonBody
let session = URLSession.shared
request.allHTTPHeaderFields = header;
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
if let httpResponse = response as? HTTPURLResponse {
if let jsonData = data {
let json:JSON = JSON(data: jsonData)
print(response)
print(json)
onCompletion(json,httpResponse, error as NSError?)
} else {
onCompletion(JSON.null,HTTPURLResponse.init(), error as NSError?)
}
}
})
task.resume()
} catch {
onCompletion(JSON.null,HTTPURLResponse.init(), nil)
}
}
}
When the request is done, it fires a 400 response with
{
"error_description" : "grant_type parameter is requiered field and it has to be non empty string.",
"error" : "invalid_request"
}
Obviously the body is not set correctly but I really don´t know why. I´m using this piece of code in other applications with no problem... .
The same request works like charm in Postman. The body in postman is set with type x-www-form-urlencoded.
Maybe the JSONSerialization is wrong ?
To send a POST request with Content-Type: application/x-www-form-urlencoded;, you need to create a URL query-like String and then convert it to a Data. Your code or any Swift Standard Library functions do not have the functionality. You may need to write it by yourself, or find a suitable third-party library. (Of course JSONSerialization is not suitable here, the String is not a JSON.)
With given a Dictionary<String, String>, you can do it like this:
var body: [String: String] = [:]
body = ["grant_type": "client_credentials", "scope": "MessageSender"]
(Simplified...)
request.httpBody = body.map{"\($0)=\($1)"}.joined(separator: "&").data(using: .utf8)
//`body.map{"\($0)=\($1)"}.joined(separator: "&")` -> grant_type=client_credentials&scope=MessageSender
(Strict... 4.10.22.6 URL-encoded form data)
extension CharacterSet {
static let wwwFormUrlencodedAllowed = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._*" + "+")
}
extension String {
var wwwFormUrlencoded: String {
return self
.replacingOccurrences(of: " ", with: "+")
.addingPercentEncoding(withAllowedCharacters: .wwwFormUrlencodedAllowed)!
}
}
class HTTPBody {
static func wwwFormUrlencodedData(withDictionary dict: [String: String]) -> Data {
return body
.map{"\($0.wwwFormUrlencoded)=\($1.wwwFormUrlencoded)"}
.joined(separator: "&").data(using: .utf8)!
}
}
request.httpBody = HTTPBody.wwwFormUrlencodedData(withDictionary: body)
(Remember, not many servers interpret the received form data as strictly generated.)
One more, this is not a critical issue in this case, but you should better use Swift classes rather than NS-something:
typealias ServiceResponse = (JSON, HTTPURLResponse?, Error?)->Void
private func makeHTTPPostRequest(path: String, header: [String : String], body: [String: String], onCompletion: #escaping ServiceResponse) {
var request = URLRequest(url: URL(string: path)!)
// Set the method to POST
request.httpMethod = "POST"
// Set the POST body for the request (assuming your server likes strict form data)
request.httpBody = HTTPBody.wwwFormUrlencodedData(withDictionary: body)
let session = URLSession.shared
request.allHTTPHeaderFields = header;
let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in
if let httpResponse = response as? HTTPURLResponse {
if let jsonData = data {
let json:JSON = JSON(data: jsonData)
print(response)
print(json)
onCompletion(json, httpResponse, error)
} else {
onCompletion(JSON.null, httpResponse, error)
}
}
})
task.resume()
}

HTTP POST request in Swift

How do I post the request on iOS? Actually when I logged into Facebook it fetches the user informations like username, from where there are login (latitude, longitude). Is it possible to use api
Link: http://buddysin.aumkiiyo.com/fbc
My code is:
#IBAction func btnAPI(sender: UIButton)
{
//startConnection()
connectToWebAPI()
}
func connectToWebAPI()
{
//setting up the base64-encoded credentials
let id = "1620912344817986"
//let password = "pass"
let loginString = NSString(format: "%#:%#", id)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedStringWithOptions(nil)
//creating the requestz
let url = NSURL(string: "http://buddysin.aumkiiyo.com/fbc")
var request = NSMutableURLRequest(URL: url!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession.sharedSession()
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let urlConnection = NSURLConnection(request: request, delegate: self)
request.HTTPMethod = "POST"
request.setValue(base64LoginString, forHTTPHeaderField: "Authorization")
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
if (error != nil) {
println(error)
}
else {
// converting the data into Dictionary
var error: NSError?
let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
println(jsonResult)
}
})
//fire off the request
task.resume()
}
while I run, the fatal error where displayed as
`fatal error: unexpectedly found nil while unwrapping an Optional value`
in the "jsonResult"
i think it is better to use Alomafire. As AFNetWorking in Objective-C it is a library which simplified a lot http request.
Visit this question to check for my post-request function (if
you don't want to use Alamofire for any reasons)
Visit this question to check for steps you need to do if you
want to add Alamofire to your XCode-project
If you need to get json-data from your server, use
SwiftyJSON. It's as simple as dragging SwiftyJSON.swift into
your project with checking "Copy items if needed" and using like
let jsonData = JSON(data: yourDataFromServer)
Also you can view this question to check out for steps to encode
json-post data to send it to server.
Hope I helped :)
You should find which varible due to this error:
for example data,
if let dataTemp = data as? NSDictionary {
}
FYI:
Here is a way of 'POST' method of AFNetworking in swift, below code should be in your connectToWebAPI method, wrap your url ready into NSURL.
let manager = AFHTTPRequestOperationManager(baseURL: NSURL(string: yourURL))
manager.POST("path", parameters: ["key":value], success: { (opeartion:AFHTTPRequestOperation!, data:AnyObject!) -> Void in
},failure: { (operation:AFHTTPRequestOperation!, error:NSError!) -> Void in
})
Tutorial to install AFNetworking.
https://github.com/AFNetworking/AFNetworking/wiki/Getting-Started-with-AFNetworking
It is quite easy to do with Alamofire
func postSomething(completionHandler: #escaping CompletionHandler){
let loginString = NSString(format: "%#:%#", id)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedStringWithOptions(nil)
let headers: HTTPHeaders = [
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": "base64LoginString",
]
let parameters: Parameters = [
"parameter": value,
"parameter2": value2
]
Alamofire.request("http://buddysin.aumkiiyo.com/fbc", method: .post, parameters: parameters, encoding: URLEncoding.default, headers: SFAppServicesManager.sharedInstance.genericHeader()).responseJSON { response in
if let JSON = response.result.value {
let userDictionary = JSON as! NSDictionary
completionHandler(true)
} else {
completionHandler(false)
}
}
}