Add partial content support to QFilesystemHandler.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ public:
|
||||
|
||||
qint64 bufferSize;
|
||||
|
||||
qint64 rangeFrom;
|
||||
qint64 rangeTo;
|
||||
|
||||
private Q_SLOTS:
|
||||
|
||||
void onReadyRead();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user