MythTV  master
servicehost.cpp
Go to the documentation of this file.
1 // Program Name: servicehost.cpp
3 // Created : Jan. 19, 2010
4 //
5 // Purpose : Service Host Abstract Class
6 //
7 // Copyright (c) 2010 David Blain <dblain@mythtv.org>
8 //
9 // Licensed under the GPL v2 or later, see LICENSE for details
10 //
12 
13 #include <QDomDocument>
14 
16 #include "servicehost.h"
17 #include "wsdl.h"
18 #include "xsd.h"
19 //#include "services/rtti.h"
20 
21 static constexpr int MAX_PARAMS = 256;
22 
24 //
26 
27 QVariant MethodInfo::Invoke( Service *pService, const QStringMap &reqParams ) const
28 {
29  HttpRedirectException exception;
30  bool bExceptionThrown = false;
31  QStringMap lowerParams;
32 
33  if (!pService)
34  throw;
35 
36  // Change params to lower case for case-insensitive comparison
37  for (auto it = reqParams.cbegin(); it != reqParams.cend(); ++it)
38  {
39  lowerParams[it.key().toLower()] = *it;
40  }
41 
42  // --------------------------------------------------------------
43  // Provide actual parameters received to method
44  // --------------------------------------------------------------
45 
46  pService->m_parsedParams = lowerParams.keys();
47 
48 
49  QList<QByteArray> paramNames = m_oMethod.parameterNames();
50  QList<QByteArray> paramTypes = m_oMethod.parameterTypes();
51 
52  // ----------------------------------------------------------------------
53  // Create Parameter array (Can't have more than MAX_PARAMS parameters)....
54  // switched to static array for performance.
55  // ----------------------------------------------------------------------
56 
57  std::array<void*,MAX_PARAMS> param {};
58  std::array<int,MAX_PARAMS> types {};
59 
60  try
61  {
62  // --------------------------------------------------------------
63  // Add a place for the Return value
64  // --------------------------------------------------------------
65 
66 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
67  int nRetIdx = QMetaType::type( m_oMethod.typeName() );
68  QMetaType oRetType = QMetaType(nRetIdx);
69 #else
70  int nRetIdx = m_oMethod.returnType();
71  QMetaType oRetType = m_oMethod.returnMetaType();
72 #endif
73 
74  if (nRetIdx != QMetaType::UnknownType)
75  {
76  param[ 0 ] = oRetType.create();
77  types[ 0 ] = nRetIdx;
78  }
79  else
80  {
81  param[ 0 ] = nullptr;
82  types[ 0 ] = QMetaType::UnknownType;
83  }
84 
85  // --------------------------------------------------------------
86  // Fill in parameters from request values
87  // --------------------------------------------------------------
88 
89  for( int nIdx = 0; nIdx < paramNames.length(); ++nIdx )
90  {
91  QString sValue = lowerParams[ paramNames[ nIdx ].toLower() ];
92  QString sParamType = paramTypes[ nIdx ];
93 
94 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
95  int nId = QMetaType::type( paramTypes[ nIdx ] );
96  void *pParam = nullptr;
97 
98  if (nId != QMetaType::UnknownType)
99  {
100  pParam = QMetaType::create( nId );
101  }
102 #else
103  QMetaType metaType = QMetaType::fromName( paramTypes[ nIdx ] );
104  void *pParam = metaType.create();
105  int nId = metaType.id();
106 #endif
107  if (nId == QMetaType::UnknownType)
108  {
109  LOG(VB_GENERAL, LOG_ERR,
110  QString("MethodInfo::Invoke - Type unknown '%1'")
111  .arg(sParamType));
112  throw;
113  }
114 
115  types[nIdx+1] = nId;
116  param[nIdx+1] = pService->ConvertToParameterPtr( nId, sParamType,
117  pParam, sValue );
118  }
119 
120 #if 0
121  QThread *currentThread = QThread::currentThread();
122  QThread *objectThread = pService->thread();
123 
124  if (currentThread == objectThread)
125  LOG(VB_HTTP, LOG_DEBUG, "*** Threads are same ***");
126  else
127  LOG(VB_HTTP, LOG_DEBUG, "*** Threads are Different!!! ***");
128 #endif
129 
130  if (pService->qt_metacall(QMetaObject::InvokeMetaMethod, m_nMethodIndex, param.data()) >= 0)
131  LOG(VB_GENERAL, LOG_WARNING, "qt_metacall error");
132 
133  // --------------------------------------------------------------
134  // Delete param array, skip return parameter since not dynamically
135  // created.
136  // --------------------------------------------------------------
137 
138  for (int nIdx=1; nIdx < paramNames.length()+1; ++nIdx)
139  {
140  if ((types[ nIdx ] != QMetaType::UnknownType) && (param[ nIdx ] != nullptr))
141  {
142  auto metaType = QMetaType( types[ nIdx ] );
143  metaType.destroy(param[ nIdx ]);
144  }
145  }
146  }
147  catch (QString &sMsg)
148  {
149  LOG(VB_GENERAL, LOG_ERR,
150  QString("MethodInfo::Invoke - An Exception Occurred: %1")
151  .arg(sMsg));
152 
153  if ((types[ 0 ] != QMetaType::UnknownType) && (param[ 0 ] != nullptr ))
154  {
155  auto metaType = QMetaType( types[ 0 ] );
156  metaType.destroy(param[ 0 ]);
157  }
158 
159  throw;
160  }
161  catch (HttpRedirectException &ex)
162  {
163  bExceptionThrown = true;
164  exception = ex;
165  }
166  catch (...)
167  {
168  LOG(VB_GENERAL, LOG_INFO,
169  "MethodInfo::Invoke - An Exception Occurred" );
170  }
171 
172  // --------------------------------------------------------------
173  // return the result after converting to a QVariant
174  // --------------------------------------------------------------
175 
176  QVariant vReturn;
177 
178  if ( param[ 0 ] != nullptr)
179  {
180  vReturn = pService->ConvertToVariant( types[ 0 ], param[ 0 ] );
181 
182  if (types[ 0 ] != QMetaType::UnknownType)
183  {
184  auto metaType = QMetaType( types[ 0 ] );
185  metaType.destroy(param[ 0 ]);
186  }
187  }
188 
189  // --------------------------------------------------------------
190  // Re-throw exception if needed.
191  // --------------------------------------------------------------
192 
193  if (bExceptionThrown)
194  throw HttpRedirectException(exception);
195 
196  return vReturn;
197 }
198 
201 //
202 //
203 //
206 
207 ServiceHost::ServiceHost(const QMetaObject &metaObject,
208  const QString &sExtensionName,
209  QString sBaseUrl,
210  const QString &sSharePath )
211  : HttpServerExtension ( sExtensionName, sSharePath ),
212  m_sBaseUrl(std::move(sBaseUrl)),
213  m_oMetaObject(metaObject)
214 {
215  // ----------------------------------------------------------------------
216  // Create an instance of the service so custom types get registered.
217  // ----------------------------------------------------------------------
218 
219  QObject *pService = m_oMetaObject.newInstance();
220 
221  // ----------------------------------------------------------------------
222  // Read in all callable methods and cache information about them
223  // ----------------------------------------------------------------------
224 
225  for (int nIdx = 0; nIdx < m_oMetaObject.methodCount(); nIdx++)
226  {
227  QMetaMethod method = m_oMetaObject.method( nIdx );
228 
229  if ((method.methodType() == QMetaMethod::Slot ) &&
230  (method.access() == QMetaMethod::Public ))
231  {
232  QString sName( method.methodSignature() );
233 
234  // --------------------------------------------------------------
235  // Ignore the following methods...
236  // --------------------------------------------------------------
237 
238  if (sName == "deleteLater()")
239  continue;
240 
241  // --------------------------------------------------------------
242 
243  MethodInfo oInfo;
244 
245  oInfo.m_nMethodIndex = nIdx;
246  oInfo.m_sName = sName.section( '(', 0, 0 );
247  oInfo.m_oMethod = method;
251 
252  QString sMethodClassInfo = oInfo.m_sName + "_Method";
253 
254  int nClassIdx =
255  m_oMetaObject.indexOfClassInfo(sMethodClassInfo.toLatin1());
256 
257  if (nClassIdx >=0)
258  {
259  QString sRequestType =
260  m_oMetaObject.classInfo(nClassIdx).value();
261 
262  if (sRequestType == "POST")
264  else if (sRequestType == "GET" )
267  }
268 
269  m_methods.insert( oInfo.m_sName, oInfo );
270  }
271  }
272 
273  // ----------------------------------------------------------------------
274 
275  delete pService;
276 }
277 
279 //
281 
283 {
284  return QStringList( m_sBaseUrl );
285 }
286 
288 //
290 
292 {
293  bool bHandled = false;
294  Service *pService = nullptr;
295 
296  try
297  {
298  if (pRequest)
299  {
300  if (pRequest->m_sBaseUrl != m_sBaseUrl)
301  return false;
302 
303  LOG(VB_HTTP, LOG_INFO,
304  QString("ServiceHost::ProcessRequest: %1 : %2")
305  .arg(pRequest->m_sMethod, pRequest->m_sRawRequest));
306 
307  // --------------------------------------------------------------
308  // Check to see if they are requesting the WSDL service Definition
309  // --------------------------------------------------------------
310 
311  if (( pRequest->m_eType == RequestTypeGet ) &&
312  ( pRequest->m_sMethod == "wsdl" ))
313  {
314  pService = qobject_cast<Service*>(m_oMetaObject.newInstance());
315 
316  Wsdl wsdl( this );
317 
318  wsdl.GetWSDL( pRequest );
319 
320  delete pService;
321  return true;
322  }
323 
324  // --------------------------------------------------------------
325  // Check to see if they are requesting XSD - Type Definition
326  // --------------------------------------------------------------
327 
328  if (( pRequest->m_eType == RequestTypeGet ) &&
329  ( pRequest->m_sMethod == "xsd" ))
330  {
331  bool bHandled2 = false;
332  if ( pRequest->m_mapParams.count() > 0)
333  {
334  pService = qobject_cast<Service*>(m_oMetaObject.newInstance());
335 
336  Xsd xsd;
337 
338  if (pRequest->m_mapParams.contains( "type" ))
339  bHandled2 = xsd.GetXSD( pRequest, pRequest->m_mapParams[ "type" ] );
340  else
341  bHandled2 = xsd.GetEnumXSD( pRequest, pRequest->m_mapParams[ "enum" ] );
342  delete pService;
343  pService = nullptr;
344  }
345 
346  if (!bHandled2)
347  throw QString("Invalid arguments to xsd query: %1")
348  .arg(pRequest->m_sRequestUrl.section('?', 1));
349 
350  return true;
351  }
352 
353  // --------------------------------------------------------------
354 
355  if (( pRequest->m_eType == RequestTypeGet ) &&
356  ( pRequest->m_sMethod == "version" ))
357  {
358 
359  int nClassIdx = m_oMetaObject.indexOfClassInfo( "version" );
360 
361  if (nClassIdx >=0)
362  {
363  QString sVersion =
364  m_oMetaObject.classInfo(nClassIdx).value();
365 
366  return FormatResponse( pRequest, QVariant( sVersion ));
367  }
368  }
369 
370  // --------------------------------------------------------------
371  // Allow a more REST like calling convention. If the Method
372  // Name isn't found, search for one with the request method
373  // appended to the name ( "Get" or "Put" for POST)
374  // --------------------------------------------------------------
375 
376  QString sMethodName = pRequest->m_sMethod;
377  bool bMethodFound = false;
378 
379  if (m_methods.contains(sMethodName))
380  bMethodFound = true;
381  else
382  {
383  switch( pRequest->m_eType )
384  {
385  case RequestTypeHead:
386  case RequestTypeGet :
387  sMethodName = "Get" + sMethodName;
388  break;
389  case RequestTypePost:
390  sMethodName = "Put" + sMethodName;
391  break;
392  case RequestTypeUnknown:
393  case RequestTypeOptions:
394  case RequestTypeMSearch:
397  case RequestTypeNotify:
398  case RequestTypeResponse:
399  // silence compiler
400  break;
401  }
402 
403  if (m_methods.contains(sMethodName))
404  bMethodFound = true;
405  }
406 
407  if (bMethodFound)
408  {
409  MethodInfo oInfo = m_methods.value( sMethodName );
410 
411  if (( pRequest->m_eType & oInfo.m_eRequestType ) != 0)
412  {
413  // ------------------------------------------------------
414  // Create new Instance of the Service Class so
415  // it's guaranteed to be on the same thread
416  // since we are making direct calls into it.
417  // ------------------------------------------------------
418 
419  pService =
420  qobject_cast<Service*>(m_oMetaObject.newInstance());
421 
422  QVariant vResult = oInfo.Invoke(pService,
423  pRequest->m_mapParams);
424 
425  bHandled = FormatResponse( pRequest, vResult );
426  }
427  }
428 
429  if (!bHandled)
431  }
432  }
433  catch (HttpRedirectException &ex)
434  {
436  bHandled = true;
437  }
438  catch (HttpException &ex)
439  {
440  LOG(VB_GENERAL, LOG_ERR, ex.m_msg);
442 
443  bHandled = true;
444 
445  }
446  catch (QString &sMsg)
447  {
448  LOG(VB_GENERAL, LOG_ERR, sMsg);
450 
451  bHandled = true;
452  }
453  catch ( ...)
454  {
455  QString sMsg( "ServiceHost::ProcessRequest - Unexpected Exception" );
456 
457  LOG(VB_GENERAL, LOG_ERR, sMsg);
459 
460  bHandled = true;
461  }
462 
463  delete pService;
464  return bHandled;
465 }
466 
468 //
470 
471 bool ServiceHost::FormatResponse( HTTPRequest *pRequest, QObject *pResults )
472 {
473  if (pResults != nullptr)
474  {
475  Serializer *pSer = pRequest->GetSerializer();
476 
477  pSer->Serialize( pResults );
478 
479  pRequest->FormatActionResponse( pSer );
480 
481  delete pResults;
482 
483  return true;
484  }
485  UPnp::FormatErrorResponse( pRequest, UPnPResult_ActionFailed, "Call to method failed" );
486 
487  return false;
488 }
489 
491 //
493 
494 bool ServiceHost::FormatResponse( HTTPRequest *pRequest, const QFileInfo& oInfo )
495 {
496  if (oInfo.exists())
497  {
498  if (oInfo.isSymLink())
499  pRequest->FormatFileResponse( oInfo.symLinkTarget() );
500  else
501  pRequest->FormatFileResponse( oInfo.absoluteFilePath() );
502  }
503  else
504  {
505  // force return as a 404...
506  pRequest->FormatFileResponse( "" );
507  }
508 
509  return true;
510 }
511 
512 
514 //
516 
517 bool ServiceHost::FormatResponse( HTTPRequest *pRequest, const QVariant& vValue )
518 {
519  if ( vValue.canConvert< QObject* >())
520  {
521  const QObject *pObject = vValue.value< QObject* >();
522 
523  return FormatResponse( pRequest, (QObject *)pObject );
524  }
525 
526  if ( vValue.canConvert< QFileInfo >())
527  {
528  const auto oFileInfo = vValue.value< QFileInfo >();
529 
530  return FormatResponse( pRequest, oFileInfo );
531  }
532 
533  // ----------------------------------------------------------------------
534  // Simple Variant... serialize it.
535  // ----------------------------------------------------------------------
536 
537  Serializer *pSer = pRequest->GetSerializer();
538 
539  pSer->Serialize( vValue, vValue.typeName() );
540 
541  pRequest->FormatActionResponse( pSer );
542 
543  return true;
544 }
HTTPRequest::m_sBaseUrl
QString m_sBaseUrl
Definition: httprequest.h:127
HttpException::m_msg
QString m_msg
Definition: httprequest.h:309
Serializer::Serialize
virtual void Serialize(const QObject *pObject, const QString &_sName=QString())
Definition: serializer.cpp:34
HTTPRequest
Definition: httprequest.h:109
xsd.h
HttpRedirectException::m_hostName
QString m_hostName
Definition: httprequest.h:323
Serializer
Definition: serializer.h:31
MethodInfo::m_nMethodIndex
int m_nMethodIndex
Definition: servicehost.h:38
ServiceHost::m_methods
MetaInfoMap m_methods
Definition: servicehost.h:70
HTTPRequest::m_sMethod
QString m_sMethod
Definition: httprequest.h:129
RequestTypePost
@ RequestTypePost
Definition: httprequest.h:50
Wsdl::GetWSDL
bool GetWSDL(HTTPRequest *pRequest)
Definition: wsdl.cpp:22
types
static const struct wl_interface * types[]
Definition: idle_inhibit_unstable_v1.c:39
RequestTypeOptions
@ RequestTypeOptions
Definition: httprequest.h:54
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
RequestTypeResponse
@ RequestTypeResponse
Definition: httprequest.h:62
ServiceHost::ServiceHost
ServiceHost(const QMetaObject &metaObject, const QString &sExtensionName, QString sBaseUrl, const QString &sSharePath)
Definition: servicehost.cpp:207
RequestTypeSubscribe
@ RequestTypeSubscribe
Definition: httprequest.h:58
RequestTypeUnsubscribe
@ RequestTypeUnsubscribe
Definition: httprequest.h:59
MAX_PARAMS
static constexpr int MAX_PARAMS
Definition: servicehost.cpp:21
HTTPRequest::GetSerializer
Serializer * GetSerializer()
Definition: httprequest.cpp:1701
UPnp::FormatErrorResponse
static void FormatErrorResponse(HTTPRequest *pRequest, UPnPResultCode eCode, const QString &sMsg="")
Definition: upnp.cpp:241
HTTPRequest::FormatActionResponse
void FormatActionResponse(Serializer *ser)
Definition: httprequest.cpp:700
Xsd
Definition: xsd.h:33
UPnp::FormatRedirectResponse
static void FormatRedirectResponse(HTTPRequest *pRequest, const QString &hostName)
Definition: upnp.cpp:278
mythlogging.h
RequestTypeMSearch
@ RequestTypeMSearch
Definition: httprequest.h:57
Service
Definition: service.h:42
MethodInfo::Invoke
QVariant Invoke(Service *pService, const QStringMap &reqParams) const
Definition: servicehost.cpp:27
HTTPRequest::m_mapParams
QStringMap m_mapParams
Definition: httprequest.h:131
QStringMap
QMap< QString, QString > QStringMap
Definition: upnputil.h:32
RequestTypeHead
@ RequestTypeHead
Definition: httprequest.h:49
ServiceHost::m_sBaseUrl
QString m_sBaseUrl
Definition: servicehost.h:67
Service::ConvertToVariant
virtual QVariant ConvertToVariant(int nType, void *pValue)
Definition: service.cpp:22
ServiceHost::GetBasePaths
QStringList GetBasePaths() override
Definition: servicehost.cpp:282
ServiceHost::m_oMetaObject
QMetaObject m_oMetaObject
Definition: servicehost.h:69
Wsdl
Definition: wsdl.h:35
HTTPRequest::m_sRequestUrl
QString m_sRequestUrl
Definition: httprequest.h:126
HttpException
Definition: httprequest.h:305
Service::ConvertToParameterPtr
virtual void * ConvertToParameterPtr(int nTypeId, const QString &sParamType, void *pParam, const QString &sValue)
Definition: service.cpp:49
Service::m_parsedParams
QList< QString > m_parsedParams
Definition: service.h:68
Xsd::GetXSD
bool GetXSD(HTTPRequest *pRequest, QString sTypeName)
Definition: xsd.cpp:204
HTTPRequest::m_sRawRequest
QString m_sRawRequest
Definition: httprequest.h:123
MethodInfo
Definition: servicehost.h:34
MethodInfo::m_eRequestType
HttpRequestType m_eRequestType
Definition: servicehost.h:41
UPnPResult_ActionFailed
@ UPnPResult_ActionFailed
Definition: upnp.h:41
UPnPResult_InvalidAction
@ UPnPResult_InvalidAction
Definition: upnp.h:39
ServiceHost::ProcessRequest
bool ProcessRequest(HTTPRequest *pRequest) override
Definition: servicehost.cpp:291
RequestTypeNotify
@ RequestTypeNotify
Definition: httprequest.h:60
HTTPRequest::FormatFileResponse
void FormatFileResponse(const QString &sFileName)
Definition: httprequest.cpp:788
std
Definition: mythchrono.h:23
HttpRedirectException
Definition: httprequest.h:319
Xsd::GetEnumXSD
bool GetEnumXSD(HTTPRequest *pRequest, const QString &sEnumName)
Definition: xsd.cpp:25
MethodInfo::m_oMethod
QMetaMethod m_oMethod
Definition: servicehost.h:40
servicehost.h
RequestTypeUnknown
@ RequestTypeUnknown
Definition: httprequest.h:46
wsdl.h
RequestTypeGet
@ RequestTypeGet
Definition: httprequest.h:48
HttpServerExtension
Definition: httpserver.h:71
ServiceHost::FormatResponse
virtual bool FormatResponse(HTTPRequest *pRequest, QObject *pResults)
Definition: servicehost.cpp:471
MethodInfo::m_sName
QString m_sName
Definition: servicehost.h:39
HttpRequestType
HttpRequestType
Definition: httprequest.h:44
HTTPRequest::m_eType
HttpRequestType m_eType
Definition: httprequest.h:120