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
90 @brief Represent an Application.
93 void* handle; /**< Handle to the shared object library. */
94 osrfHash* methods; /**< Registry of method names. */
95 void (*onExit) (void); /**< Exit handler for the application. */
98 static void register_method( osrfApplication* app, const char* methodName,
99 const char* symbolName, const char* notes, int argc, int options, void * user_data );
100 static osrfMethod* build_method( const char* methodName, const char* symbolName,
101 const char* notes, int argc, int options, void* );
102 static void osrfAppSetOnExit(osrfApplication* app, const char* appName);
103 static void register_system_methods( osrfApplication* app );
104 static inline osrfApplication* _osrfAppFindApplication( const char* name );
105 static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName );
106 static int _osrfAppRespond( osrfMethodContext* context, const jsonObject* data, int complete );
107 static int _osrfAppPostProcess( osrfMethodContext* context, int retcode );
108 static int _osrfAppRunSystemMethod(osrfMethodContext* context);
109 static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
111 static int osrfAppIntrospect( osrfMethodContext* ctx );
112 static int osrfAppIntrospectAll( osrfMethodContext* ctx );
113 static int osrfAppEcho( osrfMethodContext* ctx );
114 static void osrfMethodFree( char* name, void* p );
115 static void osrfAppFree( char* name, void* p );
118 @brief Registry of applications.
120 The key of the hash is the application name, and the associated data is an osrfApplication.
122 static osrfHash* _osrfAppHash = NULL;
125 @brief Register an application.
126 @param appName Name of the application.
127 @param soFile Name of the shared object file to be loaded for this application.
128 @return Zero if successful, or -1 upon error.
130 Open the shared object file and call its osrfAppInitialize() function, if it has one.
131 Register the standard system methods for it. Arrange for the application name to
132 appear in subsequent log messages.
134 int osrfAppRegisterApplication( const char* appName, const char* soFile ) {
135 if( !appName || ! soFile ) return -1;
138 osrfLogSetAppname( appName );
140 if( !_osrfAppHash ) {
141 _osrfAppHash = osrfNewHash();
142 osrfHashSetCallback( _osrfAppHash, osrfAppFree );
145 osrfLogInfo( OSRF_LOG_MARK, "Registering application %s with file %s", appName, soFile );
147 // Open the shared object.
148 void* handle = dlopen( soFile, RTLD_NOW );
150 const char* msg = dlerror();
151 osrfLogError( OSRF_LOG_MARK, "Failed to dlopen library file %s: %s", soFile, msg );
155 // Construct the osrfApplication.
156 osrfApplication* app = safe_malloc(sizeof(osrfApplication));
157 app->handle = handle;
158 app->methods = osrfNewHash();
159 osrfHashSetCallback( app->methods, osrfMethodFree );
162 // Add the newly-constructed app to the list.
163 osrfHashSet( _osrfAppHash, app, appName );
165 // Try to run the initialize method. Typically it will register one or more
166 // methods of the application.
168 *(void **) (&init) = dlsym( handle, "osrfAppInitialize" );
170 if( (error = dlerror()) != NULL ) {
171 osrfLogWarning( OSRF_LOG_MARK,
172 "! Unable to locate method symbol [osrfAppInitialize] for app %s: %s",
179 if( (ret = (*init)()) ) {
180 osrfLogWarning( OSRF_LOG_MARK, "Application %s returned non-zero value from "
181 "'osrfAppInitialize', not registering...", appName );
182 osrfHashRemove( _osrfAppHash, appName );
187 register_system_methods( app );
188 osrfLogInfo( OSRF_LOG_MARK, "Application %s registered successfully", appName );
189 osrfAppSetOnExit( app, appName );
195 @brief Save a pointer to the application's exit function.
196 @param app Pointer to the osrfApplication.
197 @param appName Application name (used only for log messages).
199 Look in the shared object for a symbol named "osrfAppChildExit". If you find one, save
200 it as a pointer to the application's exit function. If present, this function will be
201 called when a server's child process (a so-called "drone") is shutting down.
203 static void osrfAppSetOnExit(osrfApplication* app, const char* appName) {
204 if(!(app && appName)) return;
206 /* see if we can run the initialize method */
208 void (*onExit) (void);
209 *(void **) (&onExit) = dlsym(app->handle, "osrfAppChildExit");
211 if( (error = dlerror()) != NULL ) {
212 osrfLogDebug(OSRF_LOG_MARK, "No exit handler defined for %s", appName);
216 osrfLogInfo(OSRF_LOG_MARK, "registering exit handler for %s", appName);
217 app->onExit = (*onExit);
221 @brief Run the application-specific child initialization function for a given application.
222 @param appname Name of the application.
223 @return Zero if successful, or if the application has no child initialization function; -1
224 if the application is not registered, or if the function returns non-zero.
226 The child initialization function must be named "osrfAppChildInit" within the shared
227 object library. It initializes a drone process of a server.
229 int osrfAppRunChildInit(const char* appname) {
230 osrfApplication* app = _osrfAppFindApplication(appname);
235 int (*childInit) (void);
237 *(void**) (&childInit) = dlsym(app->handle, "osrfAppChildInit");
239 if( (error = dlerror()) != NULL ) {
240 osrfLogInfo( OSRF_LOG_MARK, "No child init defined for app %s : %s", appname, error);
244 if( (ret = (*childInit)()) ) {
245 osrfLogError(OSRF_LOG_MARK, "App %s child init failed", appname);
249 osrfLogInfo(OSRF_LOG_MARK, "%s child init succeeded", appname);
254 @brief Call the exit handler for every application that has one.
256 Normally a server's child process (a so-called "drone") calls this function just before
259 void osrfAppRunExitCode( void ) {
260 osrfHashIterator* itr = osrfNewHashIterator(_osrfAppHash);
261 osrfApplication* app;
262 while( (app = osrfHashIteratorNext(itr)) ) {
264 osrfLogInfo(OSRF_LOG_MARK, "Running onExit handler for app %s",
265 osrfHashIteratorKey(itr) );
269 osrfHashIteratorFree(itr);
273 @brief Register a method for a specified application.
275 @param appName Name of the application that implements the method.
276 @param methodName The fully qualified name of the method.
277 @param symbolName The symbol name (function name) that implements the method.
278 @param notes Public documentation for this method.
279 @param argc The minimum number of arguments for the function.
280 @param options Bit switches setting various options.
281 @return Zero on success, or -1 on error.
283 Registering a method enables us to call the right function when a client requests a
286 The @a options parameter is zero or more of the following macros, OR'd together:
288 - OSRF_METHOD_STREAMING method may return more than one response
289 - OSRF_METHOD_CACHABLE cache results in memcache
291 If the OSRF_METHOD_STREAMING bit is set, also register an ".atomic" version of the method.
293 int osrfAppRegisterMethod( const char* appName, const char* methodName,
294 const char* symbolName, const char* notes, int argc, int options ) {
296 return osrfAppRegisterExtendedMethod(
308 @brief Register an extended method for a specified application.
310 @param appName Name of the application that implements the method.
311 @param methodName The fully qualified name of the method.
312 @param symbolName The symbol name (function name) that implements the method.
313 @param notes Public documentation for this method.
314 @param argc How many arguments this method expects.
315 @param options Bit switches setting various options.
316 @param user_data Opaque pointer to be passed to the dynamically called function.
317 @return Zero if successful, or -1 upon error.
319 This function is identical to osrfAppRegisterMethod(), except that it also installs
320 a method-specific opaque pointer. When we call the corresponding function at
321 run time, this pointer will be available to the function via the method context.
323 int osrfAppRegisterExtendedMethod( const char* appName, const char* methodName,
324 const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
326 if( !appName || ! methodName ) return -1;
328 osrfApplication* app = _osrfAppFindApplication(appName);
330 osrfLogWarning( OSRF_LOG_MARK, "Unable to locate application %s", appName );
334 osrfLogDebug( OSRF_LOG_MARK, "Registering method %s for app %s", methodName, appName );
336 // Extract the only valid option bits, and ignore the rest.
337 int opts = options & ( OSRF_METHOD_STREAMING | OSRF_METHOD_CACHABLE );
339 // Build and install a non-atomic method.
341 app, methodName, symbolName, notes, argc, opts, user_data );
343 if( opts & OSRF_METHOD_STREAMING ) {
344 // Build and install an atomic version of the same method.
346 app, methodName, symbolName, notes, argc, opts | OSRF_METHOD_ATOMIC, user_data );
353 @brief Register a single method for a specified application.
355 @param appName Pointer to the application that implements the method.
356 @param methodName The fully qualified name of the method.
357 @param symbolName The symbol name (function name) that implements the method.
358 @param notes Public documentation for this method.
359 @param argc How many arguments this method expects.
360 @param options Bit switches setting various options.
361 @param user_data Opaque pointer to be passed to the dynamically called function.
363 static void register_method( osrfApplication* app, const char* methodName,
364 const char* symbolName, const char* notes, int argc, int options, void * user_data ) {
366 if( !app || ! methodName ) return;
368 // Build a method and add it to the list of methods
369 osrfMethod* method = build_method(
370 methodName, symbolName, notes, argc, options, user_data );
371 osrfHashSet( app->methods, method, method->name );
375 @brief Allocate and populate an osrfMethod.
376 @param methodName Name of the method.
377 @param symbolName Name of the function that implements the method.
378 @param notes Remarks documenting the method.
379 @param argc Minimum number of arguments to the method.
380 @param options Bit switches setting various options.
381 @param user_data An opaque pointer to be passed in the method context.
382 @return Pointer to the newly allocated osrfMethod.
384 If OSRF_METHOD_ATOMIC is set, append ".atomic" to the method name.
386 static osrfMethod* build_method( const char* methodName, const char* symbolName,
387 const char* notes, int argc, int options, void* user_data ) {
389 osrfMethod* method = safe_malloc(sizeof(osrfMethod));
392 methodName = ""; // should never happen
394 if( options & OSRF_METHOD_ATOMIC ) {
395 // Append ".atomic" to the name.
396 char mb[ strlen( methodName ) + 8 ];
397 sprintf( mb, "%s.atomic", methodName );
398 method->name = strdup( mb );
400 method->name = strdup(methodName);
404 method->symbol = strdup(symbolName);
406 method->symbol = NULL;
409 method->notes = strdup(notes);
411 method->notes = NULL;
414 method->options = options;
417 method->userData = user_data;
419 method->max_bundle_size = OSRF_MSG_BUNDLE_SIZE;
420 method->max_chunk_size = OSRF_MSG_CHUNK_SIZE;
425 @brief Set the effective output buffer size for a given method.
426 @param appName Name of the application.
427 @param methodName Name of the method.
428 @param max_bundle_size Desired size of the output buffer, in bytes.
429 @return Zero if successful, or -1 if the specified method cannot be found.
431 A smaller buffer size may result in a lower latency for the first response, since we don't
432 wait for as many messages to accumulate before flushing the output buffer. On the other
433 hand a larger buffer size may result in higher throughput due to lower network overhead.
435 Since the buffer size is not an absolute limit, it may be set to zero, in which case each
436 output transport message will contain no more than one RESULT message.
438 This function has no effect on atomic methods, because all responses are sent in a single
439 message anyway. Likewise it has no effect on a method that returns only a single response.
441 int osrfMethodSetBundleSize( const char* appName, const char* methodName, size_t max_bundle_size ) {
442 osrfMethod* method = _osrfAppFindMethod( appName, methodName );
444 osrfLogInfo( OSRF_LOG_MARK,
445 "Setting outbuf buffer size to %lu for method %s of application %s",
446 (unsigned long) max_bundle_size, methodName, appName );
447 method->max_bundle_size = max_bundle_size;
450 osrfLogWarning( OSRF_LOG_MARK,
451 "Unable to set outbuf buffer size to %lu for method %s of application %s",
452 (unsigned long) max_bundle_size, methodName, appName );
458 @brief Register all of the system methods for this application.
459 @param app Pointer to the application.
461 A client can call these methods the same way it calls application-specific methods,
462 but they are implemented by functions here in this module, not by functions in the
465 static void register_system_methods( osrfApplication* app ) {
470 app, OSRF_SYSMETHOD_INTROSPECT, NULL,
471 "Return a list of methods whose names have the same initial "
472 "substring as that of the provided method name PARAMS( methodNameSubstring )",
473 1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
477 app, OSRF_SYSMETHOD_INTROSPECT, NULL,
478 "Return a list of methods whose names have the same initial "
479 "substring as that of the provided method name PARAMS( methodNameSubstring )",
480 1, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
484 app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
485 "Returns a complete list of methods. PARAMS()",
486 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
490 app, OSRF_SYSMETHOD_INTROSPECT_ALL, NULL,
491 "Returns a complete list of methods. PARAMS()",
492 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
496 app, OSRF_SYSMETHOD_ECHO, NULL,
497 "Echos all data sent to the server back to the client. PARAMS([a, b, ...])",
498 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING,
502 app, OSRF_SYSMETHOD_ECHO, NULL,
503 "Echos all data sent to the server back to the client. PARAMS([a, b, ...])",
504 0, OSRF_METHOD_SYSTEM | OSRF_METHOD_STREAMING | OSRF_METHOD_ATOMIC,
509 @brief Look up an application by name in the application registry.
510 @param name The name of the application.
511 @return Pointer to the corresponding osrfApplication if found, or NULL if not.
513 static inline osrfApplication* _osrfAppFindApplication( const char* name ) {
514 return (osrfApplication*) osrfHashGet(_osrfAppHash, name);
518 @brief Look up a method by name for a given application.
519 @param app Pointer to the osrfApplication that owns the method.
520 @param methodName Name of the method to find.
521 @return Pointer to the corresponding osrfMethod if found, or NULL if not.
523 static inline osrfMethod* osrfAppFindMethod( osrfApplication* app, const char* methodName ) {
524 if( !app ) return NULL;
525 return (osrfMethod*) osrfHashGet( app->methods, methodName );
529 @brief Look up a method by name for an application with a given name.
530 @param appName Name of the osrfApplication.
531 @param methodName Name of the method to find.
532 @return Pointer to the corresponding osrfMethod if found, or NULL if not.
534 osrfMethod* _osrfAppFindMethod( const char* appName, const char* methodName ) {
535 if( !appName ) return NULL;
536 return osrfAppFindMethod( _osrfAppFindApplication(appName), methodName );
540 @brief Call the function that implements a specified method.
541 @param appName Name of the application.
542 @param methodName Name of the method.
543 @param ses Pointer to the current application session.
544 @param reqId The request id of the request invoking the method.
545 @param params Pointer to a jsonObject encoding the parameters to the method.
546 @return Zero if successful, or -1 upon failure.
548 If we can't find a function corresponding to the method, or if we call it and it returns
549 a negative return code, send a STATUS message to the client to report an exception.
551 A return code of -1 means that the @a appName, @a methodName, or @a ses parameter was NULL.
553 int osrfAppRunMethod( const char* appName, const char* methodName,
554 osrfAppSession* ses, int reqId, jsonObject* params ) {
556 if( !(appName && methodName && ses) ) return -1;
558 // Find the application, and then find the method for it
559 osrfApplication* app = _osrfAppFindApplication(appName);
561 return osrfAppRequestRespondException( ses,
562 reqId, "Application not found: %s", appName );
564 osrfMethod* method = osrfAppFindMethod( app, methodName );
566 return osrfAppRequestRespondException( ses, reqId,
567 "Method [%s] not found for service %s", methodName, appName );
569 #ifdef OSRF_STRICT_PARAMS
570 if( method->argc > 0 ) {
571 // Make sure that the client has passed at least the minimum number of arguments.
572 if(!params || params->type != JSON_ARRAY || params->size < method->argc )
573 return osrfAppRequestRespondException( ses, reqId,
574 "Not enough params for method %s / service %s", methodName, appName );
578 // Build an osrfMethodContext, which we will pass by pointer to the function.
579 osrfMethodContext context;
581 context.session = ses;
582 context.method = method;
583 context.params = params;
584 context.request = reqId;
585 context.responses = NULL;
589 if( method->options & OSRF_METHOD_SYSTEM ) {
590 retcode = _osrfAppRunSystemMethod(&context);
594 // Function pointer through which we will call the function dynamically
595 int (*meth) (osrfMethodContext*);
597 // Open the function that implements the method
598 meth = dlsym(app->handle, method->symbol);
600 const char* error = dlerror();
601 if( error != NULL ) {
602 return osrfAppRequestRespondException( ses, reqId,
603 "Unable to execute method [%s] for service %s", methodName, appName );
607 retcode = meth( &context );
611 return osrfAppRequestRespondException(
612 ses, reqId, "An unknown server error occurred" );
614 retcode = _osrfAppPostProcess( &context, retcode );
616 if( context.responses )
617 jsonObjectFree( context.responses );
622 @brief Either send or enqueue a response to a client.
623 @param ctx Pointer to the current method context.
624 @param data Pointer to the response, in the form of a jsonObject.
625 @return Zero if successful, or -1 upon error. The only recognized errors are if either
626 the @a context pointer or its method pointer is NULL.
628 For an atomic method, add a copy of the response data to a cache within the method
629 context, to be sent later. Otherwise, send a RESULT message to the client, with the
632 Note that, for an atomic method, this function is equivalent to osrfAppRespondComplete():
633 we send the STATUS message after the method returns, and not before.
635 int osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data ) {
636 return _osrfAppRespond( ctx, data, 0 );
640 @brief Either send or enqueue a response to a client, with a completion notice.
641 @param context Pointer to the current method context.
642 @param data Pointer to the response, in the form of a jsonObject.
643 @return Zero if successful, or -1 upon error. The only recognized errors are if either
644 the @a context pointer or its method pointer is NULL.
646 For an atomic method, add a copy of the response data to a cache within the method
647 context, to be sent later. Otherwise, send a RESULT message to the client, with the
648 results in @a data. Also send a STATUS message to indicate that the response is complete.
650 Note that, for an atomic method, this function is equivalent to osrfAppRespond(): we
651 send the STATUS message after the method returns, and not before.
653 int osrfAppRespondComplete( osrfMethodContext* context, const jsonObject* data ) {
654 return _osrfAppRespond( context, data, 1 );
658 @brief Send any response messages that have accumulated in the output buffer.
659 @param ses Pointer to the current application session.
660 @param outbuf Pointer to the output buffer.
661 @return Zero if successful, or -1 if not.
663 Used only by servers to respond to clients.
665 static int flush_responses( osrfAppSession* ses, growing_buffer* outbuf ) {
667 // Collect any inbound traffic on the socket(s). This doesn't accomplish anything for the
668 // immediate task at hand, but it may help to keep TCP from getting clogged in some cases.
669 osrf_app_session_queue_wait( ses, 0, NULL );
672 if( buffer_length( outbuf ) > 0 ) { // If there's anything to send...
673 buffer_add_char( outbuf, ']' ); // Close the JSON array
674 if( osrfSendTransportPayload( ses, OSRF_BUFFER_C_STR( ses->outbuf ))) {
675 osrfLogError( OSRF_LOG_MARK, "Unable to flush response buffer" );
679 buffer_reset( ses->outbuf );
684 @brief Add a message to an output buffer.
685 @param outbuf Pointer to the output buffer.
686 @param msg Pointer to the message to be added, in the form of a JSON string.
688 Since the output buffer is in the form of a JSON array, prepend a left bracket to the
689 first message, and a comma to subsequent ones.
691 Used only by servers to respond to clients.
693 static inline void append_msg( growing_buffer* outbuf, const char* msg ) {
694 if( outbuf && msg ) {
695 char prefix = buffer_length( outbuf ) > 0 ? ',' : '[';
696 buffer_add_char( outbuf, prefix );
697 buffer_add( outbuf, msg );
702 @brief Either send or enqueue a response to a client, optionally with a completion notice.
703 @param ctx Pointer to the method context.
704 @param data Pointer to the response, in the form of a jsonObject.
705 @param complete Boolean: if true, we will accompany the RESULT message with a STATUS
706 message indicating that the response is complete.
707 @return Zero if successful, or -1 upon error.
709 For an atomic method, add a copy of the response data to a cache within the method
710 context, to be sent later. In this case the @a complete parameter has no effect,
711 because we'll send the STATUS message later when we send the cached results.
713 If the method is not atomic, translate the message into JSON and append it to a buffer,
714 flushing the buffer as needed to avoid overflow. If @a complete is true, append
715 a STATUS message (as JSON) to the buffer and flush the buffer.
717 static int _osrfAppRespond( osrfMethodContext* ctx, const jsonObject* data, int complete ) {
718 if(!(ctx && ctx->method)) return -1;
720 if( ctx->method->options & OSRF_METHOD_ATOMIC ) {
721 osrfLogDebug( OSRF_LOG_MARK,
722 "Adding responses to stash for atomic method %s", ctx->method->name );
724 // If we don't already have one, create a JSON_ARRAY to serve as a cache.
725 if( ctx->responses == NULL )
726 ctx->responses = jsonNewObjectType( JSON_ARRAY );
728 // Add a copy of the data object to the cache.
730 jsonObjectPush( ctx->responses, jsonObjectClone(data) );
732 osrfLogDebug( OSRF_LOG_MARK,
733 "Adding responses to stash for method %s", ctx->method->name );
736 char* data_str = jsonObjectToJSON(data); // free me (below)
737 size_t raw_size = strlen(data_str);
738 size_t extra_size = osrfXmlEscapingLength(data_str);
739 size_t data_size = raw_size + extra_size;
740 size_t chunk_size = ctx->method->max_chunk_size;
742 if (data_size > chunk_size) // calculate an escape-scaled chunk size
743 chunk_size = ((double)raw_size / (double)data_size) * (double)chunk_size;
745 if (chunk_size > 0 && chunk_size < raw_size) {
746 // chunking -- response message exceeds max message size.
747 // break it up into chunks for partial delivery
749 osrfSendChunkedResult(ctx->session, ctx->request,
750 data_str, raw_size, chunk_size);
754 // bundling -- message body (may be) too small for single
755 // delivery. prepare message for bundling.
757 // Create an OSRF message
758 osrfMessage* msg = osrf_message_init( RESULT, ctx->request, 1 );
759 osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
760 osrf_message_set_result( msg, data );
762 // Serialize the OSRF message into JSON text
763 char* json = jsonObjectToJSON( osrfMessageToJSON( msg ));
764 osrfMessageFree( msg );
766 // If the new message would overflow the buffer, flush the output buffer first
767 int len_so_far = buffer_length( ctx->session->outbuf );
768 if( len_so_far && (strlen( json ) + len_so_far + 3 >= ctx->method->max_bundle_size )) {
769 if( flush_responses( ctx->session, ctx->session->outbuf ))
773 // Append the JSON text to the output buffer
774 append_msg( ctx->session->outbuf, json );
782 // Create a STATUS message
783 osrfMessage* status_msg = osrf_message_init( STATUS, ctx->request, 1 );
784 osrf_message_set_status_info( status_msg, "osrfConnectStatus", "Request Complete",
785 OSRF_STATUS_COMPLETE );
787 // Serialize the STATUS message into JSON text
788 char* json = jsonObjectToJSON( osrfMessageToJSON( status_msg ));
789 osrfMessageFree( status_msg );
791 // Add the STATUS message to the output buffer.
792 // It's short, so don't worry about avoiding overflow.
793 append_msg( ctx->session->outbuf, json );
796 // Flush the output buffer, sending any accumulated messages.
797 if( flush_responses( ctx->session, ctx->session->outbuf ))
806 @brief Finish up the processing of a request.
807 @param ctx Pointer to the method context.
808 @param retcode The return code from the method's function.
809 @return 0 if successfull, or -1 upon error.
811 For an atomic method: send whatever responses we have been saving up, together with a
812 STATUS message to say that we're finished.
814 For a non-atomic method: if the return code from the method is greater than zero, just
815 send the STATUS message. If the return code is zero, do nothing; the method presumably
816 sent the STATUS message on its own.
818 static int _osrfAppPostProcess( osrfMethodContext* ctx, int retcode ) {
819 if(!(ctx && ctx->method)) return -1;
821 osrfLogDebug( OSRF_LOG_MARK, "Postprocessing method %s with retcode %d",
822 ctx->method->name, retcode );
825 // We have cached atomic responses to return, collected in a JSON ARRAY (we
826 // haven't sent any responses yet). Now send them all at once, followed by
827 // a STATUS message to say that we're finished.
828 osrfAppRequestRespondComplete( ctx->session, ctx->request, ctx->responses );
831 // We have no cached atomic responses to return, but we may have some
832 // non-atomic messages waiting in the buffer.
834 // Send a STATUS message to say that we're finished, and to force a
835 // final flush of the buffer.
836 osrfAppRespondComplete( ctx, NULL );
843 @brief Send a STATUS message to the client, notifying it of an error.
844 @param ses Pointer to the current application session.
845 @param request Request ID of the request.
846 @param msg A printf-style format string defining an explanatory message to be sent to
847 the client. Subsequent parameters, if any, will be formatted and inserted into the
848 resulting output string.
849 @return -1 if the @a ses parameter is NULL; otherwise zero.
851 int osrfAppRequestRespondException( osrfAppSession* ses, int request, const char* msg, ... ) {
854 VA_LIST_TO_STRING(msg);
855 osrfLogWarning( OSRF_LOG_MARK, "Returning method exception with message: %s", VA_BUF );
856 osrfAppSessionStatus( ses, OSRF_STATUS_NOTFOUND, "osrfMethodException", request, VA_BUF );
861 @brief Introspect a specified method.
862 @param ctx Pointer to the method context.
863 @param method Pointer to the osrfMethod for the specified method.
864 @param resp Pointer to the jsonObject into which method information will be placed.
866 Treating the @a resp object as a JSON_HASH, insert entries for various bits of information
867 about the specified method.
869 static void _osrfAppSetIntrospectMethod( osrfMethodContext* ctx, const osrfMethod* method,
871 if(!(ctx && resp)) return;
873 jsonObjectSetKey(resp, "api_name", jsonNewObject(method->name));
874 jsonObjectSetKey(resp, "method", jsonNewObject(method->symbol));
875 jsonObjectSetKey(resp, "service", jsonNewObject(ctx->session->remote_service));
876 jsonObjectSetKey(resp, "notes", jsonNewObject(method->notes));
877 jsonObjectSetKey(resp, "argc", jsonNewNumberObject(method->argc));
879 jsonObjectSetKey(resp, "sysmethod",
880 jsonNewNumberObject( (method->options & OSRF_METHOD_SYSTEM) ? 1 : 0 ));
881 jsonObjectSetKey(resp, "atomic",
882 jsonNewNumberObject( (method->options & OSRF_METHOD_ATOMIC) ? 1 : 0 ));
883 jsonObjectSetKey(resp, "cachable",
884 jsonNewNumberObject( (method->options & OSRF_METHOD_CACHABLE) ? 1 : 0 ));
888 @brief Run the requested system method.
889 @param ctx The method context.
890 @return Zero if the method is run successfully; -1 if the method was not run; 1 if the
891 method was run and the application code now needs to send a 'request complete' message.
893 A system method is a well known method implemented here for all servers. Instead of
894 looking in the shared object, branch on the method name and call the corresponding
897 static int _osrfAppRunSystemMethod(osrfMethodContext* ctx) {
898 if( osrfMethodVerifyContext( ctx ) < 0 ) {
899 osrfLogError( OSRF_LOG_MARK, "_osrfAppRunSystemMethod: Received invalid method context" );
903 if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL ) ||
904 !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ALL_ATOMIC )) {
905 return osrfAppIntrospectAll(ctx);
908 if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT ) ||
909 !strcmp(ctx->method->name, OSRF_SYSMETHOD_INTROSPECT_ATOMIC )) {
910 return osrfAppIntrospect(ctx);
913 if( !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO ) ||
914 !strcmp(ctx->method->name, OSRF_SYSMETHOD_ECHO_ATOMIC )) {
915 return osrfAppEcho(ctx);
918 osrfAppRequestRespondException( ctx->session,
919 ctx->request, "System method implementation not found");
925 @brief Run the introspect method for a specified method or group of methods.
926 @param ctx Pointer to the method context.
927 @return 1 if successful, or if no search target is specified as a parameter; -1 if unable
928 to find a pointer to the application.
930 Traverse the list of methods, and report on each one whose name starts with the specified
931 search target. In effect, the search target ends with an implicit wild card.
933 static int osrfAppIntrospect( osrfMethodContext* ctx ) {
935 // Get the name of the method to introspect
936 const char* methodSubstring = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
937 if( !methodSubstring )
938 return 1; /* respond with no methods */
940 // Get a pointer to the application
941 osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
943 return -1; // Oops, no application...
946 osrfHashIterator* itr = osrfNewHashIterator(app->methods);
949 while( (method = osrfHashIteratorNext(itr)) ) {
950 if( (len = strlen(methodSubstring)) <= strlen(method->name) ) {
951 if( !strncmp( method->name, methodSubstring, len) ) {
952 jsonObject* resp = jsonNewObject(NULL);
953 _osrfAppSetIntrospectMethod( ctx, method, resp );
954 osrfAppRespond(ctx, resp);
955 jsonObjectFree(resp);
960 osrfHashIteratorFree(itr);
965 @brief Run the implement_all method.
966 @param ctx Pointer to the method context.
967 @return 1 if successful, or -1 if unable to find a pointer to the application.
969 Report on all of the methods of the application.
971 static int osrfAppIntrospectAll( osrfMethodContext* ctx ) {
972 osrfApplication* app = _osrfAppFindApplication( ctx->session->remote_service );
975 osrfHashIterator* itr = osrfNewHashIterator(app->methods);
977 while( (method = osrfHashIteratorNext(itr)) ) {
978 jsonObject* resp = jsonNewObject(NULL);
979 _osrfAppSetIntrospectMethod( ctx, method, resp );
980 osrfAppRespond(ctx, resp);
981 jsonObjectFree(resp);
983 osrfHashIteratorFree(itr);
990 @brief Run the echo method.
991 @param ctx Pointer to the method context.
992 @return -1 if the method context is invalid or corrupted; otherwise 1.
994 Send the client a copy of each parameter.
996 static int osrfAppEcho( osrfMethodContext* ctx ) {
997 if( osrfMethodVerifyContext( ctx ) < 0 ) {
998 osrfLogError( OSRF_LOG_MARK, "osrfAppEcho: Received invalid method context" );
1003 for( i = 0; i < ctx->params->size; i++ ) {
1004 const jsonObject* str = jsonObjectGetIndex(ctx->params,i);
1005 osrfAppRespond(ctx, str);
1011 @brief Perform a series of sanity tests on an osrfMethodContext.
1012 @param ctx Pointer to the osrfMethodContext to be checked.
1013 @return Zero if the osrfMethodContext passes all tests, or -1 if it doesn't.
1015 int osrfMethodVerifyContext( osrfMethodContext* ctx )
1018 osrfLogError( OSRF_LOG_MARK, "Context is NULL in app request" );
1022 if( !ctx->session ) {
1023 osrfLogError( OSRF_LOG_MARK, "Session is NULL in app request" );
1029 osrfLogError( OSRF_LOG_MARK, "Method is NULL in app request" );
1033 if( ctx->method->argc ) {
1034 if( !ctx->params ) {
1035 osrfLogError( OSRF_LOG_MARK,
1036 "Params is NULL in app request %s", ctx->method->name );
1039 if( ctx->params->type != JSON_ARRAY ) {
1040 osrfLogError( OSRF_LOG_MARK,
1041 "'params' is not a JSON array for method %s", ctx->method->name );
1046 if( !ctx->method->name ) {
1047 osrfLogError( OSRF_LOG_MARK, "Method name is NULL" );
1051 // Log the call, with the method and parameters
1052 char* params_str = jsonObjectToJSON( ctx->params );
1054 // params_str will at minimum be "[]"
1057 char* method = ctx->method->name;
1058 int redact_params = 0;
1059 while( (str = osrfStringArrayGetString(log_protect_arr, i++)) ) {
1060 //osrfLogInternal(OSRF_LOG_MARK, "Checking for log protection [%s]", str);
1061 if(!strncmp(method, str, strlen(str))) {
1067 char* params_logged;
1069 params_logged = strdup("**PARAMS REDACTED**");
1071 params_str[strlen(params_str) - 1] = '\0'; // drop the trailing ']'
1072 params_logged = strdup(params_str + 1);
1075 osrfLogInfo( OSRF_LOG_MARK, "CALL: %s %s %s",
1076 ctx->session->remote_service, ctx->method->name, params_logged);
1077 free( params_logged );
1083 @brief Free an osrfMethod.
1084 @param name Name of the method (not used).
1085 @param p Void pointer pointing to the osrfMethod.
1087 This function is designed to be installed as a callback for an osrfHash (hence the
1088 unused @a name parameter and the void pointer).
1090 static void osrfMethodFree( char* name, void* p ) {
1091 osrfMethod* method = p;
1093 free( method->name );
1094 free( method->symbol );
1095 free( method->notes );
1101 @brief Free an osrfApplication
1102 @param name Name of the application (not used).
1103 @param p Void pointer pointing to the osrfApplication.
1105 This function is designed to be installed as a callback for an osrfHash (hence the
1106 unused @a name parameter and the void pointer).
1108 static void osrfAppFree( char* name, void* p ) {
1109 osrfApplication* app = p;
1111 dlclose( app->handle );
1112 osrfHashFree( app->methods );