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