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