KD SOAP API Documentation  2.2
KDSoapClientInterface.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** This file is part of the KD Soap project.
4 **
5 ** SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
6 **
7 ** SPDX-License-Identifier: MIT
8 **
9 ****************************************************************************/
10 #include "KDSoapClientInterface.h"
12 #include "KDSoapMessageWriter_p.h"
13 #include "KDSoapNamespaceManager.h"
14 #ifndef QT_NO_SSL
16 #include "KDSoapSslHandler.h"
17 #endif
18 #include "KDSoapPendingCall_p.h"
19 #include <QAuthenticator>
20 #include <QBuffer>
21 #include <QDebug>
22 #include <QNetworkProxy>
23 #include <QNetworkReply>
24 #include <QNetworkRequest>
25 #include <QSslConfiguration>
26 #include <QTimer>
27 
28 KDSoapClientInterface::KDSoapClientInterface(const QString &endPoint, const QString &messageNamespace)
30 {
31  d->m_endPoint = endPoint;
32  d->m_messageNamespace = messageNamespace;
34 }
35 
37 {
38  d->m_thread.stop();
39  d->m_thread.wait();
40  delete d;
41 }
42 
44 {
45  d->m_version = static_cast<KDSoap::SoapVersion>(version);
46 }
47 
49 {
50  return static_cast<KDSoapClientInterface::SoapVersion>(d->m_version);
51 }
52 
54  : m_accessManager(nullptr)
55  , m_authentication()
56  , m_version(KDSoap::SOAP1_1)
57  , m_style(KDSoapClientInterface::RPCStyle)
58 {
59 #ifndef QT_NO_SSL
60  m_sslHandler = nullptr;
61 #endif
62 }
63 
65 {
66 #ifndef QT_NO_SSL
67  delete m_sslHandler;
68 #endif
69 }
70 
72 {
73  if (!m_accessManager) {
74  m_accessManager = new QNetworkAccessManager(this);
75  connect(m_accessManager, &QNetworkAccessManager::authenticationRequired, this, &KDSoapClientInterfacePrivate::_kd_slotAuthenticationRequired);
76  }
77  return m_accessManager;
78 }
79 
80 QNetworkRequest KDSoapClientInterfacePrivate::prepareRequest(const QString &method, const QString &action)
81 {
82  QNetworkRequest request(QUrl(this->m_endPoint));
83 
84 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
85  // HTTP/2 (on by default since Qt 6) creates trouble, disable it for now (https://github.com/KDAB/KDSoap/issues/246)
86  request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
87 #endif
88 
89  QString soapAction = action;
90 
91  if (soapAction.isNull()) {
92  // The automatic generation of SoapAction done in this block is a mistake going back to KDSoap 1.0.
93  // The spec says "there is no default value for SoapAction" (https://www.w3.org/TR/wsdl#_soap:operation)
94  // but we keep this for compatibility, when nothing was passed as argument (see the webcalls unittest)
95  soapAction = this->m_messageNamespace;
96  if (!soapAction.endsWith(QLatin1Char('/'))) {
97  soapAction += QLatin1Char('/');
98  }
99  soapAction += method;
100  }
101  // qDebug() << "soapAction=" << soapAction;
102 
103  QString soapHeader;
104  if (m_version == KDSoap::SOAP1_1) {
105  soapHeader += QString::fromLatin1("text/xml;charset=utf-8");
106  request.setRawHeader("SoapAction", '\"' + soapAction.toUtf8() + '\"');
107  } else if (m_version == KDSoap::SOAP1_2) {
108  soapHeader += QString::fromLatin1("application/soap+xml;charset=utf-8");
110  soapHeader += QString::fromLatin1(";action=") + soapAction;
111  }
112 
113  request.setHeader(QNetworkRequest::ContentTypeHeader, soapHeader.toUtf8());
114 
115  // FIXME need to find out which version of Qt this is no longer necessary
116  // without that the server might respond with gzip compressed data and
117  // Qt 4.6.2 fails to decode that properly
118  //
119  // happens with retrieval calls in against SugarCRM 5.5.1 running on Apache 2.2.15
120  // when the response seems to reach a certain size threshold
121  request.setRawHeader("Accept-Encoding", "compress");
122 
123  for (QMap<QByteArray, QByteArray>::const_iterator it = m_httpHeaders.constBegin(); it != m_httpHeaders.constEnd(); ++it) {
124  request.setRawHeader(it.key(), it.value());
125  }
126 
127 #ifndef QT_NO_SSL
128  if (!m_sslConfiguration.isNull()) {
129  request.setSslConfiguration(m_sslConfiguration);
130  }
131 #endif
132 
133  return request;
134 }
135 
136 QBuffer *KDSoapClientInterfacePrivate::prepareRequestBuffer(const QString &method, const KDSoapMessage &message, const QString &soapAction, const KDSoapHeaders &headers)
137 {
138  KDSoapMessageWriter msgWriter;
140  msgWriter.setVersion(m_version);
141  QBuffer *buffer = new QBuffer;
142  auto setBufferData = [&](const KDSoapMessage &msg) {
143  buffer->setData(msgWriter.messageToXml(msg,
144  (m_style == KDSoapClientInterface::RPCStyle) ? method : QString(),
145  headers, m_persistentHeaders,
147  };
148 
150  KDSoapMessage messageCopy = message;
153  }
156  if (!prop.action().isEmpty())
157  qWarning("Overwriting the action addressing parameter (%s) with the SOAP action (%s)",
158  prop.action().toLocal8Bit().constData(), soapAction.toLocal8Bit().constData());
159  prop.setAction(soapAction);
160  messageCopy.setMessageAddressingProperties(prop);
161  }
162  setBufferData(messageCopy);
163  } else {
164  setBufferData(message);
165  }
166  buffer->open(QIODevice::ReadOnly);
167  return buffer;
168 }
169 
170 KDSoapPendingCall KDSoapClientInterface::asyncCall(const QString &method, const KDSoapMessage &message, const QString &soapAction,
171  const KDSoapHeaders &headers)
172 {
173  QBuffer *buffer = d->prepareRequestBuffer(method, message, soapAction, headers);
174  QNetworkRequest request = d->prepareRequest(method, soapAction);
175  QNetworkReply *reply = d->accessManager()->post(request, buffer);
176  d->setupReply(reply);
177  maybeDebugRequest(buffer->data(), reply->request(), reply);
178  KDSoapPendingCall call(reply, buffer);
179  call.d->soapVersion = d->m_version;
180  return call;
181 }
182 
183 KDSoapMessage KDSoapClientInterface::call(const QString &method, const KDSoapMessage &message, const QString &soapAction,
184  const KDSoapHeaders &headers)
185 {
186  d->accessManager()->cookieJar(); // create it in the right thread, the secondary thread will use it
187  // Problem is: I don't want a nested event loop here. Too dangerous for GUI programs.
188  // I wanted a socket->waitFor... but we don't have access to the actual socket in QNetworkAccess.
189  // So the only option that remains is a thread and acquiring a semaphore...
190  KDSoapThreadTaskData *task = new KDSoapThreadTaskData(this, method, message, soapAction, headers);
192  d->m_thread.enqueue(task);
193  if (!d->m_thread.isRunning()) {
194  d->m_thread.start();
195  }
196  task->waitForCompletion();
197  KDSoapMessage ret = task->response();
199  delete task;
200  return ret;
201 }
202 
203 void KDSoapClientInterface::callNoReply(const QString &method, const KDSoapMessage &message,
204  const QString &soapAction, const KDSoapHeaders &headers)
205 {
206  QBuffer *buffer = d->prepareRequestBuffer(method, message, soapAction, headers);
207  QNetworkRequest request = d->prepareRequest(method, soapAction);
208  QNetworkReply *reply = d->accessManager()->post(request, buffer);
209  d->setupReply(reply);
210  maybeDebugRequest(buffer->data(), reply->request(), reply);
211  QObject::connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
212  QObject::connect(reply, &QNetworkReply::finished, buffer, &QBuffer::deleteLater);
213 }
214 
215 void KDSoapClientInterfacePrivate::_kd_slotAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator)
216 {
217  m_authentication.handleAuthenticationRequired(reply, authenticator);
218 }
219 
221 {
222  d->m_authentication = authentication;
223 }
224 
226 {
227  return d->m_endPoint;
228 }
229 
230 void KDSoapClientInterface::setEndPoint(const QString &endPoint)
231 {
232  d->m_endPoint = endPoint;
233 }
234 
235 void KDSoapClientInterface::setHeader(const QString &name, const KDSoapMessage &header)
236 {
237  d->m_persistentHeaders[name] = header;
238  d->m_persistentHeaders[name].setQualified(true);
239 }
240 
242 {
243  d->m_ignoreSslErrors = true;
244 }
245 
246 #ifndef QT_NO_SSL
247 void KDSoapClientInterface::ignoreSslErrors(const QList<QSslError> &errors)
248 {
249  d->m_ignoreErrorsList = errors;
250 }
251 #endif
252 
253 // Workaround for lack of connect-to-lambdas in Qt4
254 // The pure Qt5 code could read like
255 /*
256  QTimer *timeoutTimer = new QTimer(reply);
257  timeoutTimer->setSingleShot(true);
258  connect(timeoutTimer, &QTimer::timeout, reply, [reply]() { contents_of_the_slot });
259 */
260 class TimeoutHandler : public QTimer // this way a single QObject is needed
261 {
262  Q_OBJECT
263 public:
264  TimeoutHandler(QNetworkReply *reply)
265  : QTimer(reply)
266  {
267  setSingleShot(true);
268  }
269 public Q_SLOTS:
270  void replyTimeout()
271  {
272  QNetworkReply *reply = qobject_cast<QNetworkReply *>(parent());
273  Q_ASSERT(reply);
274 
275  // contents_of_the_slot:
276  reply->setProperty("kdsoap_reply_timed_out", true); // see KDSoapPendingCall.cpp
277  reply->abort();
278  }
279 };
280 
282 {
283 #ifndef QT_NO_SSL
284  if (m_ignoreSslErrors) {
285  QObject::connect(reply, &QNetworkReply::sslErrors, reply, QOverload<>::of(&QNetworkReply::ignoreSslErrors));
286  } else {
287  reply->ignoreSslErrors(m_ignoreErrorsList);
288  if (m_sslHandler) {
289  // create a child object of the reply, which will forward to m_sslHandler.
290  // this is a workaround for the lack of the reply pointer in the signal,
291  // and sender() doesn't work for sync calls (from another thread) (SOAP-79/issue29)
293  }
294  }
295 #endif
296  if (m_timeout >= 0) {
297  TimeoutHandler *timeoutHandler = new TimeoutHandler(reply);
298  connect(timeoutHandler, &TimeoutHandler::timeout, timeoutHandler, &TimeoutHandler::replyTimeout);
299  timeoutHandler->start(m_timeout);
300  }
301 }
302 
304 {
305  return d->m_lastResponseHeaders;
306 }
307 
309 {
310  d->m_style = style;
311 }
312 
314 {
315  return d->m_style;
316 }
317 
318 QNetworkCookieJar *KDSoapClientInterface::cookieJar() const
319 {
320  return d->accessManager()->cookieJar();
321 }
322 
323 void KDSoapClientInterface::setCookieJar(QNetworkCookieJar *jar)
324 {
325  QObject *oldParent = jar->parent();
326  d->accessManager()->setCookieJar(jar);
327  jar->setParent(oldParent); // see comment in QNAM::setCookieJar...
328 }
329 
330 void KDSoapClientInterface::setRawHTTPHeaders(const QMap<QByteArray, QByteArray> &headers)
331 {
332  d->m_httpHeaders = headers;
333 }
334 
335 QNetworkProxy KDSoapClientInterface::proxy() const
336 {
337  return d->accessManager()->proxy();
338 }
339 
340 void KDSoapClientInterface::setProxy(const QNetworkProxy &proxy)
341 {
342  d->accessManager()->setProxy(proxy);
343 }
344 
346 {
347  return d->m_timeout;
348 }
349 
351 {
352  d->m_timeout = msecs;
353 }
354 
356 {
359 }
360 
362 {
364 }
365 
367 {
368  d->m_sendSoapActionInHttpHeader = sendInHttpHeader;
369 }
370 
372 {
374 }
375 
377 {
378  d->m_sendSoapActionInWsAddressingHeader = sendInWsAddressingHeader;
379 }
380 
381 #ifndef QT_NO_OPENSSL
383 {
384  return d->m_sslConfiguration;
385 }
386 
387 void KDSoapClientInterface::setSslConfiguration(const QSslConfiguration &config)
388 {
389  d->m_sslConfiguration = config;
390 }
391 
393 {
394  if (!d->m_sslHandler) {
396  }
397  return d->m_sslHandler;
398 }
399 #endif
400 
401 #include "KDSoapClientInterface.moc"
402 #include "moc_KDSoapClientInterface_p.cpp"
void maybeDebugRequest(const QByteArray &data, const QNetworkRequest &request, QNetworkReply *reply)
QNetworkAccessManager * m_accessManager
KDSoapClientInterface::Style m_style
QMap< QString, KDSoapMessage > m_persistentHeaders
void setupReply(QNetworkReply *reply)
QNetworkRequest prepareRequest(const QString &method, const QString &action)
QNetworkAccessManager * accessManager()
QMap< QByteArray, QByteArray > m_httpHeaders
KDSoapMessageAddressingProperties m_messageAddressingProperties
QBuffer * prepareRequestBuffer(const QString &method, const KDSoapMessage &message, const QString &soapAction, const KDSoapHeaders &headers)
void setMessageAddressingProperties(const KDSoapMessageAddressingProperties &map)
void setSoapVersion(KDSoapClientInterface::SoapVersion version)
void callNoReply(const QString &method, const KDSoapMessage &message, const QString &soapAction=QString(), const KDSoapHeaders &headers=KDSoapHeaders())
void setProxy(const QNetworkProxy &proxy)
QSslConfiguration sslConfiguration() const
KDSoapPendingCall asyncCall(const QString &method, const KDSoapMessage &message, const QString &soapAction=QString(), const KDSoapHeaders &headers=KDSoapHeaders())
KDSoapSslHandler * sslHandler() const
void setRawHTTPHeaders(const QMap< QByteArray, QByteArray > &headers)
QNetworkProxy proxy() const
bool sendSoapActionInHttpHeader() const
sendActionInHTTP_Header
bool sendSoapActionInWsAddressingHeader() const
sendSoapActionInWsAddressingHeader
void setCookieJar(QNetworkCookieJar *jar)
QNetworkCookieJar * cookieJar() const
KDSoapClientInterface(const QString &endPoint, const QString &messageNamespace)
void setSslConfiguration(const QSslConfiguration &config)
KDSoapHeaders lastResponseHeaders() const
@ RPCStyle
the method name is sent as an xml element wrapping the message parameters
void setEndPoint(const QString &endPoint)
KDSoapMessage call(const QString &method, const KDSoapMessage &message, const QString &soapAction=QString(), const KDSoapHeaders &headers=KDSoapHeaders())
void setSendSoapActionInHttpHeader(bool sendInHttpHeader)
setSendSoapActionInHttpHeader
void setSendSoapActionInWsAddressingHeader(bool sendInWsAddressingHeader)
setSendSoapActionInWsAddressingHeader
void setAuthentication(const KDSoapAuthentication &authentication)
void setHeader(const QString &name, const KDSoapMessage &header)
KDSoapClientInterface::SoapVersion soapVersion() const
void enqueue(KDSoapThreadTaskData *taskData)
QByteArray messageToXml(const KDSoapMessage &message, const QString &method, const KDSoapHeaders &headers, const QMap< QString, KDSoapMessage > &persistentHeaders, const KDSoapAuthentication &authentication=KDSoapAuthentication()) const
void setMessageNamespace(const QString &ns)
void setVersion(KDSoap::SoapVersion version)
void setMessageAddressingProperties(const KDSoapMessageAddressingProperties &map)
KDSoapMessageAddressingProperties messageAddressingProperties() const
A class for handling SSL errors during SOAP calls.
KDSoapAuthentication m_authentication
KDSoapMessage response() const
KDSoapHeaders responseHeaders() const
@ SOAP1_1
Definition: KDSoapValue.h:42
@ SOAP1_2
Definition: KDSoapValue.h:44

© 2010-2024 Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
https://www.kdab.com/development-resources/qt-tools/kd-soap/
Generated by doxygen 1.9.1