KD SOAP API Documentation  2.1
KDSoapServerSocket.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 "KDSoapServer.h"
15 #include "KDSoapServerSocket_p.h"
16 #include "KDSoapSocketList_p.h"
21 #include <QBuffer>
22 #include <QDir>
23 #include <QFile>
24 #include <QFileInfo>
25 #include <QMetaMethod>
26 #include <QThread>
27 #include <QVarLengthArray>
28 
29 static const char s_forbidden[] = "HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n";
30 
32 #ifndef QT_NO_SSL
33  : QSslSocket()
34  ,
35 #else
36  : QTcpSocket()
37  ,
38 #endif
39  m_owner(owner)
40  , m_serverObject(serverObject)
41  , m_delayedResponse(false)
42  , m_socketEnabled(true)
43  , m_receivedData(false)
44  , m_useRawXML(false)
45  , m_bytesReceived(0)
46  , m_chunkStart(0)
47 {
48  connect(this, &QIODevice::readyRead, this, &KDSoapServerSocket::slotReadyRead);
49  m_doDebug = qEnvironmentVariableIsSet("KDSOAP_DEBUG");
50 }
51 
52 // The socket is deleted when it emits disconnected() (see KDSoapSocketList::handleIncomingConnection).
54 {
55  // same as m_owner->socketDeleted, but safe in case m_owner is deleted first
56  emit socketDeleted(this);
57 }
58 
59 typedef QMap<QByteArray, QByteArray> HeadersMap;
60 static HeadersMap parseHeaders(const QByteArray &headerData)
61 {
62  HeadersMap headersMap;
63  QBuffer sourceBuffer;
64  sourceBuffer.setData(headerData);
65  sourceBuffer.open(QIODevice::ReadOnly);
66  // The first line is special, it's the GET or POST line
67  const QList<QByteArray> firstLine = sourceBuffer.readLine().split(' ');
68  if (firstLine.count() < 3) {
69  qDebug() << "Malformed HTTP request:" << firstLine;
70  return headersMap;
71  }
72  const QByteArray &requestType = firstLine.at(0);
73  headersMap.insert("_requestType", requestType);
74 
75  // Grammar from https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1
76  // origin-form = absolute-path [ "?" query ]
77  // and https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
78  // says the path ends at the first '?' or '#' character
79  const QByteArray arg1 = firstLine.at(1);
80  const int queryPos = arg1.indexOf('?');
81  const QByteArray path = queryPos >= 0 ? arg1.left(queryPos) : arg1;
82  const QByteArray query = queryPos >= 0 ? arg1.mid(queryPos) : QByteArray();
83  // Unfortunately QDir::cleanPath works with QString
84  const QByteArray cleanedPath = QDir::cleanPath(QString::fromUtf8(path)).toUtf8();
85  headersMap.insert("_path", cleanedPath + query);
86 
87  const QByteArray &httpVersion = firstLine.at(2);
88  headersMap.insert("_httpVersion", httpVersion);
89 
90  while (!sourceBuffer.atEnd()) {
91  const QByteArray line = sourceBuffer.readLine();
92  const int pos = line.indexOf(':');
93  if (pos == -1) {
94  qDebug() << "Malformed HTTP header:" << line;
95  }
96  const QByteArray header = line.left(pos).toLower(); // RFC2616 section 4.2 "Field names are case-insensitive"
97  const QByteArray value = line.mid(pos + 1).trimmed(); // remove space before and \r\n after
98  // qDebug() << "HEADER" << header << "VALUE" << value;
99  headersMap.insert(header, value);
100  }
101  return headersMap;
102 }
103 
104 // We could parse headers as we go along looking for \r\n, and stop at empty header line, to avoid all this memory copying
105 // But in practice XML parsing (and writing) is far, far slower anyway.
106 static bool splitHeadersAndData(const QByteArray &request, QByteArray &header, QByteArray &data)
107 {
108  const int sep = request.indexOf("\r\n\r\n");
109  if (sep <= 0) {
110  return false;
111  }
112  header = request.left(sep);
113  data = request.mid(sep + 4);
114  return true;
115 }
116 
117 static QByteArray stripQuotes(const QByteArray &bar)
118 {
119  if (bar.startsWith('\"') && bar.endsWith('\"')) {
120  return bar.mid(1, bar.length() - 2);
121  }
122 
123  return bar;
124 }
125 
126 static QByteArray httpResponseHeaders(bool fault, const QByteArray &contentType, int responseDataSize, QObject *serverObject)
127 {
128  QByteArray httpResponse;
129  httpResponse.reserve(50);
130  if (fault) {
131  // https://www.w3.org/TR/2007/REC-soap12-part0-20070427 and look for 500
132  httpResponse += "HTTP/1.1 500 Internal Server Error\r\n";
133  } else if (responseDataSize == 0) {
134  httpResponse += "HTTP/1.1 204 No Content\r\n";
135  } else {
136  httpResponse += "HTTP/1.1 200 OK\r\n";
137  }
138 
139  httpResponse += "Content-Type: ";
140  httpResponse += contentType;
141  httpResponse += "\r\nContent-Length: ";
142  httpResponse += QByteArray::number(responseDataSize);
143  httpResponse += "\r\n";
144 
145  KDSoapServerObjectInterface *serverObjectInterface = qobject_cast<KDSoapServerObjectInterface *>(serverObject);
146  if (serverObjectInterface) {
147  const KDSoapServerObjectInterface::HttpResponseHeaderItems &additionalItems = serverObjectInterface->additionalHttpResponseHeaderItems();
148  for (const KDSoapServerObjectInterface::HttpResponseHeaderItem &headerItem : qAsConst(additionalItems)) {
149  httpResponse += headerItem.m_name;
150  httpResponse += ": ";
151  httpResponse += headerItem.m_value;
152  httpResponse += "\r\n";
153  }
154  }
155 
156  httpResponse += "\r\n"; // end of headers
157  return httpResponse;
158 }
159 
160 void KDSoapServerSocket::slotReadyRead()
161 {
162  if (!m_socketEnabled) {
163  return;
164  }
165 
166  // QNAM in Qt 5.x tends to connect additional sockets in advance and not use them
167  // So only count the sockets which actually sent us data (for the servertest unittest).
168  if (!m_receivedData) {
169  m_receivedData = true;
170  m_owner->increaseConnectionCount();
171  }
172 
173  // qDebug() << this << QThread::currentThread() << "slotReadyRead!";
174 
175  QByteArray buf(2048, ' ');
176  qint64 nread = -1;
177  while (nread != 0) {
178  nread = read(buf.data(), buf.size());
179  if (nread < 0) {
180  qDebug() << "Error reading from server socket:" << errorString();
181  return;
182  }
183  m_requestBuffer += buf.left(nread);
184  m_bytesReceived += nread;
185  }
186 
187  KDSoapServerRawXMLInterface *rawXmlInterface = qobject_cast<KDSoapServerRawXMLInterface *>(m_serverObject);
188 
189  if (m_httpHeaders.isEmpty()) {
190  // New request: see if we can parse headers
191  QByteArray receivedHttpHeaders, receivedData;
192  const bool splitOK = splitHeadersAndData(m_requestBuffer, receivedHttpHeaders, receivedData);
193  if (!splitOK) {
194  // qDebug() << "Incomplete SOAP request, wait for more data";
195  // incomplete request, wait for more data
196  return;
197  }
198  m_httpHeaders = parseHeaders(receivedHttpHeaders);
199  // Leave only the actual data in the buffer
200  m_requestBuffer = receivedData;
201  m_bytesReceived = receivedData.size();
202  m_useRawXML = false;
203  if (rawXmlInterface) {
204  KDSoapServerObjectInterface *serverObjectInterface = qobject_cast<KDSoapServerObjectInterface *>(m_serverObject);
205  serverObjectInterface->setServerSocket(this);
206  m_useRawXML = rawXmlInterface->newRequest(m_httpHeaders.value("_requestType"), m_httpHeaders);
207  }
208  }
209 
210  if (m_doDebug) {
211  qDebug() << "headers:" << m_httpHeaders;
212  qDebug() << "data received:" << m_requestBuffer;
213  }
214 
215  if (m_httpHeaders.value("transfer-encoding") != "chunked") {
216  if (m_useRawXML) {
217  rawXmlInterface->processXML(m_requestBuffer);
218  m_requestBuffer.clear();
219  }
220 
221  const QByteArray contentLength = m_httpHeaders.value("content-length");
222  if (m_bytesReceived < contentLength.toInt()) {
223  return; // incomplete request, wait for more data
224  }
225 
226  if (m_useRawXML) {
227  rawXmlInterface->endRequest();
228  } else {
229  handleRequest(m_httpHeaders, m_requestBuffer);
230  }
231  } else {
232  // qDebug() << "requestBuffer has " << m_requestBuffer.size() << "bytes, starting at" << m_chunkStart;
233  while (m_chunkStart >= 0) {
234  const int nextEOL = m_requestBuffer.indexOf("\r\n", m_chunkStart);
235  if (nextEOL == -1) {
236  return;
237  }
238  const QByteArray chunkSizeStr = m_requestBuffer.mid(m_chunkStart, nextEOL - m_chunkStart);
239  // qDebug() << m_chunkStart << nextEOL << "chunkSizeStr=" << chunkSizeStr;
240  bool ok;
241  int chunkSize = chunkSizeStr.toInt(&ok, 16);
242  if (!ok) {
243  const QByteArray badRequest = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n";
244  write(badRequest);
245  return;
246  }
247  if (chunkSize == 0) { // done!
248  m_requestBuffer = m_requestBuffer.mid(nextEOL);
249  m_chunkStart = -1;
250  break;
251  }
252  if (nextEOL + 2 + chunkSize + 2 >= m_requestBuffer.size()) {
253  return; // not enough data, chunk is incomplete
254  }
255  const QByteArray chunk = m_requestBuffer.mid(nextEOL + 2, chunkSize);
256  if (m_useRawXML) {
257  rawXmlInterface->processXML(chunk);
258  } else {
259  m_decodedRequestBuffer += chunk;
260  }
261  m_chunkStart = nextEOL + 2 + chunkSize + 2;
262  }
263  // We have the full data, now ensure we read trailers
264  if (!m_requestBuffer.contains("\r\n\r\n")) {
265  return;
266  }
267  if (m_useRawXML) {
268  rawXmlInterface->endRequest();
269  } else {
270  handleRequest(m_httpHeaders, m_decodedRequestBuffer);
271  }
272  m_decodedRequestBuffer.clear();
273  m_chunkStart = 0;
274  }
275  m_requestBuffer.clear();
276  m_httpHeaders.clear();
277  m_receivedData = false;
278 }
279 
280 void KDSoapServerSocket::handleRequest(const QMap<QByteArray, QByteArray> &httpHeaders, const QByteArray &receivedData)
281 {
282  const QByteArray requestType = httpHeaders.value("_requestType");
283  const QString path = QString::fromLatin1(httpHeaders.value("_path").constData());
284 
285  if (!path.startsWith(QLatin1String("/"))) {
286  // denied for security reasons (ex: path starting with "..")
287  write(s_forbidden);
288  return;
289  }
290 
291  KDSoapServerAuthInterface *serverAuthInterface = qobject_cast<KDSoapServerAuthInterface *>(m_serverObject);
292  if (serverAuthInterface) {
293  const QByteArray authValue = httpHeaders.value("authorization");
294  if (!serverAuthInterface->handleHttpAuth(authValue, path)) {
295  // send auth request (Qt supports basic, ntlm and digest)
296  const QByteArray unauthorized =
297  "HTTP/1.1 401 Authorization Required\r\nWWW-Authenticate: Basic realm=\"example\"\r\nContent-Length: 0\r\n\r\n";
298  write(unauthorized);
299  return;
300  }
301  }
302 
303  if (requestType != "GET" && requestType != "POST") {
304  KDSoapServerCustomVerbRequestInterface *serverCustomRequest = qobject_cast<KDSoapServerCustomVerbRequestInterface *>(m_serverObject);
305  QByteArray customVerbRequestAnswer;
306  if (serverCustomRequest && serverCustomRequest->processCustomVerbRequest(requestType, receivedData, httpHeaders, customVerbRequestAnswer)) {
307  write(customVerbRequestAnswer);
308  return;
309  } else {
310  qWarning() << "Unknown HTTP request:" << requestType;
311  // handleError(replyMsg, "Client.Data", QString::fromLatin1("Invalid request type '%1', should be GET or
312  // POST").arg(QString::fromLatin1(requestType.constData()))); sendReply(0, replyMsg);
313  const QByteArray methodNotAllowed = "HTTP/1.1 405 Method Not Allowed\r\nAllow: GET POST\r\nContent-Length: 0\r\n\r\n";
314  write(methodNotAllowed);
315  return;
316  }
317  }
318 
319  KDSoapServer *server = m_owner->server();
320  KDSoapMessage replyMsg;
321  replyMsg.setUse(server->use());
322 
323  KDSoapServerObjectInterface *serverObjectInterface = qobject_cast<KDSoapServerObjectInterface *>(m_serverObject);
324  if (!serverObjectInterface) {
325  const QString error = QString::fromLatin1("Server object %1 does not implement KDSoapServerObjectInterface!")
326  .arg(QString::fromLatin1(m_serverObject->metaObject()->className()));
327  handleError(replyMsg, "Server.ImplementationError", error);
328  sendReply(nullptr, replyMsg);
329  return;
330  } else {
331  serverObjectInterface->setServerSocket(this);
332  }
333 
334  if (requestType == "GET") {
335  if (path == server->wsdlPathInUrl() && handleWsdlDownload()) {
336  return;
337  } else if (handleFileDownload(serverObjectInterface, path)) {
338  return;
339  }
340 
341  // See https://www.ibm.com/developerworks/xml/library/x-tipgetr/
342  // We could implement it, but there's no SOAP request, just a query in the URL,
343  // which we'd have to pass to a different virtual than processRequest.
344  handleError(replyMsg, "Client.Data", QString::fromLatin1("Support for GET requests not implemented yet."));
345  sendReply(nullptr, replyMsg);
346  return;
347  }
348 
349  // parse message
350  KDSoapMessage requestMsg;
351  KDSoapHeaders requestHeaders;
352  KDSoapMessageReader reader;
353  KDSoapMessageReader::XmlError err = reader.xmlToMessage(receivedData, &requestMsg, &m_messageNamespace, &requestHeaders, KDSoap::SOAP1_1);
355  // qDebug() << "Incomplete SOAP message, wait for more data";
356  // This should never happen, since we check for content-size above.
357  return;
358  } // TODO handle parse errors?
359 
360  // check soap version and extract soapAction header
361  QByteArray soapAction;
362  const QByteArray contentType = httpHeaders.value("content-type");
363  if (contentType.startsWith("text/xml")) { // krazy:exclude=strings
364  // SOAP 1.1
365  soapAction = httpHeaders.value("soapaction");
366  // The SOAP standard allows quotation marks around the SoapAction, so we have to get rid of these.
367  soapAction = stripQuotes(soapAction);
368 
369  } else if (contentType.startsWith("application/soap+xml")) { // krazy:exclude=strings
370  // SOAP 1.2
371  // Example: application/soap+xml;charset=utf-8;action=ActionHex
372  const QList<QByteArray> parts = contentType.split(';');
373  for (const QByteArray &part : qAsConst(parts)) {
374  if (part.trimmed().startsWith("action=")) { // krazy:exclude=strings
375  soapAction = stripQuotes(part.mid(part.indexOf('=') + 1));
376  }
377  }
378  }
379 
380  m_method = requestMsg.name();
381 
382  if (!replyMsg.isFault()) {
383  makeCall(serverObjectInterface, requestMsg, replyMsg, requestHeaders, soapAction, path);
384  }
385 
386  if (serverObjectInterface && m_delayedResponse) {
387  // Delayed response. Disable the socket to make sure we don't handle another call at the same time.
388  setSocketEnabled(false);
389  } else {
390  sendReply(serverObjectInterface, replyMsg);
391  }
392 }
393 
394 bool KDSoapServerSocket::handleWsdlDownload()
395 {
396  KDSoapServer *server = m_owner->server();
397  const QString wsdlFile = server->wsdlFile();
398  QFile wf(wsdlFile);
399  if (wf.open(QIODevice::ReadOnly)) {
400  // qDebug() << "Returning wsdl file contents";
401  const QByteArray responseText = wf.readAll();
402  const QByteArray response = httpResponseHeaders(false, "application/xml", responseText.size(), m_serverObject);
403  write(response);
404  write(responseText);
405  return true;
406  }
407  return false;
408 }
409 
410 bool KDSoapServerSocket::handleFileDownload(KDSoapServerObjectInterface *serverObjectInterface, const QString &path)
411 {
412  QByteArray contentType;
413  QIODevice *device = serverObjectInterface->processFileRequest(path, contentType);
414  if (!device) {
415  const QByteArray notFound = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n";
416  write(notFound);
417  return true;
418  }
419  if (!device->open(QIODevice::ReadOnly)) {
420  write(s_forbidden);
421  delete device;
422  return true; // handled!
423  }
424  const QByteArray response = httpResponseHeaders(false, contentType, device->size(), m_serverObject);
425  if (m_doDebug) {
426  qDebug() << "KDSoapServerSocket: file download response" << response;
427  }
428  qint64 written = write(response);
429  Q_ASSERT(written == response.size()); // Please report a bug if you hit this.
430  Q_UNUSED(written);
431 
432  char block[4096] = {0};
433  // qint64 totalRead = 0;
434  while (!device->atEnd()) {
435  const qint64 in = device->read(block, sizeof(block));
436  if (in <= 0) {
437  break;
438  }
439  // totalRead += in;
440  if (in != write(block, in)) {
441  // error = true;
442  break;
443  }
444  }
445  // if (totalRead != device->size()) {
446  // // Unable to read from the source.
447  // error = true;
448  //}
449 
450  delete device;
451  // TODO log the file request, if logging is enabled?
452  return true;
453 }
454 
455 void KDSoapServerSocket::writeXML(const QByteArray &xmlResponse, bool isFault)
456 {
457  const QByteArray httpHeaders = httpResponseHeaders(isFault, "text/xml", xmlResponse.size(),
458  m_serverObject); // TODO return application/soap+xml;charset=utf-8 instead for SOAP 1.2
459  if (m_doDebug) {
460  qDebug() << "KDSoapServerSocket: writing" << httpHeaders << xmlResponse;
461  }
462  qint64 written = write(httpHeaders);
463  Q_ASSERT(written == httpHeaders.size()); // Please report a bug if you hit this.
464  written = write(xmlResponse);
465  Q_ASSERT(written == xmlResponse.size()); // Please report a bug if you hit this.
466  Q_UNUSED(written);
467  // flush() ?
468 }
469 
470 void KDSoapServerSocket::sendReply(KDSoapServerObjectInterface *serverObjectInterface, const KDSoapMessage &replyMsg)
471 {
472  const bool isFault = replyMsg.isFault();
473 
474  QByteArray xmlResponse;
475  if (!replyMsg.isNull()) {
476  KDSoapMessageWriter msgWriter;
477  // Note that the kdsoap client parsing code doesn't care for the name (except if it's fault), even in
478  // Document mode. Other implementations do, though.
479  QString responseName = isFault ? QString::fromLatin1("Fault") : replyMsg.name();
480  if (responseName.isEmpty()) {
481  responseName = m_method;
482  }
483  QString responseNamespace = m_messageNamespace;
484  KDSoapHeaders responseHeaders;
485  if (serverObjectInterface) {
486  responseHeaders = serverObjectInterface->responseHeaders();
487  if (!serverObjectInterface->responseNamespace().isEmpty()) {
488  responseNamespace = serverObjectInterface->responseNamespace();
489  }
490  }
491  msgWriter.setMessageNamespace(responseNamespace);
492  xmlResponse = msgWriter.messageToXml(replyMsg, responseName, responseHeaders, QMap<QString, KDSoapMessage>());
493  }
494 
495  writeXML(xmlResponse, isFault);
496 
497  // All done, check if we should log this
498  KDSoapServer *server = m_owner->server();
499  const KDSoapServer::LogLevel logLevel =
500  server->logLevel(); // we do this here in order to support dynamic settings changes (at the price of a mutex)
501  if (logLevel != KDSoapServer::LogNothing) {
502  if (logLevel == KDSoapServer::LogEveryCall || (logLevel == KDSoapServer::LogFaults && isFault)) {
503 
504  if (isFault) {
505  server->log("FAULT " + m_method.toLatin1() + " -- " + replyMsg.faultAsString().toUtf8() + '\n');
506  } else {
507  server->log("CALL " + m_method.toLatin1() + '\n');
508  }
509  }
510  }
511 }
512 
514 {
515  sendReply(serverObjectInterface, replyMsg);
516  m_delayedResponse = false;
517  setSocketEnabled(true);
518 }
519 
521 {
522  m_delayedResponse = true;
523 }
524 
525 void KDSoapServerSocket::handleError(KDSoapMessage &replyMsg, const char *errorCode, const QString &error)
526 {
527  qWarning("%s", qPrintable(error));
528  const KDSoap::SoapVersion soapVersion = KDSoap::SOAP1_1; // TODO version selection on the server side
529  replyMsg.createFaultMessage(QString::fromLatin1(errorCode), error, soapVersion);
530 }
531 
532 void KDSoapServerSocket::makeCall(KDSoapServerObjectInterface *serverObjectInterface, const KDSoapMessage &requestMsg, KDSoapMessage &replyMsg,
533  const KDSoapHeaders &requestHeaders, const QByteArray &soapAction, const QString &path)
534 {
535  Q_ASSERT(serverObjectInterface);
536 
537  if (requestMsg.isFault()) {
538  // Can this happen? Getting a fault as a request !? Doesn't make sense...
539  // reply with a fault, but we don't even know what main element name to use
540  // Oh well, just use the incoming fault :-)
541  replyMsg = requestMsg;
542  handleError(replyMsg, "Client.Data", QString::fromLatin1("Request was a fault"));
543  } else {
544 
545  // Call method on m_serverObject
546  serverObjectInterface->setRequestHeaders(requestHeaders, soapAction);
547 
548  KDSoapServer *server = m_owner->server();
549  if (path != server->path()) {
550  serverObjectInterface->processRequestWithPath(requestMsg, replyMsg, soapAction, path);
551  } else {
552  serverObjectInterface->processRequest(requestMsg, replyMsg, soapAction);
553  }
554  if (serverObjectInterface->hasFault()) {
555  // qDebug() << "Got fault!";
556  replyMsg.setFault(true);
557  serverObjectInterface->storeFaultAttributes(replyMsg);
558  }
559  }
560 }
561 
562 // Prevention against concurrent requests without waiting for a (delayed) reply,
563 // but untestable with QNAM on the client side, since it doesn't do that.
564 void KDSoapServerSocket::setSocketEnabled(bool enabled)
565 {
566  if (m_socketEnabled == enabled) {
567  return;
568  }
569 
570  m_socketEnabled = enabled;
571  if (enabled) {
572  slotReadyRead();
573  }
574 }
575 
576 #include "moc_KDSoapServerSocket_p.cpp"
static const char s_forbidden[]
static QByteArray httpResponseHeaders(bool fault, const QByteArray &contentType, int responseDataSize, QObject *serverObject)
static HeadersMap parseHeaders(const QByteArray &headerData)
QMap< QByteArray, QByteArray > HeadersMap
static QByteArray stripQuotes(const QByteArray &bar)
static bool splitHeadersAndData(const QByteArray &request, QByteArray &header, QByteArray &data)
XmlError xmlToMessage(const QByteArray &data, KDSoapMessage *pParsedMessage, QString *pMessageNamespace, KDSoapHeaders *pRequestHeaders, KDSoap::SoapVersion soapVersion) const
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 createFaultMessage(const QString &faultCode, const QString &faultText, KDSoap::SoapVersion soapVersion)
void setUse(Use use)
void setFault(bool fault)
bool isFault() const
QString faultAsString() const
virtual bool processCustomVerbRequest(const QByteArray &requestType, const QByteArray &requestData, const QMap< QByteArray, QByteArray > &httpHeaders, QByteArray &customAnswer)
virtual HttpResponseHeaderItems additionalHttpResponseHeaderItems() const
QVector< HttpResponseHeaderItem > HttpResponseHeaderItems
virtual void processRequestWithPath(const KDSoapMessage &request, KDSoapMessage &response, const QByteArray &soapAction, const QString &path)
virtual void processRequest(const KDSoapMessage &request, KDSoapMessage &response, const QByteArray &soapAction)
virtual QIODevice * processFileRequest(const QString &path, QByteArray &contentType)
virtual void processXML(const QByteArray &xmlChunk)
virtual bool newRequest(const QByteArray &requestType, const QMap< QByteArray, QByteArray > &httpHeaders)
void sendDelayedReply(KDSoapServerObjectInterface *serverObjectInterface, const KDSoapMessage &replyMsg)
void socketDeleted(KDSoapServerSocket *)
KDSoapServerSocket(KDSoapSocketList *owner, QObject *serverObject)
void sendReply(KDSoapServerObjectInterface *serverObjectInterface, const KDSoapMessage &replyMsg)
KDSoapMessage::Use use() const
QString wsdlFile() const
QString wsdlPathInUrl() const
LogLevel logLevel() const
QString path() const
KDSoapServer * server() const
QString name() const
Definition: KDSoapValue.cpp:94
@ SOAP1_1
Definition: KDSoapValue.h:42

© 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