KD SOAP API Documentation  2.1
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-2022 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  , m_ignoreSslErrors(false)
59  , m_timeout(30 * 60 * 1000) // 30 minutes, as documented
60 {
61 #ifndef QT_NO_SSL
62  m_sslHandler = nullptr;
63 #endif
64 }
65 
67 {
68 #ifndef QT_NO_SSL
69  delete m_sslHandler;
70 #endif
71 }
72 
74 {
75  if (!m_accessManager) {
76  m_accessManager = new QNetworkAccessManager(this);
77  connect(m_accessManager, &QNetworkAccessManager::authenticationRequired, this, &KDSoapClientInterfacePrivate::_kd_slotAuthenticationRequired);
78  }
79  return m_accessManager;
80 }
81 
82 QNetworkRequest KDSoapClientInterfacePrivate::prepareRequest(const QString &method, const QString &action)
83 {
84  QNetworkRequest request(QUrl(this->m_endPoint));
85 
86 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
87  // HTTP/2 (on by default since Qt 6) creates trouble, disable it for now (https://github.com/KDAB/KDSoap/issues/246)
88  request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
89 #endif
90 
91  QString soapAction = action;
92 
93  if (soapAction.isNull()) {
94  // The automatic generation of SoapAction done in this block is a mistake going back to KDSoap 1.0.
95  // The spec says "there is no default value for SoapAction" (https://www.w3.org/TR/wsdl#_soap:operation)
96  // but we keep this for compatibility, when nothing was passed as argument (see the webcalls unittest)
97  soapAction = this->m_messageNamespace;
98  if (!soapAction.endsWith(QLatin1Char('/'))) {
99  soapAction += QLatin1Char('/');
100  }
101  soapAction += method;
102  }
103  // qDebug() << "soapAction=" << soapAction;
104 
105  QString soapHeader;
106  if (m_version == KDSoap::SOAP1_1) {
107  soapHeader += QString::fromLatin1("text/xml;charset=utf-8");
108  request.setRawHeader("SoapAction", '\"' + soapAction.toUtf8() + '\"');
109  } else if (m_version == KDSoap::SOAP1_2) {
110  soapHeader += QString::fromLatin1("application/soap+xml;charset=utf-8");
112  soapHeader += QString::fromLatin1(";action=") + soapAction;
113  }
114 
115  request.setHeader(QNetworkRequest::ContentTypeHeader, soapHeader.toUtf8());
116 
117  // FIXME need to find out which version of Qt this is no longer necessary
118  // without that the server might respond with gzip compressed data and
119  // Qt 4.6.2 fails to decode that properly
120  //
121  // happens with retrieval calls in against SugarCRM 5.5.1 running on Apache 2.2.15
122  // when the response seems to reach a certain size threshold
123  request.setRawHeader("Accept-Encoding", "compress");
124 
125  for (QMap<QByteArray, QByteArray>::const_iterator it = m_httpHeaders.constBegin(); it != m_httpHeaders.constEnd(); ++it) {
126  request.setRawHeader(it.key(), it.value());
127  }
128 
129 #ifndef QT_NO_SSL
130  if (!m_sslConfiguration.isNull()) {
131  request.setSslConfiguration(m_sslConfiguration);
132  }
133 #endif
134 
135  return request;
136 }
137 
138 QBuffer *KDSoapClientInterfacePrivate::prepareRequestBuffer(const QString &method, const KDSoapMessage &message, const QString &soapAction, const KDSoapHeaders &headers)
139 {
140  KDSoapMessageWriter msgWriter;
142  msgWriter.setVersion(m_version);
143  QBuffer *buffer = new QBuffer;
144  auto setBufferData = [=](const KDSoapMessage &msg) {
145  buffer->setData(msgWriter.messageToXml(msg,
146  (m_style == KDSoapClientInterface::RPCStyle) ? method : QString(),
147  headers, m_persistentHeaders,
149  };
150 
152  KDSoapMessage messageCopy = message;
154  if (!prop.action().isEmpty())
155  qWarning("Overwriting the action addressing parameter (%s) with the SOAP action (%s)",
156  prop.action().toLocal8Bit().constData(), soapAction.toLocal8Bit().constData());
157  prop.setAction(soapAction);
158  messageCopy.setMessageAddressingProperties(prop);
159  setBufferData(messageCopy);
160  } else {
161  setBufferData(message);
162  }
163  buffer->open(QIODevice::ReadOnly);
164  return buffer;
165 }
166 
167 KDSoapPendingCall KDSoapClientInterface::asyncCall(const QString &method, const KDSoapMessage &message, const QString &soapAction,
168  const KDSoapHeaders &headers)
169 {
170  QBuffer *buffer = d->prepareRequestBuffer(method, message, soapAction, headers);
171  QNetworkRequest request = d->prepareRequest(method, soapAction);
172  QNetworkReply *reply = d->accessManager()->post(request, buffer);
173  d->setupReply(reply);
174  maybeDebugRequest(buffer->data(), reply->request(), reply);
175  KDSoapPendingCall call(reply, buffer);
176  call.d->soapVersion = d->m_version;
177  return call;
178 }
179 
180 KDSoapMessage KDSoapClientInterface::call(const QString &method, const KDSoapMessage &message, const QString &soapAction,
181  const KDSoapHeaders &headers)
182 {
183  d->accessManager()->cookieJar(); // create it in the right thread, the secondary thread will use it
184  // Problem is: I don't want a nested event loop here. Too dangerous for GUI programs.
185  // I wanted a socket->waitFor... but we don't have access to the actual socket in QNetworkAccess.
186  // So the only option that remains is a thread and acquiring a semaphore...
187  KDSoapThreadTaskData *task = new KDSoapThreadTaskData(this, method, message, soapAction, headers);
189  d->m_thread.enqueue(task);
190  if (!d->m_thread.isRunning()) {
191  d->m_thread.start();
192  }
193  task->waitForCompletion();
194  KDSoapMessage ret = task->response();
196  delete task;
197  return ret;
198 }
199 
200 void KDSoapClientInterface::callNoReply(const QString &method, const KDSoapMessage &message,
201  const QString &soapAction, const KDSoapHeaders &headers)
202 {
203  QBuffer *buffer = d->prepareRequestBuffer(method, message, soapAction, headers);
204  QNetworkRequest request = d->prepareRequest(method, soapAction);
205  QNetworkReply *reply = d->accessManager()->post(request, buffer);
206  d->setupReply(reply);
207  maybeDebugRequest(buffer->data(), reply->request(), reply);
208  QObject::connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
209  QObject::connect(reply, &QNetworkReply::finished, buffer, &QBuffer::deleteLater);
210 }
211 
212 void KDSoapClientInterfacePrivate::_kd_slotAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator)
213 {
214  m_authentication.handleAuthenticationRequired(reply, authenticator);
215 }
216 
218 {
219  d->m_authentication = authentication;
220 }
221 
223 {
224  return d->m_endPoint;
225 }
226 
227 void KDSoapClientInterface::setEndPoint(const QString &endPoint)
228 {
229  d->m_endPoint = endPoint;
230 }
231 
232 void KDSoapClientInterface::setHeader(const QString &name, const KDSoapMessage &header)
233 {
234  d->m_persistentHeaders[name] = header;
235  d->m_persistentHeaders[name].setQualified(true);
236 }
237 
239 {
240  d->m_ignoreSslErrors = true;
241 }
242 
243 #ifndef QT_NO_SSL
244 void KDSoapClientInterface::ignoreSslErrors(const QList<QSslError> &errors)
245 {
246  d->m_ignoreErrorsList = errors;
247 }
248 #endif
249 
250 // Workaround for lack of connect-to-lambdas in Qt4
251 // The pure Qt5 code could read like
252 /*
253  QTimer *timeoutTimer = new QTimer(reply);
254  timeoutTimer->setSingleShot(true);
255  connect(timeoutTimer, &QTimer::timeout, reply, [reply]() { contents_of_the_slot });
256 */
257 class TimeoutHandler : public QTimer // this way a single QObject is needed
258 {
259  Q_OBJECT
260 public:
261  TimeoutHandler(QNetworkReply *reply)
262  : QTimer(reply)
263  {
264  setSingleShot(true);
265  }
266 public Q_SLOTS:
267  void replyTimeout()
268  {
269  QNetworkReply *reply = qobject_cast<QNetworkReply *>(parent());
270  Q_ASSERT(reply);
271 
272  // contents_of_the_slot:
273  reply->setProperty("kdsoap_reply_timed_out", true); // see KDSoapPendingCall.cpp
274  reply->abort();
275  }
276 };
277 
279 {
280 #ifndef QT_NO_SSL
281  if (m_ignoreSslErrors) {
282  QObject::connect(reply, &QNetworkReply::sslErrors, reply, QOverload<>::of(&QNetworkReply::ignoreSslErrors));
283  } else {
284  reply->ignoreSslErrors(m_ignoreErrorsList);
285  if (m_sslHandler) {
286  // create a child object of the reply, which will forward to m_sslHandler.
287  // this is a workaround for the lack of the reply pointer in the signal,
288  // and sender() doesn't work for sync calls (from another thread) (SOAP-79/issue29)
290  }
291  }
292 #endif
293  if (m_timeout >= 0) {
294  TimeoutHandler *timeoutHandler = new TimeoutHandler(reply);
295  connect(timeoutHandler, &TimeoutHandler::timeout, timeoutHandler, &TimeoutHandler::replyTimeout);
296  timeoutHandler->start(m_timeout);
297  }
298 }
299 
301 {
302  return d->m_lastResponseHeaders;
303 }
304 
306 {
307  d->m_style = style;
308 }
309 
311 {
312  return d->m_style;
313 }
314 
315 QNetworkCookieJar *KDSoapClientInterface::cookieJar() const
316 {
317  return d->accessManager()->cookieJar();
318 }
319 
320 void KDSoapClientInterface::setCookieJar(QNetworkCookieJar *jar)
321 {
322  QObject *oldParent = jar->parent();
323  d->accessManager()->setCookieJar(jar);
324  jar->setParent(oldParent); // see comment in QNAM::setCookieJar...
325 }
326 
327 void KDSoapClientInterface::setRawHTTPHeaders(const QMap<QByteArray, QByteArray> &headers)
328 {
329  d->m_httpHeaders = headers;
330 }
331 
332 QNetworkProxy KDSoapClientInterface::proxy() const
333 {
334  return d->accessManager()->proxy();
335 }
336 
337 void KDSoapClientInterface::setProxy(const QNetworkProxy &proxy)
338 {
339  d->accessManager()->setProxy(proxy);
340 }
341 
343 {
344  return d->m_timeout;
345 }
346 
348 {
349  d->m_timeout = msecs;
350 }
351 
353 {
355 }
356 
358 {
359  d->m_sendSoapActionInHttpHeader = sendInHttpHeader;
360 }
361 
363 {
365 }
366 
368 {
369  d->m_sendSoapActionInWsAddressingHeader = sendInWsAddressingHeader;
370 }
371 
372 #ifndef QT_NO_OPENSSL
374 {
375  return d->m_sslConfiguration;
376 }
377 
378 void KDSoapClientInterface::setSslConfiguration(const QSslConfiguration &config)
379 {
380  d->m_sslConfiguration = config;
381 }
382 
384 {
385  if (!d->m_sslHandler) {
387  }
388  return d->m_sslHandler;
389 }
390 #endif
391 
392 #include "KDSoapClientInterface.moc"
393 #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
QBuffer * prepareRequestBuffer(const QString &method, const KDSoapMessage &message, const QString &soapAction, const KDSoapHeaders &headers)
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-2022 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 on Tue Jun 13 2023 12:18:34 for KD SOAP API Documentation by doxygen 1.9.1