PageRenderTime 51ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/src/libopensrf/osrf_application.c

https://gitlab.com/evergreen-bjwebb/opensrf-debian
C | 1080 lines | 517 code | 137 blank | 426 comment | 99 complexity | b09c87b9858ce2aebfd180b62787f88a MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
  1. #include <opensrf/osrf_application.h>
  2. /**
  3. @file osrf_application.c
  4. @brief Load and manage shared object libraries.
  5. Maintain a registry of applications, using an osrfHash keyed on application name,
  6. For each application, load a shared object library so that we can call
  7. application-specific functions dynamically. In order to map method names to the
  8. corresponding functions (i.e. symbol names in the library), maintain a registry of
  9. methods, using an osrfHash keyed on method name.
  10. */
  11. // The following macro is commented out because it ia no longer used.
  12. // Used internally to make sure the method description provided is OK
  13. /*
  14. #define OSRF_METHOD_VERIFY_DESCRIPTION(app, d) \
  15. if(!app) return -1; \
  16. if(!d) return -1;\
  17. if(!d->name) { \
  18. osrfLogError( OSRF_LOG_MARK, "No method name provided in description" ), \
  19. return -1; \
  20. } \
  21. if(!d->symbol) { \
  22. osrfLogError( OSRF_LOG_MARK, "No method symbol provided in description" ), \
  23. return -1; \
  24. } \
  25. if(!d->notes) \
  26. d->notes = ""; \
  27. if(!d->paramNotes) \
  28. d->paramNotes = "";\
  29. if(!d->returnNotes) \
  30. d->returnNotes = "";
  31. */
  32. /**
  33. @name Well known method names
  34. @brief These methods are automatically implemented for every application.
  35. */
  36. /*@{*/
  37. #define OSRF_SYSMETHOD_INTROSPECT "opensrf.system.method"
  38. #define OSRF_SYSMETHOD_INTROSPECT_ATOMIC "opensrf.system.method.atomic"
  39. #define OSRF_SYSMETHOD_INTROSPECT_ALL "opensrf.system.method.all"
  40. #define OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC "opensrf.system.method.all.atomic"
  41. #define OSRF_SYSMETHOD_ECHO "opensrf.system.echo"
  42. #define OSRF_SYSMETHOD_ECHO_ATOMIC "opensrf.system.echo.atomic"
  43. /*@}*/
  44. /**
  45. @name Method options
  46. @brief Macros that get OR'd together to form method options.
  47. These options are in addition to the ones stipulated by the caller of
  48. osrfRegisterMethod(), and are not externally visible.
  49. */
  50. /*@{*/
  51. /**
  52. @brief Marks a method as a system method.
  53. System methods are implemented by generic functions, called via static linkage. They
  54. are not loaded or executed from shared objects.
  55. */
  56. #define OSRF_METHOD_SYSTEM 1
  57. /**
  58. @brief Combines all responses into a single RESULT message.
  59. For a @em non-atomic method, the server returns each response to the client in a
  60. separate RESULT message. It sends a STATUS message at the end to signify the end of the
  61. message stream.
  62. For an @em atomic method, the server buffers all responses until the method returns,
  63. and then sends them all at once in a single RESULT message (followed by a STATUS message).
  64. Each individual response is encoded as an entry in a JSON array. This buffering is
  65. transparent to the function that implements the method.
  66. Atomic methods incur less networking overhead than non-atomic methods, at the risk of
  67. creating excessively large RESULT messages. The HTTP gateway requires the atomic versions
  68. of streaming methods because of the stateless nature of the HTTP protocol.
  69. If OSRF_METHOD_STREAMING is set for a method, the application generates both an atomic
  70. and a non-atomic method, whose names are identical except that the atomic one carries a
  71. suffix of ".atomic".
  72. */
  73. #define OSRF_METHOD_ATOMIC 4
  74. /*@}*/
  75. /**
  76. @brief Default size of output buffer.
  77. */
  78. #define OSRF_MSG_BUFFER_SIZE 10240
  79. /**
  80. @brief Represent an Application.
  81. */
  82. typedef struct {
  83. void* handle; /**< Handle to the shared object library. */
  84. osrfHash* methods; /**< Registry of method names. */
  85. void (*onExit) (void); /**< Exit handler for the application. */
  86. } osrfApplication;
  87. static void register_method( osrfApplication* app, const char* methodName,
  88. const char* symbolName, const char* notes, int argc, int options, void * user_data );
  89. static osrfMethod* build_method( const char* methodName, const char* symbolName,
  90. const char* notes, int argc, int options, void* );
  91. static void osrfAppSetOnExit(osrfApplication* app, const char* appName);
  92. static void register_system_methods( osrfApplication* app );
  93. static inline osrfApplication* _osrfAppFindApplication( const char* name );
  94. static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName );
  95. static int _osrfAppRespond( osrfMethodContext* context, const jsonObject* data, int complete );
  96. static int _osrfAppPostProcess( osrfMethodContext* context, int retcode );
  97. static int _osrfAppRunSystemMethod(osrfMethodContext* context);
  98. static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
  99. jsonObject* resp );
  100. static int osrfAppIntrospect( osrfMethodContext* ctx );
  101. static int osrfAppIntrospectAll( osrfMethodContext* ctx );
  102. static int osrfAppEcho( osrfMethodContext* ctx );
  103. static void osrfMethodFree( char* name, void* p );
  104. static void osrfAppFree( char* name, void* p );
  105. /**
  106. @brief Registry of applications.
  107. The key of the hash is the application name, and the associated data is an osrfApplication.
  108. */
  109. static osrfHash* _osrfAppHash = NULL;
  110. /**
  111. @brief Register an application.
  112. @param appName Name of the application.
  113. @param soFile Name of the shared object file to be loaded for this application.
  114. @return Zero if successful, or -1 upon error.
  115. Open the shared object file and call its osrfAppInitialize() function, if it has one.
  116. Register the standard system methods for it. Arrange for the application name to
  117. appear in subsequent log messages.
  118. */
  119. int osrfAppRegisterApplication( const char* appName, const char* soFile ) {
  120. if( !appName || ! soFile ) return -1;
  121. char* error;
  122. osrfLogSetAppname( appName );
  123. if( !_osrfAppHash ) {
  124. _osrfAppHash = osrfNewHash();
  125. osrfHashSetCallback( _osrfAppHash, osrfAppFree );
  126. }
  127. osrfLogInfo( OSRF_LOG_MARK, "Registering application %s with file %s", appName, soFile );
  128. // Open the shared object.
  129. void* handle = dlopen( soFile, RTLD_NOW );
  130. if( ! handle ) {
  131. const char* msg = dlerror();
  132. osrfLogWarning( OSRF_LOG_MARK, "Failed to dlopen library file %s: %s", soFile, msg );
  133. return -1;
  134. }
  135. // Construct the osrfApplication.
  136. osrfApplication* app = safe_malloc(sizeof(osrfApplication));
  137. app->handle = handle;
  138. app->methods = osrfNewHash();
  139. osrfHashSetCallback( app->methods, osrfMethodFree );
  140. app->onExit = NULL;
  141. // Add the newly-constructed app to the list.
  142. osrfHashSet( _osrfAppHash, app, appName );
  143. // Try to run the initialize method. Typically it will register one or more
  144. // methods of the application.
  145. int (*init) (void);
  146. *(void **) (&init) = dlsym( handle, "osrfAppInitialize" );
  147. if( (error = dlerror()) != NULL ) {
  148. osrfLogWarning( OSRF_LOG_MARK,
  149. "! Unable to locate method symbol [osrfAppInitialize] for app %s: %s",
  150. appName, error );
  151. } else {
  152. /* run the method */
  153. int ret;
  154. if( (ret = (*init)()) ) {
  155. osrfLogWarning( OSRF_LOG_MARK, "Application %s returned non-zero value from "
  156. "'osrfAppInitialize', not registering...", appName );
  157. osrfHashRemove( _osrfAppHash, appName );
  158. return ret;
  159. }
  160. }
  161. register_system_methods( app );
  162. osrfLogInfo( OSRF_LOG_MARK, "Application %s registered successfully", appName );
  163. osrfAppSetOnExit( app, appName );
  164. return 0;
  165. }
  166. /**
  167. @brief Save a pointer to the application's exit function.
  168. @param app Pointer to the osrfApplication.
  169. @param appName Application name (used only for log messages).
  170. Look in the shared object for a symbol named "osrfAppChildExit". If you find one, save
  171. it as a pointer to the application's exit function. If present, this function will be
  172. called when a server's child process (a so-called "drone") is shutting down.
  173. */
  174. static void osrfAppSetOnExit(osrfApplication* app, const char* appName) {
  175. if(!(app && appName)) return;
  176. /* see if we can run the initialize method */
  177. char* error;
  178. void (*onExit) (void);
  179. *(void **) (&onExit) = dlsym(app->handle, "osrfAppChildExit");
  180. if( (error = dlerror()) != NULL ) {
  181. osrfLogDebug(OSRF_LOG_MARK, "No exit handler defined for %s", appName);
  182. return;
  183. }
  184. osrfLogInfo(OSRF_LOG_MARK, "registering exit handler for %s", appName);
  185. app->onExit = (*onExit);
  186. }
  187. /**
  188. @brief Run the application-specific child initialization function for a given application.
  189. @param appname Name of the application.
  190. @return Zero if successful, or if the application has no child initialization function; -1
  191. if the application is not registered, or if the function returns non-zero.
  192. The child initialization function must be named "osrfAppChildInit" within the shared
  193. object library. It initializes a drone process of a server.
  194. */
  195. int osrfAppRunChildInit(const char* appname) {
  196. osrfApplication* app = _osrfAppFindApplication(appname);
  197. if(!app) return -1;
  198. char* error;
  199. int ret;
  200. int (*childInit) (void);
  201. *(void**) (&childInit) = dlsym(app->handle, "osrfAppChildInit");
  202. if( (error = dlerror()) != NULL ) {
  203. osrfLogInfo( OSRF_LOG_MARK, "No child init defined for app %s : %s", appname, error);
  204. return 0;
  205. }
  206. if( (ret = (*childInit)()) ) {
  207. osrfLogError(OSRF_LOG_MARK, "App %s child init failed", appname);
  208. return -1;
  209. }
  210. osrfLogInfo(OSRF_LOG_MARK, "%s child init succeeded", appname);
  211. return 0;
  212. }
  213. /**
  214. @brief Call the exit handler for every application that has one.
  215. Normally a server's child process (a so-called "drone") calls this function just before
  216. shutting down.
  217. */
  218. void osrfAppRunExitCode( void ) {
  219. osrfHashIterator* itr = osrfNewHashIterator(_osrfAppHash);
  220. osrfApplication* app;
  221. while( (app = osrfHashIteratorNext(itr)) ) {
  222. if( app->onExit ) {
  223. osrfLogInfo(OSRF_LOG_MARK, "Running onExit handler for app %s",
  224. osrfHashIteratorKey(itr) );
  225. app->onExit();
  226. }
  227. }
  228. osrfHashIteratorFree(itr);
  229. }
  230. /**
  231. @brief Register a method for a specified application.
  232. @param appName Name of the application that implements the method.
  233. @param methodName The fully qualified name of the method.
  234. @param symbolName The symbol name (function name) that implements the method.
  235. @param notes Public documentation for this method.
  236. @param argc The minimum number of arguments for the function.
  237. @param options Bit switches setting various options.
  238. @return Zero on success, or -1 on error.
  239. Registering a method enables us to call the right function when a client requests a
  240. method call.
  241. The @a options parameter is zero or more of the following macros, OR'd together:
  242. - OSRF_METHOD_STREAMING method may return more than one response
  243. - OSRF_METHOD_CACHABLE cache results in memcache
  244. If the OSRF_METHOD_STREAMING bit is set, also register an ".atomic" version of the method.
  245. */
  246. int osrfAppRegisterMethod( const char* appName, const char* methodName,
  247. const char* symbolName, const char* notes, int argc, int options ) {
  248. return osrfAppRegisterExtendedMethod(
  249. appName,
  250. methodName,
  251. symbolName,
  252. notes,
  253. argc,
  254. options,
  255. NULL
  256. );
  257. }
  258. /**
  259. @brief Register an extended method for a specified application.
  260. @param appName Name of the application that implements the method.
  261. @param methodName The fully qualified name of the method.
  262. @param symbolName The symbol name (function name) that implements the method.
  263. @param notes Public documentation for this method.
  264. @param argc How many arguments this method expects.
  265. @param options Bit switches setting various options.
  266. @param user_data Opaque pointer to be passed to the dynamically called function.
  267. @return Zero if successful, or -1 upon error.
  268. This function is identical to osrfAppRegisterMethod(), except that it also installs
  269. a method-specific opaque pointer. When we call the corresponding function at
  270. run time, this pointer will be available to the function via the method context.
  271. */
  272. int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName,
  273. const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
  274. if( !appName || ! methodName ) return -1;
  275. osrfApplication* app = _osrfAppFindApplication(appName);
  276. if(!app) {
  277. osrfLogWarning( OSRF_LOG_MARK, "Unable to locate application %s", appName );
  278. return -1;
  279. }
  280. osrfLogDebug( OSRF_LOG_MARK, "Registering method %s for app %s", methodName, appName );
  281. // Extract the only valid option bits, and ignore the rest.
  282. int opts = options & ( OSRF_METHOD_STREAMING | OSRF_METHOD_CACHABLE );
  283. // Build and install a non-atomic method.
  284. register_method(
  285. app, methodName, symbolName, notes, argc, opts, user_data );
  286. if( opts & OSRF_METHOD_STREAMING ) {
  287. // Build and install an atomic version of the same method.
  288. register_method(
  289. app, methodName, symbolName, notes, argc, opts | OSRF_METHOD_ATOMIC, user_data );
  290. }
  291. return 0;
  292. }
  293. /**
  294. @brief Register a single method for a specified application.
  295. @param appName Pointer to the application that implements the method.
  296. @param methodName The fully qualified name of the method.
  297. @param symbolName The symbol name (function name) that implements the method.
  298. @param notes Public documentation for this method.
  299. @param argc How many arguments this method expects.
  300. @param options Bit switches setting various options.
  301. @param user_data Opaque pointer to be passed to the dynamically called function.
  302. */
  303. static void register_method( osrfApplication* app, const char* methodName,
  304. const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
  305. if( !app || ! methodName ) return;
  306. // Build a method and add it to the list of methods
  307. osrfMethod* method = build_method(
  308. methodName, symbolName, notes, argc, options, user_data );
  309. osrfHashSet( app->methods, method, method->name );
  310. }
  311. /**
  312. @brief Allocate and populate an osrfMethod.
  313. @param methodName Name of the method.
  314. @param symbolName Name of the function that implements the method.
  315. @param notes Remarks documenting the method.
  316. @param argc Minimum number of arguments to the method.
  317. @param options Bit switches setting various options.
  318. @param user_data An opaque pointer to be passed in the method context.
  319. @return Pointer to the newly allocated osrfMethod.
  320. If OSRF_METHOD_ATOMIC is set, append ".atomic" to the method name.
  321. */
  322. static osrfMethod* build_method( const char* methodName, const char* symbolName,
  323. const char* notes, int argc, int options, void* user_data ) {
  324. osrfMethod* method = safe_malloc(sizeof(osrfMethod));
  325. if( !methodName )
  326. methodName = ""; // should never happen
  327. if( options & OSRF_METHOD_ATOMIC ) {
  328. // Append ".atomic" to the name.
  329. char mb[ strlen( methodName ) + 8 ];
  330. sprintf( mb, "%s.atomic", methodName );
  331. method->name = strdup( mb );
  332. } else {
  333. method->name = strdup(methodName);
  334. }
  335. if(symbolName)
  336. method->symbol = strdup(symbolName);
  337. else
  338. method->symbol = NULL;
  339. if(notes)
  340. method->notes = strdup(notes);
  341. else
  342. method->notes = NULL;
  343. method->argc = argc;
  344. method->options = options;
  345. if(user_data)
  346. method->userData = user_data;
  347. method->bufsize = OSRF_MSG_BUFFER_SIZE;
  348. return method;
  349. }
  350. /**
  351. @brief Set the effective output buffer size for a given method.
  352. @param appName Name of the application.
  353. @param methodName Name of the method.
  354. @param bufsize Desired size of the output buffer, in bytes.
  355. @return Zero if successful, or -1 if the specified method cannot be found.
  356. A smaller buffer size may result in a lower latency for the first response, since we don't
  357. wait for as many messages to accumulate before flushing the output buffer. On the other
  358. hand a larger buffer size may result in higher throughput due to lower network overhead.
  359. Since the buffer size is not an absolute limit, it may be set to zero, in which case each
  360. output transport message will contain no more than one RESULT message.
  361. This function has no effect on atomic methods, because all responses are sent in a single
  362. message anyway. Likewise it has no effect on a method that returns only a single response.
  363. */
  364. int osrfMethodSetBufferSize( const char* appName, const char* methodName, size_t bufsize ) {
  365. osrfMethod* method = _osrfAppFindMethod( appName, methodName );
  366. if( method ) {
  367. osrfLogInfo( OSRF_LOG_MARK,
  368. "Setting outbuf buffer size to %lu for method %s of application %s",
  369. (unsigned long) bufsize, methodName, appName );
  370. method->bufsize = bufsize;
  371. return 0;
  372. } else {
  373. osrfLogWarning( OSRF_LOG_MARK,
  374. "Unable to set outbuf buffer size to %lu for method %s of application %s",
  375. (unsigned long) bufsize, methodName, appName );
  376. return -1;
  377. }
  378. }
  379. /**
  380. @brief Register all of the system methods for this application.
  381. @param app Pointer to the application.
  382. A client can call these methods the same way it calls application-specific methods,
  383. but they are implemented by functions here in this module, not by functions in the
  384. shared object.
  385. */
  386. static void register_system_methods( osrfApplication* app ) {
  387. if( !app ) return;
  388. register_method(
  389. app, OSRF_SYSMETHOD_INTROSPECT, NULL,
  390. "Return a list of methods whose names have the same initial "
  391. "substring as that of the provided method name PARAMS( methodNameSubstring )",
  392. 1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
  393. NULL );
  394. register_method(
  395. app, OSRF_SYSMETHOD_INTROSPECT, NULL,
  396. "Return a list of methods whose names have the same initial "
  397. "substring as that of the provided method name PARAMS( methodNameSubstring )",
  398. 1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
  399. NULL );
  400. register_method(
  401. app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
  402. "Returns a complete list of methods. PARAMS()",
  403. 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
  404. NULL );
  405. register_method(
  406. app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
  407. "Returns a complete list of methods. PARAMS()",
  408. 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
  409. NULL );
  410. register_method(
  411. app, OSRF_SYSMETHOD_ECHO, NULL,
  412. "Echos all data sent to the server back to the client. PARAMS([a, b, ...])",
  413. 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
  414. NULL );
  415. register_method(
  416. app, OSRF_SYSMETHOD_ECHO, NULL,
  417. "Echos all data sent to the server back to the client. PARAMS([a, b, ...])",
  418. 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
  419. NULL );
  420. }
  421. /**
  422. @brief Look up an application by name in the application registry.
  423. @param name The name of the application.
  424. @return Pointer to the corresponding osrfApplication if found, or NULL if not.
  425. */
  426. static inline osrfApplication* _osrfAppFindApplication( const char* name ) {
  427. return (osrfApplication*) osrfHashGet(_osrfAppHash, name);
  428. }
  429. /**
  430. @brief Look up a method by name for a given application.
  431. @param app Pointer to the osrfApplication that owns the method.
  432. @param methodName Name of the method to find.
  433. @return Pointer to the corresponding osrfMethod if found, or NULL if not.
  434. */
  435. static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName ) {
  436. if( !app ) return NULL;
  437. return (osrfMethod*) osrfHashGet( app->methods, methodName );
  438. }
  439. /**
  440. @brief Look up a method by name for an application with a given name.
  441. @param appName Name of the osrfApplication.
  442. @param methodName Name of the method to find.
  443. @return Pointer to the corresponding osrfMethod if found, or NULL if not.
  444. */
  445. osrfMethod* _osrfAppFindMethod( const char* appName, const char* methodName ) {
  446. if( !appName ) return NULL;
  447. return osrfAppFindMethod( _osrfAppFindApplication(appName), methodName );
  448. }
  449. /**
  450. @brief Call the function that implements a specified method.
  451. @param appName Name of the application.
  452. @param methodName Name of the method.
  453. @param ses Pointer to the current application session.
  454. @param reqId The request id of the request invoking the method.
  455. @param params Pointer to a jsonObject encoding the parameters to the method.
  456. @return Zero if successful, or -1 upon failure.
  457. If we can't find a function corresponding to the method, or if we call it and it returns
  458. a negative return code, send a STATUS message to the client to report an exception.
  459. A return code of -1 means that the @a appName, @a methodName, or @a ses parameter was NULL.
  460. */
  461. int osrfAppRunMethod( const char* appName, const char* methodName,
  462. osrfAppSession* ses, int reqId, jsonObject* params ) {
  463. if( !(appName && methodName && ses) ) return -1;
  464. // Find the application, and then find the method for it
  465. osrfApplication* app = _osrfAppFindApplication(appName);
  466. if( !app )
  467. return osrfAppRequestRespondException( ses,
  468. reqId, "Application not found: %s", appName );
  469. osrfMethod* method = osrfAppFindMethod( app, methodName );
  470. if( !method )
  471. return osrfAppRequestRespondException( ses, reqId,
  472. "Method [%s] not found for service %s", methodName, appName );
  473. #ifdef OSRF_STRICT_PARAMS
  474. if( method->argc > 0 ) {
  475. // Make sure that the client has passed at least the minimum number of arguments.
  476. if(!params || params->type != JSON_ARRAY || params->size < method->argc )
  477. return osrfAppRequestRespondException( ses, reqId,
  478. "Not enough params for method %s / service %s", methodName, appName );
  479. }
  480. #endif
  481. // Build an osrfMethodContext, which we will pass by pointer to the function.
  482. osrfMethodContext context;
  483. context.session = ses;
  484. context.method = method;
  485. context.params = params;
  486. context.request = reqId;
  487. context.responses = NULL;
  488. int retcode = 0;
  489. if( method->options & OSRF_METHOD_SYSTEM ) {
  490. retcode = _osrfAppRunSystemMethod(&context);
  491. } else {
  492. // Function pointer through which we will call the function dynamically
  493. int (*meth) (osrfMethodContext*);
  494. // Open the function that implements the method
  495. meth = dlsym(app->handle, method->symbol);
  496. const char* error = dlerror();
  497. if( error != NULL ) {
  498. return osrfAppRequestRespondException( ses, reqId,
  499. "Unable to execute method [%s] for service %s", methodName, appName );
  500. }
  501. // Run it
  502. retcode = meth( &context );
  503. }
  504. if(retcode < 0)
  505. return osrfAppRequestRespondException(
  506. ses, reqId, "An unknown server error occurred" );
  507. retcode = _osrfAppPostProcess( &context, retcode );
  508. if( context.responses )
  509. jsonObjectFree( context.responses );
  510. return retcode;
  511. }
  512. /**
  513. @brief Either send or enqueue a response to a client.
  514. @param ctx Pointer to the current method context.
  515. @param data Pointer to the response, in the form of a jsonObject.
  516. @return Zero if successful, or -1 upon error. The only recognized errors are if either
  517. the @a context pointer or its method pointer is NULL.
  518. For an atomic method, add a copy of the response data to a cache within the method
  519. context, to be sent later. Otherwise, send a RESULT message to the client, with the
  520. results in @a data.
  521. Note that, for an atomic method, this function is equivalent to osrfAppRespondComplete():
  522. we send the STATUS message after the method returns, and not before.
  523. */
  524. int osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data ) {
  525. return _osrfAppRespond( ctx, data, 0 );
  526. }
  527. /**
  528. @brief Either send or enqueue a response to a client, with a completion notice.
  529. @param context Pointer to the current method context.
  530. @param data Pointer to the response, in the form of a jsonObject.
  531. @return Zero if successful, or -1 upon error. The only recognized errors are if either
  532. the @a context pointer or its method pointer is NULL.
  533. For an atomic method, add a copy of the response data to a cache within the method
  534. context, to be sent later. Otherwise, send a RESULT message to the client, with the
  535. results in @a data. Also send a STATUS message to indicate that the response is complete.
  536. Note that, for an atomic method, this function is equivalent to osrfAppRespond(): we
  537. send the STATUS message after the method returns, and not before.
  538. */
  539. int osrfAppRespondComplete( osrfMethodContext* context, const jsonObject* data ) {
  540. return _osrfAppRespond( context, data, 1 );
  541. }
  542. /**
  543. @brief Send any response messages that have accumulated in the output buffer.
  544. @param ses Pointer to the current application session.
  545. @param outbuf Pointer to the output buffer.
  546. @return Zero if successful, or -1 if not.
  547. Used only by servers to respond to clients.
  548. */
  549. static int flush_responses( osrfAppSession* ses, growing_buffer* outbuf ) {
  550. // Collect any inbound traffic on the socket(s). This doesn't accomplish anything for the
  551. // immediate task at hand, but it may help to keep TCP from getting clogged in some cases.
  552. osrf_app_session_queue_wait( ses, 0, NULL );
  553. int rc = 0;
  554. if( buffer_length( outbuf ) > 0 ) { // If there's anything to send...
  555. buffer_add_char( outbuf, ']' ); // Close the JSON array
  556. if( osrfSendTransportPayload( ses, OSRF_BUFFER_C_STR( ses->outbuf ))) {
  557. osrfLogError( OSRF_LOG_MARK, "Unable to flush response buffer" );
  558. rc = -1;
  559. }
  560. }
  561. buffer_reset( ses->outbuf );
  562. return rc;
  563. }
  564. /**
  565. @brief Add a message to an output buffer.
  566. @param outbuf Pointer to the output buffer.
  567. @param msg Pointer to the message to be added, in the form of a JSON string.
  568. Since the output buffer is in the form of a JSON array, prepend a left bracket to the
  569. first message, and a comma to subsequent ones.
  570. Used only by servers to respond to clients.
  571. */
  572. static inline void append_msg( growing_buffer* outbuf, const char* msg ) {
  573. if( outbuf && msg ) {
  574. char prefix = buffer_length( outbuf ) > 0 ? ',' : '[';
  575. buffer_add_char( outbuf, prefix );
  576. buffer_add( outbuf, msg );
  577. }
  578. }
  579. /**
  580. @brief Either send or enqueue a response to a client, optionally with a completion notice.
  581. @param ctx Pointer to the method context.
  582. @param data Pointer to the response, in the form of a jsonObject.
  583. @param complete Boolean: if true, we will accompany the RESULT message with a STATUS
  584. message indicating that the response is complete.
  585. @return Zero if successful, or -1 upon error.
  586. For an atomic method, add a copy of the response data to a cache within the method
  587. context, to be sent later. In this case the @a complete parameter has no effect,
  588. because we'll send the STATUS message later when we send the cached results.
  589. If the method is not atomic, translate the message into JSON and append it to a buffer,
  590. flushing the buffer as needed to avoid overflow. If @a complete is true, append
  591. a STATUS message (as JSON) to the buffer and flush the buffer.
  592. */
  593. static int _osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data, int complete ) {
  594. if(!(ctx && ctx->method)) return -1;
  595. if( ctx->method->options & OSRF_METHOD_ATOMIC ) {
  596. osrfLogDebug( OSRF_LOG_MARK,
  597. "Adding responses to stash for atomic method %s", ctx->method->name );
  598. // If we don't already have one, create a JSON_ARRAY to serve as a cache.
  599. if( ctx->responses == NULL )
  600. ctx->responses = jsonNewObjectType( JSON_ARRAY );
  601. // Add a copy of the data object to the cache.
  602. if ( data != NULL )
  603. jsonObjectPush( ctx->responses, jsonObjectClone(data) );
  604. } else {
  605. osrfLogDebug( OSRF_LOG_MARK,
  606. "Adding responses to stash for method %s", ctx->method->name );
  607. if( data ) {
  608. // If you want to flush the intput buffers for every output message,
  609. // this is the place to do it.
  610. //osrf_app_session_queue_wait( ctx->session, 0, NULL );
  611. // Create an OSRF message
  612. osrfMessage* msg = osrf_message_init( RESULT, ctx->request, 1 );
  613. osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
  614. osrf_message_set_result( msg, data );
  615. // Serialize the OSRF message into JSON text
  616. char* json = jsonObjectToJSON( osrfMessageToJSON( msg ));
  617. osrfMessageFree( msg );
  618. // If the new message would overflow the buffer, flush the output buffer first
  619. int len_so_far = buffer_length( ctx->session->outbuf );
  620. if( len_so_far && (strlen( json ) + len_so_far + 3 >= ctx->method->bufsize )) {
  621. if( flush_responses( ctx->session, ctx->session->outbuf ))
  622. return -1;
  623. }
  624. // Append the JSON text to the output buffer
  625. append_msg( ctx->session->outbuf, json );
  626. free( json );
  627. }
  628. if(complete) {
  629. // Create a STATUS message
  630. osrfMessage* status_msg = osrf_message_init( STATUS, ctx->request, 1 );
  631. osrf_message_set_status_info( status_msg, "osrfConnectStatus", "Request Complete",
  632. OSRF_STATUS_COMPLETE );
  633. // Serialize the STATUS message into JSON text
  634. char* json = jsonObjectToJSON( osrfMessageToJSON( status_msg ));
  635. osrfMessageFree( status_msg );
  636. // Add the STATUS message to the output buffer.
  637. // It's short, so don't worry about avoiding overflow.
  638. append_msg( ctx->session->outbuf, json );
  639. free( json );
  640. // Flush the output buffer, sending any accumulated messages.
  641. if( flush_responses( ctx->session, ctx->session->outbuf ))
  642. return -1;
  643. }
  644. }
  645. return 0;
  646. }
  647. /**
  648. @brief Finish up the processing of a request.
  649. @param ctx Pointer to the method context.
  650. @param retcode The return code from the method's function.
  651. @return 0 if successfull, or -1 upon error.
  652. For an atomic method: send whatever responses we have been saving up, together with a
  653. STATUS message to say that we're finished.
  654. For a non-atomic method: if the return code from the method is greater than zero, just
  655. send the STATUS message. If the return code is zero, do nothing; the method presumably
  656. sent the STATUS message on its own.
  657. */
  658. static int _osrfAppPostProcess( osrfMethodContext* ctx, int retcode ) {
  659. if(!(ctx && ctx->method)) return -1;
  660. osrfLogDebug( OSRF_LOG_MARK, "Postprocessing method %s with retcode %d",
  661. ctx->method->name, retcode );
  662. if(ctx->responses) {
  663. // We have cached atomic responses to return, collected in a JSON ARRAY (we
  664. // haven't sent any responses yet). Now send them all at once, followed by
  665. // a STATUS message to say that we're finished.
  666. osrfAppRequestRespondComplete( ctx->session, ctx->request, ctx->responses );
  667. } else {
  668. // We have no cached atomic responses to return, but we may have some
  669. // non-atomic messages waiting in the buffer.
  670. if( retcode > 0 )
  671. // Send a STATUS message to say that we're finished, and to force a
  672. // final flush of the buffer.
  673. osrfAppRespondComplete( ctx, NULL );
  674. }
  675. return 0;
  676. }
  677. /**
  678. @brief Send a STATUS message to the client, notifying it of an error.
  679. @param ses Pointer to the current application session.
  680. @param request Request ID of the request.
  681. @param msg A printf-style format string defining an explanatory message to be sent to
  682. the client. Subsequent parameters, if any, will be formatted and inserted into the
  683. resulting output string.
  684. @return -1 if the @a ses parameter is NULL; otherwise zero.
  685. */
  686. int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char* msg, ... ) {
  687. if(!ses) return -1;
  688. if(!msg) msg = "";
  689. VA_LIST_TO_STRING(msg);
  690. osrfLogWarning( OSRF_LOG_MARK, "Returning method exception with message: %s", VA_BUF );
  691. osrfAppSessionStatus( ses, OSRF_STATUS_NOTFOUND, "osrfMethodException", request, VA_BUF );
  692. return 0;
  693. }
  694. /**
  695. @brief Introspect a specified method.
  696. @param ctx Pointer to the method context.
  697. @param method Pointer to the osrfMethod for the specified method.
  698. @param resp Pointer to the jsonObject into which method information will be placed.
  699. Treating the @a resp object as a JSON_HASH, insert entries for various bits of information
  700. about the specified method.
  701. */
  702. static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
  703. jsonObject* resp ) {
  704. if(!(ctx && resp)) return;
  705. jsonObjectSetKey(resp, "api_name", jsonNewObject(method->name));
  706. jsonObjectSetKey(resp, "method", jsonNewObject(method->symbol));
  707. jsonObjectSetKey(resp, "service", jsonNewObject(ctx->session->remote_service));
  708. jsonObjectSetKey(resp, "notes", jsonNewObject(method->notes));
  709. jsonObjectSetKey(resp, "argc", jsonNewNumberObject(method->argc));
  710. jsonObjectSetKey(resp, "sysmethod",
  711. jsonNewNumberObject( (method->options & OSRF_METHOD_SYSTEM) ? 1 : 0 ));
  712. jsonObjectSetKey(resp, "atomic",
  713. jsonNewNumberObject( (method->options & OSRF_METHOD_ATOMIC) ? 1 : 0 ));
  714. jsonObjectSetKey(resp, "cachable",
  715. jsonNewNumberObject( (method->options & OSRF_METHOD_CACHABLE) ? 1 : 0 ));
  716. }
  717. /**
  718. @brief Run the requested system method.
  719. @param ctx The method context.
  720. @return Zero if the method is run successfully; -1 if the method was not run; 1 if the
  721. method was run and the application code now needs to send a 'request complete' message.
  722. A system method is a well known method implemented here for all servers. Instead of
  723. looking in the shared object, branch on the method name and call the corresponding
  724. function.
  725. */
  726. static int _osrfAppRunSystemMethod(osrfMethodContext* ctx) {
  727. if( osrfMethodVerifyContext( ctx ) < 0 ) {
  728. osrfLogError( OSRF_LOG_MARK, "_osrfAppRunSystemMethod: Received invalid method context" );
  729. return -1;
  730. }
  731. if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL ) ||
  732. !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC )) {
  733. return osrfAppIntrospectAll(ctx);
  734. }
  735. if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT ) ||
  736. !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ATOMIC )) {
  737. return osrfAppIntrospect(ctx);
  738. }
  739. if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO ) ||
  740. !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO_ATOMIC )) {
  741. return osrfAppEcho(ctx);
  742. }
  743. osrfAppRequestRespondException( ctx->session,
  744. ctx->request, "System method implementation not found");
  745. return 0;
  746. }
  747. /**
  748. @brief Run the introspect method for a specified method or group of methods.
  749. @param ctx Pointer to the method context.
  750. @return 1 if successful, or if no search target is specified as a parameter; -1 if unable
  751. to find a pointer to the application.
  752. Traverse the list of methods, and report on each one whose name starts with the specified
  753. search target. In effect, the search target ends with an implicit wild card.
  754. */
  755. static int osrfAppIntrospect( osrfMethodContext* ctx ) {
  756. // Get the name of the method to introspect
  757. const char* methodSubstring = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
  758. if( !methodSubstring )
  759. return 1; /* respond with no methods */
  760. // Get a pointer to the application
  761. osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
  762. if( !app )
  763. return -1; // Oops, no application...
  764. int len = 0;
  765. osrfHashIterator* itr = osrfNewHashIterator(app->methods);
  766. osrfMethod* method;
  767. while( (method = osrfHashIteratorNext(itr)) ) {
  768. if( (len = strlen(methodSubstring)) <= strlen(method->name) ) {
  769. if( !strncmp( method->name, methodSubstring, len) ) {
  770. jsonObject* resp = jsonNewObject(NULL);
  771. _osrfAppSetIntrospectMethod( ctx, method, resp );
  772. osrfAppRespond(ctx, resp);
  773. jsonObjectFree(resp);
  774. }
  775. }
  776. }
  777. osrfHashIteratorFree(itr);
  778. return 1;
  779. }
  780. /**
  781. @brief Run the implement_all method.
  782. @param ctx Pointer to the method context.
  783. @return 1 if successful, or -1 if unable to find a pointer to the application.
  784. Report on all of the methods of the application.
  785. */
  786. static int osrfAppIntrospectAll( osrfMethodContext* ctx ) {
  787. osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
  788. if(app) {
  789. osrfHashIterator* itr = osrfNewHashIterator(app->methods);
  790. osrfMethod* method;
  791. while( (method = osrfHashIteratorNext(itr)) ) {
  792. jsonObject* resp = jsonNewObject(NULL);
  793. _osrfAppSetIntrospectMethod( ctx, method, resp );
  794. osrfAppRespond(ctx, resp);
  795. jsonObjectFree(resp);
  796. }
  797. osrfHashIteratorFree(itr);
  798. return 1;
  799. } else
  800. return -1;
  801. }
  802. /**
  803. @brief Run the echo method.
  804. @param ctx Pointer to the method context.
  805. @return -1 if the method context is invalid or corrupted; otherwise 1.
  806. Send the client a copy of each parameter.
  807. */
  808. static int osrfAppEcho( osrfMethodContext* ctx ) {
  809. if( osrfMethodVerifyContext( ctx ) < 0 ) {
  810. osrfLogError( OSRF_LOG_MARK, "osrfAppEcho: Received invalid method context" );
  811. return -1;
  812. }
  813. int i;
  814. for( i = 0; i < ctx->params->size; i++ ) {
  815. const jsonObject* str = jsonObjectGetIndex(ctx->params,i);
  816. osrfAppRespond(ctx, str);
  817. }
  818. return 1;
  819. }
  820. /**
  821. @brief Perform a series of sanity tests on an osrfMethodContext.
  822. @param ctx Pointer to the osrfMethodContext to be checked.
  823. @return Zero if the osrfMethodContext passes all tests, or -1 if it doesn't.
  824. */
  825. int osrfMethodVerifyContext( osrfMethodContext* ctx )
  826. {
  827. if( !ctx ) {
  828. osrfLogError( OSRF_LOG_MARK, "Context is NULL in app request" );
  829. return -1;
  830. }
  831. if( !ctx->session ) {
  832. osrfLogError( OSRF_LOG_MARK, "Session is NULL in app request" );
  833. return -1;
  834. }
  835. if( !ctx->method )
  836. {
  837. osrfLogError( OSRF_LOG_MARK, "Method is NULL in app request" );
  838. return -1;
  839. }
  840. if( ctx->method->argc ) {
  841. if( !ctx->params ) {
  842. osrfLogError( OSRF_LOG_MARK,
  843. "Params is NULL in app request %s", ctx->method->name );
  844. return -1;
  845. }
  846. if( ctx->params->type != JSON_ARRAY ) {
  847. osrfLogError( OSRF_LOG_MARK,
  848. "'params' is not a JSON array for method %s", ctx->method->name );
  849. return -1;
  850. }
  851. }
  852. if( !ctx->method->name ) {
  853. osrfLogError( OSRF_LOG_MARK, "Method name is NULL" );
  854. return -1;
  855. }
  856. // Log the call, with the method and parameters
  857. char* params_str = jsonObjectToJSON( ctx->params );
  858. if( params_str ) {
  859. // params_str will at minimum be "[]"
  860. params_str[strlen(params_str) - 1] = '\0'; // drop the trailing ']'
  861. osrfLogInfo( OSRF_LOG_MARK, "CALL: %s %s %s",
  862. ctx->session->remote_service, ctx->method->name, params_str + 1);
  863. free( params_str );
  864. }
  865. return 0;
  866. }
  867. /**
  868. @brief Free an osrfMethod.
  869. @param name Name of the method (not used).
  870. @param p Void pointer pointing to the osrfMethod.
  871. This function is designed to be installed as a callback for an osrfHash (hence the
  872. unused @a name parameter and the void pointer).
  873. */
  874. static void osrfMethodFree( char* name, void* p ) {
  875. osrfMethod* method = p;
  876. if( method ) {
  877. free( method->name );
  878. free( method->symbol );
  879. free( method->notes );
  880. free( method );
  881. }
  882. }
  883. /**
  884. @brief Free an osrfApplication
  885. @param name Name of the application (not used).
  886. @param p Void pointer pointing to the osrfApplication.
  887. This function is designed to be installed as a callback for an osrfHash (hence the
  888. unused @a name parameter and the void pointer).
  889. */
  890. static void osrfAppFree( char* name, void* p ) {
  891. osrfApplication* app = p;
  892. if( app ) {
  893. dlclose( app->handle );
  894. osrfHashFree( app->methods );
  895. free( app );
  896. }
  897. }