Friendlier network requests in QML

I've never really liked XmlHttpRequest. I have always found the code using it to be difficult to read. When writing a QML app though, it's your only option unless you want to fallback to doing networking in C++ (which is fine too!). Web folks have long enjoyed a tidier API with jQuery ajax and for Node.js people, the request package is the undisputed champ. There are of course plenty of other alternatives as well.

In my web projects though I always found myself using a library called SuperAgent. The API is very clean and readable in my opinion and I wanted the same for Qt! So I started experimenting with implementing the SuperAgent API in QML. Qt's XmlHttpRequest had some issues that prevented me from using it as the foundation. Basically, it didn't have any support for multipart and it seems to return strange errors sometimes (eg: if server returns a 500). I decided to do it in C++ using Qt's networking classes and exposing an API through QJSValue worked really well for this purpose. I named the package "DuperAgent". Here's the simplest example in QML:

import com.cutehacks.duperagent 1.0 as Http

...

Http.request
    .get("http://httpbin.org/get")
    .end(function(err, res){
        console.log(JSON.stringify(res.body, 0, 4));
    });

The documentation for SuperAgent linked above has all kinds of examples of how to use the API to set headers, form fields, query string parameters, timeouts, redirects and even injecting "middleware" like functions when building requests. Here's a more complicated example:

import com.cutehacks.duperagent 1.0 as Http

...

// Middleware for adding Authorization header
var auth = function(token) {
  return function(request) {
    request.set('Authorization', 'Bearer ' + token);
    return request;
  }
};

// Middleware function for prepending the base URL
var prefix = function(request) {
  var prefix = "http://httpbin.org";
  if (request.url[0] === '/') {
    request.url = prefix + request.url;
  }
  return request;
};

Http.request
  .post('/post')
  .use(prefix)
  .use(auth("1234"))
  .type("json")
  .send({
    foo: "bar"
  })
  .timeout(10000)
  .set('X-My-Header', 'whatever the header should be')
  .on('progress', function(e) {
    if (e.direction == 'upload') {
      console.log('Uploading %s%', e.percent);
    }
  })
  .end(function (err, res) {
    console.log(JSON.stringify(res, 0, 4))
  });




In addition to implementing the SuperAgent API, I also decided to add some more features that make the library more useful out-of-the-box. The default implementation of DuperAgent will register a network disk cache so that the client can respect HTTP cache headers properly. It also adds a persistent cookie jar so cookies can be handled like you would expect. Both of these behaviors can be disabled. Multipart requests work as expected and can be used with Qt's resource system. Finally, there is a global flag for enabling usage of the system proxy. I found this useful when using something like Charles Proxy to debug network requests.

The latest version (1.2.x) of DuperAgent now implements Javascript's Promises A+ Spec and exposes a new event type called secureConnect which is fired after the SSL handshake is finished. When handling this event, you can get the peer certificate of the connected host and implement something like certificate pinning if your app requires that. Hopefully someone else finds this package as useful as we do :)