Swift - Sending binary file to a server (convert to string request) - ios

Here is my function for building a request to send to a server. This code works perfectly fine when it's a small, plain-text file. However, when I pass it a binary file, it crashes on the line indicated below (when it tries to convert that to a utf string and blows up b/c the conversion does not work, as it creates a nil value):
func buildFilePushRequest(fromUrl url: URL, httpBody: Data? = nil, fileName: String) -> NSMutableURLRequest? {
let cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
let request = NSMutableURLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: 2.0)
request.httpMethod = "POST"
// Set Content-Type in HTTP header.
let boundaryConstant = "Boundary-7MA4YWxkTLLu0UIW"; // This should be auto-generated.
let contentType = "multipart/form-data; boundary=" + boundaryConstant
let fileName = fileName
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
request.setValue("keep-alive", forHTTPHeaderField: "Connection")
var dataString = "--\(boundaryConstant)\r\n"
dataString += "Content-Disposition: form-data; name=file; filename=\"\(fileName)\"\r\n"
dataString += "Content-Type: octet-stream\r\n\r\n"
dataString += String(data: httpBody!, encoding: .utf8)! <-- CRASHES HERE
dataString += "\r\n"
dataString += "--\(boundaryConstant)--\r\n"
print("dataString: \(dataString)")
let requestBodyData = dataString.data(using: .utf8)
request.httpBody = requestBodyData
return request
}
I've read a bit that base64 is a better way to go rather than utf8 for binary type data, but I'm not really sure how best to modify the code above to use that instead, or if there is a better solution? Thanks in advance!

If the httpBody parameter can contain binary data you could compound the entire http body as Data
First of all please use native URLRequest, it's mutable as variable and why is the return value optional?
func buildFilePushRequest(fromUrl url: URL, httpBody: Data? = nil, fileName: String) -> URLRequest {
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 2.0)
...
var data = Data("--\(boundaryConstant)\r\n".utf8)
data += Data("Content-Disposition: form-data; name=file; filename=\"\(fileName)\"\r\n".utf8)
data += Data("Content-Type: octet-stream\r\n\r\n".utf8)
if let body = httpBody { data += body }
data += Data("\r\n".utf8)
data += Data("--\(boundaryConstant)--\r\n".utf8)
request.httpBody = data

Related

How i can Load POST URLRequest with parameter in WKWebView?

Sorry For this my English is weak
I try many types of a solution but not working in Xcode 11.2.1 and swift 5
I try this
var urlRequest = URLRequest(url: URL(string: "https://xxxxxx/login")!)
urlRequest.httpMethod = "POST"
let params = [
"username": SessionManager.shared.username!,
"password": SessionManager.shared.password!,
"vhost": "standard"
]
let postString = self.getPostString(params: params)
urlRequest.httpBody = postString.data(using: .utf8)
webView.load(urlRequest)
...
//helper method to build url form request
func getPostString(params:[String:String]) -> String
{
var data = [String]()
for(key, value) in params
{
data.append(key + "=\(value)")
}
return data.map { String($0) }.joined(separator: "&")
}
and this
Post Request with Parameter
And also try to add below lines in my code
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
But not Working
I fire the Request because not working the WKWebView screen is Open but not Load request.
If I not set navigationDelegate and open normal URL then it is working completely
If I set navigationDelegate then blank page come in all Request Like Normal URL fire or post parameter URL fire, All are coming to Blank Page in
I can't understand what is the Problem with WKWebView
Please help me.
Thanks in advance
The request body uses the same format as the query string:
parameter=value&also=another
Therefore the content type of your request is of type application/x-www-form-urlencoded :
let postString = self.getPostString(params: params)
urlRequest.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
urlRequest.httpMethod = "POST"
urlRequest.httpBody = postString.data(using: .utf8)
webView.load(urlRequest)
Try this, we will initiate a POST request using URLSession convert the data returned by the server to String and instead of loading the url we will use loadHTMLString which will:
Set the webpage contents and base URL.
and the content is our converted string::-
var request = URLRequest(url: URL(string: "http://www.yourWebsite")!)
request.httpMethod = "POST"
let params = "Your Parameters"
request.httpBody = params.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { (data : Data?, response : URLResponse?, error : Error?) in
if data != nil {
if let returnString = String(data: data!, encoding: .utf8) {
self.webView.loadHTMLString(returnString, baseURL: URL(string: "http://www.yourWebsite.com")!)
}
}
}
task.resume()
I think we not need to use URLSession.dataTask, simply create URLRequest and declare your method + with stating header fields like this:
private final func postRequestToURL(_ urlString: String) {
guard let url = URL(string: urlString) else {
debugPrint("Error: Invailed URL!")
return
}
var parameters = Parameters()
parameters["name"] = "Example"
parameters["surname"] = "ExmpleExample"
parameters["timeZone"] = "MiddleEast/MENA"
parameters["test"] = "YES"
var urlRequest = URLRequest(url: url)
urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
urlRequest.allowsCellularAccess = true
urlRequest.httpMethod = "POST"
let postString = parameters.getPostString()
urlRequest.httpBody = postString.data(using: .utf8)
if let wkNavigation = self.webView.load(urlRequest) {
debugPrint("Success: \(wkNavigation.description)")
} else {
debugPrint("Failure: Cannot load current request.")
}
}
Here we can convert our parameters to String by this extension:
public extension Dictionary where Key == String, Value == Any {
func getPostString() -> String {
var data = [String]()
for(key, value) in self {
data.append(key + "=\(value)")
}
return data.map { String($0) }.joined(separator: "&")
}
}
I am using this code over my commercial app.
Additional info: I allowed request eligible to run over cellular by marking allowsCellularAccess = true this is optional

How to send a request with alamofire with xml Body

I installed Alamofire in my project and now here is what I have done.
I installed postman and I put my url and inside body a xml object and I got my result.
Here is a picture of what I exactly have done with postman
How can I now use Alamofire or SWXMLHash to send it as I send it with postman
Thanks in advance!
EDIT
I tried this from another question:
Alamofire.request(.POST, "https://something.com" , parameters: Dictionary(), encoding: .Custom({
(convertible, params) in
let mutableRequest = convertible.URLRequest.copy() as! NSMutableURLRequest
let data = (self.testString as NSString).dataUsingEncoding(NSUTF8StringEncoding)
mutableRequest.HTTPBody = data
return (mutableRequest, nil)
}))
.responseJSON { response in
print(response.response)
print(response.result)
}
}
But it didn't send anything
This is the log:
Optional( { URL:
https://something.com } { status code: 200, headers {
Connection = "keep-alive";
"Content-Length" = 349;
"Content-Type" = "application/xml";
Date = "Wed, 02 Nov 2016 21:13:32 GMT";
Server = nginx;
"Strict-Transport-Security" = "max-age=31536000; includeSubDomains"; } })
FAILURE
EDIT
NEVER FORGET TO PASS parameters if you don't have simple add this , parameters: Dictionary()
Using Swift 3 and Alamofire 4
let stringParams : String = "<msg id=\"123123\" reqTime=\"123123\">" +
"<params class=\"API\">" +
"<param name=\"param1\">123213</param>" +
"<param name=\"param2\">1232131</param>" +
"</params>" +
"</msg>"
let url = URL(string:"<#URL#>")
var xmlRequest = URLRequest(url: url!)
xmlRequest.httpBody = stringParams.data(using: String.Encoding.utf8, allowLossyConversion: true)
xmlRequest.httpMethod = "POST"
xmlRequest.addValue("application/xml", forHTTPHeaderField: "Content-Type")
Alamofire.request(xmlRequest)
.responseData { (response) in
let stringResponse: String = String(data: response.data!, encoding: String.Encoding.utf8) as String!
debugPrint(stringResponse)
}
With Swift 3 and Alamofire 4 you would create a custom ParameterEncoding. As with any other XML encoded body, SOAP messages can use this parameter encoding as in the following example. Other XML body encodings can be created similarly (check the line where it says urlRequest.httpBody = ...):
struct SOAPEncoding: ParameterEncoding {
let service: String
let action: String
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("text/xml", forHTTPHeaderField: "Content-Type")
}
if urlRequest.value(forHTTPHeaderField: "SOAPACTION") == nil {
urlRequest.setValue("\(service)#\(action)", forHTTPHeaderField: "SOAPACTION")
}
let soapArguments = parameters.map({key, value in "<\(key)>\(value)</\(key)>"}).joined(separator: "")
let soapMessage =
"<s:Envelope xmlns:s='http://schemas.xmlsoap.org/soap/envelope/' s:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>" +
"<s:Body>" +
"<u:\(action) xmlns:u='\(service)'>" +
soapArguments +
"</u:\(action)>" +
"</s:Body>" +
"</s:Envelope>"
urlRequest.httpBody = soapMessage.data(using: String.Encoding.utf8)
return urlRequest
}
}
And then use it like that:
Alamofire.request(url, method: .post, parameters: ["parameter" : "value"], encoding: SOAPEncoding(service: "service", action: "action"))
Assuming you that you're missing valid HTTP headers in your request, the updated request could look like:
Alamofire.request(.POST, "https://something.com", parameters: Dictionary() , encoding: .Custom({
(convertible, params) in
let mutableRequest = convertible.URLRequest.copy() as! NSMutableURLRequest
let data = (self.testString as NSString).dataUsingEncoding(NSUTF8StringEncoding)
mutableRequest.HTTPBody = data
mutableRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
return (mutableRequest, nil)
}))
.responseJSON { response in
print(response.response)
print(response.result)
}
}
So, basically you should add one line
mutableRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
Update:
Try same, but use responseData or responseString instead of responseJSON because it is possible that your response is not JSON

swift post request encoding plus sign become white space

Recently I found out that when I send a post data which include a "+" sign, the "+" will become white space. for example { dish_name: fish+chips }. This cause server side cannot read data. So I try following, I use :
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
to encoding my request to UTF-8.However, this line cause my post data become empty. I have no clue why this happen. Or is there any better solution to keep "+" sign as itself during post method?
func PostMethod(url:NSURL,Data:String) {
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let submitContent = Data
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.HTTPMethod = "POST"
request.HTTPBody = submitContent.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
//print("——request——")
//print(request)
let task = session.dataTaskWithRequest(request){
data, response, error in
if data != nil{
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding) as! String
print(responseString)
let jsonall = self.commonControl.StringToJson(responseString)
if let dataFromString = jsonall["body"].stringValue.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
let json = JSON(data: dataFromString)
}}}}
You can try convert your + into its ASCII code. For example:
let content = exampleString.stringByReplacingOccurrencesOfString("+", withString: "%2B", options: [], range: nil)
Swift 3+
let content = exampleString.replacingOccurrences(of: "+", with: "%2B")

Uploading an image and parameters with multipart/form-data in Swift

[WARNING]
As I am seeing that this question is getting noticed more than it should,
I want to tell you not to use any of the following code.
At the time I asked the question, Swift had less than a year, was moving fast, most of the libraries were not Swift-friendly and unstable.
I strongly recommend you to try using Alamofire or another library for that kind of task. But don't do it yourself.
[/WARNING]
I want to upload an image to a Drupal endpoint.
The problem I have is that I receive an HTTP 200 OK response with text/html content type. In the HTML response, there is a clear message that the node has been correctly created. But on the server side the image is not associated with the node.
Also I am not expecting text/html but application/json as I specify it in the Accept header.
It already works in the Android app using Android Rest Template. Here is the code for reference:
String url = getUrl("node/{info_id}/attach_file");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
if (user.isLoggedIn()) {
headers.add(user.getSessionName(), user.getSessionId());
headers.add("X-CSRF-Token", user.getToken());
headers.add("Cookie", user.getSessionName() + "=" + user.getSessionId());
}
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("files[field_mobileinfo_image]",
new FileSystemResource(info.getImageUri()));
parts.add("field_name", "field_mobileinfo_image");
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(parts, headers);
return getRestTemplate().exchange(url, HttpMethod.POST, request, Void.class, info.getId()).getBody();
I know I don't check the response in Android (Void.class) but everything works fine and the image is attached to the node on the server side.
Now on iOS in Swift I tried multiple things.
With AFNetworking:
func upload(mobileInfo: MobileInfo) {
let user = userService.load()
let url = Config.buildUrl("")
let manager = AFHTTPRequestOperationManager(baseURL: NSURL(string:url)!)
let serializer = AFHTTPRequestSerializer()
serializer.setValue(user.sessionId, forHTTPHeaderField: user.sessionName)
serializer.setValue(user.token, forHTTPHeaderField: "X-CSRF-Token")
serializer.setValue("\(user.sessionName)=\(user.sessionId)", forHTTPHeaderField: "Cookie")
manager.requestSerializer = serializer
manager.responseSerializer.acceptableContentTypes.removeAll(keepCapacity: false)
manager.responseSerializer.acceptableContentTypes.insert("application/json")
let imageData = UIImageJPEGRepresentation(mobileInfo.image, 0.3)
manager.POST("/node/\(mobileInfo.id)/attach_file", parameters: nil, constructingBodyWithBlock: { (formData) -> Void in
formData.appendPartWithFileData(
imageData,
name: "files[field_mobileinfo_image]",
fileName: "field_mobileinfo_image",
mimeType: "image/jpeg")
formData.appendPartWithFormData("field_mobileinfo_image".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true), name: "field_name")
},
success: { (operation, data) -> Void in
println(data)
}) { (operation, error) -> Void in
println(error)
}
}
Manually with information grabbed from other stackoverflow questions:
func upload2(mobileInfo: MobileInfo) {
let user = userService.load()
let imageData = UIImageJPEGRepresentation(mobileInfo.image, 0.3)
let url = NSURL(string:Config.buildUrl("/node/\(mobileInfo.id)/attach_file"))!
println(url)
var request = NSMutableURLRequest(URL: url)
var session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
var boundary = "---------------------------14737809831466499882746641449"
var contentType = "multipart/form-data; boundary=\(boundary)"
println(contentType)
request.addValue(contentType, forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("\(user.sessionName)=\(user.sessionId)", forHTTPHeaderField: "Cookie")
request.addValue(user.sessionId, forHTTPHeaderField: user.sessionName)
request.addValue(user.token, forHTTPHeaderField: "X-CSRF-Token")
println(request.allHTTPHeaderFields)
var body = NSMutableData()
body.appendData("\r\n--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Disposition: form-data; name=\"field_name\"\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("field_mobileinfo_image".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!)
body.appendData("\r\n--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Disposition: form-data; name=\"files[field_mobileinfo_image]\"; filename=\"img.jpg\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Type: application/octet-stream\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData(imageData)
body.appendData("\r\n--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
var returnData = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
var returnString = NSString(data: returnData!, encoding: NSUTF8StringEncoding)
println("returnString \(returnString)")
}
With SRWebClient:
func upload3(mobileInfo: MobileInfo) {
let user = userService.load()
let imageData:NSData = NSData(data: UIImageJPEGRepresentation(mobileInfo.image, 0.3))
SRWebClient.POST("http://master.test.lesfrontaliers.lu/node/\(mobileInfo.id)/attach_file")
.headers(["Accept": "application/json",
user.sessionName: user.sessionId,
"X-CSRF-Token": user.token,
"Cookie": "\(user.sessionName)=\(user.sessionId)"])
.data(imageData, fieldName:"files[field_mobileinfo_image]", data:["field_name":"field_mobileinfo_image"])
.send({ (response: AnyObject!, status: Int) -> Void in
println(status)
println(response)
},failure:{(error:NSError!) -> Void in
println(error)
})
}
Please save me! ;-)
I tried so many things to make it work that I can't see anymore if I am doing something wrong. It seems ok for me. The only difference I can see is that I am not storing the image on the filesystem but directly sending the binary data which is the same thing in the end.
Here is an image of the request created in Postman (working and receiving json)
[EDIT] If it can help someone here is the correct code of the wrong part of the above manual request:
var body = NSMutableData()
body.appendData("--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Disposition: form-data; name=\"field_name\"\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("field_mobileinfo_image\r\n".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!)
body.appendData("--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Disposition: form-data; name=\"files[field_mobileinfo_image]\"; filename=\"img.jpg\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Type: image/jpeg\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData(imageData)
body.appendData("\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("--\(boundary)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
request.HTTPBody = body
Don't know if this will work for what you are trying to do but we use this to upload images, the key difference is to use filename and Content-type:
[self appendBody:body data:[NSString stringWithFormat:#"Content-Disposition: form-data; name=\"%#\"; filename=\".jpg\"\r\n", key]];
[self appendBody:body data:#"Content-Type: image/jpeg\r\n\r\n"];
[body appendData:imageData];
For any swift 2.0 JSON Request AND PHP code :- ( Manual )
let imageData = UIImageJPEGRepresentation(userImage, 0.3)
let url:NSURL = NSURL(string: serverURL!)! // Give ur request URL
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
let boundary = "---------------------------14737809831466499882746641449"
let contentType = "multipart/form-data; boundary=\(boundary)"
request.addValue(contentType, forHTTPHeaderField: "Content-Type")
let body = NSMutableData()
body.appendData("--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Disposition: form-data; name=\"userfile\"; filename=\"img.jpg\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Type: image/jpeg\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Transfer-Encoding: binary\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData(imageData!)
body.appendData("\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("--\(boundary)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
request.HTTPBody = body
PHP Code :-
<?php
//http://192.168.1.154/Contact/uploadImgwebservice.php
//print the username and password using php
echo $_POST[‘username’];
echo $_POST[‘password’];
//upload your file
$uploaddir = ‘./uploads/’;
$file = basename($_FILES[‘userfile’][‘name’]);
$uploadfile = $uploaddir . $file;
if (move_uploaded_file($_FILES[‘userfile’][‘tmp_name’], $uploadfile)) {
echo “http://192.168.1.154/Contact/uploads/{$file}”;
}
?>

Stuck converting an NSDictionary to a POST string

So I'm trying to do an async POST request to my server from my iOS device.
Here's the Swift code I've written:
var urlString = "https://eamorr.com/ajax/login.php"
var url = NSURL(string: urlString)
var request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
var params = ["uname":"Eamorr", "pword":"mySecret"] as Dictionary<String, String>
request.HTTPBody = params. //what to do here???
var connection = NSURLConnection(request: request, delegate: self, startImmediately: true)
The problem is, how do (reliably) convert the params to a POST string? (My server is expecting plain old POST parameters... Nothing fancy - no JSON, etc.)
I need this to be very reliable (i.e. not a hack), so I'd prefer if there was some base class (written by Apple) that I could use.
Does anyone have any ideas?
Some of what bbarnhart suggests, but to actually create the body data, use:
let body = "&".join(map(params, {
let key = $0.0.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
let value = $0.1.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
return "\(key!)=\(value!)"
}))
request.HTTPBody = body.dataUsingEncoding(NSUTF8StringEncoding)
Basically escape and convert each item into "key=value" and then string them all together separated by "&"
You'll also (probably) need to set the Content-Type header as recommended by bbarnhart:
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
To configure your post you need to do three things.
Set Content-Type to application/x-www-form-urlencoded
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
Set the body as a key/value delimited by ampersand string and convert to NSData:
let myPostBody = "uname=Eamorr&pword=mySecret"
request.HTTPBoddy = (myPostBody as NSString).dataUsingEncoding(NSUTF8StringEncoding)
Set the Content-Length:
request.addValue(String(myPostBody.length), forHTTPHeaderField: "Content-Length")

Resources