setup the HTTPManager to serve files in the document root

This commit is contained in:
Stephen Birarda 2014-01-17 08:53:55 -08:00
parent c5e10465c4
commit 036dba9c2f
5 changed files with 62 additions and 436 deletions

View file

@ -33,7 +33,7 @@ const quint16 DOMAIN_SERVER_HTTP_PORT = 8080;
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_httpManager(DOMAIN_SERVER_HTTP_PORT),
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath())),
_assignmentQueueMutex(),
_assignmentQueue(),
_staticAssignmentFile(QString("%1/config.ds").arg(QCoreApplication::applicationDirPath())),
@ -60,8 +60,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
const char METAVOXEL_CONFIG_OPTION[] = "--metavoxelServerConfig";
_metavoxelServerConfig = getCmdOption(argc, (const char**)argv, METAVOXEL_CONFIG_OPTION);
// QString documentRootString = QString("%1/resources/web").arg(QCoreApplication::applicationDirPath());
//
// char* documentRoot = new char[documentRootString.size() + 1];
// strcpy(documentRoot, documentRootString.toLocal8Bit().constData());

View file

@ -22,11 +22,8 @@ HttpConnection::HttpConnection (QTcpSocket* socket, HttpManager* parentManager)
QObject(parentManager),
_parentManager(parentManager),
_socket(socket),
_unmasker(new MaskFilter(socket, this)),
_stream(socket),
_address(socket->peerAddress()),
_webSocketPaused(false),
_closeSent(false)
_address(socket->peerAddress())
{
// take over ownership of the socket
_socket->setParent(this);
@ -50,11 +47,6 @@ HttpConnection::~HttpConnection ()
}
}
bool HttpConnection::isWebSocketRequest ()
{
return _requestHeaders.value("Upgrade") == "websocket";
}
QList<FormData> HttpConnection::parseFormData () const
{
// make sure we have the correct MIME type
@ -111,9 +103,7 @@ QList<FormData> HttpConnection::parseFormData () const
return data;
}
void HttpConnection::respond (
const char* code, const QByteArray& content, const char* contentType, const Headers& headers)
{
void HttpConnection::respond (const char* code, const QByteArray& content, const char* contentType, const Headers& headers) {
_socket->write("HTTP/1.1 ");
_socket->write(code);
_socket->write("\r\n");
@ -139,6 +129,7 @@ void HttpConnection::respond (
_socket->write("Connection: close\r\n\r\n");
if (csize > 0) {
qDebug() << "Writing" << QString(content) << "\n";
_socket->write(content);
}
@ -148,55 +139,6 @@ void HttpConnection::respond (
_socket->disconnectFromHost();
}
void HttpConnection::switchToWebSocket (const char* protocol)
{
_socket->write("HTTP/1.1 101 Switching Protocols\r\n");
_socket->write("Upgrade: websocket\r\n");
_socket->write("Connection: Upgrade\r\n");
_socket->write("Sec-WebSocket-Accept: ");
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(_requestHeaders.value("Sec-WebSocket-Key"));
hash.addData("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); // from WebSocket draft RFC
_socket->write(hash.result().toBase64());
if (protocol != 0) {
_socket->write("\r\nSec-WebSocket-Protocol: ");
_socket->write(protocol);
}
_socket->write("\r\n\r\n");
// connect socket, start reading frames
setWebSocketPaused(false);
}
void HttpConnection::setWebSocketPaused (bool paused)
{
if ((_webSocketPaused = paused)) {
_socket->disconnect(this, SLOT(readFrames()));
} else {
connect(_socket, SIGNAL(readyRead()), SLOT(readFrames()));
readFrames();
}
}
void HttpConnection::closeWebSocket (quint16 reasonCode, const char* reason)
{
if (reasonCode == NoReason) {
writeFrameHeader(ConnectionClose);
} else {
int rlen = (reason == 0) ? 0 : qstrlen(reason);
writeFrameHeader(ConnectionClose, 2 + rlen);
_stream << reasonCode;
if (rlen > 0) {
_socket->write(reason);
}
}
_closeSent = true;
}
void HttpConnection::readRequest ()
{
if (!_socket->canReadLine()) {
@ -245,7 +187,7 @@ void HttpConnection::readHeaders ()
QByteArray clength = _requestHeaders.value("Content-Length");
if (clength.isEmpty()) {
_parentManager->handleRequest(this, "", _requestUrl.path());
_parentManager->handleRequest(this, _requestUrl.path());
} else {
_requestContent.resize(clength.toInt());
@ -276,8 +218,7 @@ void HttpConnection::readHeaders ()
}
}
void HttpConnection::readContent ()
{
void HttpConnection::readContent() {
int size = _requestContent.size();
if (_socket->bytesAvailable() < size) {
return;
@ -285,201 +226,5 @@ void HttpConnection::readContent ()
_socket->read(_requestContent.data(), size);
_socket->disconnect(this, SLOT(readContent()));
_parentManager->handleRequest(this, "", _requestUrl.path());
}
void HttpConnection::readFrames()
{
// read as many messages as are available
while (maybeReadFrame());
}
void unget (QIODevice* device, quint32 value) {
device->ungetChar(value & 0xFF);
device->ungetChar((value >> 8) & 0xFF);
device->ungetChar((value >> 16) & 0xFF);
device->ungetChar(value >> 24);
}
bool HttpConnection::maybeReadFrame ()
{
// make sure we have at least the first two bytes
qint64 available = _socket->bytesAvailable();
if (available < 2 || _webSocketPaused) {
return false;
}
// read the first two, which tell us whether we need more for the length
quint8 finalOpcode, maskLength;
_stream >> finalOpcode;
_stream >> maskLength;
available -= 2;
int byteLength = maskLength & 0x7F;
bool masked = (maskLength & 0x80) != 0;
int baseLength = (masked ? 4 : 0);
int length = -1;
if (byteLength == 127) {
if (available >= 8) {
quint64 longLength;
_stream >> longLength;
if (available >= baseLength + 8 + longLength) {
length = longLength;
} else {
unget(_socket, longLength & 0xFFFFFFFF);
unget(_socket, longLength >> 32);
}
}
} else if (byteLength == 126) {
if (available >= 2) {
quint16 shortLength;
_stream >> shortLength;
if (available >= baseLength + 2 + shortLength) {
length = shortLength;
} else {
_socket->ungetChar(shortLength & 0xFF);
_socket->ungetChar(shortLength >> 8);
}
}
} else if (available >= baseLength + byteLength) {
length = byteLength;
}
if (length == -1) {
_socket->ungetChar(maskLength);
_socket->ungetChar(finalOpcode);
return false;
}
// read the mask and set it in the filter
quint32 mask = 0;
if (masked) {
_stream >> mask;
}
_unmasker->setMask(mask);
// if not final, add to continuing message
FrameOpcode opcode = (FrameOpcode)(finalOpcode & 0x0F);
if ((finalOpcode & 0x80) == 0) {
if (opcode != ContinuationFrame) {
_continuingOpcode = opcode;
}
_continuingMessage += _unmasker->read(length);
return true;
}
// if continuing, add to and read from buffer
QIODevice* device = _unmasker;
FrameOpcode copcode = opcode;
if (opcode == ContinuationFrame) {
_continuingMessage += _unmasker->read(length);
device = new QBuffer(&_continuingMessage, this);
device->open(QIODevice::ReadOnly);
copcode = _continuingOpcode;
}
// act according to opcode
switch (copcode) {
case TextFrame:
emit webSocketMessageAvailable(device, length, true);
break;
case BinaryFrame:
emit webSocketMessageAvailable(device, length, false);
break;
case ConnectionClose:
// if this is not a response to our own close request, send a close reply
if (!_closeSent) {
closeWebSocket(GoingAway);
}
if (length >= 2) {
QDataStream stream(device);
quint16 reasonCode;
stream >> reasonCode;
emit webSocketClosed(reasonCode, device->read(length - 2));
} else {
emit webSocketClosed(0, QByteArray());
}
_socket->disconnectFromHost();
break;
case Ping:
// send the pong out immediately
writeFrameHeader(Pong, length, true);
_socket->write(device->read(length));
break;
case Pong:
qWarning() << "Got unsolicited WebSocket pong." << _address << device->read(length);
break;
default:
qWarning() << "Received unknown WebSocket opcode." << _address << opcode <<
device->read(length);
break;
}
// clear the continuing message buffer
if (opcode == ContinuationFrame) {
_continuingMessage.clear();
delete device;
}
return true;
}
void HttpConnection::writeFrameHeader (FrameOpcode opcode, int size, bool final)
{
if (_closeSent) {
qWarning() << "Writing frame header after close message." << _address << opcode;
return;
}
_socket->putChar((final ? 0x80 : 0x0) | opcode);
if (size < 126) {
_socket->putChar(size);
} else if (size < 65536) {
_socket->putChar(126);
_stream << (quint16)size;
} else {
_socket->putChar(127);
_stream << (quint64)size;
}
}
MaskFilter::MaskFilter (QIODevice* device, QObject* parent) :
QIODevice(parent),
_device(device)
{
open(ReadOnly);
}
void MaskFilter::setMask (quint32 mask)
{
_mask[0] = (mask >> 24);
_mask[1] = (mask >> 16) & 0xFF;
_mask[2] = (mask >> 8) & 0xFF;
_mask[3] = mask & 0xFF;
_position = 0;
reset();
}
qint64 MaskFilter::bytesAvailable () const
{
return _device->bytesAvailable() + QIODevice::bytesAvailable();
}
qint64 MaskFilter::readData (char* data, qint64 maxSize)
{
qint64 bytes = _device->read(data, maxSize);
for (char* end = data + bytes; data < end; data++) {
*data ^= _mask[_position];
_position = (_position + 1) % 4;
}
return bytes;
}
qint64 MaskFilter::writeData (const char* data, qint64 maxSize)
{
return _device->write(data, maxSize);
_parentManager->handleRequest(this, _requestUrl.path());
}

View file

@ -81,11 +81,6 @@ public:
*/
const QByteArray& requestContent () const { return _requestContent; }
/**
* Checks whether the request is asking to switch to a WebSocket.
*/
bool isWebSocketRequest ();
/**
* Parses the request content as form data, returning a list of header/content pairs.
*/
@ -97,28 +92,6 @@ public:
void respond (const char* code, const QByteArray& content = QByteArray(),
const char* contentType = "text/plain; charset=ISO-8859-1",
const Headers& headers = Headers());
/**
* Switches to a WebSocket.
*/
void switchToWebSocket (const char* protocol = 0);
/**
* Writes a header for a WebSocket message of the specified size. The body of the message
* should be written through the socket.
*/
void writeWebSocketHeader (int size) { writeFrameHeader(BinaryFrame, size); }
/**
* Pauses or unpauses the WebSocket. A paused WebSocket buffers messages until unpaused.
*/
void setWebSocketPaused (bool paused);
/**
* Closes the WebSocket.
*/
void closeWebSocket (quint16 reasonCode = NormalClosure, const char* reason = 0);
signals:
/**
@ -148,36 +121,14 @@ protected slots:
*/
void readContent ();
/**
* Reads any incoming WebSocket frames.
*/
void readFrames ();
protected:
/** The available WebSocket frame opcodes. */
enum FrameOpcode { ContinuationFrame, TextFrame, BinaryFrame,
ConnectionClose = 0x08, Ping, Pong };
/**
* Attempts to read a single WebSocket frame, returning true if successful.
*/
bool maybeReadFrame ();
/**
* Writes a WebSocket frame header.
*/
void writeFrameHeader (FrameOpcode opcode, int size = 0, bool final = true);
/** The parent HTTP manager. */
HttpManager* _parentManager;
/** The underlying socket. */
QTcpSocket* _socket;
/** The mask filter for WebSocket frames. */
MaskFilter* _unmasker;
/** The data stream for writing to the socket. */
QDataStream _stream;
@ -198,64 +149,6 @@ protected:
/** The content of the request. */
QByteArray _requestContent;
/** The opcode for the WebSocket message being continued. */
FrameOpcode _continuingOpcode;
/** The WebSocket message being continued. */
QByteArray _continuingMessage;
/** Whether or not the WebSocket is paused (buffering messages for future processing). */
bool _webSocketPaused;
/** Whether or not we've sent a WebSocket close message. */
bool _closeSent;
};
/**
* A filter device that applies a 32-bit mask.
*/
class MaskFilter : public QIODevice
{
Q_OBJECT
public:
/**
* Creates a new masker to filter the supplied device.
*/
MaskFilter (QIODevice* device, QObject* parent = 0);
/**
* Sets the mask to apply.
*/
void setMask (quint32 mask);
/**
* Returns the number of bytes available to read.
*/
virtual qint64 bytesAvailable () const;
protected:
/**
* Reads masked data from the underlying device.
*/
virtual qint64 readData (char* data, qint64 maxSize);
/**
* Writes masked data to the underlying device.
*/
virtual qint64 writeData (const char* data, qint64 maxSize);
/** The underlying device. */
QIODevice* _device;
/** The current mask. */
char _mask[4];
/** The current position within the mask. */
int _position;
};
#endif // HTTP_CONNECTION

View file

@ -10,40 +10,59 @@
// (https://github.com/ey6es/witgap/tree/master/src/cpp/server/http)
//
#include <QTcpSocket>
#include <QtDebug>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QMimeDatabase>
#include <QtNetwork/QTcpSocket>
#include "HttpConnection.h"
#include "HttpManager.h"
void HttpSubrequestHandler::registerSubhandler (const QString& name, HttpRequestHandler* handler) {
_subhandlers.insert(name, handler);
}
bool HttpSubrequestHandler::handleRequest (
HttpConnection* connection, const QString& name, const QString& path) {
QString subpath = path;
if (subpath.startsWith('/')) {
subpath.remove(0, 1);
bool HttpManager::handleRequest(HttpConnection* connection, const QString& path) {
QString subPath = path;
// remove any slash at the beginning of the path
if (subPath.startsWith('/')) {
subPath.remove(0, 1);
}
QString subname;
int idx = subpath.indexOf('/');
if (idx == -1) {
subname = subpath;
subpath = "";
QString filePath;
// if the last thing is a trailing slash then we want to look for index file
if (subPath.endsWith('/') || subPath.size() == 0) {
QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml";
foreach (const QString& possibleIndexFilename, possibleIndexFiles) {
if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) {
filePath = _documentRoot + subPath + possibleIndexFilename;
break;
}
}
} else if (QFileInfo(_documentRoot + subPath).isFile()) {
filePath = _documentRoot + subPath;
}
if (!filePath.isEmpty()) {
static QMimeDatabase mimeDatabase;
qDebug() << "Serving file at" << filePath;
QFile localFile(filePath);
localFile.open(QIODevice::ReadOnly);
connection->respond("200 OK", localFile.readAll(), qPrintable(mimeDatabase.mimeTypeForFile(filePath).name()));
} else {
subname = subpath.left(idx);
subpath = subpath.mid(idx + 1);
}
HttpRequestHandler* handler = _subhandlers.value(subname);
if (handler == 0 || !handler->handleRequest(connection, subname, subpath)) {
// respond with a 404
connection->respond("404 Not Found", "Resource not found.");
}
return true;
}
HttpManager::HttpManager(quint16 port, QObject* parent) :
QTcpServer(parent) {
HttpManager::HttpManager(quint16 port, const QString& documentRoot, QObject* parent) :
QTcpServer(parent),
_documentRoot(documentRoot) {
// start listening on the passed port
if (!listen(QHostAddress("0.0.0.0"), port)) {
qDebug() << "Failed to open HTTP server socket:" << errorString();
@ -54,7 +73,7 @@ HttpManager::HttpManager(quint16 port, QObject* parent) :
connect(this, SIGNAL(newConnection()), SLOT(acceptConnections()));
}
void HttpManager::acceptConnections () {
void HttpManager::acceptConnections() {
QTcpSocket* socket;
while ((socket = nextPendingConnection()) != 0) {
new HttpConnection(socket, this);

View file

@ -20,48 +20,10 @@
class HttpConnection;
class HttpRequestHandler;
/**
* Interface for HTTP request handlers.
*/
class HttpRequestHandler
{
public:
/**
* Handles an HTTP request.
*/
virtual bool handleRequest (
HttpConnection* connection, const QString& name, const QString& path) = 0;
};
/**
* Handles requests by forwarding them to subhandlers.
*/
class HttpSubrequestHandler : public HttpRequestHandler
{
public:
/**
* Registers a subhandler with the given name.
*/
void registerSubhandler (const QString& name, HttpRequestHandler* handler);
/**
* Handles an HTTP request.
*/
virtual bool handleRequest (
HttpConnection* connection, const QString& name, const QString& path);
protected:
/** Subhandlers mapped by name. */
QHash<QString, HttpRequestHandler*> _subhandlers;
};
/**
* Handles HTTP connections.
*/
class HttpManager : public QTcpServer, public HttpSubrequestHandler
class HttpManager : public QTcpServer
{
Q_OBJECT
@ -70,7 +32,13 @@ public:
/**
* Initializes the manager.
*/
HttpManager(quint16 port, QObject* parent = 0);
HttpManager(quint16 port, const QString& documentRoot, QObject* parent = 0);
/**
* Handles an HTTP request.
*/
virtual bool handleRequest (HttpConnection* connection, const QString& path);
protected slots:
@ -78,6 +46,8 @@ protected slots:
* Accepts all pending connections.
*/
void acceptConnections ();
protected:
QString _documentRoot;
};
#endif // HTTP_MANAGER