2 * Simplistic threaded http server with a one thread per request
3 * threading model. Someone should write a proper solution with
4 * worker threads and giving work to them.
7 * Copyright (C) 2009 Holger Hans Peter Freyther
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
25 #include <QCoreApplication>
26 #include <QStringList>
34 #include <QSqlDatabase>
40 class HttpRequestThread;
42 friend class HttpRequestThread;
56 QByteArray host, path;
59 HttpRequest::HttpRequest()
60 : request(Request_Unknown)
63 class HttpRequestThread : public QThread {
66 HttpRequestThread(int socket);
69 static bool lookup(const QByteArray&, QByteArray&, QByteArray&, QByteArray&);
70 static bool search(const QByteArray&, QByteArray& response,
71 QByteArray& header, QByteArray& data);
76 HttpRequest parseHeader();
77 bool sendFile(const HttpRequest&);
78 bool send404(const QByteArray& reason);
85 class HttpServer : public QTcpServer {
89 void incomingConnection(int socketDescriptor);
92 HttpServer::HttpServer(QObject* parent)
96 void HttpServer::incomingConnection(int fd)
98 QThread* thread = new HttpRequestThread(fd);
99 connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
104 HttpRequestThread::HttpRequestThread(int fd)
109 HttpRequestThread::~HttpRequestThread()
113 bool HttpRequestThread::lookup(const QByteArray& req, QByteArray& response,
114 QByteArray& headers, QByteArray& data)
116 if (search(req, response, headers, data))
119 QUrl url = QUrl::fromEncoded(req);
120 return search(url.toEncoded(QUrl::RemoveFragment | QUrl::RemoveQuery), response, headers, data);
123 bool HttpRequestThread::search(const QByteArray& req, QByteArray& response,
124 QByteArray& headers, QByteArray& data)
127 query.prepare("SELECT response, header, data from responses where url = ?");
128 query.addBindValue(req);
131 qWarning() << "Query failed: " << query.lastError();
136 qWarning() << "Stepping to first result failed";
140 response = query.value(0).toByteArray();
141 headers = query.value(1).toByteArray();
142 data = query.value(2).toByteArray();
146 HttpRequest HttpRequestThread::parseHeader()
149 bool firstLine = true;
152 while (m_socket->state() == QTcpSocket::ConnectedState) {
153 while (m_socket->canReadLine()) {
154 QByteArray line = m_socket->readLine();
159 if (line.toLower().startsWith("get ") && line.contains("HTTP/1.")) {
160 static const unsigned get_length = sizeof "get " - 1;
162 req.path = line.mid(get_length, line.lastIndexOf("HTTP/1.") - get_length - 1);
163 req.request = HttpRequest::Request_GET;
165 qWarning("Unhandled operation: %p '%s'", this, line.data());
167 } else if (line == "\r\n") {
168 qWarning("Request is %p '%d' uri: '%s'", this, req.request, req.uri.data());
170 } else if (line.toLower().startsWith("host: ")) {
171 req.host = line.mid(sizeof "host: " - 1).trimmed();
172 req.uri = "http://" + req.host + req.path;
176 m_socket->waitForReadyRead(-1);
182 bool HttpRequestThread::send404(const QByteArray& reason)
184 m_socket->write("HTTP/1.0 404 " + reason + "\r\nContent-Type: text/html\r\n\r\nNot found.\r\n");
188 bool HttpRequestThread::sendFile(const HttpRequest& req)
190 if (req.uri.isEmpty() || req.request != HttpRequest::Request_GET)
191 return send404("Bad Request");
194 QByteArray response, headers, data;
196 // try the normal uri first, then strip fragment and query
197 if (!lookup(req.uri, response, headers, data)) {
198 qWarning("Searching failed: %p '%s'\n", this, req.uri.data());
199 return send404("Not found");
202 m_socket->write("HTTP/1.1 " + response + " OK\r\n");
203 m_socket->write(headers);
204 m_socket->write(data);
209 void HttpRequestThread::run()
211 qWarning() << "=> New connection" << this;
213 m_socket = new QTcpSocket();
215 if (!m_socket->setSocketDescriptor(m_fd)) {
216 qWarning() << "Adopting the socket failed";
223 sendFile(parseHeader());
225 m_socket->disconnectFromHost();
226 m_socket->waitForDisconnected();
228 qWarning() << "<= Closing down" << this;
232 int main(int argc, char** argv)
234 QCoreApplication app(argc, argv);
236 QString fileName = "crawl_db.db";
237 if (app.arguments().size() >= 2)
238 fileName = app.arguments().at(1);
240 qWarning("Seving crawl db: '%s'", qPrintable(fileName));
241 QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
242 db.setDatabaseName(fileName);
244 qCritical() << "Failed to open the db";
246 QTcpServer* server = new HttpServer(&app);
247 if (!server->listen(QHostAddress::Any, 80))
248 qFatal("Failed to listen");
253 #include "http_server.moc"