1

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()


    }
Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143
  • https://stackoverflow.com/questions/33751432/kcfstreamerrorcodekey-2102-only-with-wifi-of-some-isps ? – Paulw11 Apr 26 '19 at 21:56
  • I have seen this question, but there is some more specific error with this Content-Length. I have set it (maybe done it wrong) but it did not help me. – Michał Ziobro Apr 26 '19 at 21:58
  • Why are you appending `NSData` instances? Why don't you build your postdata string and then convert it to `Data` once? – Paulw11 Apr 26 '19 at 22:00
  • Usually I am using Alamofire, but here I have grab some code generated just from POSTMAN and I am using to for testing purposes. As both Alamofire and this doesn't work. – Michał Ziobro Apr 26 '19 at 22:01
  • Also, your request doesn't make sense; you are using `GET` but sending `POST` data. Don't you want the method to be `POST`? – Paulw11 Apr 26 '19 at 22:01
  • No it's GET. Yeah URL query params here make sense and it works. But I want to resolve this issue as it is rather general problem between iOS app and node.js server and in other endpoints (in future) I will need to use body params like application/json or application/x-www-form-urlencoded, and I would like to have it just working not limiting myself to just query params. – Michał Ziobro Apr 26 '19 at 22:08
  • If you use method `GET` then the parameters need to be in the URL. If you want the parameters to be in the body then you have to use `POST` method. – Paulw11 Apr 26 '19 at 22:20
  • It is not so strict I think. You can make such request from POST man and it works correctly – Michał Ziobro Apr 26 '19 at 22:36
  • I tested and it works with body params for POST request, and only for query params in GET request. But it seems to be some limitation of URLSession objects like not passing body params to server, as POSTMAN, curl enables doing so, and this params can be retrieved. So as this is semantically more correct to use query params or path params with GET I will stick with it, and only relay on body params with POST/PUT/DELETE etc. requests methods – Michał Ziobro Apr 26 '19 at 23:15

0 Answers0