1
1
/* **************************************************************************
2
2
qgs_mapserver.cpp
3
- A server application supporting WMS / WFS / WCS
3
+
4
+ A QGIS development HTTP server for testing/development purposes.
5
+ The server listen to localhost:8000, the port can be changed with the
6
+ environment variable QGIS_SERVER_PORT
7
+
4
8
-------------------
5
9
begin : Jan 17 2020
6
10
copyright : (C) 2020by Alessandro Pasotti
23
27
#include " qgsbufferserverresponse.h"
24
28
#include " qgsapplication.h"
25
29
#include " qgsmessagelog.h"
26
- #include " http-parser/http_parser.h"
27
30
28
31
#include < QFontDatabase>
29
32
#include < QString>
32
35
#include < QNetworkInterface>
33
36
#include < QObject>
34
37
38
+ #ifndef Q_OS_WIN
39
+ #include < csignal>
40
+ #endif
35
41
36
42
#include < string>
43
+ #include < chrono>
44
+
45
+ class HttpException : public std ::exception
46
+ {
47
+
48
+ public:
49
+
50
+ HttpException ( const QString &message )
51
+ : mMessage ( message )
52
+ {
53
+ }
54
+
55
+ QString message ( )
56
+ {
57
+ return mMessage ;
58
+ }
59
+
60
+ private:
61
+
62
+ QString mMessage ;
63
+
64
+ };
37
65
38
66
int main ( int argc, char *argv[] )
39
67
{
@@ -55,7 +83,13 @@ int main( int argc, char *argv[] )
55
83
QgsMessageLog::logMessage ( " DISPLAY not set, running in offscreen mode, all printing capabilities will not be available." , " Server" , Qgis::Info );
56
84
}
57
85
// since version 3.0 QgsServer now needs a qApp so initialize QgsApplication
58
- QgsApplication app ( argc, argv, withDisplay, QString (), QStringLiteral ( " server" ) );
86
+ QgsApplication app ( argc, argv, withDisplay, QString (), QStringLiteral ( " QGIS Development Server" ) );
87
+
88
+ QCoreApplication::setOrganizationName ( QgsApplication::QGIS_ORGANIZATION_NAME );
89
+ QCoreApplication::setOrganizationDomain ( QgsApplication::QGIS_ORGANIZATION_DOMAIN );
90
+ QCoreApplication::setApplicationName ( " QGIS Development Server" );
91
+
92
+
59
93
QgsServer server;
60
94
#ifdef HAVE_SERVER_PYTHON_PLUGINS
61
95
server.initPython ();
@@ -68,16 +102,19 @@ int main( int argc, char *argv[] )
68
102
#endif
69
103
70
104
QTcpServer tcpServer;
105
+ QString ipAddress;
106
+
107
+ // The port to listen
108
+ const QString serverPort { getenv ( " QGIS_SERVER_PORT" ) };
71
109
72
- if ( !tcpServer.listen ( QHostAddress::Any, 8081 ) )
110
+ if ( !tcpServer.listen ( QHostAddress::Any, serverPort. isEmpty () ? 8000 : serverPort. toInt ( ) ) )
73
111
{
74
112
QgsMessageLog::logMessage ( QObject::tr ( " Unable to start the server: %1." )
75
113
.arg ( tcpServer.errorString () ) );
76
114
tcpServer.close ();
77
115
}
78
116
else
79
117
{
80
- QString ipAddress;
81
118
QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses ();
82
119
// use the first non-localhost IPv4 address
83
120
for ( int i = 0 ; i < ipAddressesList.size (); ++i )
@@ -92,125 +129,201 @@ int main( int argc, char *argv[] )
92
129
// if we did not find one, use IPv4 localhost
93
130
if ( ipAddress.isEmpty () )
94
131
ipAddress = QHostAddress ( QHostAddress::LocalHost ).toString ();
95
- QgsMessageLog::logMessage ( QObject::tr ( " QGIS Server is listening on %1:%2\n " )
96
- .arg ( ipAddress ).arg ( tcpServer.serverPort () ) );
97
132
133
+ const int port { tcpServer.serverPort () };
134
+ QgsMessageLog::logMessage ( QObject::tr ( " QGIS Development Server listening on http://%1:%2" )
135
+ .arg ( ipAddress ).arg ( port ),
136
+ QStringLiteral ( " QGIS Development Server" ), Qgis::Info );
98
137
138
+ #ifndef Q_OS_WIN
139
+ QgsMessageLog::logMessage ( QObject::tr ( " CTRL+C to exit" ) );
140
+ #endif
141
+
142
+ static const QMap<int , QString> knownStatuses
143
+ {
144
+ { 200 , QStringLiteral ( " OK" ) },
145
+ { 201 , QStringLiteral ( " Created" ) },
146
+ { 202 , QStringLiteral ( " Accepted" ) },
147
+ { 204 , QStringLiteral ( " No Content" ) },
148
+ { 301 , QStringLiteral ( " Moved Permanently" ) },
149
+ { 302 , QStringLiteral ( " Moved Temporarily" ) },
150
+ { 304 , QStringLiteral ( " Not Modified" ) },
151
+ { 400 , QStringLiteral ( " Bad Request" ) },
152
+ { 401 , QStringLiteral ( " Unauthorized" ) },
153
+ { 403 , QStringLiteral ( " Forbidden" ) },
154
+ { 404 , QStringLiteral ( " Not Found" ) },
155
+ { 500 , QStringLiteral ( " Internal Server Error" ) },
156
+ { 501 , QStringLiteral ( " Not Implemented" ) },
157
+ { 502 , QStringLiteral ( " Bad Gateway" ) },
158
+ { 503 , QStringLiteral ( " Service Unavailable" ) }
159
+ };
99
160
100
161
// Starts HTTP loop with a poor man's HTTP parser
101
162
tcpServer.connect ( &tcpServer, &QTcpServer::newConnection, [ & ]
102
163
{
103
164
QTcpSocket *clientConnection = tcpServer.nextPendingConnection ();
104
- QgsMessageLog::logMessage ( QObject::tr ( " Incoming connection %1 from %2:%3" )
105
- .arg ( clientConnection->peerName () )
106
- .arg ( clientConnection->peerAddress ().toString () )
107
- .arg ( clientConnection->peerPort () ) );
108
165
109
166
clientConnection->connect ( clientConnection, &QAbstractSocket::disconnected,
110
167
clientConnection, &QObject::deleteLater );
111
168
112
- clientConnection->connect ( clientConnection, &QIODevice::readyRead, [ =, &server ] {
113
- const QString incomingData { clientConnection->readAll () };
114
- QgsMessageLog::logMessage ( QObject::tr ( " Incoming data: %1" ).arg ( incomingData ) );
169
+ // Incoming connection parser
170
+ clientConnection->connect ( clientConnection, &QIODevice::readyRead, [ & ] {
115
171
116
- // Parse protocol and URL GET /path HTTP/1.1
117
- int firstLinePos { incomingData.indexOf ( " \r\n " ) };
118
- // TODO: err if -1
119
- const QString firstLine { incomingData.left ( firstLinePos ) };
120
- const QStringList firstLinePieces { firstLine.split ( ' ' ) };
121
- // TODO: check pieces are not 3
122
- const QString methodString { firstLinePieces.at ( 0 ) };
123
-
124
- QgsServerRequest::Method method;
125
- if ( methodString == " GET" )
126
- {
127
- method = QgsServerRequest::Method::GetMethod;
128
- }
129
- else if ( methodString == " POST" )
130
- {
131
- method = QgsServerRequest::Method::PostMethod;
132
- }
133
- else if ( methodString == " HEAD" )
134
- {
135
- method = QgsServerRequest::Method::HeadMethod;
136
- }
137
- else if ( methodString == " PUT" )
138
- {
139
- method = QgsServerRequest::Method::PutMethod;
140
- }
141
- else if ( methodString == " PATCH" )
142
- {
143
- method = QgsServerRequest::Method::PatchMethod;
144
- }
145
- else if ( methodString == " DELETE" )
172
+ try
146
173
{
147
- method = QgsServerRequest::Method::DeleteMethod;
148
- }
149
- else
150
- {
151
- // TODO: err if not known
152
- }
174
+ const QString incomingData { clientConnection->readAll () };
153
175
154
- const QString url { firstLinePieces.at ( 1 )};
155
- const QString protocol { firstLinePieces.at ( 2 )};
156
- // TODO: err if not HTTP/1.0
176
+ // Parse protocol and URL GET /path HTTP/1.1
177
+ int firstLinePos { incomingData.indexOf ( " \r\n " ) };
178
+ if ( firstLinePos == -1 )
179
+ {
180
+ throw HttpException ( QStringLiteral ( " HTTP error finding protocol header" ) );
181
+ }
157
182
158
- // Headers
159
- QgsBufferServerRequest::Headers headers;
160
- int endHeadersPos { incomingData.indexOf ( " \r\n\r\n " ) };
161
- // TODO: err if -1
162
- const QStringList httpHeaders { incomingData.mid ( firstLinePos + 2 , endHeadersPos - firstLinePos ).split ( " \r\n " ) };
183
+ const QString firstLine { incomingData.left ( firstLinePos ) };
184
+ const QStringList firstLinePieces { firstLine.split ( ' ' ) };
185
+ if ( firstLinePieces.size () != 3 )
186
+ {
187
+ throw HttpException ( QStringLiteral ( " HTTP error splitting protocol header" ) );
188
+ }
163
189
164
- for ( const auto &headerLine : httpHeaders )
165
- {
166
- const int headerColonPos { headerLine. indexOf ( ' : ' ) } ;
167
- if ( headerColonPos > 0 )
190
+ const QString methodString { firstLinePieces. at ( 0 ) };
191
+
192
+ QgsServerRequest::Method method ;
193
+ if ( methodString == " GET " )
168
194
{
169
- headers. insert ( headerLine. left ( headerColonPos ), headerLine. mid ( headerColonPos + 1 ) ) ;
195
+ method = QgsServerRequest::Method::GetMethod ;
170
196
}
171
- }
197
+ else if ( methodString == " POST" )
198
+ {
199
+ method = QgsServerRequest::Method::PostMethod;
200
+ }
201
+ else if ( methodString == " HEAD" )
202
+ {
203
+ method = QgsServerRequest::Method::HeadMethod;
204
+ }
205
+ else if ( methodString == " PUT" )
206
+ {
207
+ method = QgsServerRequest::Method::PutMethod;
208
+ }
209
+ else if ( methodString == " PATCH" )
210
+ {
211
+ method = QgsServerRequest::Method::PatchMethod;
212
+ }
213
+ else if ( methodString == " DELETE" )
214
+ {
215
+ method = QgsServerRequest::Method::DeleteMethod;
216
+ }
217
+ else
218
+ {
219
+ throw HttpException ( QStringLiteral ( " HTTP error unsupported method: %1" ).arg ( methodString ) );
220
+ }
221
+
222
+ // Build URL from env ...
223
+ QString url { getenv ( " REQUEST_URI" ) };
224
+ // ... or from server ip/port and request path
225
+ if ( url.isEmpty () )
226
+ {
227
+ const QString path { firstLinePieces.at ( 1 )};
228
+ url = QStringLiteral ( " http://%1:%2%3" ).arg ( ipAddress ).arg ( port ).arg ( path );
229
+ }
230
+
231
+ const QString protocol { firstLinePieces.at ( 2 )};
232
+ if ( protocol != QStringLiteral ( " HTTP/1.0" ) )
233
+ {
234
+ throw HttpException ( QStringLiteral ( " HTTP error unsupported protocol: %1" ).arg ( protocol ) );
235
+ }
236
+
237
+ // Headers
238
+ QgsBufferServerRequest::Headers headers;
239
+ int endHeadersPos { incomingData.indexOf ( " \r\n\r\n " ) };
240
+
241
+ if ( endHeadersPos == -1 )
242
+ {
243
+ throw HttpException ( QStringLiteral ( " HTTP error finding headers" ) );
244
+ }
245
+
246
+ const QStringList httpHeaders { incomingData.mid ( firstLinePos + 2 , endHeadersPos - firstLinePos ).split ( " \r\n " ) };
172
247
173
- // Inefficient copy :(
174
- QByteArray data { incomingData.mid ( endHeadersPos + 2 ).toUtf8 () };
175
- QgsBufferServerRequest request { url, method, headers, &data };
176
- QgsBufferServerResponse response;
177
-
178
- server.handleRequest ( request, response );
179
-
180
- static const QMap<int , QString> knownStatuses {
181
- { 200 , QStringLiteral ( " OK" ) },
182
- { 201 , QStringLiteral ( " Created" ) },
183
- { 202 , QStringLiteral ( " Accepted" ) },
184
- { 204 , QStringLiteral ( " No Content" ) },
185
- { 301 , QStringLiteral ( " Moved Permanently" ) },
186
- { 302 , QStringLiteral ( " Moved Temporarily" ) },
187
- { 304 , QStringLiteral ( " Not Modified" ) },
188
- { 400 , QStringLiteral ( " Bad Request" ) },
189
- { 401 , QStringLiteral ( " Unauthorized" ) },
190
- { 403 , QStringLiteral ( " Forbidden" ) },
191
- { 404 , QStringLiteral ( " Not Found" ) },
192
- { 500 , QStringLiteral ( " Internal Server Error" ) },
193
- { 501 , QStringLiteral ( " Not Implemented" ) },
194
- { 502 , QStringLiteral ( " Bad Gateway" ) },
195
- { 503 , QStringLiteral ( " Service Unavailable" ) }
196
- };
197
-
198
- // Output stream
199
- clientConnection->write ( QStringLiteral ( " HTTP/1.0 %1 \r\n " ).arg ( response.statusCode () ).arg ( knownStatuses.value ( response.statusCode () ) ).toUtf8 () );
200
- clientConnection->write ( QStringLiteral ( " Server: QGIS\r\n " ).toUtf8 () );
201
- const auto responseHeaders { response.headers () };
202
- for ( auto it = responseHeaders.constBegin (); it != responseHeaders.constEnd (); ++it )
248
+ for ( const auto &headerLine : httpHeaders )
249
+ {
250
+ const int headerColonPos { headerLine.indexOf ( ' :' ) };
251
+ if ( headerColonPos > 0 )
252
+ {
253
+ headers.insert ( headerLine.left ( headerColonPos ), headerLine.mid ( headerColonPos + 1 ) );
254
+ }
255
+ }
256
+
257
+ // Inefficient copy :(
258
+ QByteArray data { incomingData.mid ( endHeadersPos + 2 ).toUtf8 () };
259
+
260
+ auto start = std::chrono::steady_clock::now ();
261
+
262
+ QgsBufferServerRequest request { url, method, headers, &data };
263
+ QgsBufferServerResponse response;
264
+
265
+ server.handleRequest ( request, response );
266
+
267
+ auto elapsedTime { std::chrono::steady_clock::now () - start };
268
+
269
+ if ( ! knownStatuses.contains ( response.statusCode () ) )
270
+ {
271
+ throw HttpException ( QStringLiteral ( " HTTP error unsupported status code: %1" ).arg ( response.statusCode () ) );
272
+ }
273
+
274
+ const auto responseHeaders { response.headers () };
275
+ for ( auto it = responseHeaders.constBegin (); it != responseHeaders.constEnd (); ++it )
276
+ {
277
+ clientConnection->write ( QStringLiteral ( " %1: %2\r\n " ).arg ( it.key (), it.value () ).toUtf8 () );
278
+ }
279
+ // Output stream
280
+ clientConnection->write ( QStringLiteral ( " HTTP/1.0 %1 %2\r\n " ).arg ( response.statusCode () ).arg ( knownStatuses.value ( response.statusCode () ) ).toUtf8 () );
281
+ clientConnection->write ( QStringLiteral ( " Server: QGIS\r\n " ).toUtf8 () );
282
+ clientConnection->write ( " \r\n " );
283
+ const QByteArray body { response.body () };
284
+ clientConnection->write ( body );
285
+
286
+ // 10.185.248.71 [09/Jan/2015:19:12:06 +0000] 808840 <time> "GET / HTTP/1.1" 500"
287
+ QgsMessageLog::logMessage ( QStringLiteral ( " %1 [%2] %3 %4 \" %5\" %6" )
288
+ .arg ( clientConnection->peerAddress ().toString () )
289
+ .arg ( QDateTime::currentDateTime ().toString () )
290
+ .arg ( body.size () )
291
+ .arg ( std::chrono::duration_cast<std::chrono::milliseconds>( elapsedTime ).count () )
292
+ .arg ( firstLinePieces.join ( ' ' ) )
293
+ .arg ( response.statusCode () ),
294
+ QStringLiteral ( " QGIS Development Server" ), Qgis::Info );
295
+
296
+ }
297
+ catch ( HttpException &ex )
203
298
{
204
- clientConnection->write ( QStringLiteral ( " %1: %2\r\n " ).arg ( it.key (), it.value () ).toUtf8 () );
299
+ // Output stream: send error
300
+ clientConnection->write ( QStringLiteral ( " HTTP/1.0 %1 %2\r\n " ).arg ( 500 ).arg ( knownStatuses.value ( 500 ) ).toUtf8 () );
301
+ clientConnection->write ( QStringLiteral ( " Server: QGIS\r\n " ).toUtf8 () );
302
+ clientConnection->write ( " \r\n " );
303
+ clientConnection->write ( ex.message ().toUtf8 () );
304
+
305
+ QgsMessageLog::logMessage ( QStringLiteral ( " %1 [%2] \" %3\" - - 500" )
306
+ .arg ( clientConnection->peerAddress ().toString () )
307
+ .arg ( QDateTime::currentDateTime ().toString () )
308
+ .arg ( ex.message () ),
309
+ QStringLiteral ( " QGIS Development Server" ), Qgis::Info );
310
+
205
311
}
206
- clientConnection->write ( " \r\n " );
207
- clientConnection->write ( response.body () );
208
312
clientConnection->disconnectFromHost ();
209
313
} );
210
314
211
315
} );
212
316
213
317
}
318
+
319
+ // Exit handlers
320
+ #ifndef Q_OS_WIN
321
+ signal ( SIGTERM, []( int ) { qApp->quit (); } );
322
+ signal ( SIGABRT, []( int ) { qApp->quit (); } );
323
+ signal ( SIGINT, []( int ) { qApp->quit (); } );
324
+ signal ( SIGKILL, []( int ) { qApp->quit (); } );
325
+ #endif
326
+
214
327
app.exec ();
215
328
app.exitQgis ();
216
329
return 0 ;
0 commit comments