QGIS API Documentation 3.43.0-Master (3ee7834ace6)
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
20#include "qgis.h"
22#include "moc_qgsfcgiserverresponse.cpp"
23#include "qgsmessagelog.h"
24#include <fcgi_stdio.h>
25#include <QDebug>
26#include <QThread>
27
28#include "qgslogger.h"
29
30#if defined( Q_OS_UNIX ) && !defined( Q_OS_ANDROID )
31#include <unistd.h>
32#include <sys/types.h>
33#include <sys/socket.h>
34#include <chrono>
35
36//
37// QgsFCGXStreamData copied from libfcgi FCGX_Stream_Data
38//
39typedef struct QgsFCGXStreamData
40{
41 unsigned char *buff; /* buffer after alignment */
42 int bufflen; /* number of bytes buff can store */
43 unsigned char *mBuff; /* buffer as returned by Malloc */
44 unsigned char *buffStop; /* reader: last valid byte + 1 of entire buffer.
45 * stop generally differs from buffStop for
46 * readers because of record structure.
47 * writer: buff + bufflen */
48 int type; /* reader: FCGI_PARAMS or FCGI_STDIN
49 * writer: FCGI_STDOUT or FCGI_STDERR */
50 int eorStop; /* reader: stop stream at end-of-record */
51 int skip; /* reader: don't deliver content bytes */
52 int contentLen; /* reader: bytes of unread content */
53 int paddingLen; /* reader: bytes of unread padding */
54 int isAnythingWritten; /* writer: data has been written to ipcFd */
55 int rawWrite; /* writer: write data without stream headers */
56 FCGX_Request *reqDataPtr; /* request data not specific to one stream */
57} QgsFCGXStreamData;
58#endif
59
60// to be able to use 333ms expression as a duration
61using namespace std::chrono_literals;
62
63
64// QgsSocketMonitoringThread constructor
65QgsSocketMonitoringThread::QgsSocketMonitoringThread( std::shared_ptr<QgsFeedback> feedback )
66 : mFeedback( std::move( feedback ) )
67 , mIpcFd( -1 )
68{
69 Q_ASSERT( mFeedback );
70
71 mShouldStop.store( false );
72
73#if defined( Q_OS_UNIX ) && !defined( Q_OS_ANDROID )
74 if ( FCGI_stdout && FCGI_stdout->fcgx_stream && FCGI_stdout->fcgx_stream->data )
75 {
76 QgsFCGXStreamData *stream = static_cast<QgsFCGXStreamData *>( FCGI_stdout->fcgx_stream->data );
77 if ( stream && stream->reqDataPtr )
78 {
79 mIpcFd = stream->reqDataPtr->ipcFd;
80 }
81 else
82 {
83 QgsMessageLog::logMessage( QStringLiteral( "FCGI_stdout stream data is null! Socket monitoring disabled." ), //
84 QStringLiteral( "FCGIServer" ), //
86 }
87 }
88 else
89 {
90 QgsMessageLog::logMessage( QStringLiteral( "FCGI_stdout is null! Socket monitoring disabled." ), //
91 QStringLiteral( "FCGIServer" ), //
93 }
94#endif
95}
96
97// Informs the thread to quit
99{
100 mShouldStop.store( true );
101 // Release the mutex so the try_lock in the thread will not wait anymore and
102 // the thread will end its loop as we have set 'mShouldStop' to true
103 mMutex.unlock();
104}
105
107{
108 // Lock the thread mutex: every try_lock will take 333ms
109 mMutex.lock();
110
111 if ( mIpcFd < 0 )
112 {
113 QgsMessageLog::logMessage( QStringLiteral( "Socket monitoring disabled: no socket fd!" ), QStringLiteral( "FCGIServer" ), Qgis::MessageLevel::Warning );
114 return;
115 }
116
117#if defined( Q_OS_UNIX ) && !defined( Q_OS_ANDROID )
118 quint64 threadId = reinterpret_cast<quint64>( QThread::currentThreadId() );
119
120 mShouldStop.store( false );
121 char c;
122
123 fd_set setOptions;
124 FD_ZERO( &setOptions ); // clear the set
125 FD_SET( mIpcFd, &setOptions ); // add our file descriptor to the set
126
127 struct timeval timeout;
128 timeout.tv_sec = 0;
129 timeout.tv_usec = 10000; // max 10ms of timeout for select
130
131 while ( !mShouldStop.load() )
132 {
133 // 'select' function will check if the socket is still valid after a 10ms timeout
134 // see https://stackoverflow.com/a/30395738
135 int rv = select( mIpcFd + 1, &setOptions, NULL, NULL, &timeout );
136 if ( rv == -1 )
137 {
138 // socket closed, nothing can be read
139 QgsMessageLog::logMessage( QStringLiteral( "FCGIServer %1: remote socket has been closed (select)! errno: %2" ) //
140 .arg( threadId )
141 .arg( errno ),
142 QStringLiteral( "FCGIServer" ), Qgis::MessageLevel::Warning );
143 mFeedback->cancel();
144 break;
145 }
146 else
147 {
148 // check if there is something in the socket without reading it and without blocking
149 // see https://stackoverflow.com/a/12402596
150 const ssize_t x = recv( mIpcFd, &c, 1, MSG_PEEK | MSG_DONTWAIT );
151 if ( x != 0 )
152 {
153 // Ie. we are still connected but we have an 'error' as there is nothing to read
154 QgsDebugMsgLevel( QStringLiteral( "FCGIServer %1: remote socket still connected. errno: %2, x: %3" ) //
155 .arg( threadId )
156 .arg( errno )
157 .arg( x ),
158 5 );
159 }
160 else
161 {
162 // socket closed, nothing can be read
163 QgsMessageLog::logMessage( QStringLiteral( "FCGIServer %1: remote socket has been closed (recv)! errno: %2, x: %3" ) //
164 .arg( threadId )
165 .arg( errno )
166 .arg( x ),
167 QStringLiteral( "FCGIServer" ), Qgis::MessageLevel::Warning );
168 mFeedback->cancel();
169 break;
170 }
171 }
172
173 // If lock is acquired this means the response has finished and we will exit the while loop
174 // else we will wait max for 333ms.
175 if ( mMutex.try_lock_for( 333ms ) )
176 mMutex.unlock();
177 }
178
179 if ( mShouldStop.load() )
180 {
181 QgsDebugMsgLevel( QStringLiteral( "FCGIServer::run %1: socket monitoring quits normally." ).arg( threadId ), 2 );
182 }
183 else
184 {
185 QgsMessageLog::logMessage( QStringLiteral( "FCGIServer::run %1: socket monitoring quits: no more socket." ) //
186 .arg( threadId ), //
187 QStringLiteral( "FCGIServer" ), Qgis::MessageLevel::Warning );
188 }
189#endif
190}
191
192
193//
194// QgsFcgiServerResponse
195//
197 : mMethod( method )
198 , mFeedback( new QgsFeedback )
199{
200 mBuffer.open( QIODevice::ReadWrite );
202
203 mSocketMonitoringThread = std::make_unique<QgsSocketMonitoringThread>( mFeedback );
204
205 // Start the monitoring thread
206 mThread = std::thread( &QgsSocketMonitoringThread::run, mSocketMonitoringThread.get() );
207}
208
210{
211 mFinished = true;
212
213 // Inform the thread to quit asap
214 mSocketMonitoringThread->stop();
215
216 // Just to be sure
217 mThread.join();
218}
219
220void QgsFcgiServerResponse::removeHeader( const QString &key )
221{
222 mHeaders.remove( key );
223}
224
225void QgsFcgiServerResponse::setHeader( const QString &key, const QString &value )
226{
227 mHeaders.insert( key, value );
228}
229
230QString QgsFcgiServerResponse::header( const QString &key ) const
231{
232 return mHeaders.value( key );
233}
234
236{
237 return mHeadersSent;
238}
239
241{
242 // fcgi applications must return HTTP status in header
243 mHeaders.insert( QStringLiteral( "Status" ), QStringLiteral( " %1" ).arg( code ) );
244 // Store the code to make it available for plugins
245 mStatusCode = code;
246}
247
248void QgsFcgiServerResponse::sendError( int code, const QString &message )
249{
250 if ( mHeadersSent )
251 {
252 QgsMessageLog::logMessage( "Cannot send error after headers written", QStringLiteral( "FCGIServer" ), Qgis::MessageLevel::Warning );
253 return;
254 }
255
256 clear();
257 setStatusCode( code );
258 setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/html;charset=utf-8" ) );
259 write( QStringLiteral( "<html><body>%1</body></html>" ).arg( message ) );
260 finish();
261}
262
264{
265 return &mBuffer;
266}
267
269{
270 if ( mFinished )
271 {
272 QgsMessageLog::logMessage( "finish() called twice", QStringLiteral( "FCGIServer" ), Qgis::MessageLevel::Warning );
273 return;
274 }
275
276 if ( mFeedback->isCanceled() )
277 {
278 clear(); // we clear all buffers as the socket is dead
279 FCGI_stdout->fcgx_stream->wasFCloseCalled = true; // avoid sending FCGI end protocol as the socket is dead
280 mFinished = true;
281 return;
282 }
283
284 if ( !mHeadersSent )
285 {
286 if ( !mHeaders.contains( "Content-Length" ) )
287 {
288 mHeaders.insert( QStringLiteral( "Content-Length" ), QString::number( mBuffer.pos() ) );
289 }
290 }
291 flush();
292 mFinished = true;
293}
294
296{
297 if ( !mHeadersSent )
298 {
299 // Send all headers
300 QMap<QString, QString>::const_iterator it;
301 for ( it = mHeaders.constBegin(); it != mHeaders.constEnd(); ++it )
302 {
303 fputs( it.key().toUtf8(), FCGI_stdout );
304 fputs( ": ", FCGI_stdout );
305 fputs( it.value().toUtf8(), FCGI_stdout );
306 fputs( "\n", FCGI_stdout );
307 }
308 fputs( "\n", FCGI_stdout );
309 mHeadersSent = true;
310 }
311
312 mBuffer.seek( 0 );
313 if ( mMethod == QgsServerRequest::HeadMethod )
314 {
315 // Ignore data for head method as we only
316 // write headers for HEAD requests
317 mBuffer.buffer().clear();
318 }
319 else if ( mBuffer.bytesAvailable() > 0 )
320 {
321 QByteArray &ba = mBuffer.buffer();
322 const size_t count = fwrite( ( void * ) ba.data(), ba.size(), 1, FCGI_stdout );
323#ifdef QGISDEBUG
324 qDebug() << QStringLiteral( "Sent %1 blocks of %2 bytes" ).arg( count ).arg( ba.size() );
325#else
326 Q_UNUSED( count )
327#endif
328 // Reset the internal buffer
329 ba.clear();
330 }
331}
332
333
335{
336 mHeaders.clear();
337 mBuffer.seek( 0 );
338 mBuffer.buffer().clear();
339
340 // Restore default headers
342}
343
344
346{
347 return mBuffer.data();
348}
349
350
352{
353 mBuffer.seek( 0 );
354 mBuffer.buffer().clear();
355}
356
357
359{
360 mHeaders.insert( QStringLiteral( "Server" ), QStringLiteral( " QGIS FCGI server - QGIS version %1" ).arg( Qgis::version() ) );
361}
static QString version()
Version string.
Definition qgis.cpp:259
@ Warning
Warning message.
Definition qgis.h:156
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 Header entry Add Header entry to the response Note that it is usually an error to set Header afte...
void flush() override
Flushes the current output buffer to the network.
virtual ~QgsFcgiServerResponse() override
void removeHeader(const QString &key) override
Clear header Undo a previous 'setHeader' call.
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 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 the header value.
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())
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:41