I want to make a POST request from iOS (swift3) which passes a chunk of raw bytes as the body. I had done some experimenting which made me thought the following worked:
let url = URL(string: "https://bla/foo/bar")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = Data(hex: "600DF00D")
let session = URLSession.shared
let task = session.dataTask(with: request) { (data, response, error) in
"DATA \(data ?? Data()) RESPONSE \(response) ERROR \(error)".print()
}
task.resume()
Didn't know it was a problem until I tried sending something simple like a single 0xF0. At which point my tornado server started complaining that I was sending it
WARNING:tornado.general:Invalid x-www-form-urlencoded body: 'utf-8' codec can't decode byte 0xf0 in position 2: invalid continuation byte
Am I just supposed to set some header somehow? Or is there something different I need to do?
The two common solutions are:
Your error message tells us that the web service is expecting a x-www-form-urlencoded request (e.g. key=value) and in for the value, you can perform a base-64 encoding of the binary payload.
Unfortunately, base-64 strings still need to be percent escaped (because web servers generally parse + characters as spaces), so you have to do something like:
let base64Encoded = data
.base64EncodedString(options: [])
.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
.data(using: String.Encoding.utf8)!
var body = "key=".data(using: .utf8)!
body.append(base64Encoded)
var request = URLRequest(url: url)
request.httpBody = body
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
print(error!)
return
}
...
}
task.resume()
Where:
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
}()
}
For more discussion on that character set, see point 2 in this answer: https://stackoverflow.com/a/35912606/1271826.
Anyway, when you receive this on your server, you can retrieve it as and then reverse the base-64 encoding, and you'll have your original binary payload.
Alternatively, you can use multipart/formdata request (in which you can supply binary payload, but you have to wrap it in as part of the broader multipart/formdata format). See https://stackoverflow.com/a/26163136/1271826 if you want to do this yourself.
For both of these approaches, libraries like Alamofire make it even easier, getting you out of the weeds of constructing these requests.
Related
Below is my code for HTTP request for getting and post. What I wanted to know is how to do HTTP request for delete and put. I have made it possible for get and post to work. I want to know how on the part of delete and put based on my code below. what to change in my postcode when I want to change it to delete? what is lacking? I wanted to delete using ID
like for example "id": 16,
let parameters = ["name": "test", "desc": "test" , "reward":"1.00" , "sched":"2018-04-05T11:49:51+08:00", "occurrence":["name": "once"]
, "status": "created", "created_by": "test#gmail.com","created_for": "test.com"] as [String : Any]
guard let url = URL(string: "http://test.tesst.eu:8000/api/v1/test/") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else { return }
request.httpBody = httpBody
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()
get
guard let url = URL(string: "http://test.test:8000/api/v1/test") else { return }
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
print(data)
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}
}.resume()
The only one that differs is GET with the parameters are in the url itself , POST & DELETE & PUT are work the same way , just change the httpMethod parameter and specify httpBody if you wanted to , meaning
DELETE : means delete a resource from a specific url
PUT : place a resource in to a web server
//
let parameters = ["ID": "16"] as [String : Any]
guard let url = URL(string: "http://test.tesst.eu:8000/api/v1/test/") else { return }
var request = URLRequest(url: url)
request.httpMethod = "DELETE"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
guard let httpBody = try? JSONSerialization.data(withJSONObject:parameters, options: []) else { return }
request.httpBody = httpBody
You should go through difference between each method types. It will help you, what should you do and when.
PUT
Store an entity at a URI. PUT can create a new entity or update an existing one. A PUT request is idempotent. Idempotency is the main difference between the expectations of PUT versus a POST request.
The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response. If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request. If the resource could not be created or modified with the Request-URI, an appropriate error response SHOULD be given that reflects the nature of the problem. The recipient of the entity MUST NOT ignore any Content-* (e.g. Content-Range) headers that it does not understand or implement and MUST return a 501 (Not Implemented) response in such cases.
Modify the address with an ID of 1:
PUT /addresses/1
Note: PUT replaces an existing entity. If only a subset of data elements are provided, the rest will be replaced with empty or null.
urlRequestInstance.httpMethod = "PUT"
DELETE
Request that a resource be removed; however, the resource does not have to be removed immediately. It could be an asynchronous or long-running request.
The DELETE method requests that the origin server delete the resource identified by the Request-URI. This method MAY be overridden by human intervention (or other means) on the origin server. The client cannot be guaranteed that the operation has been carried out, even if the status code returned from the origin server indicates that the action has been completed successfully. However, the server SHOULD NOT indicate success unless, at the time the response is given, it intends to delete the resource or move it to an inaccessible location.
A successful response SHOULD be 200 (OK) if the response includes an entity describing the status, 202 (Accepted) if the action has not yet been enacted, or 204 (No Content) if the action has been enacted but the response does not include an entity.
Delete an address with an ID of 1:
DELETE /addresses/1
urlRequestInstance.httpMethod = "DELETE"
Here are nice tutorial references for you:
Understanding REST
REST Methods
What is the usefulness of PUT and DELETE HTTP request methods?
Now you can try a sample code, answered by Sh_Khan
I've been trying to get data by Http "POST" method.In my php script i have a key call "categoryWise" which has a value called "flower".I put all the necessary codes but it doesn't work and says The data couldn’t be read because it isn’t in the correct format.Please help.
let values = "categoryWise= nature"
let parameter = values.data(using: .utf8)
let url = "https://mahadehasancom.000webhostapp.com/WallpaperApp/php_scripts/getImageByCategory.php"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.httpBody = parameter
request.setValue("application/x-content-type-options", forHTTPHeaderField: "Content-Type")
request.setValue("application/x-content-type-options", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if (error != nil)
{
print(error!)
}
else
{
do
{
let fetchData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
//print(fetchData)
let actorArray = fetchData?["result"] as? NSArray
for actor in actorArray!
{
let nameDict = actor as? NSDictionary
let name = nameDict?["date"] as! String
let countryname = nameDict?["category"] as! String
let imageUrl = nameDict?["url"] as! String
//let pageUrl = nameDict?["url"] as! String
authorArray.append(name)
titleArray.append(countryname)
imageURL.append(imageUrl)
//urlArray.append(pageUrl)
}
DispatchQueue.main.async {
self.CountryNameTable.reloadData()
}
print(authorArray)
print(titleArray)
print(imageURL)
print(urlArray)
}
catch let Error2
{
print(Error2.localizedDescription)
if let string = String(data: data!, encoding: .utf8)
{
print(string)
print(response!)
}
}
}
}
task.resume()
A few observations:
You shared PHP that is using $_POST. That means it's expecting x-www-form-urlencoded request. So, in Swift, you should set Content-Type of the request to be application/x-www-form-urlencoded because that's what you're sending. Likewise, in Swift, the Accept of the request should be application/json because your code will "accept" (or expect) a JSON response.
The values string you've supplied has a space in it. There can be no spaces in the key-value pairs that you send in a x-www-form-urlencoded request. (Note, if you have any non-alphanumeric characters in your values key pairs, you should be percent encoding them.)
In your Swift error handler, in addition to printing the error, you might want to try converting the data to a String, and looking to see what it says, e.g.
if let string = String(data: data!, encoding: .utf8) {
print(string)
}
You might also want to look at response and see what statusCode it reported. Having done that, you subsequently told us that it reported a statusCode of 500.
Status code 500 means that there was some internal error in the web service. (The code is 200 if successful.) This is generally a result of some error with the request not being handled correctly. For example, if the request neglected to format the request correctly and the web service doesn't anticipate/catch that, or if there was some other internal error on the web server, you could get 500 error code. For list of status codes, see http://w3.org/Protocols/rfc2616/rfc2616-sec10.html.
If the text in the body of the response from your web service is not illuminating, you might want to turn on error reporting (see How to get useful error messages in PHP? or How do I catch a PHP Fatal Error) and then look at the body of the response again. For example, you might include the following in your PHP:
<?php
function __fatalHandler() {
$error = error_get_last();
//check if it's a core/fatal error, otherwise it's a normal shutdown
if ($error !== NULL && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING))) {
header("Content-Type: application/json");
$result = Array("success" => false, "error" => $error);
echo json_encode($result);
die;
}
}
register_shutdown_function('__fatalHandler');
// the rest of your PHP here
?>
I am trying to work with a MS Translator API from Swift 3 (right now playing in playgrounds, but the target platform is iOS). However, I got stuck when I was trying to get an access token for OAuth2. I have following code (I tried to port the code from example at Obtaining an access token):
let clientId = "id".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let clientSecret = "secret".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let scope = "http://api.microsofttranslator.com".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let translatorAccessURI = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13"
let requestDetails = "grant_type=client_credentials&client_id=\(clientId)&client_secret=\(clientSecret)&scope=\(scope)"
let postData = requestDetails.data(using: .ascii)!
let postLength = postData.count
var request = URLRequest(url: URL(string: translatorAccessURI)!)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("\(postLength)", forHTTPHeaderField: "Content-Length")
request.httpBody = postData
URLSession.shared.dataTask(with: webRequest) { (returnedData, response, error) in
let data = String(data: returnedData!, encoding: .ascii)
print(data)
print("**************")
print(response)
print("**************")
print(error)
}.resume()
Of course, I used a valid clientId and a valid clientSecret.
Now the callback prints following information. First, the returnedData contain a message that the request was invalid, along with a following message:
"ACS90004: The request is not properly formatted."
Second, the response comes with a 400 code (which fits the fact that the request is not properly formatted).
Third, the error is nil.
Now I was testing the call using Postman, and when I used the same URI, and put the requestDetails string as a raw body message (I added the Content-Type header manually), I got the same response. However, when I changed the body type in Postman UI to application/x-www-form-urlencoded and typed in the request details as key value pairs through its UI, the call succeeded. Now it seems that I am doing something wrong with the message formatting, or maybe even something bad with the Swift URLRequest/URLSession API, however, I cannot get a hold on to what. Can somebody help me out, please? Thanks.
OK, so after some more desperate googling and experimenting I have found my error. For the future generations:
The problem resided in encoding the parameters in the body of the PUT http request. Instead of:
let scope = "http://api.microsofttranslator.com"
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
I have to use the following:
let scope = "http://api.microsofttranslator.com"
.addingPercentEncoding(withAllowedCharacters:
CharacterSet(charactersIn: ";/?:#&=$+{}<>,").inverted)!
Seems that the API (or the HTTP protocol, I am not an expert in this) have problems with / and : characters in the request body. I have to give credit to Studiosus' answer on Polyglot issue report.
Why aren't any results being printed?
I have been searching all over for an answer. I've tried many different example blocks of code. The print statements never fires unless an error is produced. For example: If I change the URL to "http" only with nothing else, I naturally get an error and it prints the error. However, any valid URL produces no result in the print statement.
func post()
{
let request = NSMutableURLRequest(URL: NSURL(string: "http://www.thebeerstore.ca")!)
request.HTTPMethod = "POST"
let postString = "experiment"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error=\(error)")
return
}
print("response = \(response)")
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("responseString = \(responseString!)")
}
task.resume()
}
Edit: It works if I use a playground, but only in a playground.
In your application's Info.plist file, add the property App Transport Security Settings and under that, add Allow Arbitrary Loads and assign YES for it.
With the above settings in your Info.plist, the app should be able to load your http:// URLs as well
Note: I would recommend not to use the above settings in your
production build, it could result in security issues.
There is no good explanation for this, but having finally stuck this same problem code into my primary project, it now works perfectly ok. What changed??? Arrrrg! How many hours wasted and I still don't really know why it wasn't working outside of my primary app. Thanks for your help everyone.
Final code:
I do hope I can send over 100MB through a post request to be interpreted on the server side and store to the database. Haven't done this before. I'm a newbie.
func post()
{
let request = NSMutableURLRequest(URL: NSURL(string: "http://bruceexpress.com/database.php")!)
request.HTTPMethod = "POST"
request.HTTPBody = try! NSJSONSerialization.dataWithJSONObject(stores, options: [])
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
if error != nil {
print("error=\(error)")
return
}
print("response = \(response)")
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("responseString = \(responseString!)")
}
task.resume()
}
Edit: Yes, you can post a huge block of data. Is this the correct way to do it? ( Forgive my silly questions, but I am recovering programming skills left over from 2001. I am a very outdated individual. )
Basically, what I have done is read all of the data on http://www.thebeerstore.ca, extracted every beer and its info and prices, and extracted every store and its info. I interpreted this data, converted it into a large JSON block, and sent it to my server to be interpreted by a php script which will store this data to a database.
Sound like the right thing to do, or is there a better way to fill the database?
I have seen two kinds of methods to make up the HTTP body.
First one is:
let url = NSURL(string: "http://example.com")
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
let postString = "id=13&name=Jack"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
Second one is:
let url = NSURL(string: "http://example.com")
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
let params = ["id":"13", "name":"Jack"] as Dictionary<String, String>
var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil, error: &err)
When I directly print out the request.HTTPBody the data is different. So I am wondering are there any differences between these two methods in terms of the implementation of the server side? Assuming I'am using PHP.
there're two format data.
in code using postString.dataUsingEncoding it will send data in urlencoded format. In client you must set request's Content-Type header to "application/x-www-form-urlencoded" or something like "application/x-www-form-urlencoded charset=utf-8"
in code using NSJSONSerialization.dataWithJSONObject it will send data in json format. In client you must set request's Content-Type header field to "application/json"
I'm iOS dev so I don't know about format's effect to server side PHP. to answer your question you must find difference between application/x-www-form-urlencoded and application/json format in server side