1 #include <opensrf/osrf_application.h>
4 @file osrf_application.c
5 @brief Load and manage shared object libraries.
7 Maintain a registry of applications, using an osrfHash keyed on application name,
9 For each application, load a shared object library so that we can call
10 application-specific functions dynamically. In order to map method names to the
11 corresponding functions (i.e. symbol names in the library), maintain a registry of
12 methods, using an osrfHash keyed on method name.
15 // The following macro is commented out because it ia no longer used.
17 // Used internally to make sure the method description provided is OK
19 #define OSRF_METHOD_VERIFY_DESCRIPTION(app, d) \
23 osrfLogError( OSRF_LOG_MARK, "No method name provided in description" ), \
27 osrfLogError( OSRF_LOG_MARK, "No method symbol provided in description" ), \
39 @name Well known method names
40 @brief These methods are automatically implemented for every application.
43 #define OSRF_SYSMETHOD_INTROSPECT "opensrf.system.method"
44 #define OSRF_SYSMETHOD_INTROSPECT_ATOMIC "opensrf.system.method.atomic"
45 #define OSRF_SYSMETHOD_INTROSPECT_ALL "opensrf.system.method.all"
46 #define OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC "opensrf.system.method.all.atomic"
47 #define OSRF_SYSMETHOD_ECHO "opensrf.system.echo"
48 #define OSRF_SYSMETHOD_ECHO_ATOMIC "opensrf.system.echo.atomic"
53 @brief Macros that get OR'd together to form method options.
55 These options are in addition to the ones stipulated by the caller of
56 osrfRegisterMethod(), and are not externally visible.
60 @brief Marks a method as a system method.
62 System methods are implemented by generic functions, called via static linkage. They
63 are not loaded or executed from shared objects.
65 #define OSRF_METHOD_SYSTEM 1
67 @brief Combines all responses into a single RESULT message.
69 For a @em non-atomic method, the server returns each response to the client in a
70 separate RESULT message. It sends a STATUS message at the end to signify the end of the
73 For an @em atomic method, the server buffers all responses until the method returns,
74 and then sends them all at once in a single RESULT message (followed by a STATUS message).
75 Each individual response is encoded as an entry in a JSON array. This buffering is
76 transparent to the function that implements the method.
78 Atomic methods incur less networking overhead than non-atomic methods, at the risk of
79 creating excessively large RESULT messages. The HTTP gateway requires the atomic versions
80 of streaming methods because of the stateless nature of the HTTP protocol.
82 If OSRF_METHOD_STREAMING is set for a method, the application generates both an atomic
83 and a non-atomic method, whose names are identical except that the atomic one carries a
86 #define OSRF_METHOD_ATOMIC 4
89 #define OSRF_MSG_BUFFER_SIZE 10240
92 @brief Represent an Application.
95 void* handle; /**< Handle to the shared object library. */
96 osrfHash* methods; /**< Registry of method names. */
97 void (*onExit) (void); /**< Exit handler for the application. */
100 static void register_method( osrfApplication* app, const char* methodName,
101 const char* symbolName, const char* notes, int argc, int options, void * user_data );
102 static osrfMethod* build_method( const char* methodName, const char* symbolName,
103 const char* notes, int argc, int options, void* );
104 static void osrfAppSetOnExit(osrfApplication* app, const char* appName);
105 static void register_system_methods( osrfApplication* app );
106 static inline osrfApplication* _osrfAppFindApplication( const char* name );
107 static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName );
108 static int _osrfAppRespond( osrfMethodContext* context, const jsonObject* data, int complete );
109 static int _osrfAppPostProcess( osrfMethodContext* context, int retcode );
110 static int _osrfAppRunSystemMethod(osrfMethodContext* context);
111 static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
113 static int osrfAppIntrospect( osrfMethodContext* ctx );
114 static int osrfAppIntrospectAll( osrfMethodContext* ctx );
115 static int osrfAppEcho( osrfMethodContext* ctx );
116 static void osrfMethodFree( char* name, void* p );
117 static void osrfAppFree( char* name, void* p );
120 @brief Registry of applications.
122 The key of the hash is the application name, and the associated data is an osrfApplication.
124 static osrfHash* _osrfAppHash = NULL;
127 @brief Register an application.
128 @param appName Name of the application.
129 @param soFile Name of the shared object file to be loaded for this application.
130 @return Zero if successful, or -1 upon error.
132 Open the shared object file and call its osrfAppInitialize() function, if it has one.
133 Register the standard system methods for it. Arrange for the application name to
134 appear in subsequent log messages.
136 int osrfAppRegisterApplication( const char* appName, const char* soFile ) {
137 if( !appName || ! soFile ) return -1;
140 osrfLogSetAppname( appName );
142 if( !_osrfAppHash ) {
143 _osrfAppHash = osrfNewHash();
144 osrfHashSetCallback( _osrfAppHash, osrfAppFree );
147 osrfLogInfo( OSRF_LOG_MARK, "Registering application %s with file %s", appName, soFile );
149 // Open the shared object.
150 void* handle = dlopen( soFile, RTLD_NOW );
152 const char* msg = dlerror();
153 osrfLogWarning( OSRF_LOG_MARK, "Failed to dlopen library file %s: %s", soFile, msg );
157 // Construct the osrfApplication.
158 osrfApplication* app = safe_malloc(sizeof(osrfApplication));
159 app->handle = handle;
160 app->methods = osrfNewHash();
161 osrfHashSetCallback( app->methods, osrfMethodFree );
164 // Add the newly-constructed app to the list.
165 osrfHashSet( _osrfAppHash, app, appName );
167 // Try to run the initialize method. Typically it will register one or more
168 // methods of the application.
170 *(void **) (&init) = dlsym( handle, "osrfAppInitialize" );
172 if( (error = dlerror()) != NULL ) {
173 osrfLogWarning( OSRF_LOG_MARK,
174 "! Unable to locate method symbol [osrfAppInitialize] for app %s: %s",
181 if( (ret = (*init)()) ) {
182 osrfLogWarning( OSRF_LOG_MARK, "Application %s returned non-zero value from "
183 "'osrfAppInitialize', not registering...", appName );
184 osrfHashRemove( _osrfAppHash, appName );
189 register_system_methods( app );
190 osrfLogInfo( OSRF_LOG_MARK, "Application %s registered successfully", appName );
191 osrfAppSetOnExit( app, appName );
197 @brief Save a pointer to the application's exit function.
198 @param app Pointer to the osrfApplication.
199 @param appName Application name (used only for log messages).
201 Look in the shared object for a symbol named "osrfAppChildExit". If you find one, save
202 it as a pointer to the application's exit function. If present, this function will be
203 called when a server's child process (a so-called "drone") is shutting down.
205 static void osrfAppSetOnExit(osrfApplication* app, const char* appName) {
206 if(!(app && appName)) return;
208 /* see if we can run the initialize method */
210 void (*onExit) (void);
211 *(void **) (&onExit) = dlsym(app->handle, "osrfAppChildExit");
213 if( (error = dlerror()) != NULL ) {
214 osrfLogDebug(OSRF_LOG_MARK, "No exit handler defined for %s", appName);
218 osrfLogInfo(OSRF_LOG_MARK, "registering exit handler for %s", appName);
219 app->onExit = (*onExit);
223 @brief Run the application-specific child initialization function for a given application.
224 @param appname Name of the application.
225 @return Zero if successful, or if the application has no child initialization function; -1
226 if the application is not registered, or if the function returns non-zero.
228 The child initialization function must be named "osrfAppChildInit" within the shared
229 object library. It initializes a drone process of a server.
231 int osrfAppRunChildInit(const char* appname) {
232 osrfApplication* app = _osrfAppFindApplication(appname);
237 int (*childInit) (void);
239 *(void**) (&childInit) = dlsym(app->handle, "osrfAppChildInit");
241 if( (error = dlerror()) != NULL ) {
242 osrfLogInfo( OSRF_LOG_MARK, "No child init defined for app %s : %s", appname, error);
246 if( (ret = (*childInit)()) ) {
247 osrfLogError(OSRF_LOG_MARK, "App %s child init failed", appname);
251 osrfLogInfo(OSRF_LOG_MARK, "%s child init succeeded", appname);
256 @brief Call the exit handler for every application that has one.
258 Normally a server's child process (a so-called "drone") calls this function just before
261 void osrfAppRunExitCode( void ) {
262 osrfHashIterator* itr = osrfNewHashIterator(_osrfAppHash);
263 osrfApplication* app;
264 while( (app = osrfHashIteratorNext(itr)) ) {
266 osrfLogInfo(OSRF_LOG_MARK, "Running onExit handler for app %s",
267 osrfHashIteratorKey(itr) );
271 osrfHashIteratorFree(itr);
275 @brief Register a method for a specified application.
277 @param appName Name of the application that implements the method.
278 @param methodName The fully qualified name of the method.
279 @param symbolName The symbol name (function name) that implements the method.
280 @param notes Public documentation for this method.
281 @param argc The minimum number of arguments for the function.
282 @param options Bit switches setting various options.
283 @return Zero on success, or -1 on error.
285 Registering a method enables us to call the right function when a client requests a
288 The @a options parameter is zero or more of the following macros, OR'd together:
290 - OSRF_METHOD_STREAMING method may return more than one response
291 - OSRF_METHOD_CACHABLE cache results in memcache
293 If the OSRF_METHOD_STREAMING bit is set, also register an ".atomic" version of the method.
295 int osrfAppRegisterMethod( const char* appName, const char* methodName,
296 const char* symbolName, const char* notes, int argc, int options ) {
298 return osrfAppRegisterExtendedMethod(
310 @brief Register a method for a specified application.
312 @param appName Name of the application that implements the method.
313 @param methodName The fully qualified name of the method.
314 @param symbolName The symbol name (function name) that implements the method.
315 @param notes Public documentation for this method.
316 @param argc How many arguments this method expects.
317 @param options Bit switches setting various options.
318 @param user_data Opaque pointer to be passed to the dynamically called function.
319 @return Zero if successful, or -1 upon error.
321 This function is identical to osrfAppRegisterMethod(), except that it also installs
322 a method-specific opaque pointer. When we call the corresponding function at
323 run time, this pointer will be available to the function via the method context.
325 int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName,
326 const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
328 if( !appName || ! methodName ) return -1;
330 osrfApplication* app = _osrfAppFindApplication(appName);
332 osrfLogWarning( OSRF_LOG_MARK, "Unable to locate application %s", appName );
336 osrfLogDebug( OSRF_LOG_MARK, "Registering method %s for app %s", methodName, appName );
338 // Extract the only valid option bits, and ignore the rest.
339 int opts = options & ( OSRF_METHOD_STREAMING | OSRF_METHOD_CACHABLE );
341 // Build and install a non-atomic method.
343 app, methodName, symbolName, notes, argc, opts, user_data );
345 if( opts & OSRF_METHOD_STREAMING ) {
346 // Build and install an atomic version of the same method.
348 app, methodName, symbolName, notes, argc, opts | OSRF_METHOD_ATOMIC, user_data );
355 @brief Register a single method for a specified application.
357 @param appName Pointer to the application that implements the method.
358 @param methodName The fully qualified name of the method.
359 @param symbolName The symbol name (function name) that implements the method.
360 @param notes Public documentation for this method.
361 @param argc How many arguments this method expects.
362 @param options Bit switches setting various options.
363 @param user_data Opaque pointer to be passed to the dynamically called function.
365 static void register_method( osrfApplication* app, const char* methodName,
366 const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
368 if( !app || ! methodName ) return;
370 // Build a method and add it to the list of methods
371 osrfMethod* method = build_method(
372 methodName, symbolName, notes, argc, options, user_data );
373 osrfHashSet( app->methods, method, method->name );
377 @brief Allocate and populate an osrfMethod.
378 @param methodName Name of the method.
379 @param symbolName Name of the function that implements the method.
380 @param notes Remarks documenting the method.
381 @param argc Minimum number of arguments to the method.
382 @param options Bit switches setting various options.
383 @param user_data An opaque pointer to be passed in the method context.
384 @return Pointer to the newly allocated osrfMethod.
386 If OSRF_METHOD_ATOMIC is set, append ".atomic" to the method name.
388 static osrfMethod* build_method( const char* methodName, const char* symbolName,
389 const char* notes, int argc, int options, void* user_data ) {
391 osrfMethod* method = safe_malloc(sizeof(osrfMethod));
394 methodName = ""; // should never happen
396 if( options & OSRF_METHOD_ATOMIC ) {
397 // Append ".atomic" to the name.
398 char mb[ strlen( methodName ) + 8 ];
399 sprintf( mb, "%s.atomic", methodName );
400 method->name = strdup( mb );
402 method->name = strdup(methodName);
406 method->symbol = strdup(symbolName);
408 method->symbol = NULL;
411 method->notes = strdup(notes);
413 method->notes = NULL;
416 method->options = options;
419 method->userData = user_data;
425 @brief Register all of the system methods for this application.
426 @param app Pointer to the application.
428 A client can call these methods the same way it calls application-specific methods,
429 but they are implemented by functions here in this module, not by functions in the
432 static void register_system_methods( osrfApplication* app ) {
437 app, OSRF_SYSMETHOD_INTROSPECT, NULL,
438 "Return a list of methods whose names have the same initial "
439 "substring as that of the provided method name PARAMS( methodNameSubstring )",
440 1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
444 app, OSRF_SYSMETHOD_INTROSPECT, NULL,
445 "Return a list of methods whose names have the same initial "
446 "substring as that of the provided method name PARAMS( methodNameSubstring )",
447 1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
451 app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
452 "Returns a complete list of methods. PARAMS()",
453 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
457 app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
458 "Returns a complete list of methods. PARAMS()",
459 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
463 app, OSRF_SYSMETHOD_ECHO, NULL,
464 "Echos all data sent to the server back to the client. PARAMS([a, b, ...])",
465 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
469 app, OSRF_SYSMETHOD_ECHO, NULL,
470 "Echos all data sent to the server back to the client. PARAMS([a, b, ...])",
471 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
476 @brief Look up an application by name in the application registry.
477 @param name The name of the application.
478 @return Pointer to the corresponding osrfApplication if found, or NULL if not.
480 static inline osrfApplication* _osrfAppFindApplication( const char* name ) {
481 return (osrfApplication*) osrfHashGet(_osrfAppHash, name);
485 @brief Look up a method by name for a given application.
486 @param app Pointer to the osrfApplication that owns the method.
487 @param methodName Name of the method to find.
488 @return Pointer to the corresponding osrfMethod if found, or NULL if not.
490 static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName ) {
491 if( !app ) return NULL;
492 return (osrfMethod*) osrfHashGet( app->methods, methodName );
496 @brief Look up a method by name for an application with a given name.
497 @param appName Name of the osrfApplication.
498 @param methodName Name of the method to find.
499 @return Pointer to the corresponding osrfMethod if found, or NULL if not.
501 osrfMethod* _osrfAppFindMethod( const char* appName, const char* methodName ) {
502 if( !appName ) return NULL;
503 return osrfAppFindMethod( _osrfAppFindApplication(appName), methodName );
507 @brief Call the function that implements a specified method.
508 @param appName Name of the application.
509 @param methodName Name of the method.
510 @param ses Pointer to the current application session.
511 @param reqId The request id of the request invoking the method.
512 @param params Pointer to a jsonObject encoding the parameters to the method.
513 @return Zero if successful, or -1 upon failure.
515 If we can't find a function corresponding to the method, or if we call it and it returns
516 a negative return code, send a STATUS message to the client to report an exception.
518 A return code of -1 means that the @a appName, @a methodName, or @a ses parameter was NULL.
520 int osrfAppRunMethod( const char* appName, const char* methodName,
521 osrfAppSession* ses, int reqId, jsonObject* params ) {
523 if( !(appName && methodName && ses) ) return -1;
525 // Find the application, and then find the method for it
526 osrfApplication* app = _osrfAppFindApplication(appName);
528 return osrfAppRequestRespondException( ses,
529 reqId, "Application not found: %s", appName );
531 osrfMethod* method = osrfAppFindMethod( app, methodName );
533 return osrfAppRequestRespondException( ses, reqId,
534 "Method [%s] not found for service %s", methodName, appName );
536 #ifdef OSRF_STRICT_PARAMS
537 if( method->argc > 0 ) {
538 // Make sure that the client has passed at least the minimum number of arguments.
539 if(!params || params->type != JSON_ARRAY || params->size < method->argc )
540 return osrfAppRequestRespondException( ses, reqId,
541 "Not enough params for method %s / service %s", methodName, appName );
545 // Build an osrfMethodContext, which we will pass by pointer to the function.
546 osrfMethodContext context;
548 context.session = ses;
549 context.method = method;
550 context.params = params;
551 context.request = reqId;
552 context.responses = NULL;
556 if( method->options & OSRF_METHOD_SYSTEM ) {
557 retcode = _osrfAppRunSystemMethod(&context);
561 // Function pointer through which we will call the function dynamically
562 int (*meth) (osrfMethodContext*);
564 // Open the function that implements the method
565 meth = dlsym(app->handle, method->symbol);
567 const char* error = dlerror();
568 if( error != NULL ) {
569 return osrfAppRequestRespondException( ses, reqId,
570 "Unable to execute method [%s] for service %s", methodName, appName );
574 retcode = meth( &context );
578 return osrfAppRequestRespondException(
579 ses, reqId, "An unknown server error occurred" );
581 retcode = _osrfAppPostProcess( &context, retcode );
583 if( context.responses )
584 jsonObjectFree( context.responses );
589 @brief Either send or enqueue a response to a client.
590 @param ctx Pointer to the current method context.
591 @param data Pointer to the response, in the form of a jsonObject.
592 @return Zero if successful, or -1 upon error. The only recognized errors are if either
593 the @a context pointer or its method pointer is NULL.
595 For an atomic method, add a copy of the response data to a cache within the method
596 context, to be sent later. Otherwise, send a RESULT message to the client, with the
599 Note that, for an atomic method, this function is equivalent to osrfAppRespondComplete():
600 we send the STATUS message after the method returns, and not before.
602 int osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data ) {
603 return _osrfAppRespond( ctx, data, 0 );
607 @brief Either send or enqueue a response to a client, with a completion notice.
608 @param context Pointer to the current method context.
609 @param data Pointer to the response, in the form of a jsonObject.
610 @return Zero if successful, or -1 upon error. The only recognized errors are if either
611 the @a context pointer or its method pointer is NULL.
613 For an atomic method, add a copy of the response data to a cache within the method
614 context, to be sent later. Otherwise, send a RESULT message to the client, with the
615 results in @a data. Also send a STATUS message to indicate that the response is complete.
617 Note that, for an atomic method, this function is equivalent to osrfAppRespond(): we
618 send the STATUS message after the method returns, and not before.
620 int osrfAppRespondComplete( osrfMethodContext* context, const jsonObject* data ) {
621 return _osrfAppRespond( context, data, 1 );
625 @brief Send any response messages that have accumulated in the output buffer.
626 @param ses Pointer to the current application session.
627 @param outbuf Pointer to the output buffer.
628 @return Zero if successful, or -1 if not.
630 Used only by servers to respond to clients.
632 static int flush_responses( osrfAppSession* ses, growing_buffer* outbuf ) {
634 // Collect any inbound traffic on the socket(s). This doesn't accomplish anything for the
635 // immediate task at hand, but it may help to keep TCP from getting clogged in some cases.
636 osrf_app_session_queue_wait( ses, 0, NULL );
639 if( buffer_length( outbuf ) > 0 ) { // If there's anything to send...
640 buffer_add_char( outbuf, ']' ); // Close the JSON array
641 if( osrfSendTransportPayload( ses, OSRF_BUFFER_C_STR( ses->outbuf ))) {
642 osrfLogError( OSRF_LOG_MARK, "Unable to flush response buffer" );
646 buffer_reset( ses->outbuf );
651 @brief Add a message to an output buffer.
652 @param outbuf Pointer to the output buffer.
653 @param msg Pointer to the message to be added, in the form of a JSON string.
655 Since the output buffer is in the form of a JSON array, prepend a left bracket to the
656 first message, and a comma to subsequent ones.
658 Used only by servers to respond to clients.
660 static inline void append_msg( growing_buffer* outbuf, const char* msg ) {
661 if( outbuf && msg ) {
662 char prefix = buffer_length( outbuf ) > 0 ? ',' : '[';
663 buffer_add_char( outbuf, prefix );
664 buffer_add( outbuf, msg );
669 @brief Either send or enqueue a response to a client, optionally with a completion notice.
670 @param ctx Pointer to the method context.
671 @param data Pointer to the response, in the form of a jsonObject.
672 @param complete Boolean: if true, we will accompany the RESULT message with a STATUS
673 message indicating that the response is complete.
674 @return Zero if successful, or -1 upon error.
676 For an atomic method, add a copy of the response data to a cache within the method
677 context, to be sent later. In this case the @a complete parameter has no effect,
678 because we'll send the STATUS message later when we send the cached results.
680 If the method is not atomic, translate the message into JSON and append it to a buffer,
681 flushing the buffer as needed to avoid overflow. If @a complete is true, append
682 a STATUS message (as JSON) to the buffer and flush the buffer.
684 static int _osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data, int complete ) {
685 if(!(ctx && ctx->method)) return -1;
687 if( ctx->method->options & OSRF_METHOD_ATOMIC ) {
688 osrfLogDebug( OSRF_LOG_MARK,
689 "Adding responses to stash for atomic method %s", ctx->method->name );
691 // If we don't already have one, create a JSON_ARRAY to serve as a cache.
692 if( ctx->responses == NULL )
693 ctx->responses = jsonNewObjectType( JSON_ARRAY );
695 // Add a copy of the data object to the cache.
697 jsonObjectPush( ctx->responses, jsonObjectClone(data) );
699 osrfLogDebug( OSRF_LOG_MARK,
700 "Adding responses to stash for method %s", ctx->method->name );
703 // If you want to flush the intput buffers for every output message,
704 // this is the place to do it.
705 //osrf_app_session_queue_wait( ctx->session, 0, NULL );
707 // Create an OSRF message
708 osrfMessage* msg = osrf_message_init( RESULT, ctx->request, 1 );
709 osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
710 osrf_message_set_result( msg, data );
712 // Serialize the OSRF message into JSON text
713 char* json = jsonObjectToJSON( osrfMessageToJSON( msg ));
714 osrfMessageFree( msg );
716 // If the new message would overflow the buffer, flush the output buffer first
717 int len_so_far = buffer_length( ctx->session->outbuf );
718 if( len_so_far && (strlen( json ) + len_so_far >= OSRF_MSG_BUFFER_SIZE - 3) ) {
719 if( flush_responses( ctx->session, ctx->session->outbuf ))
723 // Append the JSON text to the output buffer
724 append_msg( ctx->session->outbuf, json );
729 // Create a STATUS message
730 osrfMessage* status_msg = osrf_message_init( STATUS, ctx->request, 1 );
731 osrf_message_set_status_info( status_msg, "osrfConnectStatus", "Request Complete",
732 OSRF_STATUS_COMPLETE );
734 // Serialize the STATUS message into JSON text
735 char* json = jsonObjectToJSON( osrfMessageToJSON( status_msg ));
736 osrfMessageFree( status_msg );
738 // Add the STATUS message to the output buffer.
739 // It's short, so don't worry about avoiding overflow.
740 append_msg( ctx->session->outbuf, json );
743 // Flush the output buffer, sending any accumulated messages.
744 if( flush_responses( ctx->session, ctx->session->outbuf ))
753 @brief Finish up the processing of a request.
754 @param ctx Pointer to the method context.
755 @param retcode The return code from the method's function.
756 @return 0 if successfull, or -1 upon error.
758 For an atomic method: send whatever responses we have been saving up, together with a
759 STATUS message to say that we're finished.
761 For a non-atomic method: if the return code from the method is greater than zero, just
762 send the STATUS message. If the return code is zero, do nothing; the method presumably
763 sent the STATUS message on its own.
765 static int _osrfAppPostProcess( osrfMethodContext* ctx, int retcode ) {
766 if(!(ctx && ctx->method)) return -1;
768 osrfLogDebug( OSRF_LOG_MARK, "Postprocessing method %s with retcode %d",
769 ctx->method->name, retcode );
772 // We have cached atomic responses to return, collected in a JSON ARRAY (we
773 // haven't sent any responses yet). Now send them all at once, followed by
774 // a STATUS message to say that we're finished.
775 osrfAppRequestRespondComplete( ctx->session, ctx->request, ctx->responses );
778 // We have no cached atomic responses to return, but we may have some
779 // non-atomic messages waiting in the buffer.
781 // Send a STATUS message to say that we're finished, and to force a
782 // final flush of the buffer.
783 osrfAppRespondComplete( ctx, NULL );
790 @brief Send a STATUS message to the client, notifying it of an error.
791 @param ses Pointer to the current application session.
792 @param request Request ID of the request.
793 @param msg A printf-style format string defining an explanatory message to be sent to
794 the client. Subsequent parameters, if any, will be formatted and inserted into the
795 resulting output string.
796 @return -1 if the @a ses parameter is NULL; otherwise zero.
798 int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char* msg, ... ) {
801 VA_LIST_TO_STRING(msg);
802 osrfLogWarning( OSRF_LOG_MARK, "Returning method exception with message: %s", VA_BUF );
803 osrfAppSessionStatus( ses, OSRF_STATUS_NOTFOUND, "osrfMethodException", request, VA_BUF );
808 @brief Introspect a specified method.
809 @param ctx Pointer to the method context.
810 @param method Pointer to the osrfMethod for the specified method.
811 @param resp Pointer to the jsonObject into which method information will be placed.
813 Treating the @a resp object as a JSON_HASH, insert entries for various bits of information
814 about the specified method.
816 static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
818 if(!(ctx && resp)) return;
820 jsonObjectSetKey(resp, "api_name", jsonNewObject(method->name));
821 jsonObjectSetKey(resp, "method", jsonNewObject(method->symbol));
822 jsonObjectSetKey(resp, "service", jsonNewObject(ctx->session->remote_service));
823 jsonObjectSetKey(resp, "notes", jsonNewObject(method->notes));
824 jsonObjectSetKey(resp, "argc", jsonNewNumberObject(method->argc));
826 jsonObjectSetKey(resp, "sysmethod",
827 jsonNewNumberObject( (method->options & OSRF_METHOD_SYSTEM) ? 1 : 0 ));
828 jsonObjectSetKey(resp, "atomic",
829 jsonNewNumberObject( (method->options & OSRF_METHOD_ATOMIC) ? 1 : 0 ));
830 jsonObjectSetKey(resp, "cachable",
831 jsonNewNumberObject( (method->options & OSRF_METHOD_CACHABLE) ? 1 : 0 ));
835 @brief Run the requested system method.
836 @param ctx The method context.
837 @return Zero if the method is run successfully; -1 if the method was not run; 1 if the
838 method was run and the application code now needs to send a 'request complete' message.
840 A system method is a well known method implemented here for all servers. Instead of
841 looking in the shared object, branch on the method name and call the corresponding
844 static int _osrfAppRunSystemMethod(osrfMethodContext* ctx) {
845 if( osrfMethodVerifyContext( ctx ) < 0 ) {
846 osrfLogError( OSRF_LOG_MARK, "_osrfAppRunSystemMethod: Received invalid method context" );
850 if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL ) ||
851 !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC )) {
852 return osrfAppIntrospectAll(ctx);
855 if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT ) ||
856 !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ATOMIC )) {
857 return osrfAppIntrospect(ctx);
860 if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO ) ||
861 !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO_ATOMIC )) {
862 return osrfAppEcho(ctx);
865 osrfAppRequestRespondException( ctx->session,
866 ctx->request, "System method implementation not found");
872 @brief Run the introspect method for a specified method or group of methods.
873 @param ctx Pointer to the method context.
874 @return 1 if successful, or if no search target is specified as a parameter; -1 if unable
875 to find a pointer to the application.
877 Traverse the list of methods, and report on each one whose name starts with the specified
878 search target. In effect, the search target ends with an implicit wild card.
880 static int osrfAppIntrospect( osrfMethodContext* ctx ) {
882 // Get the name of the method to introspect
883 const char* methodSubstring = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
884 if( !methodSubstring )
885 return 1; /* respond with no methods */
887 // Get a pointer to the application
888 osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
890 return -1; // Oops, no application...
893 osrfHashIterator* itr = osrfNewHashIterator(app->methods);
896 while( (method = osrfHashIteratorNext(itr)) ) {
897 if( (len = strlen(methodSubstring)) <= strlen(method->name) ) {
898 if( !strncmp( method->name, methodSubstring, len) ) {
899 jsonObject* resp = jsonNewObject(NULL);
900 _osrfAppSetIntrospectMethod( ctx, method, resp );
901 osrfAppRespond(ctx, resp);
902 jsonObjectFree(resp);
907 osrfHashIteratorFree(itr);
912 @brief Run the implement_all method.
913 @param ctx Pointer to the method context.
914 @return 1 if successful, or -1 if unable to find a pointer to the application.
916 Report on all of the methods of the application.
918 static int osrfAppIntrospectAll( osrfMethodContext* ctx ) {
919 osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
922 osrfHashIterator* itr = osrfNewHashIterator(app->methods);
924 while( (method = osrfHashIteratorNext(itr)) ) {
925 jsonObject* resp = jsonNewObject(NULL);
926 _osrfAppSetIntrospectMethod( ctx, method, resp );
927 osrfAppRespond(ctx, resp);
928 jsonObjectFree(resp);
930 osrfHashIteratorFree(itr);
937 @brief Run the echo method.
938 @param ctx Pointer to the method context.
939 @return -1 if the method context is invalid or corrupted; otherwise 1.
941 Send the client a copy of each parameter.
943 static int osrfAppEcho( osrfMethodContext* ctx ) {
944 if( osrfMethodVerifyContext( ctx ) < 0 ) {
945 osrfLogError( OSRF_LOG_MARK, "osrfAppEcho: Received invalid method context" );
950 for( i = 0; i < ctx->params->size; i++ ) {
951 const jsonObject* str = jsonObjectGetIndex(ctx->params,i);
952 osrfAppRespond(ctx, str);
958 @brief Perform a series of sanity tests on an osrfMethodContext.
959 @param ctx Pointer to the osrfMethodContext to be checked.
960 @return Zero if the osrfMethodContext passes all tests, or -1 if it doesn't.
962 int osrfMethodVerifyContext( osrfMethodContext* ctx )
965 osrfLogError( OSRF_LOG_MARK, "Context is NULL in app request" );
969 if( !ctx->session ) {
970 osrfLogError( OSRF_LOG_MARK, "Session is NULL in app request" );
976 osrfLogError( OSRF_LOG_MARK, "Method is NULL in app request" );
980 if( ctx->method->argc ) {
982 osrfLogError( OSRF_LOG_MARK,
983 "Params is NULL in app request %s", ctx->method->name );
986 if( ctx->params->type != JSON_ARRAY ) {
987 osrfLogError( OSRF_LOG_MARK,
988 "'params' is not a JSON array for method %s", ctx->method->name );
993 if( !ctx->method->name ) {
994 osrfLogError( OSRF_LOG_MARK, "Method name is NULL" );
998 // Log the call, with the method and parameters
999 char* params_str = jsonObjectToJSON( ctx->params );
1001 osrfLogInfo( OSRF_LOG_MARK, "CALL:\t%s %s - %s",
1002 ctx->session->remote_service, ctx->method->name, params_str );
1009 @brief Free an osrfMethod.
1010 @param name Name of the method (not used).
1011 @param p Void pointer pointing to the osrfMethod.
1013 This function is designed to be installed as a callback for an osrfHash (hence the
1014 unused @a name parameter and the void pointer).
1016 static void osrfMethodFree( char* name, void* p ) {
1017 osrfMethod* method = p;
1019 free( method->name );
1020 free( method->symbol );
1021 free( method->notes );
1027 @brief Free an osrfApplication
1028 @param name Name of the application (not used).
1029 @param p Void pointer pointing to the osrfApplication.
1031 This function is designed to be installed as a callback for an osrfHash (hence the
1032 unused @a name parameter and the void pointer).
1034 static void osrfAppFree( char* name, void* p ) {
1035 osrfApplication* app = p;
1037 dlclose( app->handle );
1038 osrfHashFree( app->methods );