QGIS API Documentation 3.99.0-Master (8e76e220402)
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 {
86 QgsMessageLog::logMessage( u"FCGI_stdout stream data is null! Socket monitoring disabled."_s, //
87 u"FCGIServer"_s, //
89 }
90 }
91 else
92 {
93 QgsMessageLog::logMessage( u"FCGI_stdout is null! Socket monitoring disabled."_s, //
94 u"FCGIServer"_s, //
96 }
97#endif
98}
99
100// Informs the thread to quit
102{
103 mShouldStop.store( true );
104 // Release the mutex so the try_lock in the thread will not wait anymore and
105 // the thread will end its loop as we have set 'mShouldStop' to true
106 mMutex.unlock();
107}
108
110{
111 // Lock the thread mutex: every try_lock will take 333ms
112 mMutex.lock();
113
114 if ( mIpcFd < 0 )
115 {
116 QgsMessageLog::logMessage( u"Socket monitoring disabled: no socket fd!"_s, u"FCGIServer"_s, Qgis::MessageLevel::Warning );
117 return;
118 }
119
120#if defined( Q_OS_UNIX ) && !defined( Q_OS_ANDROID )
121 quint64 threadId = reinterpret_cast<quint64>( QThread::currentThreadId() );
122
123 char c;
124
125 fd_set setOptions;
126 FD_ZERO( &setOptions ); // clear the set
127 FD_SET( mIpcFd, &setOptions ); // add our file descriptor to the set
128
129 struct timeval timeout;
130 timeout.tv_sec = 0;
131 timeout.tv_usec = 10000; // max 10ms of timeout for select
132
133 while ( !mShouldStop.load() )
134 {
135 // 'select' function will check if the socket is still valid after a 10ms timeout
136 // see https://stackoverflow.com/a/30395738
137 int rv = select( mIpcFd + 1, &setOptions, nullptr, nullptr, &timeout );
138 if ( rv == -1 )
139 {
140 // socket closed, nothing can be read
141 QgsMessageLog::logMessage( u"FCGIServer %1: remote socket has been closed (select)! errno: %2"_s //
142 .arg( threadId )
143 .arg( errno ),
144 u"FCGIServer"_s, Qgis::MessageLevel::Warning );
145 mFeedback->cancel();
146 break;
147 }
148 else
149 {
150 // check if there is something in the socket without reading it and without blocking
151 // see https://stackoverflow.com/a/12402596
152 const ssize_t x = recv( mIpcFd, &c, 1, MSG_PEEK | MSG_DONTWAIT );
153 if ( x != 0 )
154 {
155 // Ie. we are still connected but we have an 'error' as there is nothing to read
156 QgsDebugMsgLevel( u"FCGIServer %1: remote socket still connected. errno: %2, x: %3"_s //
157 .arg( threadId )
158 .arg( errno )
159 .arg( x ),
160 5 );
161 }
162 else
163 {
164 // socket closed, nothing can be read
165 QgsMessageLog::logMessage( u"FCGIServer %1: remote socket has been closed (recv)! errno: %2, x: %3"_s //
166 .arg( threadId )
167 .arg( errno )
168 .arg( x ),
169 u"FCGIServer"_s, Qgis::MessageLevel::Warning );
170 mFeedback->cancel();
171 break;
172 }
173 }
174
175 // If lock is acquired this means the response has finished and we will exit the while loop
176 // else we will wait max for 333ms.
177 if ( mMutex.try_lock_for( 333ms ) )
178 mMutex.unlock();
179 }
180
181 if ( mShouldStop.load() )
182 {
183 QgsMessageLog::logMessage( u"FCGIServer::run %1: socket monitoring quits normally."_s.arg( threadId ), u"FCGIServer"_s, Qgis::MessageLevel::Info );
184 }
185 else
186 {
187 QgsMessageLog::logMessage( u"FCGIServer::run %1: socket monitoring quits: no more socket."_s //
188 .arg( threadId ), //
189 u"FCGIServer"_s, Qgis::MessageLevel::Warning );
190 }
191#endif
192}
193
194
195//
196// QgsFcgiServerResponse
197//
199 : mMethod( method )
200 , mFeedback( new QgsFeedback )
201{
202 mBuffer.open( QIODevice::ReadWrite );
204
205 mSocketMonitoringThread = std::make_unique<QgsSocketMonitoringThread>( mFeedback );
206
207 // Start the monitoring thread
208 mThread = std::thread( &QgsSocketMonitoringThread::run, mSocketMonitoringThread.get() );
209}
210
212{
213 mFinished = true;
214
215 // Inform the thread to quit asap
216 mSocketMonitoringThread->stop();
217
218 // Just to be sure
219 mThread.join();
220}
221
222void QgsFcgiServerResponse::removeHeader( const QString &key )
223{
224 mHeaders.remove( key );
225}
226
227void QgsFcgiServerResponse::setHeader( const QString &key, const QString &value )
228{
229 mHeaders.insert( key, value );
230}
231
232QString QgsFcgiServerResponse::header( const QString &key ) const
233{
234 return mHeaders.value( key );
235}
236
238{
239 return mHeadersSent;
240}
241
243{
244 // fcgi applications must return HTTP status in header
245 mHeaders.insert( u"Status"_s, u" %1"_s.arg( code ) );
246 // Store the code to make it available for plugins
247 mStatusCode = code;
248}
249
250void QgsFcgiServerResponse::sendError( int code, const QString &message )
251{
252 if ( mHeadersSent )
253 {
254 QgsMessageLog::logMessage( "Cannot send error after headers written", u"FCGIServer"_s, Qgis::MessageLevel::Warning );
255 return;
256 }
257
258 clear();
259 setStatusCode( code );
260 setHeader( u"Content-Type"_s, u"text/html;charset=utf-8"_s );
261 write( u"<html><body>%1</body></html>"_s.arg( message ) );
262 finish();
263}
264
266{
267 return &mBuffer;
268}
269
271{
272 if ( mFinished )
273 {
274 QgsMessageLog::logMessage( "finish() called twice", u"FCGIServer"_s, Qgis::MessageLevel::Warning );
275 return;
276 }
277
278 if ( mFeedback->isCanceled() )
279 {
280 clear(); // we clear all buffers as the socket is dead
281 FCGI_stdout->fcgx_stream->wasFCloseCalled = true; // avoid sending FCGI end protocol as the socket is dead
282 mFinished = true;
283 return;
284 }
285
286 if ( !mHeadersSent )
287 {
288 if ( !mHeaders.contains( "Content-Length" ) )
289 {
290 mHeaders.insert( u"Content-Length"_s, QString::number( mBuffer.pos() ) );
291 }
292 }
293 flush();
294 mFinished = true;
295}
296
298{
299 if ( !mHeadersSent )
300 {
301 // Send all headers
302 QMap<QString, QString>::const_iterator it;
303 for ( it = mHeaders.constBegin(); it != mHeaders.constEnd(); ++it )
304 {
305 fputs( it.key().toUtf8(), FCGI_stdout );
306 fputs( ": ", FCGI_stdout );
307 fputs( it.value().toUtf8(), FCGI_stdout );
308 fputs( "\n", FCGI_stdout );
309 }
310 fputs( "\n", FCGI_stdout );
311 mHeadersSent = true;
312 }
313
314 mBuffer.seek( 0 );
315 if ( mMethod == QgsServerRequest::HeadMethod )
316 {
317 // Ignore data for head method as we only
318 // write headers for HEAD requests
319 mBuffer.buffer().clear();
320 }
321 else if ( mBuffer.bytesAvailable() > 0 )
322 {
323 QByteArray &ba = mBuffer.buffer();
324 const size_t count = fwrite( ( void * ) ba.data(), ba.size(), 1, FCGI_stdout );
325#ifdef QGISDEBUG
326 qDebug() << u"Sent %1 blocks of %2 bytes"_s.arg( count ).arg( ba.size() );
327#else
328 Q_UNUSED( count )
329#endif
330 // Reset the internal buffer
331 ba.clear();
332 }
333}
334
335
337{
338 mHeaders.clear();
339 mBuffer.seek( 0 );
340 mBuffer.buffer().clear();
341
342 // Restore default headers
344}
345
346
348{
349 return mBuffer.data();
350}
351
352
354{
355 mBuffer.seek( 0 );
356 mBuffer.buffer().clear();
357}
358
359
361{
362 mHeaders.insert( u"Server"_s, u" QGIS FCGI server - QGIS version %1"_s.arg( Qgis::version() ) );
363}
static QString version()
Version string.
Definition qgis.cpp:682
@ Warning
Warning message.
Definition qgis.h:161
@ Info
Information message.
Definition qgis.h:160
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())
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