MythTV master
servicehost.cpp
Go to the documentation of this file.
1
2// 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#include "servicehost.h"
13
15
16#include "libmythupnp/upnp.h"
17#include "wsdl.h"
18#include "xsd.h"
19
20static constexpr int MAX_PARAMS = 256;
21
23//
25
26QVariant MethodInfo::Invoke( Service *pService, const QStringMap &reqParams ) const
27{
28 HttpRedirectException exception;
29 bool bExceptionThrown = false;
30 QStringMap lowerParams;
31
32 if (!pService)
33 throw QString("Invalid argument to MethodInfo::Invoke. pService in nullptr");
34
35 // Change params to lower case for case-insensitive comparison
36 for (auto it = reqParams.cbegin(); it != reqParams.cend(); ++it)
37 {
38 lowerParams[it.key().toLower()] = *it;
39 }
40
41 // --------------------------------------------------------------
42 // Provide actual parameters received to method
43 // --------------------------------------------------------------
44
45 pService->m_parsedParams = lowerParams.keys();
46
47
48 QList<QByteArray> paramNames = m_oMethod.parameterNames();
49 QList<QByteArray> paramTypes = m_oMethod.parameterTypes();
50
51 // ----------------------------------------------------------------------
52 // Create Parameter array (Can't have more than MAX_PARAMS parameters)....
53 // switched to static array for performance.
54 // ----------------------------------------------------------------------
55
56 std::array<void*,MAX_PARAMS> param {};
57 std::array<int,MAX_PARAMS> types {};
58
59 try
60 {
61 // --------------------------------------------------------------
62 // Add a place for the Return value
63 // --------------------------------------------------------------
64
65#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
66 int nRetIdx = QMetaType::type( m_oMethod.typeName() );
67 QMetaType oRetType = QMetaType(nRetIdx);
68#else
69 int nRetIdx = m_oMethod.returnType();
70 QMetaType oRetType = m_oMethod.returnMetaType();
71#endif
72
73 if (nRetIdx != QMetaType::UnknownType)
74 {
75 param[ 0 ] = oRetType.create();
76 types[ 0 ] = nRetIdx;
77 }
78 else
79 {
80 param[ 0 ] = nullptr;
81 types[ 0 ] = QMetaType::UnknownType;
82 }
83
84 // --------------------------------------------------------------
85 // Fill in parameters from request values
86 // --------------------------------------------------------------
87
88 for( int nIdx = 0; nIdx < paramNames.length(); ++nIdx )
89 {
90 QString sValue = lowerParams[ paramNames[ nIdx ].toLower() ];
91 QString sParamType = paramTypes[ nIdx ];
92
93#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
94 int nId = QMetaType::type( paramTypes[ nIdx ] );
95 void *pParam = nullptr;
96
97 if (nId != QMetaType::UnknownType)
98 {
99 pParam = QMetaType::create( nId );
100 }
101#else
102 QMetaType metaType = QMetaType::fromName( paramTypes[ nIdx ] );
103 void *pParam = metaType.create();
104 int nId = metaType.id();
105#endif
106 if (nId == QMetaType::UnknownType)
107 {
108 LOG(VB_GENERAL, LOG_ERR,
109 QString("MethodInfo::Invoke - Type unknown '%1'")
110 .arg(sParamType));
111 throw QString("MethodInfo::Invoke - Type unknown '%1'")
112 .arg(sParamType);
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
207ServiceHost::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;
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
471bool 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
494bool 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
517bool 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}
QString m_sMethod
Definition: httprequest.h:130
void FormatFileResponse(const QString &sFileName)
QString m_sRequestUrl
Definition: httprequest.h:127
Serializer * GetSerializer()
HttpRequestType m_eType
Definition: httprequest.h:121
void FormatActionResponse(Serializer *ser)
QString m_sBaseUrl
Definition: httprequest.h:128
QString m_sRawRequest
Definition: httprequest.h:124
QStringMap m_mapParams
Definition: httprequest.h:132
QString m_msg
Definition: httprequest.h:310
HttpRequestType m_eRequestType
Definition: servicehost.h:42
int m_nMethodIndex
Definition: servicehost.h:39
QString m_sName
Definition: servicehost.h:40
QVariant Invoke(Service *pService, const QStringMap &reqParams) const
Definition: servicehost.cpp:26
QMetaMethod m_oMethod
Definition: servicehost.h:41
virtual void Serialize(const QObject *pObject, const QString &_sName=QString())
Definition: serializer.cpp:34
MetaInfoMap m_methods
Definition: servicehost.h:71
virtual bool FormatResponse(HTTPRequest *pRequest, QObject *pResults)
bool ProcessRequest(HTTPRequest *pRequest) override
QString m_sBaseUrl
Definition: servicehost.h:68
QStringList GetBasePaths() override
ServiceHost(const QMetaObject &metaObject, const QString &sExtensionName, QString sBaseUrl, const QString &sSharePath)
QMetaObject m_oMetaObject
Definition: servicehost.h:70
QList< QString > m_parsedParams
Definition: service.h:66
virtual void * ConvertToParameterPtr(int nTypeId, const QString &sParamType, void *pParam, const QString &sValue)
Definition: service.cpp:49
virtual QVariant ConvertToVariant(int nType, void *pValue)
Definition: service.cpp:22
static void FormatRedirectResponse(HTTPRequest *pRequest, const QString &hostName)
Definition: upnp.cpp:281
static void FormatErrorResponse(HTTPRequest *pRequest, UPnPResultCode eCode, const QString &sMsg="")
Definition: upnp.cpp:242
Definition: wsdl.h:34
bool GetWSDL(HTTPRequest *pRequest)
Definition: wsdl.cpp:22
Definition: xsd.h:32
bool GetEnumXSD(HTTPRequest *pRequest, const QString &sEnumName)
Definition: xsd.cpp:24
bool GetXSD(HTTPRequest *pRequest, QString sTypeName)
Definition: xsd.cpp:203
HttpRequestType
Definition: httprequest.h:46
@ RequestTypeMSearch
Definition: httprequest.h:58
@ RequestTypeSubscribe
Definition: httprequest.h:59
@ RequestTypeNotify
Definition: httprequest.h:61
@ RequestTypePost
Definition: httprequest.h:51
@ RequestTypeOptions
Definition: httprequest.h:55
@ RequestTypeUnsubscribe
Definition: httprequest.h:60
@ RequestTypeGet
Definition: httprequest.h:49
@ RequestTypeHead
Definition: httprequest.h:50
@ RequestTypeResponse
Definition: httprequest.h:63
@ RequestTypeUnknown
Definition: httprequest.h:47
static const struct wl_interface * types[]
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
STL namespace.
static constexpr int MAX_PARAMS
Definition: servicehost.cpp:20
@ UPnPResult_InvalidAction
Definition: upnp.h:37
@ UPnPResult_ActionFailed
Definition: upnp.h:39
QMap< QString, QString > QStringMap
Definition: upnputil.h:28