Swift/iOS URL request to Node.js API endpoint - ios

I have standard code in Swift like below:
private func testFormUrlEncodedRequest() {
let headers = [
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json"
]
let postData = NSMutableData(data: "user_id=5874ae8ae9a98c2d6cef1da8".data(using: String.Encoding.utf8)!)
postData.append("&offset=0".data(using: String.Encoding.utf8)!)
postData.append("&limit=20".data(using: String.Encoding.utf8)!)
let request = NSMutableURLRequest(url: NSURL(string: "http://www.example.com/endpoint")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data
ViewController.log(request: request as! URLRequest)
print((request as URLRequest).curlString)
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
ViewController.log(data: data, response: response as? HTTPURLResponse, error: error)
if (error != nil) {
print(error)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
}
})
dataTask.resume()
}
But the problem is that it hangs on, and then request times out with error.
REST API is written in Node.js and gets error in body-parser module like request aborted.
I can make successfully the same request with POSTMAN or curl (from Terminal) and I get correct response.
Code on server which I have no access to seems to be also rather standard, and was used in previous projects where it was tested to work correctly with iOS apps.
I have no idea why this request goes ok with POSTMAN and doesn't work with URLSession in Swift.
Any help will be beneficial.
Here is error message printed to console I am getting:
Optional(Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSUnderlyingError=0x6000013196e0
{Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}},
NSErrorFailingURLStringKey=http://example.com/api/endpoint, NSErrorFailingURLKey=http://example.com/api/endpoint,
_kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102, NSLocalizedDescription=The request timed out.})
This request gives error in such cases:
1. form-url-encoded params in HTTP request body
2. raw application/json params in HTTP request body
3. It works if params are passed in query params
4. It crashes with request aborted error on server side (body-parser module)
5. node.js uses standard app.use()
// support parsing of application/json type post data
app.use(bodyParser.json());
//support parsing of application/x-www-form-urlencoded post data
app.use(bodyParser.urlencoded({ extended: true }));
It uses http without SSL but in Info.plist there is App Transport Security > Allow Arbitrary Loads set to YES etc.
UPDATE:
This is error on server side
BadRequestError: request aborted
at IncomingMessage.onAborted (/Users/michzio/Click5Interactive/Reusable Parts/NetworkApi/node_modules/raw-body/index.js:231:10)
at emitNone (events.js:86:13)
at IncomingMessage.emit (events.js:188:7)
at abortIncoming (_http_server.js:381:9)
at socketOnClose (_http_server.js:375:3)
at emitOne (events.js:101:20)
at Socket.emit (events.js:191:7)
at TCP.Socket._destroy.cb._handle.close [as _onclose] (net.js:510:12)
Node.js Test Code:
const express = require('express');
const port = 9001;
const app = express();
const bodyParser = require('body-parser');
var todos = [{id:1, title:'buy the milk'}, {id:2, title:'rent a car'}, {id:3, title:'feed the cat'}];
var count = todos.length;
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json());
app.get('/test', (request, response) => {
console.log("-----")
console.log(request.params);
console.log(request.body);
console.log(request.query);
console.log("-----")
response.status(200).json( todos );
});
app.listen(port);
It seems that GET + query params works, and POST + body params (url-from-encoded or application/json) also works correctly.
So it doesn't work for GET body params url-form encoded and GET body params application/json. Is it some limitation of URLSession/URLRequest in Swift. POSTMAN can pass params in body with GET and server receives it in request.body !
UPDATE 2!
Yes, it seems that in Android/Kotlin with OkHttpClient there even is not possible to define Request Body with GET method. And there is also this error. Maybe this only works with POSTMAN and curl, and should not be used in real application scenario to join GET and body params.
public fun makeNetworkRequest(v: View) {
object : Thread() {
override fun run() {
val client = OkHttpClient()
val mediaType = MediaType.parse("application/json")
val body = RequestBody.create(mediaType, "{ \"test\" : \"nowy\", \"test2\" : \"lol\" }")
/*
val request = Request.Builder()
.url("http://10.0.2.2:9001/test")
.get()
.addHeader("Content-Type", "application/json")
.build()
*/
val mySearchUrl = HttpUrl.Builder()
.scheme("http")
.host("10.0.2.2")
.port(9001)
.addPathSegment("test")
.addQueryParameter("q", "polar bears")
.build()
val request = Request.Builder()
.url(mySearchUrl)
.addHeader("Accept", "application/json")
.method("GET", body)
.build()
val response = client.newCall(request).execute()
Log.d("RESPONSE", response.toString())
}
}.start()
}

Related

Alamofire not creating autnentication header to send credentials

I'm attempting to access an api using my username and api key. An example they give, which I believe is .NET, is:
Public Sub GET_Products()
Dim request As HttpWebRequest = WebRequest.Create("https://api.ssactivewear.com/v2/products/?style=39")
request.Method = "GET"
request.Credentials = New NetworkCredential("YOurCustomerNumber", "YourAPIKey")
Try
Dim response As HttpWebResponse = request.GetResponse
Dim StreamReader As New StreamReader(response.GetResponseStream())
Result = StreamReader.ReadToEnd
If response.StatusCode = HtppStatusCode.OK Then
Products = serializer.Deserialize(Of List(Of Sku))(Result)
Else
End If
Catch ex As Exception
End Try
End Sub
I've used the following to test the request for a response:
guard let url = URL(string: "https://api.ssactivewear.com/v2/products/") else { return }
let username = "myusername"
let password = "myapikey"
AF.request(url).authenticate(username: username, password: password).responseJSON { response in
print("Request: \(String(describing: response.request))")
print("Response: \(String(describing: response.response))")
print("HeaderFields: \(String(describing: response.request?.allHTTPHeaderFields))")
if let json = response.value {
print("JSON: \(json)")
//self.responseText.text = "JSON: \(json)"
}
if let error = response.error {
print("ERROR: \(error.localizedDescription)")
//self.responseText.text = "ERROR: \(error.localizedDescription)"
}
}
This fails authentication because no authentication header is sent. I believe I read this is expected behavior but didn't find a solution.
create APIMiddle class:
class APIManager
{
class func headers() -> HTTPHeaders
{
let headers: HTTPHeaders = [
"Content-Type": "application/json",
"Accept": "application/json"
]
return headers
}
}
your api call:
let request = AF.request(path, method: .post, parameters: params, encoding: JSONEncoding.default, headers: APIManager.headers(), interceptor: nil)
request.responseDecodable(of: UserModel?.self) {(resposnse) in
let user = resposnse.value
print(user)
}
Alamofire's authenticate method adds a URLCredential to the Request which is used to respond to server challenges. If the server never sends a challenge for those credentials, the credentials will never be used. api.ssactivewear.com appears to use HTTP Basic auth, which should work fine, but I couldn't find any specific documentation about that. There may be other requirements to properly make a request to the API. I suggest you investigate those requirements as well as the actual network traffic being sent to see what's actually happening.

Alamofire post method in iOS Swift 4?

For getting push notification here i am sending postitem, token, like count and currentname using alamofire post method(pod version alamofire 4.5). I did not get any response when post method called and it does not show any errors.
I tried keeping breaking points in alamofire function, it call alamofire.requestion then it goes out function.
Here is the code tried to send post method to backend:
func postNotification(postItem: String, post: Post) {
print("Get token from post:::",post.token)
print(postItem)
let token = UserDefaults.standard.string(forKey: "token")
let headers: HTTPHeaders = ["Content-Type" :"application/x-www-form-urlencoded"]
let parameters : [String:Any] = ["count":post.likeCount!, "likedby":currentName, "postId=":postItem, "token": post.token!]
Alamofire.request("http://highavenue.co:9000/likesnotification/", method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: nil).responseJSON { (response:DataResponse<Any>) in
switch(response.result) {
case .success(_):
if let data = response.result.value{
print(data)
}
break
case .failure(_):
print(response.result.error as Any)
break
}
}
}
Getting console error like this
2018-07-10 14:21:07.980212+0530 HighAvenue[10584:4236493] Task <B5FC98AB-C3FE-
4D4F-9A93-72D3FFE35DF7>.<1> finished with error - code: -1001
Optional(Error Domain=NSURLErrorDomain Code=-1001 "The request timed out."
UserInfo={NSUnderlyingError=0x1c0e478f0 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey=http://highavenue.co:9000/likesnotification/, NSErrorFailingURLKey=http://highavenue.co:9000/likesnotification/, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102, NSLocalizedDescription=The request timed out.})
That is because you are not setting request time in your network call, by default your request time is a small interval, so please increase request timeout time. something like this,
let request = NSMutableURLRequest(url: URL(string: "")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.timeoutInterval = 120 // 120 secs
let values = ["key": "value"]
request.httpBody = try! JSONSerialization.data(withJSONObject: values, options: [])
Alamofire.request(request as! URLRequestConvertible).responseJSON {
response in
// do whatever you want here
}
Second mistake in your code is you are trying to access http url which are by default are not allowed so you have to by pass this security from your app, Please refer to this answer in order to remove this security layer from your app.
The resource could not be loaded because the App Transport Security policy requires the use of a secure connection

Can not fetch data using Alamofire?

I am trying to fetch data in my iOS app from my Django backend. In postman if I perform a GET request on the following URL http://127.0.0.1:8000/api/places/categories with the params being Key:"Authorization" Value: "Bearer access_token".I get a JSON response.
Inside my app I am doing something like this with the help of Alamofire:
let access_token = "123"
let headers = ["Authorization": "Bearer" + access_token]
Alamofire.request(self.categoriesUrl, method: .get, parameters:nil,encoding: JSONEncoding.default,headers: headers).response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.error)")
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)")
}
}
I get an error saying Authorization Credentials were not provided. I understand this and it asks me to pass in the parameters but the parameters just need a token. So I do something like this:
let access_token = "123"
let params = ["Authorization": "Bearer" + access_token]
Alamofire.request(self.categoriesUrl, method: .get, parameters:params,encoding: JSONEncoding.default,headers: nil).response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.error)")
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)")
}
}
It waits for a while but fails to fetch the data with the following error:
Response: nil
Error: Optional(Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSUnderlyingError=0x61800004b0d0 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey=http://127.0.0.1:8000/api/places/categories/, NSErrorFailingURLKey=http://127.0.0.1:8000/api/places/categories/, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102, NSLocalizedDescription=The request timed out.})
Data:
EDIT1:
This is very simple to fix, I guess you are using iOS10 or later version of OS. So instead of calling http , just call https, which means the API calling protocol has been changed to http to https in iOS10 and later.
You have a typo here :
let params = ["Authorization": "Bearer" + access_token]
You're missing a space after Bearer.

Send body in PUT request in swift

I tried to send body in PUT request but the data is not received on backend
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "PUT"
let putBody = "bucket=\(bucket)&day=\(day)"
request.HTTPBody = putBody.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {(data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue(), {() -> Void in
completion(data: data, response: response as! NSHTTPURLResponse, error: error)
})
})
task.resume()
The value of bucket (Int) is 2015040 and day (String) is day27. I tried making same request in Postman, the server received body data so there is nothing wrong with the server.
Is there any other way to set body of a request?
EDIT :
It's working perfectly if I change request method to POST in my request and server as well. So the question comes down to how to set body in PUT request?
I've just been dealing with this. Had the exact same issue where it was working perfectly fine with POST but not with PUT or PATCH requests. It seems that for PUT and PATCH requests, Swift is picky about the Content-Type header.
So try adding this:
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
This will work as long as your body is url style, so like this:
key=value&key=value&key=value
For those of you using JSON, just use application/json instead for the Content-Type.

Swift POST request delivers body to node.js/express application in wrong format

I am trying to send a JSON object from an ios app using Swift. Everything works but the fact that the body in the request body on the node.js/express backend is in an aca-awkward format where the whole JSON object parsed in Swift is the actual key. So what is being received on the server is:
req.body = { '{"email":"email","password":"password"}': '' }
I want it to be:
{ "email":"email","password":"password" }
So I can access the key-values with, for example, req.body.email
I am new to http communication between ios and nodejs so maybe this is
normal but its very annoying.
My ios http post code is :
let url = NSURL(string: "http://localhost:3000/users")
let request = NSMutableURLRequest(URL:url!)
request.HTTPMethod = "POST"
var login_details: [String: AnyObject] = [
"email" : "\(self.email_field.text)",
"password" : "\(self.password_field.text)"
]
let valid = NSJSONSerialization.isValidJSONObject(login_details)
var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(login_details, options: nil, error: &err)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
println("error=\(error)")
return
}
}
My node router module is:
router.post('/', function(req, res) {
console.log(req.body);
});
Node automatically parses the JSON request body to object only if it knows that it is the right content type. You have two possible solutions, use one or the other but not both since they conflict.
1) client side, more correct IMO. Set the right content type. Add this to your Swift code:
request.setValue("application/json" forHTTPHeaderField:"Content-Type")
2) server side. Just parse the body yourself:
parsedBody = JSON.parse(body)
// parsedBody will now be an object

Resources