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 QString("Invalid argument to MethodInfo::Invoke. pService in nullptr");
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 QString("MethodInfo::Invoke - Type unknown '%1'")
113  .arg(sParamType);
114  }
115 
116  types[nIdx+1] = nId;
117  param[nIdx+1] = pService->ConvertToParameterPtr( nId, sParamType,
118  pParam, sValue );
119  }
120 
121 #if 0
122  QThread *currentThread = QThread::currentThread();
123  QThread *objectThread = pService->thread();
124 
125  if (currentThread == objectThread)
126  LOG(VB_HTTP, LOG_DEBUG, "*** Threads are same ***");
127  else
128  LOG(VB_HTTP, LOG_DEBUG, "*** Threads are Different!!! ***");
129 #endif
130 
131  if (pService->qt_metacall(QMetaObject::InvokeMetaMethod, m_nMethodIndex, param.data()) >= 0)
132  LOG(VB_GENERAL, LOG_WARNING, "qt_metacall error");
133 
134  // --------------------------------------------------------------
135  // Delete param array, skip return parameter since not dynamically
136  // created.
137  // --------------------------------------------------------------
138 
139  for (int nIdx=1; nIdx < paramNames.length()+1; ++nIdx)
140  {
141  if ((types[ nIdx ] != QMetaType::UnknownType) && (param[ nIdx ] != nullptr))
142  {
143  auto metaType = QMetaType( types[ nIdx ] );
144  metaType.destroy(param[ nIdx ]);
145  }
146  }
147  }
148  catch (QString &sMsg)
149  {
150  LOG(VB_GENERAL, LOG_ERR,
151  QString("MethodInfo::Invoke - An Exception Occurred: %1")
152  .arg(sMsg));
153 
154  if ((types[ 0 ] != QMetaType::UnknownType) && (param[ 0 ] != nullptr ))
155  {
156  auto metaType = QMetaType( types[ 0 ] );
157  metaType.destroy(param[ 0 ]);
158  }
159 
160  throw;
161  }
162  catch (HttpRedirectException &ex)
163  {
164  bExceptionThrown = true;
165  exception = ex;
166  }
167  catch (...)
168  {
169  LOG(VB_GENERAL, LOG_INFO,
170  "MethodInfo::Invoke - An Exception Occurred" );
171  }
172 
173  // --------------------------------------------------------------
174  // return the result after converting to a QVariant
175  // --------------------------------------------------------------
176 
177  QVariant vReturn;
178 
179  if ( param[ 0 ] != nullptr)
180  {
181  vReturn = pService->ConvertToVariant( types[ 0 ], param[ 0 ] );
182 
183  if (types[ 0 ] != QMetaType::UnknownType)
184  {
185  auto metaType = QMetaType( types[ 0 ] );
186  metaType.destroy(param[ 0 ]);
187  }
188  }
189 
190  // --------------------------------------------------------------
191  // Re-throw exception if needed.
192  // --------------------------------------------------------------
193 
194  if (bExceptionThrown)
195  throw HttpRedirectException(exception);
196 
197  return vReturn;
198 }
199 
202 //
203 //
204 //
207 
208 ServiceHost::ServiceHost(const QMetaObject &metaObject,
209  const QString &sExtensionName,
210  QString sBaseUrl,
211  const QString &sSharePath )
212  : HttpServerExtension ( sExtensionName, sSharePath ),
213  m_sBaseUrl(std::move(sBaseUrl)),
214  m_oMetaObject(metaObject)
215 {
216  // ----------------------------------------------------------------------
217  // Create an instance of the service so custom types get registered.
218  // ----------------------------------------------------------------------
219 
220  QObject *pService = m_oMetaObject.newInstance();
221 
222  // ----------------------------------------------------------------------
223  // Read in all callable methods and cache information about them
224  // ----------------------------------------------------------------------
225 
226  for (int nIdx = 0; nIdx < m_oMetaObject.methodCount(); nIdx++)
227  {
228  QMetaMethod method = m_oMetaObject.method( nIdx );
229 
230  if ((method.methodType() == QMetaMethod::Slot ) &&
231  (method.access() == QMetaMethod::Public ))
232  {
233  QString sName( method.methodSignature() );
234 
235  // --------------------------------------------------------------
236  // Ignore the following methods...
237  // --------------------------------------------------------------
238 
239  if (sName == "deleteLater()")
240  continue;
241 
242  // --------------------------------------------------------------
243 
244  MethodInfo oInfo;
245 
246  oInfo.m_nMethodIndex = nIdx;
247  oInfo.m_sName = sName.section( '(', 0, 0 );
248  oInfo.m_oMethod = method;
252 
253  QString sMethodClassInfo = oInfo.m_sName + "_Method";
254 
255  int nClassIdx =
256  m_oMetaObject.indexOfClassInfo(sMethodClassInfo.toLatin1());
257 
258  if (nClassIdx >=0)
259  {
260  QString sRequestType =
261  m_oMetaObject.classInfo(nClassIdx).value();
262 
263  if (sRequestType == "POST")
265  else if (sRequestType == "GET" )
268  }
269 
270  m_methods.insert( oInfo.m_sName, oInfo );
271  }
272  }
273 
274  // ----------------------------------------------------------------------
275 
276  delete pService;
277 }
278 
280 //
282 
284 {
285  return QStringList( m_sBaseUrl );
286 }
287 
289 //
291 
293 {
294  bool bHandled = false;
295  Service *pService = nullptr;
296 
297  try
298  {
299  if (pRequest)
300  {
301  if (pRequest->m_sBaseUrl != m_sBaseUrl)
302  return false;
303 
304  LOG(VB_HTTP, LOG_INFO,
305  QString("ServiceHost::ProcessRequest: %1 : %2")
306  .arg(pRequest->m_sMethod, pRequest->m_sRawRequest));
307 
308  // --------------------------------------------------------------
309  // Check to see if they are requesting the WSDL service Definition
310  // --------------------------------------------------------------
311 
312  if (( pRequest->m_eType == RequestTypeGet ) &&
313  ( pRequest->m_sMethod == "wsdl" ))
314  {
315  pService = qobject_cast<Service*>(m_oMetaObject.newInstance());
316 
317  Wsdl wsdl( this );
318 
319  wsdl.GetWSDL( pRequest );
320 
321  delete pService;
322  return true;
323  }
324 
325  // --------------------------------------------------------------
326  // Check to see if they are requesting XSD - Type Definition
327  // --------------------------------------------------------------
328 
329  if (( pRequest->m_eType == RequestTypeGet ) &&
330  ( pRequest->m_sMethod == "xsd" ))
331  {
332  bool bHandled2 = false;
333  if ( pRequest->m_mapParams.count() > 0)
334  {
335  pService = qobject_cast<Service*>(m_oMetaObject.newInstance());
336 
337  Xsd xsd;
338 
339  if (pRequest->m_mapParams.contains( "type" ))
340  bHandled2 = xsd.GetXSD( pRequest, pRequest->m_mapParams[ "type" ] );
341  else
342  bHandled2 = xsd.GetEnumXSD( pRequest, pRequest->m_mapParams[ "enum" ] );
343  delete pService;
344  pService = nullptr;
345  }
346 
347  if (!bHandled2)
348  throw QString("Invalid arguments to xsd query: %1")
349  .arg(pRequest->m_sRequestUrl.section('?', 1));
350 
351  return true;
352  }
353 
354  // --------------------------------------------------------------
355 
356  if (( pRequest->m_eType == RequestTypeGet ) &&
357  ( pRequest->m_sMethod == "version" ))
358  {
359 
360  int nClassIdx = m_oMetaObject.indexOfClassInfo( "version" );
361 
362  if (nClassIdx >=0)
363  {
364  QString sVersion =
365  m_oMetaObject.classInfo(nClassIdx).value();
366 
367  return FormatResponse( pRequest, QVariant( sVersion ));
368  }
369  }
370 
371  // --------------------------------------------------------------
372  // Allow a more REST like calling convention. If the Method
373  // Name isn't found, search for one with the request method
374  // appended to the name ( "Get" or "Put" for POST)
375  // --------------------------------------------------------------
376 
377  QString sMethodName = pRequest->m_sMethod;
378  bool bMethodFound = false;
379 
380  if (m_methods.contains(sMethodName))
381  bMethodFound = true;
382  else
383  {
384  switch( pRequest->m_eType )
385  {
386  case RequestTypeHead:
387  case RequestTypeGet :
388  sMethodName = "Get" + sMethodName;
389  break;
390  case RequestTypePost:
391  sMethodName = "Put" + sMethodName;
392  break;
393  case RequestTypeUnknown:
394  case RequestTypeOptions:
395  case RequestTypeMSearch:
398  case RequestTypeNotify:
399  case RequestTypeResponse:
400  // silence compiler
401  break;
402  }
403 
404  if (m_methods.contains(sMethodName))
405  bMethodFound = true;
406  }
407 
408  if (bMethodFound)
409  {
410  MethodInfo oInfo = m_methods.value( sMethodName );
411 
412  if (( pRequest->m_eType & oInfo.m_eRequestType ) != 0)
413  {
414  // ------------------------------------------------------
415  // Create new Instance of the Service Class so
416  // it's guaranteed to be on the same thread
417  // since we are making direct calls into it.
418  // ------------------------------------------------------
419 
420  pService =
421  qobject_cast<Service*>(m_oMetaObject.newInstance());
422 
423  QVariant vResult = oInfo.Invoke(pService,
424  pRequest->m_mapParams);
425 
426  bHandled = FormatResponse( pRequest, vResult );
427  }
428  }
429 
430  if (!bHandled)
432  }
433  }
434  catch (HttpRedirectException &ex)
435  {
437  bHandled = true;
438  }
439  catch (HttpException &ex)
440  {
441  LOG(VB_GENERAL, LOG_ERR, ex.m_msg);
443 
444  bHandled = true;
445 
446  }
447  catch (QString &sMsg)
448  {
449  LOG(VB_GENERAL, LOG_ERR, sMsg);
451 
452  bHandled = true;
453  }
454  catch ( ...)
455  {
456  QString sMsg( "ServiceHost::ProcessRequest - Unexpected Exception" );
457 
458  LOG(VB_GENERAL, LOG_ERR, sMsg);
460 
461  bHandled = true;
462  }
463 
464  delete pService;
465  return bHandled;
466 }
467 
469 //
471 
472 bool ServiceHost::FormatResponse( HTTPRequest *pRequest, QObject *pResults )
473 {
474  if (pResults != nullptr)
475  {
476  Serializer *pSer = pRequest->GetSerializer();
477 
478  pSer->Serialize( pResults );
479 
480  pRequest->FormatActionResponse( pSer );
481 
482  delete pResults;
483 
484  return true;
485  }
486  UPnp::FormatErrorResponse( pRequest, UPnPResult_ActionFailed, "Call to method failed" );
487 
488  return false;
489 }
490 
492 //
494 
495 bool ServiceHost::FormatResponse( HTTPRequest *pRequest, const QFileInfo& oInfo )
496 {
497  if (oInfo.exists())
498  {
499  if (oInfo.isSymLink())
500  pRequest->FormatFileResponse( oInfo.symLinkTarget() );
501  else
502  pRequest->FormatFileResponse( oInfo.absoluteFilePath() );
503  }
504  else
505  {
506  // force return as a 404...
507  pRequest->FormatFileResponse( "" );
508  }
509 
510  return true;
511 }
512 
513 
515 //
517 
518 bool ServiceHost::FormatResponse( HTTPRequest *pRequest, const QVariant& vValue )
519 {
520  if ( vValue.canConvert< QObject* >())
521  {
522  const QObject *pObject = vValue.value< QObject* >();
523 
524  return FormatResponse( pRequest, (QObject *)pObject );
525  }
526 
527  if ( vValue.canConvert< QFileInfo >())
528  {
529  const auto oFileInfo = vValue.value< QFileInfo >();
530 
531  return FormatResponse( pRequest, oFileInfo );
532  }
533 
534  // ----------------------------------------------------------------------
535  // Simple Variant... serialize it.
536  // ----------------------------------------------------------------------
537 
538  Serializer *pSer = pRequest->GetSerializer();
539 
540  pSer->Serialize( vValue, vValue.typeName() );
541 
542  pRequest->FormatActionResponse( pSer );
543 
544  return true;
545 }
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:208
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:1682
UPnp::FormatErrorResponse
static void FormatErrorResponse(HTTPRequest *pRequest, UPnPResultCode eCode, const QString &sMsg="")
Definition: upnp.cpp:242
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:279
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:283
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:292
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:472
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