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