http_server: Proper fix for the socket close during header parse..
[qtwebkit:performance.git] / host-tools / http_server / http_server.cpp
1 /*
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.
5  *
6  *
7  * Copyright (C) 2009 Holger Hans Peter Freyther
8  *
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.
13  *
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.
18  *
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.
23  */
24
25 #include <QCoreApplication>
26 #include <QStringList>
27 #include <QThread>
28 #include <QUrl>
29 #include <QVariant>
30
31 #include <QTcpSocket>
32 #include <QTcpServer>
33
34 #include <QSqlDatabase>
35 #include <QSqlError>
36 #include <QSqlQuery>
37
38 #include <unistd.h>
39
40 class HttpRequestThread;
41 class HttpRequest {
42     friend class HttpRequestThread;
43 public:
44     HttpRequest();
45
46     enum Request {
47         Request_Unknown,
48         Request_GET,
49         Request_POST,
50     };
51
52     QByteArray uri;
53     Request request;
54
55 private:
56     QByteArray host, path;
57 };
58
59 HttpRequest::HttpRequest()
60     : request(Request_Unknown)
61 {}
62
63 class HttpRequestThread : public QThread {
64     Q_OBJECT
65 public:
66     HttpRequestThread(int socket);
67     ~HttpRequestThread();
68
69     static bool lookup(const QByteArray&, QByteArray&, QByteArray&, QByteArray&);
70     static bool search(const QByteArray&, QByteArray& response,
71                        QByteArray& header, QByteArray& data);
72
73 private:
74     void run();
75
76     HttpRequest parseHeader();
77     bool sendFile(const HttpRequest&);
78     bool send404(const QByteArray& reason);
79
80 private:
81     int m_fd;
82     QTcpSocket* m_socket;
83 };
84
85 class HttpServer : public QTcpServer {
86     Q_OBJECT
87 public:
88     HttpServer(QObject*);
89     void incomingConnection(int socketDescriptor);
90 };
91
92 HttpServer::HttpServer(QObject* parent)
93     : QTcpServer(parent)
94 {}
95
96 void HttpServer::incomingConnection(int fd)
97 {
98     QThread* thread = new HttpRequestThread(fd);
99     connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
100     thread->start();
101 }
102
103
104 HttpRequestThread::HttpRequestThread(int fd)
105     : m_fd(fd)
106     , m_socket(0)
107 {}
108
109 HttpRequestThread::~HttpRequestThread()
110 {
111 }
112
113 bool HttpRequestThread::lookup(const QByteArray& req, QByteArray& response,
114                                QByteArray& headers, QByteArray& data)
115 {
116     if (search(req, response, headers, data))
117         return true;
118
119     QUrl url = QUrl::fromEncoded(req);
120     return search(url.toEncoded(QUrl::RemoveFragment | QUrl::RemoveQuery), response, headers, data); 
121 }
122
123 bool HttpRequestThread::search(const QByteArray& req, QByteArray& response,
124                                QByteArray& headers, QByteArray& data)
125 {
126     QSqlQuery query;
127     query.prepare("SELECT response, header, data from responses where url = ?");
128     query.addBindValue(req);
129
130     if (!query.exec()) {
131         qWarning() << "Query failed: " << query.lastError();
132         return false;
133     }
134
135     if (!query.next()) {
136         qWarning() << "Stepping to first result failed";
137         return false;
138     }
139
140     response = query.value(0).toByteArray();
141     headers = query.value(1).toByteArray();
142     data = query.value(2).toByteArray();
143     return true;
144 }
145
146 HttpRequest HttpRequestThread::parseHeader()
147 {
148     HttpRequest req;
149     bool firstLine = true;
150
151     // blocking reads...
152     while (m_socket->state() == QTcpSocket::ConnectedState) {
153         while (m_socket->canReadLine()) {
154             QByteArray line = m_socket->readLine();
155
156             if (firstLine) {
157                 firstLine = false;
158
159                 if (line.toLower().startsWith("get ") && line.contains("HTTP/1.")) {
160                     static const unsigned get_length = sizeof "get " - 1;
161
162                     req.path = line.mid(get_length, line.lastIndexOf("HTTP/1.") - get_length - 1);
163                     req.request = HttpRequest::Request_GET;
164                 } else {
165                     qWarning("Unhandled operation: %p '%s'", this, line.data());
166                 }
167             } else if (line == "\r\n") {
168                 qWarning("Request is %p '%d' uri: '%s'", this, req.request, req.uri.data());
169                 return req;
170             } else if (line.toLower().startsWith("host: ")) {
171                 req.host = line.mid(sizeof "host: " - 1).trimmed();
172                 req.uri = "http://" + req.host + req.path;
173             }
174         }
175
176         m_socket->waitForReadyRead(-1);
177     }
178
179     return req;
180 }
181
182 bool HttpRequestThread::send404(const QByteArray& reason)
183 {
184     m_socket->write("HTTP/1.0 404 " + reason + "\r\nContent-Type: text/html\r\n\r\nNot found.\r\n");
185     return false;
186 }
187
188 bool HttpRequestThread::sendFile(const HttpRequest& req)
189 {
190     if (req.uri.isEmpty() || req.request != HttpRequest::Request_GET)
191         return send404("Bad Request");
192
193
194     QByteArray response, headers, data;
195
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");
200     }
201
202     m_socket->write("HTTP/1.1 " + response + " OK\r\n");
203     m_socket->write(headers);
204     m_socket->write(data);
205
206     return true;
207 }
208
209 void HttpRequestThread::run()
210 {
211     qWarning() << "=> New connection" << this;
212
213     m_socket = new QTcpSocket();
214
215     if (!m_socket->setSocketDescriptor(m_fd)) {
216         qWarning() << "Adopting the socket failed";
217         close(m_fd);
218         m_fd = -1;
219         delete m_socket;
220         return;
221     }
222
223     sendFile(parseHeader());
224     m_socket->flush();
225     m_socket->disconnectFromHost();
226     m_socket->waitForDisconnected();
227     delete m_socket;
228     qWarning() << "<= Closing down" << this;
229 }
230
231
232 int main(int argc, char** argv)
233 {
234     QCoreApplication app(argc, argv);
235
236     QString fileName = "crawl_db.db";
237     if (app.arguments().size() >= 2)
238         fileName = app.arguments().at(1);
239
240     qWarning("Seving crawl db: '%s'", qPrintable(fileName));
241     QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
242     db.setDatabaseName(fileName);
243     if (!db.open())
244         qCritical() << "Failed to open the db";
245
246     QTcpServer* server = new HttpServer(&app);
247     if (!server->listen(QHostAddress::Any, 80))
248         qFatal("Failed to listen");
249
250     return app.exec();
251 }
252
253 #include "http_server.moc"