QGIS API Documentation 4.1.0-Master (31622b25bb0)
Loading...
Searching...
No Matches
qgsfcgiserverresponse.cpp
Go to the documentation of this file.
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
21
22#include "qgis.h"
23#include "qgslogger.h"
24#include "qgsmessagelog.h"
25
26#include <QDebug>
27#include <QString>
28#include <QThread>
29
30#include <fcgi_stdio.h>
31
32using namespace Qt::StringLiterals;
33
34#if defined( Q_OS_UNIX ) && !defined( Q_OS_ANDROID )
35#include <unistd.h>
36#include <sys/types.h>
37#include <sys/socket.h>
38#include <chrono>
39
40//
41// QgsFCGXStreamData copied from libfcgi FCGX_Stream_Data
42//
43typedef struct QgsFCGXStreamData
44{
45 unsigned char *buff; /* buffer after alignment */
46 int bufflen; /* number of bytes buff can store */
47 unsigned char *mBuff; /* buffer as returned by Malloc */
48 unsigned char *buffStop; /* reader: last valid byte + 1 of entire buffer.
49 * stop generally differs from buffStop for
50 * readers because of record structure.
51 * writer: buff + bufflen */
52 int type; /* reader: FCGI_PARAMS or FCGI_STDIN
53 * writer: FCGI_STDOUT or FCGI_STDERR */
54 int eorStop; /* reader: stop stream at end-of-record */
55 int skip; /* reader: don't deliver content bytes */
56 int contentLen; /* reader: bytes of unread content */
57 int paddingLen; /* reader: bytes of unread padding */
58 int isAnythingWritten; /* writer: data has been written to ipcFd */
59 int rawWrite; /* writer: write data without stream headers */
60 FCGX_Request *reqDataPtr; /* request data not specific to one stream */
61} QgsFCGXStreamData;
62#endif
63
64// to be able to use 333ms expression as a duration
65using namespace std::chrono_literals;
66
67
68// QgsSocketMonitoringThread constructor
69QgsSocketMonitoringThread::QgsSocketMonitoringThread( std::shared_ptr<QgsFeedback> feedback )
70 : mFeedback( std::move( feedback ) )
71{
72 Q_ASSERT( mFeedback );
73
74 mShouldStop.store( false );
75
76#if defined( Q_OS_UNIX ) && !defined( Q_OS_ANDROID )
77 if ( FCGI_stdout && FCGI_stdout->fcgx_stream && FCGI_stdout->fcgx_stream->data )
78 {
79 QgsFCGXStreamData *stream = static_cast<QgsFCGXStreamData *>( FCGI_stdout->fcgx_stream->data );
80 if ( stream && stream->reqDataPtr )
81 {
82 mIpcFd = stream->reqDataPtr->ipcFd;
83 }
84 else
85 {
87 u"FCGI_stdout stream data is null! Socket monitoring disabled."_s, //
88 u"FCGIServer"_s, //
90 );
91 }
92 }
93 else
94 {
96 u"FCGI_stdout is null! Socket monitoring disabled."_s, //
97 u"FCGIServer"_s, //
99 );
100 }
101#endif
102}
103
104// Informs the thread to quit
106{
107 mShouldStop.store( true );
108 // Release the mutex so the try_lock in the thread will not wait anymore and
109 // the thread will end its loop as we have set 'mShouldStop' to true
110 mMutex.unlock();
111}
112
114{
115 // Lock the thread mutex: every try_lock will take 333ms
116 mMutex.lock();
117
118 if ( mIpcFd < 0 )
119 {
120 QgsMessageLog::logMessage( u"Socket monitoring disabled: no socket fd!"_s, u"FCGIServer"_s, Qgis::MessageLevel::Warning );
121 return;
122 }
123
124#if defined( Q_OS_UNIX ) && !defined( Q_OS_ANDROID )
125 quint64 threadId = reinterpret_cast<quint64>( QThread::currentThreadId() );
126
127 char c;
128
129 fd_set setOptions;
130 FD_ZERO( &setOptions ); // clear the set
131 FD_SET( mIpcFd, &setOptions ); // add our file descriptor to the set
132
133 struct timeval timeout;
134 timeout.tv_sec = 0;
135 timeout.tv_usec = 10000; // max 10ms of timeout for select
136
137 while ( !mShouldStop.load() )
138 {
139 // 'select' function will check if the socket is still valid after a 10ms timeout
140 // see https://stackoverflow.com/a/30395738
141 int rv = select( mIpcFd + 1, &setOptions, nullptr, nullptr, &timeout );
142 if ( rv == -1 )
143 {
144 // socket closed, nothing can be read
146 u"FCGIServer %1: remote socket has been closed (select)! errno: %2"_s //
147 .arg( threadId )
148 .arg( errno ),
149 u"FCGIServer"_s,
151 );
152 mFeedback->cancel();
153 break;
154 }
155 else
156 {
157 // check if there is something in the socket without reading it and without blocking
158 // see https://stackoverflow.com/a/12402596
159 const ssize_t x = recv( mIpcFd, &c, 1, MSG_PEEK | MSG_DONTWAIT );
160 if ( x != 0 )
161 {
162 // Ie. we are still connected but we have an 'error' as there is nothing to read
164 u"FCGIServer %1: remote socket still connected. errno: %2, x: %3"_s //
165 .arg( threadId )
166 .arg( errno )
167 .arg( x ),
168 5
169 );
170 }
171 else
172 {
173 // socket closed, nothing can be read
175 u"FCGIServer %1: remote socket has been closed (recv)! errno: %2, x: %3"_s //
176 .arg( threadId )
177 .arg( errno )
178 .arg( x ),
179 u"FCGIServer"_s,
181 );
182 mFeedback->cancel();
183 break;
184 }
185 }
186
187 // If lock is acquired this means the response has finished and we will exit the while loop
188 // else we will wait max for 333ms.
189 if ( mMutex.try_lock_for( 333ms ) )
190 mMutex.unlock();
191 }
192
193 if ( mShouldStop.load() )
194 {
195 QgsMessageLog::logMessage( u"FCGIServer::run %1: socket monitoring quits normally."_s.arg( threadId ), u"FCGIServer"_s, Qgis::MessageLevel::Info );
196 }
197 else
198 {
200 u"FCGIServer::run %1: socket monitoring quits: no more socket."_s //
201 .arg( threadId ), //
202 u"FCGIServer"_s,
204 );
205 }
206#endif
207}
208
209
210//
211// QgsFcgiServerResponse
212//
214 : mMethod( method )
215 , mFeedback( new QgsFeedback )
216{
217 mBuffer.open( QIODevice::ReadWrite );
219
220 mSocketMonitoringThread = std::make_unique<QgsSocketMonitoringThread>( mFeedback );
221
222 // Start the monitoring thread
223 mThread = std::thread( &QgsSocketMonitoringThread::run, mSocketMonitoringThread.get() );
224}
225
227{
228 mFinished = true;
229
230 // Inform the thread to quit asap
231 mSocketMonitoringThread->stop();
232
233 // Just to be sure
234 mThread.join();
235}
236
237void QgsFcgiServerResponse::removeHeader( const QString &key )
238{
239 mHeaders.remove( key );
240}
241
242void QgsFcgiServerResponse::addHeader( const QString &key, const QString &value )
243{
244 if ( !mHeadersSent )
245 {
246 if ( !mHeaders.contains( key ) )
247 {
248 mHeaders[key] = QList<QString>();
249 }
250 mHeaders[key].append( value );
251 }
252}
253
254
255void QgsFcgiServerResponse::setHeader( const QString &key, const QString &value )
256{
257 mHeaders[key] = QList<QString>() << value;
258}
259
260QString QgsFcgiServerResponse::header( const QString &key ) const
261{
262 const QList<QString> values = mHeaders.value( key );
263 return values.isEmpty() ? QString() : values.last();
264}
265
266QMap<QString, QString> QgsFcgiServerResponse::headers() const
267{
268 QMap<QString, QString> singleHeaders;
269 for ( auto it = mHeaders.keyBegin(); it != mHeaders.keyEnd(); ++it )
270 {
271 singleHeaders.insert( *it, header( *it ) );
272 }
273 return singleHeaders;
274}
275
276QList<QString> QgsFcgiServerResponse::fullHeader( const QString &key ) const
277{
278 return mHeaders.value( key );
279}
280
282{
283 return mHeadersSent;
284}
285
287{
288 // fcgi applications must return HTTP status in header
289 removeHeader( u"Status"_s );
290 setHeader( u"Status"_s, u" %1"_s.arg( code ) );
291 // Store the code to make it available for plugins
292 mStatusCode = code;
293}
294
295void QgsFcgiServerResponse::sendError( int code, const QString &message )
296{
297 if ( mHeadersSent )
298 {
299 QgsMessageLog::logMessage( "Cannot send error after headers written", u"FCGIServer"_s, Qgis::MessageLevel::Warning );
300 return;
301 }
302
303 clear();
304 setStatusCode( code );
305 setHeader( u"Content-Type"_s, u"text/html;charset=utf-8"_s );
306 write( u"<html><body>%1</body></html>"_s.arg( message ) );
307 finish();
308}
309
311{
312 return &mBuffer;
313}
314
316{
317 if ( mFinished )
318 {
319 QgsMessageLog::logMessage( "finish() called twice", u"FCGIServer"_s, Qgis::MessageLevel::Warning );
320 return;
321 }
322
323 if ( mFeedback->isCanceled() )
324 {
325 clear(); // we clear all buffers as the socket is dead
326 FCGI_stdout->fcgx_stream->wasFCloseCalled = true; // avoid sending FCGI end protocol as the socket is dead
327 mFinished = true;
328 return;
329 }
330
331 if ( !mHeadersSent )
332 {
333 if ( !mHeaders.contains( "Content-Length" ) )
334 {
335 setHeader( u"Content-Length"_s, QString::number( mBuffer.pos() ) );
336 }
337 }
338 flush();
339 mFinished = true;
340}
341
343{
344 if ( !mHeadersSent )
345 {
346 // Send all headers
347 QMap<QString, QList<QString>>::const_iterator it;
348 for ( it = mHeaders.constBegin(); it != mHeaders.constEnd(); ++it )
349 {
350 for ( const QString &headerValue : std::as_const( it.value() ) )
351 {
352 fputs( it.key().toUtf8(), FCGI_stdout );
353 fputs( ": ", FCGI_stdout );
354 fputs( headerValue.toUtf8(), FCGI_stdout );
355 fputs( "\n", FCGI_stdout );
356 }
357 }
358 fputs( "\n", FCGI_stdout );
359 mHeadersSent = true;
360 }
361
362 mBuffer.seek( 0 );
363 if ( mMethod == QgsServerRequest::HeadMethod )
364 {
365 // Ignore data for head method as we only
366 // write headers for HEAD requests
367 mBuffer.buffer().clear();
368 }
369 else if ( mBuffer.bytesAvailable() > 0 )
370 {
371 QByteArray &ba = mBuffer.buffer();
372 const size_t count = fwrite( ( void * ) ba.data(), ba.size(), 1, FCGI_stdout );
373#ifdef QGISDEBUG
374 qDebug() << u"Sent %1 blocks of %2 bytes"_s.arg( count ).arg( ba.size() );
375#else
376 Q_UNUSED( count )
377#endif
378 // Reset the internal buffer
379 ba.clear();
380 }
381}
382
383
385{
386 mHeaders.clear();
387 mBuffer.seek( 0 );
388 mBuffer.buffer().clear();
389}
390
391
393{
394 return mBuffer.data();
395}
396
397
399{
400 mBuffer.seek( 0 );
401 mBuffer.buffer().clear();
402}
403
404
406{
407 QgsFcgiServerResponse::setHeader( u"Server"_s, u" QGIS FCGI server - QGIS version %1"_s.arg( Qgis::version() ) );
408}
static QString version()
Version string.
Definition qgis.cpp:682
@ Warning
Warning message.
Definition qgis.h:162
@ Info
Information message.
Definition qgis.h:161
void setDefaultHeaders()
Set the default headers.
void clear() override
Reset all headers and content for this response.
void setHeader(const QString &key, const QString &value) override
Set a single header value replacing any existing value(s) for the same key.
void flush() override
Flushes the current output buffer to the network.
QList< QString > fullHeader(const QString &key) const override
Returns a (possibly empty) list of all the header values for the given key.
void removeHeader(const QString &key) override
Clear all header values for the given key Undo a previous 'setHeader' call.
QMap< QString, QString > headers() const override
Returns the header values as a map: only the last value is returned if multiple values are set for th...
QByteArray data() const override
Gets the data written so far.
QIODevice * io() override
Returns the underlying QIODevice.
bool headersSent() const override
Returns true if the headers have already been sent.
void setStatusCode(int code) override
Set the http status code.
QgsFcgiServerResponse(QgsServerRequest::Method method=QgsServerRequest::GetMethod)
Constructor for QgsFcgiServerResponse.
void addHeader(const QString &key, const QString &value) override
Add a header value for the given key, without replacing any existing value for the same key Add Heade...
void sendError(int code, const QString &message) override
Send error This method delegates error handling at the server level.
void truncate() override
Truncate data.
void finish() override
Finish the response, ending the transaction.
QString header(const QString &key) const override
Returns a single header value for a given key.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
Method
HTTP Method (or equivalent) used for the request.
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device.
void run()
main thread function
QgsSocketMonitoringThread(std::shared_ptr< QgsFeedback > feedback)
Constructor for QgsSocketMonitoringThread.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63