Wednesday, November 13, 2013

Qt 5.1 zlib compression compatible with Python zlib.decompress. Uploading files to the server.

Qt 5.1 zlib compression compatible with Python zlib.decompress. Uploading files to the server.


Task

I want transfer file from Qt 5.1 application to the server. File must be packed before transmission (in Qt application), then must be unpacked in server's (in Python script). We will use zlib compression in both sides.

Stage 1. Sending file without compression.

We will use QHttpMultiPart to transfer data as multipart/form-data. As we can see in Qt documentation all that we need is create QHttpMultiPart object and then link to this object many QHttpPart objects (for each parameter we need to use QHttpPart object).

As we can see in Qt documentation it's must be very simple. But there is a little problem... So we use this code:
C++ code:

QByteArray MyClass::loadFileContent(const QString &fileName)
{
    QFile   file(fileName);

    if(!file.open(QIODevice::ReadOnly))
        return QByteArray();

    return file.readAll();
}

void MyClass::makePostRequest()
{
    QHttpMultiPart *multiPart=
          new QHttpMultiPart(QHttpMultiPart::FormDataType);

    //adding string parameter
    QHttpPart       stringParam;
    stringParam.setHeader
      (
        QNetworkRequest::ContentDispositionHeader,
        QLatin1String("form-data; name=\"strParam\"")
      );

    stringParam.setBody(QString(QLatin1String("alfa-bravo")).toUtf8());
    multiPart->append(stringParam);

    //trying to transfer file
    QHttpPart       bytesParam;
    QString         fileName = QLatin1String("d:/file.dat");
    QFileInfo       info(fileName);


    bytesParam.setHeader
      (
        QNetworkRequest::ContentDispositionHeader,
        QString(QLatin1String("form-data; name=\"bytesParam\"; filename=\"%1\""))
                .arg(info.baseName())
      );

    bytesParam.setBody(loadFileContent(fileName));
    multiPart->append(bytesParam);

    QUrl                    url(QLatin1String("http://server.com/cgi-bin/upload.py"));
    QNetworkRequest         request(url);

    request.setRawHeader("User-Agent", "MyApp/1.4.4.0");

    QNetworkAccessManager   *networkManager = new QNetworkAccessManager(this);

    connect
        (
            networkManager,
            &QNetworkAccessManager::finished,
            this,
            &MyClass::processHttpRequestFinished
        );

    networkManager->post(request, multiPart);
}

The trick in bytesParam.setHeader() call. We will set not only name property for multipart parameter but filename property. We will see later why do we need this tune.

Let's try to get strParam in Python script. The code is very simple.
Python code:

import cgi
form = cgi.FieldStorage()

strParam = None
if "strParam" in form:
    strParam = str(form.getvalue("strParam")).strip()


But if we will try to get bytesParam in same way we will get str value (value with string type). But we want to get bytes, so correct code is:
Python code:

bytesParam = None
if "bytesParam" in form:
    fileitem = form["bytesParam"]

    if fileitem.file:   
        linecount = 0
        while 1:
            line = fileitem.file.readline()
            if linecount != 0:
                bytesParam += line
            else:
                bytesParam = line

            if not line:
                break
                
            linecount = linecount + 1

After that we will get bytes in bytesParam value. Main trick in "if fileitem.file" string here we check that this form parameter is file. It will work only when in the post request (from our Qt application) filename property for form multipart parameter is set.

After that we will save uploaded media to the file system.
Python code:

fileName = "d:/uploaded/data.dat"
file = open(fileName, 'wb')
if not bytesParam is None:
    file.write(bytesParam)
file.close()

Stage 2. Sending compressed file.

In our Qt application we can use qCompress() method to compress bytes. So, all that we want is:
C++ code:

QbyteArray data = loadFileContent(fileName);
data = qCompress(data, 6);

On Python side we can use zlib.decompress() for data decompression before saving to file system.
Python code:

import zlib
...
bytesParam = zlib.decompress(bytesParam)
...

But this will not work :( Unfortunately we will get error when will try to unpack data. As I saw in Qt sources qCompress() adds 4 bytes (byte array size) in the beginning of compressed byte array. So, all that we need to do is remove 4 bytes in the beginning of the packed bytes array.
C++ code:

QbyteArray data = loadFileContent(fileName);
data = qCompress(data, 6);
data.remove(0, 4);


Hope it was useful.

No comments:

Post a Comment