Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[Server][FEATURE] Handle request from QgsServer with a QgsProject
With this commit, it's posssible to handle a request from a QgsProject without writing it to the disk.

```python
server = QgsServer()
project = QgsProject()
vlayer = QgsVectorLayer("/path/to/shapefile/file.shp", "layer_name_you_like", "ogr")
project.addMapLayer(vlayer)

query_string = 'https://www.qgis.org/?SERVICE=WMS&VERSION=1.3&REQUEST=GetCapabilities'
request = QgsBufferServerRequest(query_string, QgsServerRequest.GetMethod, {}, data)
response = QgsBufferServerResponse()
server.handleRequest(request, response, project)
```
  • Loading branch information
rldhont committed Oct 20, 2017
1 parent 13a00aa commit 7b455d8
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 11 deletions.
5 changes: 4 additions & 1 deletion python/server/qgsserver.sip
Expand Up @@ -35,7 +35,7 @@ class QgsServer
.. versionadded:: 2.14
%End

void handleRequest( QgsServerRequest &request, QgsServerResponse &response );
void handleRequest( QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project = 0 );
%Docstring
Handles the request.
The query string is normally read from environment
Expand All @@ -44,6 +44,9 @@ class QgsServer

\param request a QgsServerRequest holding request parameters
\param response a QgsServerResponse for handling response I/O)
\param project a QgsProject or None, if it is None the project
is created from the MAP param specified in request or from
the QGIS_PROJECT_FILE setting
%End


Expand Down
24 changes: 15 additions & 9 deletions src/server/qgsserver.cpp
Expand Up @@ -300,8 +300,7 @@ void QgsServer::putenv( const QString &var, const QString &val )
* @param queryString
* @return response headers and body
*/

void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &response )
void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project )
{
QgsMessageLog::MessageLevel logLevel = QgsServerLogger::instance()->logLevel();
QTime time; //used for measuring request time if loglevel < 1
Expand Down Expand Up @@ -346,16 +345,23 @@ void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &res
printRequestParameters( parameterMap, logLevel );

//Config file path
QString configFilePath = configPath( *sConfigFilePath, parameterMap );

// load the project if needed and not empty
const QgsProject *project = mConfigCache->project( configFilePath );
if ( ! project )
{
throw QgsServerException( QStringLiteral( "Project file error" ) );
}
QString configFilePath = configPath( *sConfigFilePath, parameterMap );

// load the project if needed and not empty
project = mConfigCache->project( configFilePath );
if ( ! project )
{
throw QgsServerException( QStringLiteral( "Project file error" ) );
}

sServerInterface->setConfigFilePath( configFilePath );
sServerInterface->setConfigFilePath( configFilePath );
}
else
{
sServerInterface->setConfigFilePath( project->fileName() );
}

//Service parameter
QString serviceString = parameterMap.value( QStringLiteral( "SERVICE" ) );
Expand Down
5 changes: 4 additions & 1 deletion src/server/qgsserver.h
Expand Up @@ -75,8 +75,11 @@ class SERVER_EXPORT QgsServer
*
* \param request a QgsServerRequest holding request parameters
* \param response a QgsServerResponse for handling response I/O)
* \param project a QgsProject or nullptr, if it is nullptr the project
* is created from the MAP param specified in request or from
* the QGIS_PROJECT_FILE setting
*/
void handleRequest( QgsServerRequest &request, QgsServerResponse &response );
void handleRequest( QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project = nullptr );


//! Returns a pointer to the server interface
Expand Down
21 changes: 21 additions & 0 deletions tests/src/python/test_qgsserver.py
Expand Up @@ -214,6 +214,17 @@ def _execute_request(self, qs, requestMethod=QgsServerRequest.GetMethod, data=No
headers.append(("%s: %s" % (k, rh[k])).encode('utf-8'))
return b"\n".join(headers) + b"\n\n", bytes(response.body())

def _execute_request_project(self, qs, project, requestMethod=QgsServerRequest.GetMethod, data=None):
request = QgsBufferServerRequest(qs, requestMethod, {}, data)
response = QgsBufferServerResponse()
self.server.handleRequest(request, response, project)
headers = []
rh = response.headers()
rk = sorted(rh.keys())
for k in rk:
headers.append(("%s: %s" % (k, rh[k])).encode('utf-8'))
return b"\n".join(headers) + b"\n\n", bytes(response.body())


class TestQgsServerTestBase(unittest.TestCase):

Expand Down Expand Up @@ -285,6 +296,16 @@ def test_requestHandler(self):
self.assertEqual(response.headers(), {'Content-Length': '54', 'Content-Type': 'text/xml; charset=utf-8'})
self.assertEqual(response.statusCode(), 500)

def test_requestHandlerProject(self):
"""Test request handler with none project"""
headers = {'header-key-1': 'header-value-1', 'header-key-2': 'header-value-2'}
request = QgsBufferServerRequest('http://somesite.com/somepath', QgsServerRequest.GetMethod, headers)
response = QgsBufferServerResponse()
self.server.handleRequest(request, response, None)
self.assertEqual(bytes(response.body()), b'<ServerException>Project file error</ServerException>\n')
self.assertEqual(response.headers(), {'Content-Length': '54', 'Content-Type': 'text/xml; charset=utf-8'})
self.assertEqual(response.statusCode(), 500)

def test_api(self):
"""Using an empty query string (returns an XML exception)
we are going to test if headers and body are returned correctly"""
Expand Down
26 changes: 26 additions & 0 deletions tests/src/python/test_qgsserver_wms.py
Expand Up @@ -32,6 +32,7 @@
import osgeo.gdal # NOQA

from test_qgsserver import QgsServerTestBase
from qgis.core import QgsProject

# Strip path and content length because path may vary
RE_STRIP_UNCHECKABLE = b'MAP=[^"]+|Content-Length: \d+'
Expand Down Expand Up @@ -196,6 +197,31 @@ def test_wms_getschemaextension(self):
'',
'getschemaextension')

def wms_request_compare_project(self, request, extra=None, reference_file=None):
projectPath = self.testdata_path + "test_project.qgs"
assert os.path.exists(projectPath), "Project file not found: " + projectPath

project = QgsProject()
project.read(projectPath)

query_string = 'https://www.qgis.org/?SERVICE=WMS&VERSION=1.3&REQUEST=%s' % (request)
if extra is not None:
query_string += extra
header, body = self._execute_request_project(query_string, project)
response = header + body
reference_path = self.testdata_path + (request.lower() if not reference_file else reference_file) + '.txt'
self.store_reference(reference_path, response)
f = open(reference_path, 'rb')
expected = f.read()
f.close()
response = re.sub(RE_STRIP_UNCHECKABLE, b'*****', response)
expected = re.sub(RE_STRIP_UNCHECKABLE, b'*****', expected)

self.assertXMLEqual(response, expected, msg="request %s failed.\nQuery: %s\nExpected file: %s\nResponse:\n%s" % (query_string, request, reference_path, response.decode('utf-8')))

def test_wms_getcapabilities_project(self):
self.wms_request_compare_project('GetCapabilities')

def wms_inspire_request_compare(self, request):
"""WMS INSPIRE tests"""
project = self.testdata_path + "test_project_inspire.qgs"
Expand Down

0 comments on commit 7b455d8

Please sign in to comment.