Speeding up your Cascades app by pre-connecting to a Web server

by BlackBerry Employee on ‎08-19-2013 10:39 AM - edited on ‎08-19-2013 10:39 AM by BlackBerry Development Advisor (3,752 Views)

When starting up a Cascades™ app, it usually loads a lot of files at once, like translation files and lots of QML files to display the UI. When data is loaded from the network, it is often a better choice to start the network requests before loading files for performance reasons, as explained in the article Improving startup performance of Cascades apps. However, sometimes before making a network request, it is required to read credentials (like e.g. an OAuth access token) from disk or some other form of persistent storage.

 

Since BlackBerry® 10 OS version 10.2, Qt® offers methods to connect to a server first without sending a HTTP(S) request. Because the server used for a Web service is usually hardcoded and very unlikely to change (e.g. https://graph.facebook.com or https://api.twitter.com), it is easy to start a connection to the server first thing when starting the app, then reading the necessary credentials, and then sending the actual HTTP(S) request. Using the new pre-connect functionality has the advantage that network traffic is started as early as possible, so when the HTTP(S) request is made, the necessary TCP or SSL handshakes are already completed or at least already in-flight.

 

There are 3 "stages" of pre-connecting to a server:

 

  1. DNS lookup: Makes the DNS lookup (1 UDP round trip), which is cached for 1 minute inside Qt.
  2. TCP handshake to the server: Makes the DNS lookup and the TCP handshake (1 TCP roundtrip). Usually the server leaves the socket open for a long enough time to make the HTTP request later (e.g. graph.facebook.com: 2 minutes, api.twitter.com: 30 seconds).
  3. SSL handshake to the server: Makes the DNS lookup, TCP handshake and SSL handshake (1-2 TCP roundtrip(s)). Just like with the TCP handshake, the server will leave the socket open after the SSL handshake, usually for the same amount of time as with the TCP handshake.

 

1. API for making a DNS lookup

 

Making a DNS lookup can be done in one line:

 

QHostInfo::lookupHost(QString::fromAscii("api.twitter.com"), 0, 0);

 

The two last "0" arguments just denote that there is no slot to be called when the lookup has finished, because in our case we just want to warm up the cache, and not be notified upon completion. The line above will lookup the host and keep it in the cache for 1 minute. Please note that the Qt event loop needs to be running in order for this to work (see Improving startup performance of Cascades apps).

 

 

2. API for making a TCP handshake

 

Making a TCP handshake can be done with the following code snippet:

 

QUrl url("http://api.twitter.com");
QNetworkRequest preConnectRequest(url);
QNetworkRequest::Attribute preConnectAttribute =
    static_cast<QNetworkRequest::Attribute>(
        static_cast<int>(QNetworkRequest::User)-4);
preConnectRequest.setAttribute(preConnectAttribute, true);

QNetworkAccessManager *m_networkAccessManager = new QNetworkAccessManager;
m_networkAccessManager->get(preConnectRequest);

 

This looks pretty much just like making a real HTTP request, except that we set a special attribute on the request (the preConnectAttribute), which tells Qt to only do the TCP handshake, but not send a HTTP request. Just like with the DNS lookup, we are not interested in when the handshake finishes, but only in creating a TCP connection as early as possible.

 

Note: It might be good practice to warm up several TCP connections if the application is reasonably sure that it will need them later anyhow. A good idea might be to be more agressive on WLAN than on a cellular interface (in case of limited data plans on a cellular interface); e.g. the following code will open 6 sockets on WLAN, but only 3 on a cellular interface:

 

QNetworkConfigurationManager *m_configurationManager =
new QNetworkConfigurationManager; int preConnectChannelCount = (m_configurationManager->
        defaultConfiguration().bearerType()
            == QNetworkConfiguration::BearerWLAN) ? 6 : 3; for (int a = 0; a < preConnectChannelCount; ++a) { m_networkAccessManager->get(preConnectRequest);
}

 

 

3. API for making a SSL handshake

 

Making a SSL handshake to a server after DNS lookup and TCP handshake have completed can be done like this:

 

QUrl url("https://api.twitter.com");
QNetworkRequest preConnectRequest(url);
QNetworkRequest::Attribute preConnectAttribute =
    static_cast<QNetworkRequest::Attribute>(
        static_cast<int>(QNetworkRequest::User)-4);
preConnectRequest.setAttribute(preConnectAttribute, true);

QNetworkAccessManager *m_networkAccessManager = new QNetworkAccessManager;
m_networkAccessManager->get(preConnectRequest);

 

The only difference between making a TCP handshake and a SSL handshake is that now we are using a "https" URL to create the network request with. If you know that the server you are connecting to is using SSL, you probably always want to do a SSL handshake. In other words, just always use the same server URL for preconnecting as you will use later when sending the HTTP(S) requests.

As with several TCP handshakes, you might also want to initiate several SSL connections at the same time.

 

Note: When using SSL, you might also want to save 1 network roundtrip by persisting and re-using TLS sessions, as explained in the article re-using SSL sessions in Cascades apps at startup.

 

 

Example

 

Below is an example that is putting it all together: The constructor just calls an "init()" method with a QueuedConnection to start the event loop right away; the first thing that method does is looking up the host, and then connecting to the server (the host lookup is actually excessive; pre-connecting to the server will do the DNS lookup anyhow right afterwards). After the connection requests have been started, the application continues to load the translation and QML files.

 

ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
        QObject(app), m_app(app)
{
	QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection);
}

void ApplicationUI::init()
{
    QHostInfo::lookupHost(QString::fromAscii("api.twitter.com"), 0, 0);

    preConnectToServer();

    // here e.g. retrieve OAuth tokens and send HTTP(S) requests
// prepare the localization m_pTranslator = new QTranslator(this); m_pLocaleHandler = new LocaleHandler(this); QObject::connect(m_pLocaleHandler, SIGNAL(systemLanguageChanged()),
            this, SLOT(onSystemLanguageChanged())); // initial load onSystemLanguageChanged(); // Create scene document from main.qml asset, the parent is set // to ensure the document gets destroyed properly at shut down. QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this); // Create root object for the UI AbstractPane *root = qml->createRootObject<AbstractPane>(); // Set created root object as the application scene m_app->setScene(root); } void ApplicationUI::preConnectToServer() {     QUrl url("https://api.twitter.com");
    QNetworkRequest preConnectRequest(url);
    QNetworkRequest::Attribute preConnectAttribute =
            static_cast<QNetworkRequest::Attribute>(
                    static_cast<int>(QNetworkRequest::User)-4);
    preConnectRequest.setAttribute(preConnectAttribute, true);

    m_configurationManager = new QNetworkConfigurationManager;
    int preConnectChannelCount = (m_configurationManager->
            defaultConfiguration().bearerType()
            == QNetworkConfiguration::BearerWLAN) ? 6 : 3;

    m_networkAccessManager = new QNetworkAccessManager;
    for (int a = 0; a < preConnectChannelCount; ++a) {
        m_networkAccessManager->get(preConnectRequest);
    } }

 

 

 

Summary

 

Pre-connecting to a server before loading QML files can speed up your app considerably; it might often be best for an application to try to start the event loop first, then make the network requests, and then load the QML files.