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