Add partial content support to QFilesystemHandler.

This commit is contained in:
Aleksey Yermakov
2016-10-10 04:43:22 +03:00
parent f7290e0cb9
commit 7e7ec8c7af
8 changed files with 195 additions and 7 deletions

View File

@@ -97,6 +97,8 @@ public:
enum {
/// Request was successful
OK = 200,
/// Range request was successful
PartialContent = 206,
/// Resource has moved permanently
MovedPermanently = 301,
/// Resource is available at an alternate URI

View File

@@ -76,6 +76,11 @@ public:
*/
void setBufferSize(qint64 size);
/**
* @brief Set range of data to copy, if src device is not sequential
*/
void setRange(qint64 from, qint64 to);
Q_SIGNALS:
/**

View File

@@ -27,6 +27,7 @@
#include "QHttpEngine/qfilesystemhandler.h"
#include "QHttpEngine/qiodevicecopier.h"
#include "QHttpEngine/qhttprange.h"
#include "qfilesystemhandler_p.h"
// Template for listing directory contents
@@ -84,9 +85,35 @@ void QFilesystemHandlerPrivate::processFile(QHttpSocket *socket, const QString &
connect(copier, SIGNAL(finished()), copier, SLOT(deleteLater()));
connect(copier, SIGNAL(finished()), file, SLOT(deleteLater()));
qint64 fileSize = file->size();
// Checking for partial content request
QByteArray rangeHeader = socket->headers().value("Range");
QHttpRange range;
if(!rangeHeader.isEmpty() && rangeHeader.startsWith("bytes=")) {
// Skiping 'bytes=' - first 6 chars and spliting ranges by comma
QList<QByteArray> rangeList = rangeHeader.mid(6).split(',');
// Taking only first range, as multiple ranges require multipart
// reply support
range = QHttpRange(QString(rangeList.at(0)), fileSize);
}
// If range is valid, send partial content
if(range.isValid()) {
socket->setStatusCode(QHttpSocket::PartialContent);
socket->setHeader("Content-Length", QByteArray::number(range.length()));
socket->setHeader("Content-Range", QByteArray("bytes ") + range.contentRange().toLatin1());
copier->setRange(range.from(), range.to());
} else {
// If range is invalid or if it is not a partial content request,
// send full file
socket->setHeader("Content-Length", QByteArray::number(fileSize));
}
// Set the mimetype and content length
socket->setHeader("Content-Type", mimeType(absolutePath));
socket->setHeader("Content-Length", QByteArray::number(file->size()));
socket->writeHeaders();
// Start the copy

View File

@@ -59,6 +59,7 @@ QByteArray QHttpSocketPrivate::statusReason(int statusCode) const
{
switch(statusCode) {
case QHttpSocket::OK: return "OK";
case QHttpSocket::PartialContent: return "PARTIAL CONTENT";
case QHttpSocket::MovedPermanently: return "MOVED PERMANENTLY";
case QHttpSocket::Found: return "FOUND";
case QHttpSocket::BadRequest: return "BAD REQUEST";

View File

@@ -33,7 +33,9 @@ QIODeviceCopierPrivate::QIODeviceCopierPrivate(QIODeviceCopier *copier, QIODevic
q(copier),
src(srcDevice),
dest(destDevice),
bufferSize(DefaultBufferSize)
bufferSize(DefaultBufferSize),
rangeFrom(0),
rangeTo(-1)
{
}
@@ -69,6 +71,12 @@ void QIODeviceCopierPrivate::nextBlock()
return;
}
// If range is specified (rangeTo >= 0), check if end of range is reached.
// If it is, send only part from buffer truncated by range end.
if(rangeTo != -1 && src->pos() > rangeTo) {
dataRead -= src->pos() - rangeTo - 1;
}
// Write the data to the destination device
if(dest->write(data.constData(), dataRead) == -1) {
Q_EMIT q->error(dest->errorString());
@@ -76,10 +84,10 @@ void QIODeviceCopierPrivate::nextBlock()
return;
}
// Check if the end of the device has been reached - if so,
// emit the finished signal and if not, continue to read
// data at the next iteration of the event loop
if(src->atEnd()) {
// Check if the end of the device has been reached or if the end of
// the requested range is reached - if so, emit the finished signal and
// if not, continue to read data at the next iteration of the event loop
if(src->atEnd() || (rangeTo != -1 && src->pos() > rangeTo)) {
Q_EMIT q->finished();
} else {
QTimer::singleShot(0, this, SLOT(nextBlock()));
@@ -99,6 +107,12 @@ void QIODeviceCopier::setBufferSize(qint64 size)
d->bufferSize = size;
}
void QIODeviceCopier::setRange(qint64 from, qint64 to)
{
d->rangeFrom = from;
d->rangeTo = to;
}
void QIODeviceCopier::start()
{
if(!d->src->isOpen()) {
@@ -117,6 +131,15 @@ void QIODeviceCopier::start()
}
}
// If range is set and d->src is not sequential, seek to starting position
if(d->rangeFrom > 0 && !d->src->isSequential()) {
if(!d->src->seek(d->rangeFrom)) {
Q_EMIT error(tr("Unable to seek source device for specified range"));
Q_EMIT finished();
return;
}
}
// These signals cannot be connected in the constructor since they may
// begin firing before the start() method is called

View File

@@ -41,6 +41,9 @@ public:
qint64 bufferSize;
qint64 rangeFrom;
qint64 rangeTo;
private Q_SLOTS:
void onReadyRead();

View File

@@ -43,6 +43,9 @@ private Q_SLOTS:
void initTestCase();
void testRangeRequests_data();
void testRangeRequests();
void testRequests_data();
void testRequests();
@@ -111,6 +114,85 @@ void TestQFilesystemHandler::testRequests()
}
}
void TestQFilesystemHandler::testRangeRequests_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<QString>("range");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QString>("contentRange");
QTest::addColumn<QByteArray>("data");
QTest::newRow("full file")
<< "inside" << ""
<< static_cast<int>(QHttpSocket::OK)
<< ""
<< Data;
QTest::newRow("range 0-2")
<< "inside" << "0-2"
<< static_cast<int>(QHttpSocket::PartialContent)
<< "bytes 0-2/4"
<< Data.mid(0, 3);
QTest::newRow("range 1-2")
<< "inside" << "1-2"
<< static_cast<int>(QHttpSocket::PartialContent)
<< "bytes 1-2/4"
<< Data.mid(1, 2);
QTest::newRow("skip first 1 byte")
<< "inside" << "1-"
<< static_cast<int>(QHttpSocket::PartialContent)
<< "bytes 1-3/4"
<< Data.mid(1);
QTest::newRow("last 2 bytes")
<< "inside" << "-2"
<< static_cast<int>(QHttpSocket::PartialContent)
<< "bytes 2-3/4"
<< Data.mid(2);
QTest::newRow("bad range request")
<< "inside" << "abcd"
<< static_cast<int>(QHttpSocket::OK)
<< ""
<< Data;
}
void TestQFilesystemHandler::testRangeRequests()
{
QFETCH(QString, path);
QFETCH(QString, range);
QFETCH(int, statusCode);
QFETCH(QString, contentRange);
QFETCH(QByteArray, data);
QFilesystemHandler handler(QDir(dir.path()).absoluteFilePath("root"));
QSocketPair pair;
QTRY_VERIFY(pair.isConnected());
QSimpleHttpClient client(pair.client());
QHttpSocket socket(pair.server(), &pair);
if(!range.isEmpty()) {
QHttpHeaderMap inHeaders;
inHeaders.insert("Range", QByteArray("bytes=") + range.toUtf8());
client.sendHeaders("GET", path.toUtf8(), inHeaders);
QTRY_VERIFY(socket.isHeadersParsed());
}
handler.route(&socket, path);
QTRY_COMPARE(client.statusCode(), statusCode);
if(!data.isNull()) {
QTRY_COMPARE(client.data(), data);
QCOMPARE(client.headers().value("Content-Length").toInt(), data.length());
QCOMPARE(client.headers().value("Content-Range"), contentRange.toLatin1());
}
}
bool TestQFilesystemHandler::createFile(const QString &path)
{
QFile file(QDir(dir.path()).absoluteFilePath(path));

View File

@@ -31,7 +31,7 @@
#include "common/qsocketpair.h"
const QByteArray SampleData = "1234567890";
const QByteArray SampleData = "1234567890123456789012345678901234567890";
class TestQIODeviceCopier : public QObject
{
@@ -39,6 +39,9 @@ class TestQIODeviceCopier : public QObject
private Q_SLOTS:
void testRange_data();
void testRange();
void testQBuffer();
void testQTcpSocket();
void testStop();
@@ -110,5 +113,47 @@ void TestQIODeviceCopier::testStop()
QTRY_COMPARE(destData, SampleData);
}
void TestQIODeviceCopier::testRange_data()
{
QTest::addColumn<int>("from");
QTest::addColumn<int>("to");
QTest::addColumn<int>("bufferSize");
QTest::newRow("range: 1-21, bufSize: 8")
<< 1 << 21 << 8;
QTest::newRow("range: 0-21, bufSize: 7")
<< 0 << 21 << 7;
QTest::newRow("range: 10-, bufSize: 5")
<< 10 << -1 << 5;
}
void TestQIODeviceCopier::testRange()
{
QFETCH(int, from);
QFETCH(int, to);
QFETCH(int, bufferSize);
QBuffer src;
src.setData(SampleData);
QByteArray destData;
QBuffer dest(&destData);
QIODeviceCopier copier(&src, &dest);
copier.setBufferSize(bufferSize);
copier.setRange(from, to);
QSignalSpy errorSpy(&copier, SIGNAL(error(QString)));
QSignalSpy finishedSpy(&copier, SIGNAL(finished()));
copier.start();
QTRY_COMPARE(finishedSpy.count(), 1);
QCOMPARE(errorSpy.count(), 0);
QCOMPARE(destData, SampleData.mid(from, to - from + 1));
}
QTEST_MAIN(TestQIODeviceCopier)
#include "TestQIODeviceCopier.moc"