QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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::setHeader( const QString &key, const QString &value )
243{
244 mHeaders.insert( key, value );
245}
246
247QString QgsFcgiServerResponse::header( const QString &key ) const
248{
249 return mHeaders.value( key );
250}
251
253{
254 return mHeadersSent;
255}
256
258{
259 // fcgi applications must return HTTP status in header
260 mHeaders.insert( u"Status"_s, u" %1"_s.arg( code ) );
261 // Store the code to make it available for plugins
262 mStatusCode = code;
263}
264
265void QgsFcgiServerResponse::sendError( int code, const QString &message )
266{
267 if ( mHeadersSent )
268 {
269 QgsMessageLog::logMessage( "Cannot send error after headers written", u"FCGIServer"_s, Qgis::MessageLevel::Warning );
270 return;
271 }
272
273 clear();
274 setStatusCode( code );
275 setHeader( u"Content-Type"_s, u"text/html;charset=utf-8"_s );
276 write( u"<html><body>%1</body></html>"_s.arg( message ) );
277 finish();
278}
279
281{
282 return &mBuffer;
283}
284
286{
287 if ( mFinished )
288 {
289 QgsMessageLog::logMessage( "finish() called twice", u"FCGIServer"_s, Qgis::MessageLevel::Warning );
290 return;
291 }
292
293 if ( mFeedback->isCanceled() )
294 {
295 clear(); // we clear all buffers as the socket is dead
296 FCGI_stdout->fcgx_stream->wasFCloseCalled = true; // avoid sending FCGI end protocol as the socket is dead
297 mFinished = true;
298 return;
299 }
300
301 if ( !mHeadersSent )
302 {
303 if ( !mHeaders.contains( "Content-Length" ) )
304 {
305 mHeaders.insert( u"Content-Length"_s, QString::number( mBuffer.pos() ) );
306 }
307 }
308 flush();
309 mFinished = true;
310}
311
313{
314 if ( !mHeadersSent )
315 {
316 // Send all headers
317 QMap<QString, QString>::const_iterator it;
318 for ( it = mHeaders.constBegin(); it != mHeaders.constEnd(); ++it )
319 {
320 fputs( it.key().toUtf8(), FCGI_stdout );
321 fputs( ": ", FCGI_stdout );
322 fputs( it.value().toUtf8(), FCGI_stdout );
323 fputs( "\n", FCGI_stdout );
324 }
325 fputs( "\n", FCGI_stdout );
326 mHeadersSent = true;
327 }
328
329 mBuffer.seek( 0 );
330 if ( mMethod == QgsServerRequest::HeadMethod )
331 {
332 // Ignore data for head method as we only
333 // write headers for HEAD requests
334 mBuffer.buffer().clear();
335 }
336 else if ( mBuffer.bytesAvailable() > 0 )
337 {
338 QByteArray &ba = mBuffer.buffer();
339 const size_t count = fwrite( ( void * ) ba.data(), ba.size(), 1, FCGI_stdout );
340#ifdef QGISDEBUG
341 qDebug() << u"Sent %1 blocks of %2 bytes"_s.arg( count ).arg( ba.size() );
342#else
343 Q_UNUSED( count )
344#endif
345 // Reset the internal buffer
346 ba.clear();
347 }
348}
349
350
352{
353 mHeaders.clear();
354 mBuffer.seek( 0 );
355 mBuffer.buffer().clear();
356
357 // Restore default headers
359}
360
361
363{
364 return mBuffer.data();
365}
366
367
369{
370 mBuffer.seek( 0 );
371 mBuffer.buffer().clear();
372}
373
374
376{
377 mHeaders.insert( u"Server"_s, u" QGIS FCGI server - QGIS version %1"_s.arg( Qgis::version() ) );
378}
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 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.
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(), 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