Skip to content

Commit 3043f7f

Browse files
committedSep 7, 2015
Merge pull request #2296 from elpaso/qgsserver-return-pair
QgsServer handleRequest now returns QPair of QByteArray with headers and body
2 parents 5ae2881 + f56054f commit 3043f7f

File tree

7 files changed

+181
-108
lines changed

7 files changed

+181
-108
lines changed
 

‎python/server/qgsserver.sip

Lines changed: 139 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,140 @@
1717
***************************************************************************/
1818

1919

20+
%MappedType QPair<QByteArray, QByteArray>
21+
{
22+
%TypeHeaderCode
23+
#include <QPair>
24+
#include <QByteArray>
25+
%End
26+
27+
28+
%TypeCode
29+
// Convenience function for converting a QByteArray to a Python str object. (from QtCore/qbytearray.sip)
30+
static PyObject *QByteArrayToPyStr(QByteArray *ba)
31+
{
32+
char *data = ba->data();
33+
34+
if (data)
35+
// QByteArrays may have embedded '\0's so set the size explicitly.
36+
return SIPBytes_FromStringAndSize(data, ba->size());
37+
return SIPBytes_FromString("");
38+
}
39+
40+
%End
41+
42+
43+
%ConvertFromTypeCode
44+
// Create the tuple.
45+
return Py_BuildValue((char *)"OO", QByteArrayToPyStr( &sipCpp->first ), QByteArrayToPyStr( &sipCpp->second ) );
46+
%End
47+
48+
%ConvertToTypeCode
49+
50+
// See if we are just being asked to check the type of the Python
51+
// object.
52+
if (!sipIsErr)
53+
{
54+
// Checking whether or not None has been passed instead of a list
55+
// has already been done.
56+
if (!PyTuple_Check(sipPy) || PyTuple_Size(sipPy) != 2)
57+
return 0;
58+
59+
// Check the type of each element. We specify SIP_NOT_NONE to
60+
// disallow None because it is a list of QPoint, not of a pointer
61+
// to a QPoint, so None isn't appropriate.
62+
for (int i = 0; i < PyTuple_Size(sipPy); ++i)
63+
if (!sipCanConvertToType(PyTuple_GET_ITEM(sipPy, i),
64+
sipType_QByteArray, SIP_NOT_NONE))
65+
return 0;
66+
67+
// The type is valid.
68+
return 1;
69+
}
70+
71+
// Create the instance on the heap.
72+
QPair<QByteArray, QByteArray> *qp = new QPair<QByteArray, QByteArray>;
73+
74+
QByteArray *qba1;
75+
int state;
76+
77+
// Get the address of the element's C++ instance. Note that, in
78+
// this case, we don't apply any ownership changes to the list
79+
// elements, only to the list itself.
80+
qba1 = reinterpret_cast<QByteArray *>(sipConvertToType(
81+
PyTuple_GET_ITEM(sipPy, 0),
82+
sipType_QByteArray, 0,
83+
SIP_NOT_NONE,
84+
&state, sipIsErr));
85+
86+
// Deal with any errors.
87+
if (*sipIsErr)
88+
{
89+
sipReleaseType(qba1, sipType_QByteArray, state);
90+
91+
// Tidy up.
92+
delete qp;
93+
94+
// There is no temporary instance.
95+
return 0;
96+
}
97+
98+
qp->first = *qba1;
99+
100+
// A copy of the QByteArray was assigned to the pair so we no longer
101+
// need it. It may be a temporary instance that should be
102+
// destroyed, or a wrapped instance that should not be destroyed.
103+
// sipReleaseType() will do the right thing.
104+
sipReleaseType(qba1, sipType_QByteArray, state);
105+
106+
/////////////////////////////////////////////
107+
// Second item
108+
109+
QByteArray *qba2;
110+
111+
// Get the address of the element's C++ instance. Note that, in
112+
// this case, we don't apply any ownership changes to the list
113+
// elements, only to the list itself.
114+
qba2 = reinterpret_cast<QByteArray *>(sipConvertToType(
115+
PyTuple_GET_ITEM(sipPy, 1),
116+
sipType_QByteArray, 0,
117+
SIP_NOT_NONE,
118+
&state, sipIsErr));
119+
120+
// Deal with any errors.
121+
if (*sipIsErr)
122+
{
123+
sipReleaseType(qba1, sipType_QByteArray, state);
124+
sipReleaseType(qba2, sipType_QByteArray, state);
125+
126+
// Tidy up.
127+
delete qp;
128+
129+
// There is no temporary instance.
130+
return 0;
131+
}
132+
133+
qp->second = *qba2;
134+
135+
136+
// A copy of the QByteArray was assigned to the pair so we no longer
137+
// need it. It may be a temporary instance that should be
138+
// destroyed, or a wrapped instance that should not be destroyed.
139+
// sipReleaseType() will do the right thing.
140+
sipReleaseType(qba2, sipType_QByteArray, state);
141+
142+
143+
// Return the instance.
144+
*sipCppPtr = qp;
145+
146+
// The instance should be regarded as temporary (and be destroyed as
147+
// soon as it has been used) unless it has been transferred from
148+
// Python. sipGetState() is a convenience function that implements
149+
// this common transfer behaviour.
150+
return sipGetState(sipTransferObj);
151+
152+
%End
153+
};
20154

21155
class QgsServer
22156
{
@@ -31,21 +165,15 @@ class QgsServer
31165
//void init( int argc, char* argv[] );
32166
// init for python bindings:
33167
void init( );
34-
QByteArray handleRequest( const QString queryString = QString( ) );
35-
// TODO: if HAVE_SERVER_PYTHON
36-
QByteArray handleRequest( const QString queryString,
37-
const bool returnHeaders,
38-
const bool returnBody );
39-
QByteArray handleRequestGetBody( const QString queryString = QString( ) );
40-
QByteArray handleRequestGetHeaders( const QString queryString = QString( ) );
41-
//QgsServerContext& serverContext ( ) /KeepReference/;
168+
QPair<QByteArray, QByteArray> handleRequest( const QString queryString = QString( ) );
169+
/* The following code was used to test type conversion in python bindings
170+
QPair<QByteArray, QByteArray> testQPair( QPair<QByteArray, QByteArray> pair );
171+
*/
42172
%If (HAVE_SERVER_PYTHON_PLUGINS)
43173
QgsServerInterface* serverInterface( );
44-
// Needs %MethodCode
45-
//QMultiMap<int, QgsServerFilter*> pluginFilters( );
46174
%End
47175

48-
// The following is needed because otherwise SIP fails trying to create copy
176+
// The following is needed because otherwise SIP fails trying to create copy
49177
// ctor
50178
private:
51179
QgsServer( const QgsServer& );

‎src/server/qgshttprequesthandler.cpp

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -218,21 +218,11 @@ void QgsHttpRequestHandler::sendResponse()
218218
clearBody();
219219
}
220220

221-
QByteArray QgsHttpRequestHandler::getResponse( const bool returnHeaders,
222-
const bool returnBody )
221+
QPair<QByteArray, QByteArray> QgsHttpRequestHandler::getResponse()
223222
{
224-
if ( ! returnHeaders )
225-
{
226-
return mResponseBody;
227-
}
228-
else if ( ! returnBody )
229-
{
230-
return mResponseHeader;
231-
}
232-
else
233-
{
234-
return mResponseHeader.append( mResponseBody );
235-
}
223+
// TODO: check that this is not an evil bug!
224+
QPair<QByteArray, QByteArray> response( mResponseHeader, mResponseBody );
225+
return response;
236226
}
237227

238228
QString QgsHttpRequestHandler::formatToMimeType( const QString& format ) const

‎src/server/qgshttprequesthandler.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,8 @@ class QgsHttpRequestHandler: public QgsRequestHandler
6363
#ifdef HAVE_SERVER_PYTHON_PLUGINS
6464
virtual void setPluginFilters( QgsServerFiltersMap pluginFilters ) override;
6565
#endif
66-
// TODO: if HAVE_SERVER_PYTHON
67-
QByteArray getResponseHeader( ) override { return mResponseHeader; }
68-
QByteArray getResponseBody( ) override { return mResponseBody; }
6966
/** Return the response if capture output is activated */
70-
QByteArray getResponse( const bool returnHeaders = TRUE,
71-
const bool returnBody = TRUE ) override;
67+
QPair<QByteArray, QByteArray> getResponse( ) override;
7268

7369
protected:
7470
virtual void sendHeaders( ) override;

‎src/server/qgsrequesthandler.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <QMap>
2929
#include <QString>
3030
#include <QStringList>
31+
#include <QPair>
3132

3233
#ifdef HAVE_SERVER_PYTHON_PLUGINS
3334
#include "qgsserverfilter.h"
@@ -113,11 +114,7 @@ class QgsRequestHandler
113114
*/
114115
virtual void setPluginFilters( QgsServerFiltersMap pluginFilters ) = 0;
115116
#endif
116-
// TODO: if HAVE_SERVER_PYTHON
117-
virtual QByteArray getResponseHeader( ) = 0;
118-
virtual QByteArray getResponseBody( ) = 0;
119-
virtual QByteArray getResponse( const bool returnHeaders = TRUE,
120-
const bool returnBody = TRUE ) = 0;
117+
virtual QPair<QByteArray, QByteArray> getResponse( ) = 0;
121118

122119
protected:
123120
virtual void sendHeaders( ) = 0;

‎src/server/qgsserver.cpp

Lines changed: 11 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -403,47 +403,14 @@ bool QgsServer::init( int & argc, char ** argv )
403403
}
404404

405405

406-
/**
407-
* Handles the request
408-
*/
409-
QByteArray QgsServer::handleRequest( const QString queryString /*= QString( )*/ )
410-
{
411-
return handleRequest( queryString, TRUE, TRUE );
412-
}
413-
414-
/**
415-
* @brief Handles the request, returning only the body
416-
* @param queryString
417-
* @return response body if mCaptureOutput is set, empty QByteArray if not
418-
*/
419-
QByteArray QgsServer::handleRequestGetBody( const QString queryString /*= QString( )*/ )
420-
{
421-
return handleRequest( queryString, FALSE, TRUE );
422-
}
423-
424-
/**
425-
* @brief Handles the request, returning only the headers
426-
* @param queryString
427-
* @return response headers if mCaptureOutput is set, empty QByteArray if not
428-
*/
429-
QByteArray QgsServer::handleRequestGetHeaders( const QString queryString /*= QString( )*/ )
430-
{
431-
return handleRequest( queryString, TRUE, FALSE );
432-
}
433406

434407
/**
435408
* @brief Handles the request
436409
* @param queryString
437-
* @param returnBody
438-
* @param returnHeaders
439-
* @return response body and headers if mCaptureOutput is set and the
440-
* flags are set, empty QByteArray if not
410+
* @return response headers and body
441411
*/
442-
QByteArray QgsServer::handleRequest( const QString queryString ,
443-
bool returnHeaders,
444-
bool returnBody )
412+
QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString queryString /*= QString( )*/ )
445413
{
446-
447414
// Run init if handleRequest was called without previously initialising
448415
// the server
449416
if ( ! mInitialised )
@@ -501,7 +468,6 @@ QByteArray QgsServer::handleRequest( const QString queryString ,
501468
//Pass the filters to the requestHandler, this is needed for the following reasons:
502469
// 1. allow core services to access plugin filters and implement thir own plugin hooks
503470
// 2. allow requestHandler to call sendResponse plugin hook
504-
505471
theRequestHandler->setPluginFilters( mServerInterface->filters() );
506472
#endif
507473

@@ -601,8 +567,14 @@ QByteArray QgsServer::handleRequest( const QString queryString ,
601567
{
602568
QgsMessageLog::logMessage( "Request finished in " + QString::number( time.elapsed() ) + " ms", "Server", QgsMessageLog::INFO );
603569
}
604-
// TODO: if HAVE_SERVER_PYTHON
605-
// Returns the response bytestream
606-
return theRequestHandler->getResponse( returnHeaders , returnBody );
570+
// Returns the header and response bytestreams (to be used in Python bindings)
571+
return theRequestHandler->getResponse( );
572+
}
573+
574+
/* The following code was used to test type conversion in python bindings
575+
QPair<QByteArray, QByteArray> QgsServer::testQPair(QPair<QByteArray, QByteArray> pair)
576+
{
577+
return pair;
607578
}
579+
*/
608580

‎src/server/qgsserver.h

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -61,36 +61,18 @@ class SERVER_EXPORT QgsServer
6161
/** Handles the request. The output is normally printed trough FCGI printf
6262
* by the request handler or, in case the server has been invoked from python
6363
* bindings, a flag is set that captures all the output headers and body, instead
64-
* of printing it returns the output as a QByteArray.
65-
* When calling handleRequest() from python bindings an additional argument
66-
* specify if we only want the headers or the body back, this is mainly useful
67-
* for testing purposes.
64+
* of printing it returns the output as a QPair of QByteArray.
6865
* The query string is normally read from environment
6966
* but can be also passed in args and in this case overrides the environment
7067
* variable
7168
*
7269
* @param queryString optional QString containing the query string
73-
* @return the response QByteArray if called from python bindings, empty otherwise
70+
* @return the response headers and body QPair of QByteArray if called from python bindings, empty otherwise
7471
*/
75-
QByteArray handleRequest( const QString queryString = QString( ) );
76-
QByteArray handleRequest( const QString queryString,
77-
const bool returnHeaders,
78-
const bool returnBody );
79-
/**
80-
* Handles the request and returns only the body
81-
*
82-
* @param queryString optional QString containing the query string
83-
* @return the response body QByteArray if called from python bindings, empty otherwise
84-
*/
85-
QByteArray handleRequestGetBody( const QString queryString = QString( ) );
86-
87-
/**
88-
* Handles the request and returns only the headers
89-
*
90-
* @param queryString optional QString containing the query string
91-
* @return the response headers QByteArray if called from python bindings, empty otherwise
92-
*/
93-
QByteArray handleRequestGetHeaders( const QString queryString = QString( ) );
72+
QPair<QByteArray, QByteArray> handleRequest( const QString queryString = QString( ) );
73+
/* The following code was used to test type conversion in python bindings
74+
QPair<QByteArray, QByteArray> testQPair( QPair<QByteArray, QByteArray> pair );
75+
*/
9476

9577
/** Returns a pointer to the server interface */
9678
#ifdef HAVE_SERVER_PYTHON_PLUGINS

‎tests/src/python/test_qgsserver.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,15 @@ def test_api(self):
5353
"""Using an empty query string (returns an XML exception)
5454
we are going to test if headers and body are returned correctly"""
5555
# Test as a whole
56-
response = str(self.server.handleRequest())
56+
header, body = [str(_v) for _v in self.server.handleRequest()]
57+
response = header + body
5758
expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
5859
self.assertEqual(response, expected)
59-
# Test header
60-
response = str(self.server.handleRequestGetHeaders())
6160
expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n'
62-
self.assertEqual(response, expected)
61+
self.assertEqual(header, expected)
6362
# Test body
64-
response = str(self.server.handleRequestGetBody())
6563
expected = '<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
66-
self.assertEqual(response, expected)
64+
self.assertEqual(body, expected)
6765

6866
def test_pluginfilters(self):
6967
"""Test python plugins filters"""
@@ -122,7 +120,8 @@ def responseComplete(self):
122120
self.assertTrue(filter2 in serverIface.filters()[100])
123121
self.assertEqual(filter1, serverIface.filters()[101][0])
124122
self.assertEqual(filter2, serverIface.filters()[200][0])
125-
response = str(self.server.handleRequest('service=simple'))
123+
header, body = [str(_v) for _v in self.server.handleRequest('service=simple')]
124+
response = header + body
126125
expected = 'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
127126
self.assertEqual(response, expected)
128127

@@ -133,7 +132,8 @@ def responseComplete(self):
133132
self.assertTrue(filter2 in serverIface.filters()[100])
134133
self.assertEqual(filter1, serverIface.filters()[101][0])
135134
self.assertEqual(filter2, serverIface.filters()[200][0])
136-
response = str(self.server.handleRequest('service=simple'))
135+
header, body = [str(_v) for _v in self.server.handleRequest('service=simple')]
136+
response = header + body
137137
expected = 'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
138138
self.assertEqual(response, expected)
139139

@@ -143,11 +143,12 @@ def wms_request_compare(self, request):
143143
assert os.path.exists(project), "Project file not found: " + project
144144

145145
query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3&REQUEST=%s' % (urllib.quote(project), request)
146-
response = str(self.server.handleRequest(query_string))
146+
header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
147+
response = header + body
147148
f = open(self.testdata_path + request.lower() + '.txt')
148149
expected = f.read()
149150
f.close()
150-
# Store for debug or to regenerate the reference documents:
151+
# Store the output for debug or to regenerate the reference documents:
151152
"""
152153
f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
153154
f.write(expected)
@@ -165,6 +166,13 @@ def test_project_wms(self):
165166
for request in ('GetCapabilities', 'GetProjectSettings'):
166167
self.wms_request_compare(request)
167168

169+
# The following code was used to test type conversion in python bindings
170+
#def test_qpair(self):
171+
# """Test QPair bindings"""
172+
# f, s = self.server.testQPair(('First', 'Second'))
173+
# self.assertEqual(f, 'First')
174+
# self.assertEqual(s, 'Second')
175+
168176

169177
if __name__ == '__main__':
170178
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.