Skip to content

Commit 64fc56c

Browse files
committedJan 10, 2017
Implement QgsFcgiRequest and QgsFcgiResponse
1 parent 891e163 commit 64fc56c

33 files changed

+1101
-595
lines changed
 

‎python/server/qgsrequesthandler.sip

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,32 +66,21 @@ class QgsRequestHandler
6666
//! @note not available in Python bindings
6767
virtual void setGetCoverageResponse( QByteArray* ba ) = 0;
6868

69-
virtual void setDefaultHeaders();
70-
7169
/** Set an HTTP header*/
7270
virtual void setHeader( const QString &name, const QString &value ) = 0;
7371

7472
/** Remove an HTTP header*/
75-
virtual int removeHeader( const QString &name ) = 0;
73+
virtual void removeHeader( const QString &name ) = 0;
7674

7775
/** Delete all HTTP headers*/
78-
virtual void clearHeaders() = 0;
76+
virtual void clear() = 0;
7977

8078
/** Append the bytestream to response body*/
8179
virtual void appendBody( const QByteArray &body ) = 0;
8280

83-
/** Clears the response body*/
84-
virtual void clearBody() = 0;
85-
86-
/** Return the response body*/
87-
virtual QByteArray body();
88-
8981
/** Set the info format string such as "text/xml"*/
9082
virtual void setInfoFormat( const QString &format ) = 0;
9183

92-
/** Check whether there is any header set or the body is not empty*/
93-
virtual bool responseReady() const = 0;
94-
9584
/** Send out HTTP headers and flush output buffer*/
9685
virtual void sendResponse() = 0;
9786

‎python/server/qgsserver.sip

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ class QgsServer
165165
/** Creates the server instance
166166
* @param captureOutput set to false for stdout output (FCGI)
167167
*/
168-
QgsServer( bool captureOutput = true );
168+
QgsServer();
169169
~QgsServer();
170170

171171
/** Set environment variable
@@ -175,22 +175,25 @@ class QgsServer
175175
*/
176176
void putenv( const QString &var, const QString &val );
177177

178-
/** Handles the request. The output is normally printed trough FCGI printf
179-
* by the request handler or, in case the server has been invoked from python
180-
* bindings, a flag is set that captures all the output headers and body, instead
181-
* of printing it returns the output as a QPair of QByteArray.
178+
/** Handles the request.
182179
* The query string is normally read from environment
183180
* but can be also passed in args and in this case overrides the environment
184181
* variable
185182
*
186-
* @param queryString optional QString containing the query string
187-
* @return the response headers and body QPair of QByteArray if called from python bindings, empty otherwise
183+
* @param request a QgsServerRequest holding request parameters
184+
* @param response a QgsServerResponse for handling response I/O)
188185
*/
189-
QPair<QByteArray, QByteArray> handleRequest( const QString& queryString = QString() );
190-
/*
191-
// The following code was used to test type conversion in python bindings
192-
QPair<QByteArray, QByteArray> testQPair( QPair<QByteArray, QByteArray> pair );
193-
*/
186+
void handleRequest( const QgsServerRequest& request, QgsServerResponse& response );
187+
188+
/** Handles the request from query strinf
189+
* The query string is normally read from environment
190+
* but can be also passed in args and in this case overrides the environment
191+
* variable.
192+
*
193+
* @param queryString QString containing the query string
194+
* @return the response headers and body QPair of QByteArray
195+
*/
196+
QPair<QByteArray, QByteArray> handleRequest( const QString& queryString );
194197

195198
/** Returns a pointer to the server interface */
196199
QgsServerInterface* serverInterface();

‎python/server/qgsserverrequest.sip

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class QgsServerRequest
3535
HeadMethod, PutMethod, GetMethod, PostMethod, DeleteMethod
3636
};
3737

38+
/**
39+
* Constructor
40+
*/
41+
QgsServerRequest();
42+
3843
/**
3944
* Constructor
4045
*
@@ -81,5 +86,15 @@ class QgsServerRequest
8186
*/
8287
virtual QByteArray data() const;
8388

89+
/**
90+
* Set the request url
91+
*/
92+
void setUrl( const QUrl& url );
93+
94+
/**
95+
* Set the request method
96+
*/
97+
void setMethod( Method method );
98+
8499
};
85100

‎python/server/qgsserverresponse.sip

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ class QgsServerResponse
4242
*/
4343
virtual void setHeader( const QString& key, const QString& value ) = 0;
4444

45+
/**
46+
* Clear header
47+
* Undo a previous 'set_header' call
48+
*/
49+
virtual void clearHeader( const QString& key ) = 0;
50+
51+
4552
/** Set the http return code
4653
* @param code HTTP return code value
4754
*/
@@ -76,5 +83,23 @@ class QgsServerResponse
7683
*/
7784
virtual QIODevice* io() = 0;
7885

86+
/**
87+
* End the transaction
88+
*/
89+
virtual void finish() = 0;
90+
91+
/**
92+
* Flushes the current output buffer to the network
93+
*
94+
* 'flush()' may be called multiple times. For HTTP transactions
95+
* headers will be written on the first call to 'flush()'.
96+
*/
97+
virtual void flush() = 0;
98+
99+
/**
100+
* Reset all headers and content for this response
101+
*/
102+
virtual void clear() = 0;
103+
79104
};
80105

‎src/server/CMakeLists.txt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ SET ( qgis_mapserv_SRCS
2424
qgscapabilitiescache.cpp
2525
qgsconfigcache.cpp
2626
qgshttprequesthandler.cpp
27-
# qgshttptransaction.cpp
28-
qgsgetrequesthandler.cpp
29-
qgspostrequesthandler.cpp
3027
qgsowsserver.cpp
3128
qgswmsserver.cpp
3229
qgswfsserver.cpp
@@ -57,6 +54,8 @@ SET ( qgis_mapserv_SRCS
5754
qgsserviceregistry.cpp
5855
qgsserverrequest.cpp
5956
qgsserverresponse.cpp
57+
qgsfcgiserverresponse.cpp
58+
qgsbufferserverresponse.cpp
6059
#----------------------------
6160
)
6261
IF("${Qt5Network_VERSION}" VERSION_LESS "5.0.0")

‎src/server/qgis_map_serv.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
//for CMAKE_INSTALL_PREFIX
2020
#include "qgsconfig.h"
2121
#include "qgsserver.h"
22+
#include "qgsfcgiserverresponse.h"
2223

2324
#include <fcgi_stdio.h>
2425
#include <stdlib.h>
@@ -38,14 +39,23 @@ int fcgi_accept()
3839
int main( int argc, char * argv[] )
3940
{
4041
QgsApplication app( argc, argv, getenv( "DISPLAY" ), QString(), QStringLiteral( "server" ) );
41-
QgsServer server( false );
42+
QgsServer server;
4243
#ifdef HAVE_SERVER_PYTHON_PLUGINS
4344
server.initPython();
4445
#endif
4546
// Starts FCGI loop
4647
while ( fcgi_accept() >= 0 )
4748
{
48-
server.handleRequest();
49+
QgsFcgiServerRequest request;
50+
QgsFcgiServerResponse response( request.method() );
51+
if ( ! request.hasError() )
52+
{
53+
server.handleRequest( request, response );
54+
}
55+
else
56+
{
57+
response.sendError( 400, "Bad request" );
58+
}
4959
}
5060
app.exitQgis();
5161
return 0;
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/***************************************************************************
2+
qgsfcgiserverresponse.cpp
3+
4+
Define response wrapper for fcgi response
5+
-------------------
6+
begin : 2017-01-03
7+
copyright : (C) 2017 by David Marteau
8+
email : david dot marteau at 3liz dot com
9+
***************************************************************************/
10+
11+
/***************************************************************************
12+
* *
13+
* This program is free software; you can redistribute it and/or modify *
14+
* it under the terms of the GNU General Public License as published by *
15+
* the Free Software Foundation; either version 2 of the License, or *
16+
* (at your option) any later version. *
17+
* *
18+
***************************************************************************/
19+
20+
#include "qgsbufferserverresponse.h"
21+
#include "qgslogger.h"
22+
#include "qgsmessagelog.h"
23+
24+
#include <QDebug>
25+
26+
//
27+
// QgsBufferServerResponse
28+
//
29+
30+
QgsBufferServerResponse::QgsBufferServerResponse()
31+
{
32+
mBuffer.open( QIODevice::ReadWrite );
33+
mHeadersWritten = false;
34+
mFinished = false;
35+
mReturnCode = 200;
36+
}
37+
38+
QgsBufferServerResponse::~QgsBufferServerResponse()
39+
{
40+
41+
}
42+
43+
void QgsBufferServerResponse::clearHeader( const QString& key )
44+
{
45+
if ( !mHeadersWritten )
46+
mHeaders.remove( key );
47+
}
48+
49+
void QgsBufferServerResponse::setHeader( const QString& key, const QString& value )
50+
{
51+
if ( ! mHeadersWritten )
52+
mHeaders.insert( key, value );
53+
}
54+
55+
void QgsBufferServerResponse::setReturnCode( int code )
56+
{
57+
mReturnCode = code;
58+
}
59+
60+
void QgsBufferServerResponse::sendError( int code, const QString& message )
61+
{
62+
if ( mHeadersWritten )
63+
{
64+
QgsMessageLog::logMessage( "Cannot send error after headers written" );
65+
return;
66+
}
67+
68+
clear();
69+
setReturnCode( code );
70+
setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/plain; charset=utf-8" ) );
71+
write( message );
72+
finish();
73+
}
74+
75+
QIODevice* QgsBufferServerResponse::io()
76+
{
77+
return &mBuffer;
78+
}
79+
80+
void QgsBufferServerResponse::finish()
81+
{
82+
if ( mFinished )
83+
{
84+
QgsMessageLog::logMessage( "finish() called twice" );
85+
return;
86+
}
87+
88+
if ( !mHeadersWritten )
89+
{
90+
if ( ! mHeaders.contains( "Content-Length" ) )
91+
{
92+
mHeaders.insert( QStringLiteral( "Content-Length" ), QStringLiteral( "%1" ).arg( mBuffer.pos() ) );
93+
}
94+
}
95+
flush();
96+
mFinished = true;
97+
}
98+
99+
void QgsBufferServerResponse::flush()
100+
{
101+
if ( ! mHeadersWritten )
102+
{
103+
mHeadersWritten = true;
104+
}
105+
106+
mBuffer.seek( 0 );
107+
QByteArray& ba = mBuffer.buffer();
108+
mBody.append( ba );
109+
ba.clear();
110+
}
111+
112+
113+
void QgsBufferServerResponse::clear()
114+
{
115+
if ( !mHeadersWritten )
116+
mHeaders.clear();
117+
118+
mBuffer.seek( 0 );
119+
mBuffer.buffer().clear();
120+
}
121+
122+
123+
//QgsBufferServerRequest
124+
//
125+
QgsBufferServerRequest::QgsBufferServerRequest( const QString& url, Method method, QByteArray* data )
126+
: QgsServerRequest( url, method )
127+
{
128+
if ( data )
129+
{
130+
mData = *data;
131+
}
132+
}
133+
134+
QgsBufferServerRequest::QgsBufferServerRequest( const QUrl& url, Method method, QByteArray* data )
135+
: QgsServerRequest( url, method )
136+
{
137+
if ( data )
138+
{
139+
mData = *data;
140+
}
141+
}
142+
143+
QgsBufferServerRequest::~QgsBufferServerRequest()
144+
{
145+
}
146+
147+
148+

‎src/server/qgsbufferserverresponse.h

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/***************************************************************************
2+
qgsfcgiserverresponse.h
3+
4+
Define response wrapper for storing responsea in buffer
5+
-------------------
6+
begin : 2017-01-03
7+
copyright : (C) 2017 by David Marteau
8+
email : david dot marteau at 3liz dot com
9+
***************************************************************************/
10+
11+
/***************************************************************************
12+
* *
13+
* This program is free software; you can redistribute it and/or modify *
14+
* it under the terms of the GNU General Public License as published by *
15+
* the Free Software Foundation; either version 2 of the License, or *
16+
* (at your option) any later version. *
17+
* *
18+
***************************************************************************/
19+
#ifndef QGSBUFFERSERVERRESPONSE_H
20+
#define QGSBUFFERSERVERRESPONSE_H
21+
22+
#include "qgsserverresponse.h"
23+
#include "qgsserverrequest.h"
24+
25+
#include <QBuffer>
26+
#include <QByteArray>
27+
#include <QMap>
28+
29+
/**
30+
* \ingroup server
31+
* QgsBufferServerResponse
32+
* Class defining buffered response
33+
*/
34+
class QgsBufferServerResponse: public QgsServerResponse
35+
{
36+
public:
37+
38+
QgsBufferServerResponse();
39+
~QgsBufferServerResponse();
40+
41+
virtual void setHeader( const QString& key, const QString& value ) override;
42+
43+
virtual void clearHeader( const QString& key ) override;
44+
45+
virtual void setReturnCode( int code ) override;
46+
47+
virtual void sendError( int code, const QString& message ) override;
48+
49+
virtual QIODevice* io() override;
50+
51+
virtual void finish() override;
52+
53+
virtual void flush() override;
54+
55+
virtual void clear() override;
56+
57+
/**
58+
* Return body
59+
*/
60+
const QByteArray& body() const { return mBody; }
61+
62+
/**
63+
* Return header's map
64+
*/
65+
const QMap<QString, QString>& headers() const { return mHeaders; }
66+
67+
/**
68+
* Return the status code
69+
*/
70+
int returnCode() const { return mReturnCode; }
71+
72+
private:
73+
QMap<QString, QString> mHeaders;
74+
QBuffer mBuffer;
75+
QByteArray mBody;
76+
bool mFinished;
77+
bool mHeadersWritten;
78+
int mReturnCode;
79+
};
80+
81+
/**
82+
* \ingroup server
83+
* QgsBufferServerRequest
84+
* Class defining request with data
85+
*/
86+
class QgsBufferServerRequest : public QgsServerRequest
87+
{
88+
public:
89+
90+
/**
91+
* Constructor
92+
*
93+
* @param url the url string
94+
* @param method the request method
95+
*/
96+
QgsBufferServerRequest( const QString& url, Method method = GetMethod, QByteArray* data = nullptr );
97+
98+
/**
99+
* Constructor
100+
*
101+
* @param url QUrl
102+
* @param method the request method
103+
*/
104+
QgsBufferServerRequest( const QUrl& url, Method method = GetMethod, QByteArray* data = nullptr );
105+
106+
~QgsBufferServerRequest();
107+
108+
virtual QByteArray data() const { return mData; }
109+
110+
private:
111+
QByteArray mData;
112+
};
113+
114+
#endif
115+
116+
117+
118+
119+

‎src/server/qgsfcgiserverresponse.cpp

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/***************************************************************************
2+
qgsfcgiserverresponse.cpp
3+
4+
Define response wrapper for fcgi response
5+
-------------------
6+
begin : 2017-01-03
7+
copyright : (C) 2017 by David Marteau
8+
email : david dot marteau at 3liz dot com
9+
***************************************************************************/
10+
11+
/***************************************************************************
12+
* *
13+
* This program is free software; you can redistribute it and/or modify *
14+
* it under the terms of the GNU General Public License as published by *
15+
* the Free Software Foundation; either version 2 of the License, or *
16+
* (at your option) any later version. *
17+
* *
18+
***************************************************************************/
19+
20+
#include "qgis.h"
21+
#include "qgsfcgiserverresponse.h"
22+
#include "qgslogger.h"
23+
#include "qgsmessagelog.h"
24+
#include <fcgi_stdio.h>
25+
26+
#include <QDebug>
27+
28+
//
29+
// QgsFcgiServerResponse
30+
//
31+
32+
QgsFcgiServerResponse::QgsFcgiServerResponse( QgsServerRequest::Method method )
33+
{
34+
mBuffer.open( QIODevice::ReadWrite );
35+
mHeadersWritten = false;
36+
mFinished = false;
37+
mMethod = method;
38+
}
39+
40+
QgsFcgiServerResponse::~QgsFcgiServerResponse()
41+
{
42+
43+
}
44+
45+
void QgsFcgiServerResponse::clearHeader( const QString& key )
46+
{
47+
mHeaders.remove( key );
48+
}
49+
50+
void QgsFcgiServerResponse::setHeader( const QString& key, const QString& value )
51+
{
52+
mHeaders.insert( key, value );
53+
}
54+
55+
void QgsFcgiServerResponse::setReturnCode( int code )
56+
{
57+
// fcgi applications must return HTTP status in header
58+
mHeaders.insert( QStringLiteral( "Status" ), QStringLiteral( " %1" ).arg( code ) );
59+
}
60+
61+
void QgsFcgiServerResponse::sendError( int code, const QString& message )
62+
{
63+
if ( mHeadersWritten )
64+
{
65+
QgsMessageLog::logMessage( "Cannot send error after headers written" );
66+
return;
67+
}
68+
69+
clear();
70+
setDefaultHeaders();
71+
setReturnCode( code );
72+
setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/html;charset=utf-8" ) );
73+
write( QStringLiteral( "<html><body>%1</body></html>" ).arg( message ) );
74+
finish();
75+
}
76+
77+
QIODevice* QgsFcgiServerResponse::io()
78+
{
79+
return &mBuffer;
80+
}
81+
82+
void QgsFcgiServerResponse::finish()
83+
{
84+
if ( mFinished )
85+
{
86+
QgsMessageLog::logMessage( "finish() called twice" );
87+
return;
88+
}
89+
90+
if ( !mHeadersWritten )
91+
{
92+
if ( ! mHeaders.contains( "Content-Length" ) )
93+
{
94+
mHeaders.insert( QStringLiteral( "Content-Length" ), QStringLiteral( "%1" ).arg( mBuffer.pos() ) );
95+
}
96+
}
97+
flush();
98+
mFinished = true;
99+
}
100+
101+
void QgsFcgiServerResponse::flush()
102+
{
103+
if ( ! mHeadersWritten )
104+
{
105+
// Send all headers
106+
QMap<QString, QString>::const_iterator it;
107+
for ( it = mHeaders.constBegin(); it != mHeaders.constEnd(); ++it )
108+
{
109+
fputs( it.key().toUtf8(), FCGI_stdout );
110+
fputs( ": ", FCGI_stdout );
111+
fputs( it.value().toUtf8(), FCGI_stdout );
112+
fputs( "\n" , FCGI_stdout );
113+
}
114+
fputs( "\n", FCGI_stdout );
115+
mHeadersWritten = true;
116+
}
117+
118+
mBuffer.seek( 0 );
119+
if ( mMethod == QgsServerRequest::HeadMethod )
120+
{
121+
// Ignore data for head method as we only
122+
// write headers for HEAD requests
123+
mBuffer.buffer().clear();
124+
}
125+
else if ( mBuffer.bytesAvailable() > 0 )
126+
{
127+
QByteArray& ba = mBuffer.buffer();
128+
size_t count = fwrite(( void* )ba.data(), ba.size(), 1, FCGI_stdout );
129+
#ifdef QGISDEBUG
130+
qDebug() << QStringLiteral( "Sent %1 blocks of %2 bytes" ).arg( count ).arg( ba.size() );
131+
#else
132+
Q_UNUSED( count );
133+
#endif
134+
// Reset the internal buffer
135+
ba.clear();
136+
}
137+
}
138+
139+
140+
void QgsFcgiServerResponse::clear()
141+
{
142+
mHeaders.clear();
143+
mBuffer.seek( 0 );
144+
mBuffer.buffer().clear();
145+
}
146+
147+
void QgsFcgiServerResponse::setDefaultHeaders()
148+
{
149+
setHeader( QStringLiteral( "Server" ), QStringLiteral( " Qgis FCGI server - QGis version %1" ).arg( Qgis::QGIS_VERSION ) );
150+
}
151+
152+
153+
//
154+
// QgsFcgiServerRequest
155+
//
156+
157+
QgsFcgiServerRequest::QgsFcgiServerRequest()
158+
{
159+
mHasError = false;
160+
161+
// Get the REQUEST_URI from the environment
162+
QUrl url;
163+
const char* uri = getenv( "REQUEST_URI" );
164+
if ( uri )
165+
{
166+
url.setUrl( uri );
167+
}
168+
// XXX OGC paremetrs are passed with the query string
169+
// we override the query string url in case it is
170+
// defined independently of REQUEST_URI
171+
const char* qs = getenv( "QUERY_STRING" );
172+
if ( qs )
173+
{
174+
url.setQuery( qs );
175+
}
176+
177+
#ifdef QGISDEBUG
178+
qDebug() << "fcgi query string: " << url.query();
179+
#endif
180+
181+
QgsServerRequest::Method method = GetMethod;
182+
183+
// Get method
184+
const char* me = getenv( "REQUEST_METHOD" );
185+
186+
if ( me )
187+
{
188+
if ( strcmp( me, "POST" ) == 0 )
189+
{
190+
method = PostMethod;
191+
}
192+
else if ( strcmp( me, "PUT" ) == 0 )
193+
{
194+
method = PutMethod;
195+
}
196+
else if ( strcmp( me, "DELETE" ) == 0 )
197+
{
198+
method = DeleteMethod;
199+
}
200+
else if ( strcmp( me, "HEAD" ) == 0 )
201+
{
202+
method = HeadMethod;
203+
}
204+
}
205+
206+
if ( method == PostMethod || method == PutMethod )
207+
{
208+
// Get post/put data
209+
readData();
210+
}
211+
212+
setUrl( url );
213+
setMethod( method );
214+
}
215+
216+
QgsFcgiServerRequest::~QgsFcgiServerRequest()
217+
{
218+
219+
}
220+
221+
QByteArray QgsFcgiServerRequest::data() const
222+
{
223+
return mData;
224+
}
225+
226+
// Read post put data
227+
void QgsFcgiServerRequest::readData()
228+
{
229+
// Check if we have CONTENT_LENGTH defined
230+
const char* lengthstr = getenv( "CONTENT_LENGTH" );
231+
if ( lengthstr )
232+
{
233+
#ifdef QGISDEBUG
234+
qDebug() << "fcgi: reading " << lengthstr << " bytes from stdin";
235+
#endif
236+
bool success = false;
237+
int length = QString( lengthstr ).toInt( &success );
238+
if ( success )
239+
{
240+
// XXX This not efficiont at all !!
241+
for ( int i = 0; i < length; ++i )
242+
{
243+
mData.append( getchar() );
244+
}
245+
}
246+
else
247+
{
248+
QgsMessageLog::logMessage( "fcgi: Failed to parse CONTENT_LENGTH",
249+
QStringLiteral( "Server" ), QgsMessageLog::CRITICAL );
250+
mHasError = true;
251+
}
252+
}
253+
else
254+
{
255+
QgsMessageLog::logMessage( "fcgi: No POST data" );
256+
}
257+
}
258+

‎src/server/qgsfcgiserverresponse.h

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/***************************************************************************
2+
qgsfcgiserverresponse.h
3+
4+
Define response wrapper for fcgi response
5+
-------------------
6+
begin : 2017-01-03
7+
copyright : (C) 2017 by David Marteau
8+
email : david dot marteau at 3liz dot com
9+
***************************************************************************/
10+
11+
/***************************************************************************
12+
* *
13+
* This program is free software; you can redistribute it and/or modify *
14+
* it under the terms of the GNU General Public License as published by *
15+
* the Free Software Foundation; either version 2 of the License, or *
16+
* (at your option) any later version. *
17+
* *
18+
***************************************************************************/
19+
#ifndef QGSFCGISERVERRESPONSE_H
20+
#define QGSFCGISERVERRESPONSE_H
21+
22+
#include "qgsserverrequest.h"
23+
#include "qgsserverresponse.h"
24+
25+
#include <QBuffer>
26+
27+
/**
28+
* \ingroup server
29+
* QgsFcgiServerResponse
30+
* Class defining fcgi response
31+
*/
32+
class QgsFcgiServerResponse: public QgsServerResponse
33+
{
34+
public:
35+
36+
QgsFcgiServerResponse( QgsServerRequest::Method method = QgsServerRequest::GetMethod );
37+
~QgsFcgiServerResponse();
38+
39+
virtual void setHeader( const QString& key, const QString& value ) override;
40+
41+
virtual void clearHeader( const QString& key ) override;
42+
43+
virtual void setReturnCode( int code ) override;
44+
45+
virtual void sendError( int code, const QString& message ) override;
46+
47+
virtual QIODevice* io() override;
48+
49+
virtual void finish() override;
50+
51+
virtual void flush() override;
52+
53+
virtual void clear() override;
54+
55+
/**
56+
* Set the default headers
57+
*/
58+
virtual void setDefaultHeaders();
59+
60+
private:
61+
QMap<QString, QString> mHeaders;
62+
QBuffer mBuffer;
63+
bool mFinished;
64+
bool mHeadersWritten;
65+
QgsServerRequest::Method mMethod;
66+
};
67+
68+
/**
69+
* \ingroup server
70+
* QgsFcgiServerResquest
71+
* Class defining fcgi request
72+
*/
73+
class QgsFcgiServerRequest: public QgsServerRequest
74+
{
75+
public:
76+
QgsFcgiServerRequest();
77+
~QgsFcgiServerRequest();
78+
79+
virtual QByteArray data() const override;
80+
81+
/**
82+
* Return true if an error occured during initialization
83+
*/
84+
bool hasError() const { return mHasError; }
85+
86+
private:
87+
void readData();
88+
89+
QByteArray mData;
90+
bool mHasError;
91+
};
92+
93+
#endif
94+
95+
96+
97+
98+

‎src/server/qgsgetrequesthandler.cpp

Lines changed: 0 additions & 44 deletions
This file was deleted.

‎src/server/qgsgetrequesthandler.h

Lines changed: 0 additions & 32 deletions
This file was deleted.

‎src/server/qgshttprequesthandler.cpp

Lines changed: 106 additions & 208 deletions
Large diffs are not rendered by default.

‎src/server/qgshttprequesthandler.h

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
#include <QPair>
2626
#include <QHash>
2727

28+
class QgsServerRequest;
29+
class QgsServerResponse;
30+
2831
typedef QList< QPair<QRgb, int> > QgsColorBox; //Color / number of pixels
2932
typedef QMultiMap< int, QgsColorBox > QgsColorBoxMap; // sum of pixels / color box
3033

@@ -33,7 +36,8 @@ It provides a method to set data to the client*/
3336
class QgsHttpRequestHandler: public QgsRequestHandler
3437
{
3538
public:
36-
explicit QgsHttpRequestHandler( const bool captureOutput );
39+
// QgsServerRequest and QgsServerResponse MUST live in the same scope
40+
explicit QgsHttpRequestHandler( const QgsServerRequest& request, QgsServerResponse& response );
3741
~QgsHttpRequestHandler();
3842

3943
virtual void setGetMapResponse( const QString& service, QImage* img, int imageQuality ) override;
@@ -49,36 +53,29 @@ class QgsHttpRequestHandler: public QgsRequestHandler
4953
virtual void setGetCoverageResponse( QByteArray* ba ) override;
5054
//! Send out HTTP headers and flush output buffer
5155
virtual void sendResponse() override;
52-
virtual void setDefaultHeaders() override;
5356
virtual void setHeader( const QString &name, const QString &value ) override;
54-
virtual int removeHeader( const QString &name ) override;
55-
virtual void clearHeaders() override;
57+
virtual void removeHeader( const QString &name ) override;
58+
virtual void clear() override;
5659
virtual void appendBody( const QByteArray &body ) override;
57-
virtual void clearBody() override;
5860
virtual void setInfoFormat( const QString &format ) override;
59-
virtual bool responseReady() const override;
6061
virtual bool exceptionRaised() const override;
6162
virtual void setParameter( const QString &key, const QString &value ) override;
6263
virtual QString parameter( const QString &key ) const override;
6364
virtual int removeParameter( const QString &key ) override;
6465
#ifdef HAVE_SERVER_PYTHON_PLUGINS
6566
virtual void setPluginFilters( const QgsServerFiltersMap &pluginFilters ) override;
6667
#endif
67-
//! Return the response if capture output is activated
68-
QPair<QByteArray, QByteArray> getResponse() override;
68+
69+
virtual void parseInput() override;
6970

7071
protected:
71-
virtual void sendHeaders() override;
72-
virtual void sendBody() override;
7372
void setHttpResponse( QByteArray *ba, const QString &format );
7473

7574
/** Converts format to official mimetype (e.g. 'jpg' to 'image/jpeg')
7675
@return mime string (or the entered string if not found)*/
7776
QString formatToMimeType( const QString& format ) const;
7877

79-
void requestStringToParameterMap( const QString& request, QMap<QString, QString>& parameters );
80-
//! Read CONTENT_LENGTH characters from stdin
81-
QString readPostBody() const;
78+
void requestStringToParameterMap( QMap<QString, QString>& parameters );
8279

8380
private:
8481
static void medianCut( QVector<QRgb>& colorTable, int nColors, const QImage& inputImage );
@@ -93,11 +90,10 @@ class QgsHttpRequestHandler: public QgsRequestHandler
9390
//! Calculates a representative color for a box (pixel weighted average)
9491
static QRgb boxColor( const QgsColorBox& box, int boxPixels );
9592
// TODO: if HAVE_SERVER_PYTHON
96-
QByteArray mResponseHeader;
97-
QByteArray mResponseBody;
98-
bool mCaptureOutput;
99-
void addToResponseHeader( const char * response );
100-
void addToResponseBody( const char * response );
93+
94+
const QgsServerRequest& mRequest;
95+
QgsServerResponse& mResponse;
96+
10197
};
10298

10399
#endif

‎src/server/qgspostrequesthandler.cpp

Lines changed: 0 additions & 89 deletions
This file was deleted.

‎src/server/qgspostrequesthandler.h

Lines changed: 0 additions & 33 deletions
This file was deleted.

‎src/server/qgsrequesthandler.h

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -96,33 +96,26 @@ class QgsRequestHandler
9696
//! @note not available in Python bindings
9797
virtual void setGetCoverageResponse( QByteArray* ba ) = 0;
9898

99-
virtual void setDefaultHeaders() {}
100-
10199
//! Set an HTTP header
102100
virtual void setHeader( const QString &name, const QString &value ) = 0;
103101

104102
//! Remove an HTTP header
105-
virtual int removeHeader( const QString &name ) = 0;
106-
107-
//! Delete all HTTP headers
108-
virtual void clearHeaders() = 0;
103+
virtual void removeHeader( const QString &name ) = 0;
109104

110105
//! Append the bytestream to response body
111106
virtual void appendBody( const QByteArray &body ) = 0;
112107

113-
//! Clears the response body
114-
virtual void clearBody() = 0;
115-
116-
//! Return the response body
117-
virtual QByteArray body() { return mBody; }
Code has comments. Press enter to view.
108+
//! Clears the response body and headers
109+
virtual void clear() = 0;
118110

119111
//! Set the info format string such as "text/xml"
120112
virtual void setInfoFormat( const QString &format ) = 0;
121113

122-
//! Check whether there is any header set or the body is not empty
123-
virtual bool responseReady() const = 0;
124-
125-
//! Send out HTTP headers and flush output buffer
114+
/** Send out HTTP headers and flush output buffer
115+
*
116+
* This method is intended osly for streaming
117+
* partial content.
118+
*/
126119
virtual void sendResponse() = 0;
127120

128121
//! Pointer to last raised exception
@@ -160,37 +153,19 @@ class QgsRequestHandler
160153
virtual void setPluginFilters( const QgsServerFiltersMap& pluginFilters ) = 0;
161154
#endif
162155

163-
//! @note not available in Python bindings
164-
virtual QPair<QByteArray, QByteArray> getResponse() = 0;
165-
166156
protected:
167-
//! @note not available in Python bindings
168-
virtual void sendHeaders() = 0;
169-
170-
//! @note not available in Python bindings
171-
virtual void sendBody() = 0;
172157
#ifdef HAVE_SERVER_PYTHON_PLUGINS
173158
QgsServerFiltersMap mPluginFilters;
174159
#endif
175-
QByteArray mBody; // The response payload
176160
//! This is set by the parseInput methods of the subclasses (parameter FORMAT, e.g. 'FORMAT=PNG')
177161
QString mFormat;
178162
QString mFormatString; //format string as it is passed in the request (with base)
179163
bool mHeadersSent;
180164
QString mService;
181165
QString mInfoFormat;
182-
QgsMapServiceException* mException; // Stores the exception
183166
QMap<QString, QString> mParameterMap;
167+
QgsMapServiceException* mException; // Stores the exception
184168

185-
/** Response headers. They can be empty, in this case headers are
186-
automatically generated from the content mFormat */
187-
QMap<QString, QString> mHeaders;
188-
// TODO: if HAVE_SERVER_PYTHON
189-
190-
/** Response output buffers, used by Python bindings to return
191-
* output instead of printing with fcgi printf */
192-
// QByteArray mResponseHeader;
193-
// QByteArray mResponseBody;
194169
};
195170

196171
#endif

‎src/server/qgsserver.cpp

Lines changed: 88 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626
#include "qgsauthmanager.h"
2727
#include "qgscapabilitiescache.h"
2828
#include "qgsfontutils.h"
29-
#include "qgsgetrequesthandler.h"
30-
#include "qgspostrequesthandler.h"
29+
#include "qgshttprequesthandler.h"
3130
#include "qgsproject.h"
3231
#include "qgsproviderregistry.h"
3332
#include "qgslogger.h"
@@ -40,6 +39,8 @@
4039
#include "qgsserverlogger.h"
4140
#include "qgseditorwidgetregistry.h"
4241
#include "qgsaccesscontrolfilter.h"
42+
#include "qgsserverrequest.h"
43+
#include "qgsbufferserverresponse.h"
4344

4445
#include <QDomDocument>
4546
#include <QNetworkDiskCache>
@@ -62,19 +63,17 @@ QgsCapabilitiesCache* QgsServer::sCapabilitiesCache = nullptr;
6263
QgsServerInterfaceImpl* QgsServer::sServerInterface = nullptr;
6364
// Initialization must run once for all servers
6465
bool QgsServer::sInitialised = false;
65-
bool QgsServer::sCaptureOutput = true;
6666

6767
QgsServiceRegistry QgsServer::sServiceRegistry;
6868

69-
QgsServer::QgsServer( bool captureOutput )
69+
QgsServer::QgsServer( )
7070
{
7171
// QgsApplication must exist
7272
if ( qobject_cast<QgsApplication*>( qApp ) == nullptr )
7373
{
7474
qFatal( "A QgsApplication must exist before a QgsServer instance can be created." );
7575
abort();
7676
}
77-
sCaptureOutput = captureOutput;
7877
init();
7978
}
8079

@@ -112,33 +111,6 @@ void QgsServer::setupNetworkAccessManager()
112111
nam->setCache( cache );
113112
}
114113

115-
/**
116-
* @brief QgsServer::createRequestHandler factory, creates a request instance
117-
* @param captureOutput
118-
* @return request instance
119-
*/
120-
QgsRequestHandler* QgsServer::createRequestHandler( const bool captureOutput )
121-
{
122-
QgsRequestHandler* requestHandler = nullptr;
123-
char* requestMethod = getenv( "REQUEST_METHOD" );
124-
if ( requestMethod )
125-
{
126-
if ( strcmp( requestMethod, "POST" ) == 0 )
127-
{
128-
requestHandler = new QgsPostRequestHandler( captureOutput );
129-
}
130-
else
131-
{
132-
requestHandler = new QgsGetRequestHandler( captureOutput );
133-
}
134-
}
135-
else
136-
{
137-
requestHandler = new QgsGetRequestHandler( captureOutput );
138-
}
139-
return requestHandler;
140-
}
141-
142114
/**
143115
* @brief QgsServer::defaultProjectFile
144116
* @return the default project file
@@ -375,14 +347,10 @@ void QgsServer::putenv( const QString &var, const QString &val )
375347
* @param queryString
376348
* @return response headers and body
377349
*/
378-
QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryString )
350+
351+
void QgsServer::handleRequest( const QgsServerRequest& request, QgsServerResponse& response )
379352
{
380-
/*
381-
* This is mainly for python bindings, passing QUERY_STRING
382-
* to handleRequest without using os.environment
383-
*/
384-
if ( ! queryString.isEmpty() )
385-
putenv( QStringLiteral( "QUERY_STRING" ), queryString );
353+
Q_UNUSED( response );
386354

387355
QgsMessageLog::MessageLevel logLevel = QgsServerLogger::instance()->logLevel();
388356
QTime time; //used for measuring request time if loglevel < 1
@@ -397,22 +365,22 @@ QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryStri
397365
}
398366

399367
//Request handler
400-
QScopedPointer<QgsRequestHandler> theRequestHandler( createRequestHandler( sCaptureOutput ) );
368+
QgsHttpRequestHandler theRequestHandler( request, response );
401369

402370
try
403371
{
404372
// TODO: split parse input into plain parse and processing from specific services
405-
theRequestHandler->parseInput();
373+
theRequestHandler.parseInput();
406374
}
407375
catch ( QgsMapServiceException& e )
408376
{
409377
QgsMessageLog::logMessage( "Parse input exception: " + e.message(), QStringLiteral( "Server" ), QgsMessageLog::CRITICAL );
410-
theRequestHandler->setServiceException( e );
378+
theRequestHandler.setServiceException( e );
411379
}
412380

413381
#ifdef HAVE_SERVER_PYTHON_PLUGINS
414382
// Set the request handler into the interface for plugins to manipulate it
415-
sServerInterface->setRequestHandler( theRequestHandler.data() );
383+
sServerInterface->setRequestHandler( &theRequestHandler );
416384
// Iterate filters and call their requestReady() method
417385
QgsServerFiltersMap::const_iterator filtersIterator;
418386
QgsServerFiltersMap filters = sServerInterface->filters();
@@ -424,11 +392,11 @@ QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryStri
424392
//Pass the filters to the requestHandler, this is needed for the following reasons:
425393
// 1. allow core services to access plugin filters and implement thir own plugin hooks
426394
// 2. allow requestHandler to call sendResponse plugin hook
427-
theRequestHandler->setPluginFilters( sServerInterface->filters() );
395+
theRequestHandler.setPluginFilters( sServerInterface->filters() );
428396
#endif
429397

430398
// Copy the parameters map
431-
QMap<QString, QString> parameterMap( theRequestHandler->parameterMap() );
399+
QMap<QString, QString> parameterMap( theRequestHandler.parameterMap() );
432400
#ifdef HAVE_SERVER_PYTHON_PLUGINS
433401
const QgsAccessControl* accessControl = nullptr;
434402
accessControl = sServerInterface->accessControls();
@@ -442,28 +410,27 @@ QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryStri
442410
sServerInterface->setConfigFilePath( configFilePath );
443411
#endif
444412
//Service parameter
445-
QString serviceString = theRequestHandler->parameter( QStringLiteral( "SERVICE" ) );
413+
QString serviceString = theRequestHandler.parameter( QStringLiteral( "SERVICE" ) );
446414

447415
if ( serviceString.isEmpty() )
448416
{
449417
// SERVICE not mandatory for WMS 1.3.0 GetMap & GetFeatureInfo
450-
QString requestString = theRequestHandler->parameter( QStringLiteral( "REQUEST" ) );
418+
QString requestString = theRequestHandler.parameter( QStringLiteral( "REQUEST" ) );
451419
if ( requestString == QLatin1String( "GetMap" ) || requestString == QLatin1String( "GetFeatureInfo" ) )
452420
{
453421
serviceString = QStringLiteral( "WMS" );
454422
}
455423
}
456424

457425
//possibility for client to suggest a download filename
458-
QString outputFileName = theRequestHandler->parameter( QStringLiteral( "FILE_NAME" ) );
426+
QString outputFileName = theRequestHandler.parameter( QStringLiteral( "FILE_NAME" ) );
459427
if ( !outputFileName.isEmpty() )
460428
{
461-
theRequestHandler->setDefaultHeaders();
462-
theRequestHandler->setHeader( QStringLiteral( "Content-Disposition" ), "attachment; filename=\"" + outputFileName + "\"" );
429+
theRequestHandler.setHeader( QStringLiteral( "Content-Disposition" ), "attachment; filename=\"" + outputFileName + "\"" );
463430
}
464431

465432
// Enter core services main switch
466-
if ( !theRequestHandler->exceptionRaised() )
433+
if ( !theRequestHandler.exceptionRaised() )
467434
{
468435
if ( serviceString == QLatin1String( "WCS" ) )
469436
{
@@ -475,15 +442,15 @@ QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryStri
475442
);
476443
if ( !p )
477444
{
478-
theRequestHandler->setServiceException( QgsMapServiceException( QStringLiteral( "Project file error" ), QStringLiteral( "Error reading the project file" ) ) );
445+
theRequestHandler.setServiceException( QgsMapServiceException( QStringLiteral( "Project file error" ), QStringLiteral( "Error reading the project file" ) ) );
479446
}
480447
else
481448
{
482449
QgsWCSServer wcsServer(
483450
configFilePath
484451
, parameterMap
485452
, p
486-
, theRequestHandler.data()
453+
, &theRequestHandler
487454
#ifdef HAVE_SERVER_PYTHON_PLUGINS
488455
, accessControl
489456
#endif
@@ -501,15 +468,15 @@ QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryStri
501468
);
502469
if ( !p )
503470
{
504-
theRequestHandler->setServiceException( QgsMapServiceException( QStringLiteral( "Project file error" ), QStringLiteral( "Error reading the project file" ) ) );
471+
theRequestHandler.setServiceException( QgsMapServiceException( QStringLiteral( "Project file error" ), QStringLiteral( "Error reading the project file" ) ) );
505472
}
506473
else
507474
{
508475
QgsWfsServer wfsServer(
509476
configFilePath
510477
, parameterMap
511478
, p
512-
, theRequestHandler.data()
479+
, &theRequestHandler
513480
#ifdef HAVE_SERVER_PYTHON_PLUGINS
514481
, accessControl
515482
#endif
@@ -527,15 +494,15 @@ QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryStri
527494
);
528495
if ( !p )
529496
{
530-
theRequestHandler->setServiceException( QgsMapServiceException( QStringLiteral( "WMS configuration error" ), QStringLiteral( "There was an error reading the project file or the SLD configuration" ) ) );
497+
theRequestHandler.setServiceException( QgsMapServiceException( QStringLiteral( "WMS configuration error" ), QStringLiteral( "There was an error reading the project file or the SLD configuration" ) ) );
531498
}
532499
else
533500
{
534501
QgsWmsServer wmsServer(
535502
configFilePath
536503
, parameterMap
537504
, p
538-
, theRequestHandler.data()
505+
, &theRequestHandler
539506
, sCapabilitiesCache
540507
#ifdef HAVE_SERVER_PYTHON_PLUGINS
541508
, accessControl
@@ -546,7 +513,7 @@ QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryStri
546513
}
547514
else
548515
{
549-
theRequestHandler->setServiceException( QgsMapServiceException( QStringLiteral( "Service configuration error" ), QStringLiteral( "Service unknown or unsupported" ) ) );
516+
theRequestHandler.setServiceException( QgsMapServiceException( QStringLiteral( "Service configuration error" ), QStringLiteral( "Service unknown or unsupported" ) ) );
550517
} // end switch
551518
} // end if not exception raised
552519

@@ -562,14 +529,74 @@ QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryStri
562529
sServerInterface->clearRequestHandler();
563530
#endif
564531

565-
theRequestHandler->sendResponse();
532+
#ifdef HAVE_SERVER_PYTHON_PLUGINS
533+
filters = sServerInterface->filters();
534+
for ( filtersIterator = filters.constBegin(); filtersIterator != filters.constEnd(); ++filtersIterator )
535+
{
536+
filtersIterator.value()->sendResponse();
537+
}
538+
#endif
539+
response.finish();
566540

567541
if ( logLevel == QgsMessageLog::INFO )
568542
{
569543
QgsMessageLog::logMessage( "Request finished in " + QString::number( time.elapsed() ) + " ms", QStringLiteral( "Server" ), QgsMessageLog::INFO );
570544
}
571-
// Returns the header and response bytestreams (to be used in Python bindings)
572-
return theRequestHandler->getResponse();
545+
}
546+
547+
QPair<QByteArray, QByteArray> QgsServer::handleRequest( const QString& queryString )
548+
{
549+
/*
550+
* This is mainly for python bindings, passing QUERY_STRING
551+
* to handleRequest without using os.environment
552+
*
553+
* XXX To be removed because query string is now handled in QgsServerRequest
554+
*
555+
*/
556+
if ( ! queryString.isEmpty() )
557+
putenv( QStringLiteral( "QUERY_STRING" ), queryString );
558+
559+
QgsServerRequest::Method method = QgsServerRequest::GetMethod;
560+
QByteArray ba;
561+
562+
// XXX This is mainly used in tests
563+
char* requestMethod = getenv( "REQUEST_METHOD" );
564+
if ( requestMethod && strcmp( requestMethod, "POST" ) == 0 )
565+
{
566+
method = QgsServerRequest::PostMethod;
567+
const char* data = getenv( "REQUEST_BODY" );
568+
if ( data )
569+
{
570+
ba.append( data );
571+
}
572+
}
573+
574+
QUrl url;
575+
url.setQuery( queryString );
576+
577+
QgsBufferServerRequest request( url, method, &ba );
578+
QgsBufferServerResponse response;
579+
580+
handleRequest( request, response );
581+
582+
/*
583+
* XXX For compatibility only:
584+
* We should return a (moved) QgsBufferServerResponse instead
585+
*/
586+
QByteArray headerBuffer;
587+
QMap<QString, QString>::const_iterator it;
588+
for ( it = response.headers().constBegin(); it != response.headers().constEnd(); ++it )
589+
{
590+
headerBuffer.append( it.key().toUtf8() );
591+
headerBuffer.append( ": " );
592+
headerBuffer.append( it.value().toUtf8() );
593+
headerBuffer.append( "\n" );
594+
}
595+
headerBuffer.append( "\n" );
596+
597+
// TODO: check that this is not an evil bug!
598+
return QPair<QByteArray, QByteArray>( headerBuffer, response.body() );
599+
573600
}
574601

575602
#ifdef HAVE_SERVER_PYTHON_PLUGINS

‎src/server/qgsserver.h

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,13 @@
3535
#include "qgsmapsettings.h"
3636
#include "qgsmessagelog.h"
3737
#include "qgsserviceregistry.h"
38-
39-
#ifdef HAVE_SERVER_PYTHON_PLUGINS
4038
#include "qgsserverplugins.h"
4139
#include "qgsserverfilter.h"
40+
#include "qgsserverinterfaceimpl.h"
4241
#include "qgis_server.h"
43-
#endif
4442

45-
#include "qgsserverinterfaceimpl.h"
43+
class QgsServerRequest;
44+
class QgsServerResponse;
4645

4746
/** \ingroup server
4847
* The QgsServer class provides OGC web services.
@@ -54,7 +53,7 @@ class SERVER_EXPORT QgsServer
5453
/** Creates the server instance
5554
* @param captureOutput set to false for stdout output (FCGI)
5655
*/
57-
QgsServer( bool captureOutput = true );
56+
QgsServer();
5857

5958
/** Set environment variable
6059
* @param var environment variable name
@@ -63,18 +62,29 @@ class SERVER_EXPORT QgsServer
6362
*/
6463
void putenv( const QString &var, const QString &val );
6564

66-
/** Handles the request. The output is normally printed trough FCGI printf
67-
* by the request handler or, in case the server has been invoked from python
68-
* bindings, a flag is set that captures all the output headers and body, instead
69-
* of printing it returns the output as a QPair of QByteArray.
65+
/** Handles the request.
7066
* The query string is normally read from environment
7167
* but can be also passed in args and in this case overrides the environment
7268
* variable
7369
*
74-
* @param queryString optional QString containing the query string
75-
* @return the response headers and body QPair of QByteArray if called from python bindings, empty otherwise
70+
* @param request a QgsServerRequest holding request parameters
71+
* @param response a QgsServerResponse for handling response I/O)
72+
*/
73+
void handleRequest( const QgsServerRequest& request, QgsServerResponse& response );
74+
75+
/** Handles the request from query strinf
76+
* The query string is normally read from environment
77+
* but can be also passed in args and in this case overrides the environment
78+
* variable.
79+
*
80+
* @param queryString QString containing the query string
81+
* @return the response headers and body QPair of QByteArray
7682
*/
77-
QPair<QByteArray, QByteArray> handleRequest( const QString& queryString = QString() );
83+
QPair<QByteArray, QByteArray> handleRequest( const QString& queryString );
84+
85+
86+
87+
7888

7989
//! Returns a pointer to the server interface
8090
QgsServerInterfaceImpl* serverInterface() { return sServerInterface; }
@@ -112,7 +122,7 @@ class SERVER_EXPORT QgsServer
112122
static QFileInfo defaultAdminSLD();
113123
static void setupNetworkAccessManager();
114124
//! Create and return a request handler instance
115-
static QgsRequestHandler* createRequestHandler( const bool captureOutput = false );
125+
static QgsRequestHandler* createRequestHandler( const QgsServerRequest& request, QgsServerResponse& response );
116126

117127
// Return the server name
118128
static QString &serverName();

‎src/server/qgsserverinterfaceimpl.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121

2222
#include "qgsserverinterface.h"
2323
#include "qgscapabilitiescache.h"
24-
#include "qgsgetrequesthandler.h"
25-
#include "qgspostrequesthandler.h"
2624

2725
/**
2826
* QgsServerInterface

‎src/server/qgsserverrequest.cpp

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,26 @@
2020
#include "qgsserverrequest.h"
2121
#include <QUrlQuery>
2222

23+
QgsServerRequest::QgsServerRequest()
24+
: mUrl()
25+
, mMethod( GetMethod )
26+
, mDecoded( false )
27+
{
28+
29+
}
30+
2331
QgsServerRequest::QgsServerRequest( const QString& url, Method method )
2432
: mUrl( url )
2533
, mMethod( method )
34+
, mDecoded( false )
2635
{
2736

2837
}
2938

3039
QgsServerRequest::QgsServerRequest( const QUrl& url, Method method )
3140
: mUrl( url )
3241
, mMethod( method )
42+
, mDecoded( false )
3343
{
3444

3545
}
@@ -59,16 +69,17 @@ QgsServerRequest::Method QgsServerRequest::method() const
5969
QMap<QString, QString> QgsServerRequest::parameters() const
6070
{
6171
// Lazy build of the parameter map
62-
if ( mParams.isEmpty() && mUrl.hasQuery() )
72+
if ( !mDecoded && mUrl.hasQuery() )
6373
{
6474
typedef QPair<QString, QString> pair_t;
6575

6676
QUrlQuery query( mUrl );
67-
QList<pair_t> items = query.queryItems();
77+
QList<pair_t> items = query.queryItems( QUrl::FullyDecoded );
6878
Q_FOREACH ( const pair_t& pair, items )
6979
{
7080
mParams.insert( pair.first.toUpper(), pair.second );
7181
}
82+
mDecoded = true;
7283
}
7384
return mParams;
7485
}
@@ -78,4 +89,34 @@ QByteArray QgsServerRequest::data() const
7889
return QByteArray();
7990
}
8091

92+
void QgsServerRequest::setParameter( const QString& key, const QString& value )
93+
{
94+
parameters();
95+
mParams.insert( key, value );
96+
}
97+
98+
QString QgsServerRequest::getParameter( const QString& key ) const
99+
{
100+
parameters();
101+
return mParams.value( key );
102+
}
103+
104+
void QgsServerRequest::removeParameter( const QString& key )
105+
{
106+
parameters();
107+
mParams.remove( key );
108+
}
109+
110+
void QgsServerRequest::setUrl( const QUrl& url )
111+
{
112+
mUrl = url;
113+
mDecoded = false;
114+
mParams.clear();
115+
}
116+
117+
void QgsServerRequest::setMethod( Method method )
118+
{
119+
mMethod = method;
120+
}
121+
81122

‎src/server/qgsserverrequest.h

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include <QUrl>
2323
#include <QMap>
24+
#include "qgis_server.h"
2425

2526
/**
2627
* \ingroup server
@@ -42,13 +43,19 @@ class SERVER_EXPORT QgsServerRequest
4243
HeadMethod, PutMethod, GetMethod, PostMethod, DeleteMethod
4344
};
4445

46+
47+
/**
48+
* Constructor
49+
*/
50+
QgsServerRequest();
51+
4552
/**
4653
* Constructor
4754
*
4855
* @param url the url string
4956
* @param method the request method
5057
*/
51-
QgsServerRequest( const QString& url, Method method );
58+
QgsServerRequest( const QString& url, Method method = GetMethod );
5259

5360
/**
5461
* Constructor
@@ -77,6 +84,21 @@ class SERVER_EXPORT QgsServerRequest
7784
*/
7885
Parameters parameters() const;
7986

87+
/**
88+
* Set a parameter
89+
*/
90+
void setParameter( const QString& key, const QString& value );
91+
92+
/**
93+
* Get a parameter value
94+
*/
95+
QString getParameter( const QString& key ) const;
96+
97+
/**
98+
* Remove a parameter
99+
*/
100+
void removeParameter( const QString& key );
101+
80102
/**
81103
* Return post/put data
82104
* Check for QByteArray::isNull() to check if data
@@ -89,13 +111,24 @@ class SERVER_EXPORT QgsServerRequest
89111
*/
90112
virtual QString getHeader( const QString& name ) const;
91113

114+
/**
115+
* Set the request url
116+
*/
117+
void setUrl( const QUrl& url );
118+
119+
/**
120+
* Set the request method
121+
*/
122+
void setMethod( Method method );
123+
92124
private:
93125
QUrl mUrl;
94126
Method mMethod;
95127
// We mark as mutable in order
96128
// to support lazy initialization
97129
// Use QMap here because it will be faster for small
98130
// number of elements
131+
mutable bool mDecoded;
99132
mutable QMap<QString, QString> mParams;
100133
};
101134

‎src/server/qgsserverresponse.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
#include "qgsserverresponse.h"
2121
#include "qgsmessagelog.h"
22-
#include <QTextStream>
22+
//#include <QTextStream>
2323

2424
//! constructor
2525
QgsServerResponse::QgsServerResponse()
@@ -39,8 +39,9 @@ void QgsServerResponse::write( const QString& data )
3939
QIODevice* iodev = io();
4040
if ( iodev )
4141
{
42-
QTextStream stream( iodev );
43-
stream << data;
42+
//QTextStream stream( iodev );
43+
//stream << data;
44+
iodev->write( data.toUtf8() );
4445
}
4546
else
4647
{

‎src/server/qgsserverresponse.h

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#ifndef QGSSERVERRESPONSE_H
2020
#define QGSSERVERRESPONSE_H
2121

22+
#include "qgis_server.h"
23+
2224
#include <QString>
2325
#include <QIODevice>
2426

@@ -42,12 +44,20 @@ class SERVER_EXPORT QgsServerResponse
4244
//! destructor
4345
virtual ~QgsServerResponse();
4446

45-
/** Set Header entry
47+
/**
48+
* Set Header entry
4649
* Add Header entry to the response
4750
* Note that it is usually an error to set Header after writing data
4851
*/
4952
virtual void setHeader( const QString& key, const QString& value ) = 0;
5053

54+
/**
55+
* Clear header
56+
* Undo a previous 'set_header' call
57+
*/
58+
virtual void clearHeader( const QString& key ) = 0;
59+
60+
5161
/** Set the http return code
5262
* @param code HTTP return code value
5363
*/
@@ -106,6 +116,23 @@ class SERVER_EXPORT QgsServerResponse
106116
*/
107117
virtual QIODevice* io() = 0;
108118

119+
/**
120+
* Finish the response, ending the transaction
121+
*/
122+
virtual void finish() = 0;
123+
124+
/**
125+
* Flushes the current output buffer to the network
126+
*
127+
* 'flush()' may be called multiple times. For HTTP transactions
128+
* headers will be written on the first call to 'flush()'.
129+
*/
130+
virtual void flush() = 0;
131+
132+
/**
133+
* Reset all headers and content for this response
134+
*/
135+
virtual void clear() = 0;
109136
};
110137

111138
#endif

‎src/server/qgsservice.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include "qgsserverrequest.h"
2525
#include "qgsserverresponse.h"
26+
#include "qgis_server.h"
2627

2728
class QgsProject;
2829

‎src/server/qgsservicemodule.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#ifndef QGSSERVICEMODULE_H
2222
#define QGSSERVICEMODULE_H
2323

24+
#include "qgis_server.h"
25+
2426
class QgsServiceRegistry;
2527
class QgsServerInterface;
2628

‎src/server/qgsservicenativeloader.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class QgsServiceRegistry;
2424
class QgsServiceNativeModuleEntry;
2525
class QgsServerInterface;
2626

27+
#include "qgis_server.h"
28+
2729
#include <QHash>
2830
#include <memory>
2931

‎src/server/services/DummyService/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ ADD_LIBRARY (dummy MODULE ${dummy_SRCS})
1313

1414

1515
INCLUDE_DIRECTORIES(
16+
${CMAKE_BINARY_DIR}/src/core
17+
${CMAKE_BINARY_DIR}/src/gui
18+
${CMAKE_BINARY_DIR}/src/python
19+
${CMAKE_BINARY_DIR}/src/analysis
20+
${CMAKE_BINARY_DIR}/src/server
1621
${CMAKE_CURRENT_BINARY_DIR}
1722
../../../core ../../../core/geometry ../../../core/raster
1823
../..

‎src/server/services/wms/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ INCLUDE_DIRECTORIES(SYSTEM
3434
)
3535

3636
INCLUDE_DIRECTORIES(
37+
${CMAKE_BINARY_DIR}/src/core
38+
${CMAKE_BINARY_DIR}/src/gui
39+
${CMAKE_BINARY_DIR}/src/python
40+
${CMAKE_BINARY_DIR}/src/analysis
41+
${CMAKE_BINARY_DIR}/src/server
3742
${CMAKE_CURRENT_BINARY_DIR}
3843
../../../core
3944
../../../core/dxf

‎tests/src/python/qgis_wrapped_server.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,9 @@ def responseComplete(self):
8989
and password.decode('utf-8') == os.environ.get('QGIS_SERVER_PASSWORD', 'password')):
9090
return
9191
# No auth ...
92-
request.clearHeaders()
92+
request.clear()
9393
request.setHeader('Status', '401 Authorization required')
9494
request.setHeader('WWW-Authenticate', 'Basic realm="QGIS Server"')
95-
request.clearBody()
9695
request.appendBody(b'<h1>Authorization required</h1>')
9796

9897
filter = HTTPBasicFilter(qgs_server.serverInterface())

‎tests/src/python/test_qgsserver.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ def test_multiple_servers(self):
105105
"""Segfaults?"""
106106
for i in range(10):
107107
locals()["s%s" % i] = QgsServer()
108-
locals()["s%s" % i].handleRequest()
108+
locals()["s%s" % i].handleRequest("")
109109

110110
def test_api(self):
111111
"""Using an empty query string (returns an XML exception)
112112
we are going to test if headers and body are returned correctly"""
113113
# Test as a whole
114-
header, body = [_v for _v in self.server.handleRequest()]
114+
header, body = [_v for _v in self.server.handleRequest("")]
115115
response = self.strip_version_xmlns(header + body)
116116
expected = self.strip_version_xmlns(b'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')
117117
self.assertEqual(response, expected)
@@ -142,9 +142,8 @@ def responseComplete(self):
142142
params = request.parameterMap()
143143
QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
144144
if params.get('SERVICE', '').upper() == 'SIMPLE':
145-
request.clearHeaders()
145+
request.clear()
146146
request.setHeader('Content-type', 'text/plain')
147-
request.clearBody()
148147
request.appendBody('Hello from SimpleServer!'.encode('utf-8'))
149148

150149
serverIface = self.server.serverInterface()
@@ -180,7 +179,7 @@ def responseComplete(self):
180179
self.assertEqual(filter2, serverIface.filters()[200][0])
181180
header, body = [_v for _v in self.server.handleRequest('service=simple')]
182181
response = header + body
183-
expected = b'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
182+
expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
184183
self.assertEqual(response, expected)
185184

186185
# Test that the bindings for complex type QgsServerFiltersMap are working
@@ -192,7 +191,7 @@ def responseComplete(self):
192191
self.assertEqual(filter2, serverIface.filters()[200][0])
193192
header, body = [_v for _v in self.server.handleRequest('service=simple')]
194193
response = header + body
195-
expected = b'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
194+
expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
196195
self.assertEqual(response, expected)
197196

198197
# WMS tests

‎tests/src/python/test_qgsserver_accesscontrol.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def setUpClass(cls):
165165
"""Run before all tests"""
166166
cls._app = QgsApplication([], False)
167167
cls._server = QgsServer()
168-
cls._server.handleRequest()
168+
cls._server.handleRequest("")
169169
cls._server_iface = cls._server.serverInterface()
170170
cls._accesscontrol = RestrictedAccessControl(cls._server_iface)
171171
cls._server_iface.registerAccessControl(cls._accesscontrol, 100)

‎tests/src/python/test_qgsserver_services.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
QgsServerRequest,
88
QgsServerResponse)
99

10+
from qgis.core import QgsApplication
1011

1112
class Response(QgsServerResponse):
1213

@@ -22,12 +23,24 @@ def setReturnCode( self, code ):
2223
def setHeader( self, key, val ):
2324
pass
2425

26+
def clearHeader( self, key ):
27+
pass
28+
2529
def sendError( self, code, message ):
2630
pass
2731

2832
def io(self):
2933
return self._buffer
3034

35+
def finish(self):
36+
pass
37+
38+
def flush(self):
39+
pass
40+
41+
def clear(self):
42+
pass
43+
3144

3245
class MyService(QgsService):
3346

@@ -55,22 +68,30 @@ class TestServices(unittest.TestCase):
5568
"""
5669
"""
5770

71+
@classmethod
72+
def setUpClass(cls):
73+
cls.app = QgsApplication([], False)
74+
75+
@classmethod
76+
def tearDownClass(cls):
77+
cls.app.exitQgis()
78+
5879
def test_register(self):
5980

6081
reg = QgsServiceRegistry()
6182

62-
myserv = MyService("STUFF", "1.0", "Hello world")
83+
myserv = MyService("TEST", "1.0", "Hello world")
6384

6485
reg.registerService( myserv )
6586

6687
# Retrieve service
6788
request = QgsServerRequest("http://DoStufff", QgsServerRequest.GetMethod)
6889
response = Response()
6990

70-
service = reg.getService("STUFF")
91+
service = reg.getService("TEST")
7192
if service:
7293
service.executeRequest(request, response)
73-
94+
7495
io = response.io();
7596
io.seek(0)
7697

@@ -79,48 +100,48 @@ def test_register(self):
79100
def test_0_version_registration(self):
80101

81102
reg = QgsServiceRegistry()
82-
myserv1 = MyService("STUFF", "1.0", "Hello")
83-
myserv2 = MyService("STUFF", "1.1", "Hello")
103+
myserv1 = MyService("TEST", "1.0", "Hello")
104+
myserv2 = MyService("TEST", "1.1", "Hello")
84105

85106
reg.registerService( myserv1 )
86107
reg.registerService( myserv2)
87108

88-
service = reg.getService("STUFF")
109+
service = reg.getService("TEST")
89110
self.assertIsNotNone(service)
90111
self.assertEqual(service.version(), "1.1")
91112

92-
service = reg.getService("STUFF", "2.0")
113+
service = reg.getService("TEST", "2.0")
93114
self.assertIsNone(service)
94115

95116
def test_1_unregister_services(self):
96117

97118
reg = QgsServiceRegistry()
98-
serv1 = MyService("STUFF", "1.0a", "Hello")
99-
serv2 = MyService("STUFF", "1.0b", "Hello")
100-
serv3 = MyService("STUFF", "1.0c", "Hello")
119+
serv1 = MyService("TEST", "1.0a", "Hello")
120+
serv2 = MyService("TEST", "1.0b", "Hello")
121+
serv3 = MyService("TEST", "1.0c", "Hello")
101122

102123
reg.registerService(serv1)
103124
reg.registerService(serv2)
104125
reg.registerService(serv3)
105126

106127
# Check we get the highest version
107-
service = reg.getService("STUFF")
128+
service = reg.getService("TEST")
108129
self.assertEqual( service.version(), "1.0c" )
109130

110131
# Remove one service
111-
removed = reg.unregisterService("STUFF", "1.0c")
132+
removed = reg.unregisterService("TEST", "1.0c")
112133
self.assertEqual( removed, 1 )
113134

114135
# Check that we get the highest version
115-
service = reg.getService("STUFF")
136+
service = reg.getService("TEST")
116137
self.assertEqual( service.version(), "1.0b" )
117138

118139
# Remove all services
119-
removed = reg.unregisterService("STUFF")
140+
removed = reg.unregisterService("TEST")
120141
self.assertEqual( removed, 2 )
121142

122143
# Check that there is no more services available
123-
service = reg.getService("STUFF")
144+
service = reg.getService("TEST")
124145
self.assertIsNone(service)
125146

126147

0 commit comments

Comments
 (0)
Please sign in to comment.