Skip to content

Commit c7976dc

Browse files
committedJan 19, 2020
Standalone server exception handling
1 parent 5fdb3e8 commit c7976dc

File tree

1 file changed

+210
-97
lines changed

1 file changed

+210
-97
lines changed
 

‎src/server/qgis_mapserver.cpp

Lines changed: 210 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
/***************************************************************************
22
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+
48
-------------------
59
begin : Jan 17 2020
610
copyright : (C) 2020by Alessandro Pasotti
@@ -23,7 +27,6 @@
2327
#include "qgsbufferserverresponse.h"
2428
#include "qgsapplication.h"
2529
#include "qgsmessagelog.h"
26-
#include "http-parser/http_parser.h"
2730

2831
#include <QFontDatabase>
2932
#include <QString>
@@ -32,8 +35,33 @@
3235
#include <QNetworkInterface>
3336
#include <QObject>
3437

38+
#ifndef Q_OS_WIN
39+
#include <csignal>
40+
#endif
3541

3642
#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+
};
3765

3866
int main( int argc, char *argv[] )
3967
{
@@ -55,7 +83,13 @@ int main( int argc, char *argv[] )
5583
QgsMessageLog::logMessage( "DISPLAY not set, running in offscreen mode, all printing capabilities will not be available.", "Server", Qgis::Info );
5684
}
5785
// 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+
5993
QgsServer server;
6094
#ifdef HAVE_SERVER_PYTHON_PLUGINS
6195
server.initPython();
@@ -68,16 +102,19 @@ int main( int argc, char *argv[] )
68102
#endif
69103

70104
QTcpServer tcpServer;
105+
QString ipAddress;
106+
107+
// The port to listen
108+
const QString serverPort { getenv( "QGIS_SERVER_PORT" ) };
71109

72-
if ( !tcpServer.listen( QHostAddress::Any, 8081 ) )
110+
if ( !tcpServer.listen( QHostAddress::Any, serverPort.isEmpty() ? 8000 : serverPort.toInt( ) ) )
73111
{
74112
QgsMessageLog::logMessage( QObject::tr( "Unable to start the server: %1." )
75113
.arg( tcpServer.errorString() ) );
76114
tcpServer.close();
77115
}
78116
else
79117
{
80-
QString ipAddress;
81118
QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
82119
// use the first non-localhost IPv4 address
83120
for ( int i = 0; i < ipAddressesList.size(); ++i )
@@ -92,125 +129,201 @@ int main( int argc, char *argv[] )
92129
// if we did not find one, use IPv4 localhost
93130
if ( ipAddress.isEmpty() )
94131
ipAddress = QHostAddress( QHostAddress::LocalHost ).toString();
95-
QgsMessageLog::logMessage( QObject::tr( "QGIS Server is listening on %1:%2\n" )
96-
.arg( ipAddress ).arg( tcpServer.serverPort() ) );
97132

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 );
98137

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+
};
99160

100161
// Starts HTTP loop with a poor man's HTTP parser
101162
tcpServer.connect( &tcpServer, &QTcpServer::newConnection, [ & ]
102163
{
103164
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() ) );
108165

109166
clientConnection->connect( clientConnection, &QAbstractSocket::disconnected,
110167
clientConnection, &QObject::deleteLater );
111168

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, [ & ] {
115171

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
146173
{
147-
method = QgsServerRequest::Method::DeleteMethod;
148-
}
149-
else
150-
{
151-
// TODO: err if not known
152-
}
174+
const QString incomingData { clientConnection->readAll() };
153175

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+
}
157182

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+
}
163189

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" )
168194
{
169-
headers.insert( headerLine.left( headerColonPos ), headerLine.mid( headerColonPos + 1 ) );
195+
method = QgsServerRequest::Method::GetMethod;
170196
}
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" ) };
172247

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 )
203298
{
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+
205311
}
206-
clientConnection->write( "\r\n" );
207-
clientConnection->write( response.body() );
208312
clientConnection->disconnectFromHost();
209313
} );
210314

211315
} );
212316

213317
}
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+
214327
app.exec();
215328
app.exitQgis();
216329
return 0;

0 commit comments

Comments
 (0)
Please sign in to comment.