QGIS API Documentation 3.37.0-Master (684a802617f)
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 "qgsmessagelog.h"
23#include <fcgi_stdio.h>
24#include <QDebug>
25
26#include "qgslogger.h"
27
28#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
29#include <sys/types.h>
30#include <sys/socket.h>
31
32//
33// QgsFCGXStreamData copied from libfcgi FCGX_Stream_Data
34//
35typedef struct QgsFCGXStreamData
36{
37 unsigned char *buff; /* buffer after alignment */
38 int bufflen; /* number of bytes buff can store */
39 unsigned char *mBuff; /* buffer as returned by Malloc */
40 unsigned char *buffStop; /* reader: last valid byte + 1 of entire buffer.
41 * stop generally differs from buffStop for
42 * readers because of record structure.
43 * writer: buff + bufflen */
44 int type; /* reader: FCGI_PARAMS or FCGI_STDIN
45 * writer: FCGI_STDOUT or FCGI_STDERR */
46 int eorStop; /* reader: stop stream at end-of-record */
47 int skip; /* reader: don't deliver content bytes */
48 int contentLen; /* reader: bytes of unread content */
49 int paddingLen; /* reader: bytes of unread padding */
50 int isAnythingWritten; /* writer: data has been written to ipcFd */
51 int rawWrite; /* writer: write data without stream headers */
52 FCGX_Request *reqDataPtr; /* request data not specific to one stream */
53} QgsFCGXStreamData;
54#endif
55
57 : mIsResponseFinished( isResponseFinished )
58 , mFeedback( feedback )
59 , mIpcFd( -1 )
60{
61 setObjectName( "FCGI socket monitor" );
62 Q_ASSERT( mIsResponseFinished );
63 Q_ASSERT( mFeedback );
64
65#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
66 if ( FCGI_stdout && FCGI_stdout->fcgx_stream && FCGI_stdout->fcgx_stream->data )
67 {
68 QgsFCGXStreamData *stream = static_cast<QgsFCGXStreamData *>( FCGI_stdin->fcgx_stream->data );
69 if ( stream && stream->reqDataPtr )
70 {
71 mIpcFd = stream->reqDataPtr->ipcFd;
72 }
73 else
74 {
75 QgsMessageLog::logMessage( QStringLiteral( "FCGI_stdin stream data is null! Socket monitoring disable." ),
76 QStringLiteral( "FCGIServer" ),
78 }
79 }
80 else
81 {
82 QgsMessageLog::logMessage( QStringLiteral( "FCGI_stdin is null! Socket monitoring disable." ),
83 QStringLiteral( "FCGIServer" ),
85 }
86#endif
87}
88
90{
91 if ( mIpcFd < 0 )
92 {
93 QgsMessageLog::logMessage( QStringLiteral( "Socket monitoring disabled: no socket fd!" ),
94 QStringLiteral( "FCGIServer" ),
96 return;
97 }
98
99#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
100 char c;
101 while ( !*mIsResponseFinished )
102 {
103 const ssize_t x = recv( mIpcFd, &c, 1, MSG_PEEK | MSG_DONTWAIT ); // see https://stackoverflow.com/a/12402596
104 if ( x < 0 )
105 {
106 // Ie. we are still connected but we have an 'error' as there is nothing to read
107 QgsDebugMsgLevel( QStringLiteral( "FCGIServer: remote socket still connected. errno: %1" ).arg( errno ), 5 );
108 }
109 else
110 {
111 // socket closed, nothing can be read
112 QgsDebugMsgLevel( QStringLiteral( "FCGIServer: remote socket has been closed! errno: %1" ).arg( errno ), 2 );
113 mFeedback->cancel();
114 break;
115 }
116
117 QThread::msleep( 333L );
118 }
119
120 if ( *mIsResponseFinished )
121 {
122 QgsDebugMsgLevel( QStringLiteral( "FCGIServer: socket monitoring quits normally." ), 2 );
123 }
124 else
125 {
126 QgsDebugMsgLevel( QStringLiteral( "FCGIServer: socket monitoring quits: no more socket." ), 2 );
127 }
128#endif
129}
130
131
132
133//
134// QgsFcgiServerResponse
135//
137 : mMethod( method )
138 , mFeedback( new QgsFeedback )
139{
140 mBuffer.open( QIODevice::ReadWrite );
142
143 mSocketMonitoringThread = std::make_unique<QgsSocketMonitoringThread>( &mFinished, mFeedback.get() );
144 mSocketMonitoringThread->start();
145}
146
148{
149 mFinished = true;
150 mSocketMonitoringThread->exit();
151 mSocketMonitoringThread->wait();
152}
153
154void QgsFcgiServerResponse::removeHeader( const QString &key )
155{
156 mHeaders.remove( key );
157}
158
159void QgsFcgiServerResponse::setHeader( const QString &key, const QString &value )
160{
161 mHeaders.insert( key, value );
162}
163
164QString QgsFcgiServerResponse::header( const QString &key ) const
165{
166 return mHeaders.value( key );
167}
168
170{
171 return mHeadersSent;
172}
173
175{
176 // fcgi applications must return HTTP status in header
177 mHeaders.insert( QStringLiteral( "Status" ), QStringLiteral( " %1" ).arg( code ) );
178 // Store the code to make it available for plugins
179 mStatusCode = code;
180}
181
182void QgsFcgiServerResponse::sendError( int code, const QString &message )
183{
184 if ( mHeadersSent )
185 {
186 QgsMessageLog::logMessage( "Cannot send error after headers written",
187 QStringLiteral( "FCGIServer" ),
189 return;
190 }
191
192 clear();
193 setStatusCode( code );
194 setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/html;charset=utf-8" ) );
195 write( QStringLiteral( "<html><body>%1</body></html>" ).arg( message ) );
196 finish();
197}
198
200{
201 return &mBuffer;
202}
203
205{
206 if ( mFinished )
207 {
208 QgsMessageLog::logMessage( "finish() called twice",
209 QStringLiteral( "FCGIServer" ),
211 return;
212 }
213
214 if ( mFeedback->isCanceled() )
215 {
216 clear(); // we clear all buffers as the socket is dead
217 FCGI_stdout->fcgx_stream->wasFCloseCalled = true; // avoid sending FCGI end protocol as the socket is dead
218 mFinished = true;
219 return;
220 }
221
222 if ( !mHeadersSent )
223 {
224 if ( ! mHeaders.contains( "Content-Length" ) )
225 {
226 mHeaders.insert( QStringLiteral( "Content-Length" ), QString::number( mBuffer.pos() ) );
227 }
228 }
229 flush();
230 mFinished = true;
231}
232
234{
235 if ( ! mHeadersSent )
236 {
237 // Send all headers
238 QMap<QString, QString>::const_iterator it;
239 for ( it = mHeaders.constBegin(); it != mHeaders.constEnd(); ++it )
240 {
241 fputs( it.key().toUtf8(), FCGI_stdout );
242 fputs( ": ", FCGI_stdout );
243 fputs( it.value().toUtf8(), FCGI_stdout );
244 fputs( "\n", FCGI_stdout );
245 }
246 fputs( "\n", FCGI_stdout );
247 mHeadersSent = true;
248 }
249
250 mBuffer.seek( 0 );
251 if ( mMethod == QgsServerRequest::HeadMethod )
252 {
253 // Ignore data for head method as we only
254 // write headers for HEAD requests
255 mBuffer.buffer().clear();
256 }
257 else if ( mBuffer.bytesAvailable() > 0 )
258 {
259 QByteArray &ba = mBuffer.buffer();
260 const size_t count = fwrite( ( void * )ba.data(), ba.size(), 1, FCGI_stdout );
261#ifdef QGISDEBUG
262 qDebug() << QStringLiteral( "Sent %1 blocks of %2 bytes" ).arg( count ).arg( ba.size() );
263#else
264 Q_UNUSED( count )
265#endif
266 // Reset the internal buffer
267 ba.clear();
268 }
269}
270
271
273{
274 mHeaders.clear();
275 mBuffer.seek( 0 );
276 mBuffer.buffer().clear();
277
278 // Restore default headers
280}
281
282
284{
285 return mBuffer.data();
286}
287
288
290{
291 mBuffer.seek( 0 );
292 mBuffer.buffer().clear();
293}
294
295
297{
298 setHeader( QStringLiteral( "Server" ), QStringLiteral( " QGIS FCGI server - QGIS version %1" ).arg( Qgis::version() ) );
299}
static QString version()
Version string.
Definition qgis.cpp:258
@ Warning
Warning message.
Definition qgis.h:101
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
void cancel()
Tells the internal routines that the current operation should be canceled. This should be run by the ...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
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.
QgsSocketMonitoringThread(bool *isResponseFinished, QgsFeedback *feedback)
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:39