30 #include "qgsconfig.h" 
   37 #include <QFontDatabase> 
   41 #include <QNetworkInterface> 
   42 #include <QCommandLineParser> 
   56 QAtomicInt IS_RUNNING = 1;
 
   62 class HttpException: 
public std::exception
 
   70     HttpException( 
const QString &message )
 
   89 int main( 
int argc, 
char *argv[] )
 
  100   const QString display { qgetenv( 
"DISPLAY" ) };
 
  101   bool withDisplay = 
true;
 
  102   if ( display.isEmpty() )
 
  105     qputenv( 
"QT_QPA_PLATFORM", 
"offscreen" );
 
  109   QgsApplication app( argc, argv, withDisplay, QString(), QStringLiteral( 
"QGIS Development Server" ) );
 
  113   QCoreApplication::setApplicationName( 
"QGIS Development Server" );
 
  114   QCoreApplication::setApplicationVersion( VERSION );
 
  118     QgsMessageLog::logMessage( 
"DISPLAY environment variable is not set, running in offscreen mode, all printing capabilities will not be available.\n" 
  119                                "Consider installing an X server like 'xvfb' and export DISPLAY to the actual display value.", 
"Server", 
Qgis::Warning );
 
  125   QFontDatabase fontDB;
 
  129   QString serverPort { qgetenv( 
"QGIS_SERVER_PORT" ) };
 
  131   QString ipAddress { qgetenv( 
"QGIS_SERVER_ADDRESS" ) };
 
  133   if ( serverPort.isEmpty() )
 
  135     serverPort = QStringLiteral( 
"8000" );
 
  138   if ( ipAddress.isEmpty() )
 
  140     ipAddress = QStringLiteral( 
"localhost" );
 
  143   QCommandLineParser parser;
 
  144   parser.setApplicationDescription( QObject::tr( 
"QGIS Development Server %1" ).arg( VERSION ) );
 
  145   parser.addHelpOption();
 
  146   parser.addVersionOption();
 
  147   parser.addPositionalArgument( QStringLiteral( 
"addressAndPort" ),
 
  148                                 QObject::tr( 
"Address and port (default: \"localhost:8000\")\n" 
  149                                     "address and port can also be specified with the environment\n" 
  150                                     "variables QGIS_SERVER_ADDRESS and QGIS_SERVER_PORT." ), QStringLiteral( 
"[address:port]" ) );
 
  151   QCommandLineOption logLevelOption( 
"l", QObject::tr( 
"Log level (default: 0)\n" 
  154                                      "2: CRITICAL" ), 
"logLevel", 
"0" );
 
  155   parser.addOption( logLevelOption );
 
  157   QCommandLineOption projectOption( 
"p", QObject::tr( 
"Path to a QGIS project file (*.qgs or *.qgz),\n" 
  158                                     "if specified it will override the query string MAP argument\n" 
  159                                     "and the QGIS_PROJECT_FILE environment variable." ), 
"projectPath", 
"" );
 
  160   parser.addOption( projectOption );
 
  162   parser.process( app );
 
  163   const QStringList args = parser.positionalArguments();
 
  165   if ( args.size() == 1 )
 
  167     QStringList addressAndPort { args.at( 0 ).split( 
':' ) };
 
  168     if ( addressAndPort.size() == 2 )
 
  170       ipAddress = addressAndPort.at( 0 );
 
  171       serverPort = addressAndPort.at( 1 );
 
  175   QString logLevel = parser.value( logLevelOption );
 
  176   qunsetenv( 
"QGIS_SERVER_LOG_FILE" );
 
  177   qputenv( 
"QGIS_SERVER_LOG_LEVEL", logLevel.toUtf8() );
 
  178   qputenv( 
"QGIS_SERVER_LOG_STDERR", 
"1" );
 
  180   if ( ! parser.value( projectOption ).isEmpty( ) )
 
  183     const QString projectFilePath { parser.value( projectOption ) };
 
  184     if ( ! QFile::exists( projectFilePath ) )
 
  186       std::cout << QObject::tr( 
"Project file not found, the option will be ignored." ).toStdString() << std::endl;
 
  190       qputenv( 
"QGIS_PROJECT_FILE", projectFilePath.toUtf8() );
 
  198   QTcpServer tcpServer;
 
  200   QHostAddress address { QHostAddress::AnyIPv4 };
 
  201   address.setAddress( ipAddress );
 
  203   if ( ! tcpServer.listen( address, serverPort.toInt( ) ) )
 
  205     std::cerr << QObject::tr( 
"Unable to start the server: %1." )
 
  206               .arg( tcpServer.errorString() ).toStdString() << std::endl;
 
  213     const int port { tcpServer.serverPort() };
 
  215     QAtomicInt connCounter { 0 };
 
  217     static const QMap<int, QString> knownStatuses
 
  219       { 200, QStringLiteral( 
"OK" ) },
 
  220       { 201, QStringLiteral( 
"Created" ) },
 
  221       { 202, QStringLiteral( 
"Accepted" ) },
 
  222       { 204, QStringLiteral( 
"No Content" ) },
 
  223       { 301, QStringLiteral( 
"Moved Permanently" ) },
 
  224       { 302, QStringLiteral( 
"Moved Temporarily" ) },
 
  225       { 304, QStringLiteral( 
"Not Modified" ) },
 
  226       { 400, QStringLiteral( 
"Bad Request" ) },
 
  227       { 401, QStringLiteral( 
"Unauthorized" ) },
 
  228       { 403, QStringLiteral( 
"Forbidden" ) },
 
  229       { 404, QStringLiteral( 
"Not Found" ) },
 
  230       { 500, QStringLiteral( 
"Internal Server Error" ) },
 
  231       { 501, QStringLiteral( 
"Not Implemented" ) },
 
  232       { 502, QStringLiteral( 
"Bad Gateway" ) },
 
  233       { 503, QStringLiteral( 
"Service Unavailable" ) }
 
  238 #ifdef HAVE_SERVER_PYTHON_PLUGINS 
  242     std::cout << QObject::tr( 
"QGIS Development Server listening on http://%1:%2" )
 
  243               .arg( ipAddress ).arg( port ).toStdString() << std::endl;
 
  245     std::cout << QObject::tr( 
"CTRL+C to exit" ).toStdString() << std::endl;
 
  252     auto httpHandler = [ & ]( QTcpSocket * clientConnection )
 
  259       QString incomingData;
 
  262       while ( IS_RUNNING && clientConnection->state() == QAbstractSocket::SocketState::ConnectedState )
 
  265         if ( ! clientConnection->bytesAvailable() )
 
  267           qApp->processEvents();
 
  272         while ( IS_RUNNING && clientConnection->bytesAvailable() > 0 )
 
  274           incomingData.append( clientConnection->readAll() );
 
  280           int firstLinePos { incomingData.indexOf( 
"\r\n" ) };
 
  281           if ( firstLinePos == -1 )
 
  283             throw HttpException( QStringLiteral( 
"HTTP error finding protocol header" ) );
 
  286           const QString firstLine { incomingData.left( firstLinePos ) };
 
  287           const QStringList firstLinePieces { firstLine.split( 
' ' ) };
 
  288           if ( firstLinePieces.size() != 3 )
 
  290             throw HttpException( QStringLiteral( 
"HTTP error splitting protocol header" ) );
 
  293           const QString methodString { firstLinePieces.at( 0 ) };
 
  296           if ( methodString == 
"GET" )
 
  298             method = QgsServerRequest::Method::GetMethod;
 
  300           else if ( methodString == 
"POST" )
 
  302             method = QgsServerRequest::Method::PostMethod;
 
  304           else if ( methodString == 
"HEAD" )
 
  306             method = QgsServerRequest::Method::HeadMethod;
 
  308           else if ( methodString == 
"PUT" )
 
  310             method = QgsServerRequest::Method::PutMethod;
 
  312           else if ( methodString == 
"PATCH" )
 
  314             method = QgsServerRequest::Method::PatchMethod;
 
  316           else if ( methodString == 
"DELETE" )
 
  318             method = QgsServerRequest::Method::DeleteMethod;
 
  322             throw HttpException( QStringLiteral( 
"HTTP error unsupported method: %1" ).arg( methodString ) );
 
  325           const QString protocol { firstLinePieces.at( 2 )};
 
  326           if ( protocol != QLatin1String( 
"HTTP/1.0" ) && protocol != QLatin1String( 
"HTTP/1.1" ) )
 
  328             throw HttpException( QStringLiteral( 
"HTTP error unsupported protocol: %1" ).arg( protocol ) );
 
  333           int endHeadersPos { incomingData.indexOf( 
"\r\n\r\n" ) };
 
  335           if ( endHeadersPos == -1 )
 
  337             throw HttpException( QStringLiteral( 
"HTTP error finding headers" ) );
 
  340           const QStringList httpHeaders { incomingData.mid( firstLinePos + 2, endHeadersPos - firstLinePos ).split( 
"\r\n" ) };
 
  342           for ( 
const auto &headerLine : httpHeaders )
 
  344             const int headerColonPos { headerLine.indexOf( 
':' ) };
 
  345             if ( headerColonPos > 0 )
 
  347               headers.insert( headerLine.left( headerColonPos ), headerLine.mid( headerColonPos + 2 ) );
 
  351           const int headersSize { endHeadersPos + 4 };
 
  354           if ( headers.contains( QStringLiteral( 
"Content-Length" ) ) )
 
  357             const int contentLength { headers.value( QStringLiteral( 
"Content-Length" ) ).toInt( &ok ) };
 
  358             if ( ok && contentLength > incomingData.length() - headersSize )
 
  367           QString url { qgetenv( 
"REQUEST_URI" ) };
 
  371             const QString path { firstLinePieces.at( 1 )};
 
  373             if ( headers.contains( QStringLiteral( 
"Host" ) ) )
 
  375               url = QStringLiteral( 
"http://%1%2" ).arg( headers.value( QStringLiteral( 
"Host" ) ), path );
 
  379               url = QStringLiteral( 
"http://%1:%2%3" ).arg( ipAddress ).arg( port ).arg( path );
 
  384           QByteArray data { incomingData.mid( headersSize ).toUtf8() };
 
  386           auto start = std::chrono::steady_clock::now();
 
  395           if ( clientConnection->state() != QAbstractSocket::SocketState::ConnectedState )
 
  400           auto elapsedTime { std::chrono::steady_clock::now() - start };
 
  402           if ( ! knownStatuses.contains( response.
statusCode() ) )
 
  404             throw HttpException( QStringLiteral( 
"HTTP error unsupported status code: %1" ).arg( response.
statusCode() ) );
 
  408           clientConnection->write( QStringLiteral( 
"HTTP/1.0 %1 %2\r\n" ).arg( response.
statusCode() ).arg( knownStatuses.value( response.
statusCode() ) ).toUtf8() );
 
  409           clientConnection->write( QStringLiteral( 
"Server: QGIS\r\n" ).toUtf8() );
 
  410           const auto responseHeaders { response.
headers() };
 
  411           for ( 
auto it = responseHeaders.constBegin(); it != responseHeaders.constEnd(); ++it )
 
  413             clientConnection->write( QStringLiteral( 
"%1: %2\r\n" ).arg( it.key(), it.value() ).toUtf8() );
 
  415           clientConnection->write( 
"\r\n" );
 
  416           const QByteArray body { response.
body() };
 
  417           clientConnection->write( body );
 
  420           std::cout << QStringLiteral( 
"\033[1;92m%1 [%2] %3 %4ms \"%5\" %6\033[0m" )
 
  421                     .arg( clientConnection->peerAddress().toString(),
 
  422                           QDateTime::currentDateTime().toString(),
 
  423                           QString::number( body.size() ),
 
  424                           QString::number( std::chrono::duration_cast<std::chrono::milliseconds>( elapsedTime ).count() ),
 
  425                           firstLinePieces.join( 
' ' ),
 
  430           clientConnection->disconnectFromHost();
 
  432         catch ( HttpException &ex )
 
  435           if ( clientConnection->state() != QAbstractSocket::SocketState::ConnectedState )
 
  441           clientConnection->write( QStringLiteral( 
"HTTP/1.0 %1 %2\r\n" ).arg( 500 ).arg( knownStatuses.value( 500 ) ).toUtf8() );
 
  442           clientConnection->write( QStringLiteral( 
"Server: QGIS\r\n" ).toUtf8() );
 
  443           clientConnection->write( 
"\r\n" );
 
  444           clientConnection->write( ex.message().toUtf8() );
 
  446           std::cout << QStringLiteral( 
"\033[1;31m%1 [%2] \"%3\" - - 500\033[0m" )
 
  447                     .arg( clientConnection->peerAddress().toString() )
 
  448                     .arg( QDateTime::currentDateTime().toString() )
 
  449                     .arg( ex.message() ).toStdString() << std::endl;
 
  451           clientConnection->disconnectFromHost();
 
  456       clientConnection->deleteLater();
 
  462     QTimer::singleShot( 0, [ & ]
 
  466         if ( tcpServer.hasPendingConnections() )
 
  468           QTcpSocket *clientConnection = tcpServer.nextPendingConnection();
 
  469           if ( clientConnection )
 
  471             httpHandler( clientConnection );
 
  476           qApp->processEvents( );
 
  477           std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
 
  486   auto exitHandler = [ ]( 
int signal )
 
  488     std::cout << QStringLiteral( 
"Signal %1 received: quitting" ).arg( signal ).toStdString() << std::endl;
 
  493   signal( SIGTERM, exitHandler );
 
  494   signal( SIGABRT, exitHandler );
 
  495   signal( SIGINT, exitHandler );
 
  496   signal( SIGPIPE, [ ]( 
int )
 
  498     std::cerr << QStringLiteral( 
"Signal SIGPIPE received: ignoring" ).toStdString() << std::endl;
 
Extends QApplication to provide access to QGIS specific resources such as theme paths,...
static const char * QGIS_ORGANIZATION_DOMAIN
static const char * QGIS_ORGANIZATION_NAME
Class defining request with data.
Class defining buffered response.
QByteArray body() const
Returns body.
QMap< QString, QString > headers() const override
Returns all the headers.
int statusCode() const override
Returns the http status code.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Method
HTTP Method (or equivalent) used for the request.
QMap< QString, QString > Headers
The QgsServer class provides OGC web services.
void handleRequest(QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project=nullptr)
Handles the request.
int main(int argc, char *argv[])