2 @file osrf_app_session.c
3 @brief Implementation of osrfAppSession.
7 #include "opensrf/osrf_app_session.h"
8 #include "opensrf/osrf_stack.h"
10 struct osrf_app_request_struct {
11 /** The controlling session. */
12 struct osrf_app_session_struct* session;
14 /** Request id. It is the same as the thread_trace of the REQUEST message
15 for which it was created.
18 /** True if we have received a 'request complete' message from our request. */
20 /** The original REQUEST message payload. */
22 /** Linked list of responses to the request. */
25 /** Boolean; if true, then a call that is waiting on a response will reset the
26 timeout and set this variable back to false. */
28 /** Linkage pointers for a linked list. We maintain a hash table of pending requests,
29 and each slot of the hash table is a doubly linked list. */
34 static inline unsigned int request_id_hash( int req_id );
35 static osrfAppRequest* find_app_request( const osrfAppSession* session, int req_id );
36 static void add_app_request( osrfAppSession* session, osrfAppRequest* req );
38 /* Send the given message */
39 static int _osrf_app_session_send( osrfAppSession*, osrfMessage* msg );
41 static int osrfAppSessionMakeLocaleRequest(
42 osrfAppSession* session, const jsonObject* params, const char* method_name,
43 int protocol, osrfStringArray* param_strings, char* locale );
45 /** @brief The global session cache.
47 Key: session_id. Data: osrfAppSession.
49 static osrfHash* osrfAppSessionCache = NULL;
51 // --------------------------------------------------------------------------
53 // --------------------------------------------------------------------------
56 @brief Create a new osrfAppRequest.
57 @param session Pointer to the osrfAppSession that will own the new osrfAppRequest.
58 @param msg Pointer to the osrfMessage representing the request.
59 @return Pointer to the new osrfAppRequest.
61 The calling code is responsible for freeing the osrfAppRequest by calling
62 _osrf_app_request_free().
64 static osrfAppRequest* _osrf_app_request_init(
65 osrfAppSession* session, osrfMessage* msg ) {
67 osrfAppRequest* req = safe_malloc(sizeof(osrfAppRequest));
69 req->session = session;
70 req->request_id = msg->thread_trace;
74 req->reset_timeout = 0;
83 @brief Free an osrfAppRequest and everything it owns.
84 @param req Pointer to an osrfAppRequest.
86 static void _osrf_app_request_free( osrfAppRequest * req ) {
89 osrfMessageFree( req->payload );
91 /* Free the messages in the result queue */
92 osrfMessage* next_msg;
93 while( req->result ) {
94 next_msg = req->result->next;
95 osrfMessageFree( req->result );
96 req->result = next_msg;
104 @brief Append a new message to the list of responses to a request.
105 @param req Pointer to the osrfAppRequest for the original REQUEST message.
106 @param result Pointer to an osrfMessage received in response to the request.
108 For each osrfAppRequest we maintain a linked list of response messages, and traverse
111 static void _osrf_app_request_push_queue( osrfAppRequest* req, osrfMessage* result ){
112 if(req == NULL || result == NULL)
115 osrfLogDebug( OSRF_LOG_MARK, "App Session pushing request [%d] onto request queue",
116 result->thread_trace );
117 if(req->result == NULL) {
118 req->result = result; // Add the first node
122 // Find the last node in the list, and append the new node to it
123 osrfMessage* ptr = req->result;
124 osrfMessage* ptr2 = req->result->next;
134 @brief Remove an osrfAppRequest (identified by request_id) from an osrfAppSession.
135 @param session Pointer to the osrfAppSession that owns the osrfAppRequest.
136 @param req_id request_id of the osrfAppRequest to be removed.
138 void osrf_app_session_request_finish( osrfAppSession* session, int req_id ) {
141 // Search the hash table for the request in question
142 unsigned int index = request_id_hash( req_id );
143 osrfAppRequest* old_req = session->request_hash[ index ];
145 if( old_req->request_id == req_id )
148 old_req = old_req->next;
152 // Remove the request from the doubly linked list
154 old_req->prev->next = old_req->next;
156 session->request_hash[ index ] = old_req->next;
159 old_req->next->prev = old_req->prev;
161 _osrf_app_request_free( old_req );
167 @brief Derive a hash key from a request id.
168 @param req_id The request id.
169 @return The corresponding hash key; an index into request_hash[].
171 If OSRF_REQUEST_HASH_SIZE is a power of two, then this calculation should
172 reduce to a binary AND.
174 static inline unsigned int request_id_hash( int req_id ) {
175 return ((unsigned int) req_id ) % OSRF_REQUEST_HASH_SIZE;
179 @brief Search for an osrfAppRequest in the hash table, given a request id.
180 @param session Pointer to the relevant osrfAppSession.
181 @param req_id The request_id of the osrfAppRequest being sought.
182 @return A pointer to the osrfAppRequest if found, or NULL if not.
184 static osrfAppRequest* find_app_request( const osrfAppSession* session, int req_id ) {
186 osrfAppRequest* req = session->request_hash[ request_id_hash( req_id) ];
188 if( req->request_id == req_id )
198 @brief Add an osrfAppRequest to the hash table of a given osrfAppSession.
199 @param session Pointer to the session to which the request belongs.
200 @param req Pointer to the osrfAppRequest to be stored.
202 Find the right spot in the hash table; then add the request to the linked list at that
203 spot. We just add it to the head of the list, without trying to maintain any particular
206 static void add_app_request( osrfAppSession* session, osrfAppRequest* req ) {
207 if( session && req ) {
208 unsigned int index = request_id_hash( req->request_id );
209 req->next = session->request_hash[ index ];
211 session->request_hash[ index ] = req;
216 @brief Request a reset of the timeout period for a request.
217 @param session Pointer to the relevant osrfAppSession.
218 @param req_id Request ID of the request whose timeout is to be reset.
220 This happens when a client receives a STATUS message with a status code
221 OSRF_STATUS_CONTINUE; in effect the server is asking for more time.
223 The request to be reset is identified by the combination of session and request id.
225 void osrf_app_session_request_reset_timeout( osrfAppSession* session, int req_id ) {
228 osrfLogDebug( OSRF_LOG_MARK, "Resetting request timeout %d", req_id );
229 osrfAppRequest* req = find_app_request( session, req_id );
231 req->reset_timeout = 1;
235 @brief Fetch the next response message to a given previous request, subject to a timeout.
236 @param req Pointer to the osrfAppRequest representing the request.
237 @param timeout Maxmimum time to wait, in seconds.
239 @return Pointer to the next osrfMessage for this request, if one is available, or if it
240 becomes available before the end of the timeout; otherwise NULL;
242 If there is already a message available in the input queue for this request, dequeue and
243 return it immediately. Otherwise wait up to timeout seconds until you either get an
244 input message for the specified request, run out of time, or encounter an error.
246 If the only message we receive for this request is a STATUS message with a status code
247 OSRF_STATUS_COMPLETE, then return NULL. That means that the server has nothing further
248 to send in response to this request.
250 You may also receive other messages for other requests, and other sessions. These other
251 messages will be wholly or partially processed behind the scenes while you wait for the
254 static osrfMessage* _osrf_app_request_recv( osrfAppRequest* req, int timeout ) {
256 if(req == NULL) return NULL;
258 if( req->result != NULL ) {
259 /* Dequeue the next message in the list */
260 osrfMessage* tmp_msg = req->result;
261 req->result = req->result->next;
265 time_t start = time(NULL);
266 time_t remaining = (time_t) timeout;
268 // Wait repeatedly for input messages until you either receive one for the request
269 // you're interested in, run out of time, or encounter an error.
270 // Wait repeatedly because you may also receive messages for other requests, or for
271 // other sessions, and process them behind the scenes. These are not the messages
272 // you're looking for.
273 while( remaining >= 0 ) {
274 /* tell the session to wait for stuff */
275 osrfLogDebug( OSRF_LOG_MARK, "In app_request receive with remaining time [%d]",
279 osrf_app_session_queue_wait( req->session, 0, NULL );
280 if(req->session->transport_error) {
281 osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
285 if( req->result != NULL ) { /* if we received any results for this request */
286 /* dequeue the first message in the list */
287 osrfLogDebug( OSRF_LOG_MARK, "app_request_recv received a message, returning it" );
288 osrfMessage* ret_msg = req->result;
289 req->result = ret_msg->next;
290 if (ret_msg->sender_locale)
291 osrf_app_session_set_locale(req->session, ret_msg->sender_locale);
299 osrf_app_session_queue_wait( req->session, (int) remaining, NULL );
301 if(req->session->transport_error) {
302 osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
306 if( req->result != NULL ) { /* if we received any results for this request */
307 /* dequeue the first message in the list */
308 osrfLogDebug( OSRF_LOG_MARK, "app_request_recv received a message, returning it");
309 osrfMessage* ret_msg = req->result;
310 req->result = ret_msg->next;
311 if (ret_msg->sender_locale)
312 osrf_app_session_set_locale(req->session, ret_msg->sender_locale);
320 // Determine how much time is left
321 if(req->reset_timeout) {
322 // We got a reprieve. This happens when a client receives a STATUS message
323 // with a status code OSRF_STATUS_CONTINUE. We restart the timer from the
324 // beginning -- but only once. We reset reset_timeout to zero. so that a
325 // second attempted reprieve will allow, at most, only one more second.
326 remaining = (time_t) timeout;
327 req->reset_timeout = 0;
328 osrfLogDebug( OSRF_LOG_MARK, "Received a timeout reset");
330 remaining -= (int) (time(NULL) - start);
334 // Timeout exhausted; no messages for the request in question
335 char* paramString = jsonObjectToJSON(req->payload->_params);
336 osrfLogInfo( OSRF_LOG_MARK, "Returning NULL from app_request_recv after timeout: %s %s",
337 req->payload->method_name, paramString);
343 // --------------------------------------------------------------------------
345 // --------------------------------------------------------------------------
348 @brief Install a copy of a locale string in a specified session.
349 @param session Pointer to the osrfAppSession in which the locale is to be installed.
350 @param locale The locale string to be copied and installed.
351 @return A pointer to the installed copy of the locale string.
353 char* osrf_app_session_set_locale( osrfAppSession* session, const char* locale ) {
354 if (!session || !locale)
357 if(session->session_locale) {
358 if( strlen(session->session_locale) >= strlen(locale) ) {
359 /* There's room available; just copy */
360 strcpy(session->session_locale, locale);
362 free(session->session_locale);
363 session->session_locale = strdup( locale );
366 session->session_locale = strdup( locale );
369 return session->session_locale;
373 @brief Find the osrfAppSession for a given session id.
374 @param session_id The session id to look for.
375 @return Pointer to the corresponding osrfAppSession if found, or NULL if not.
377 Search the global session cache for the specified session id.
379 osrfAppSession* osrf_app_session_find_session( const char* session_id ) {
381 return osrfHashGet( osrfAppSessionCache, session_id );
387 @brief Add a session to the global session cache, keyed by session id.
388 @param session Pointer to the osrfAppSession to be added.
390 If a cache doesn't exist yet, create one. It's an osrfHash using session ids for the
391 key and osrfAppSessions for the data.
393 static void _osrf_app_session_push_session( osrfAppSession* session ) {
395 if( osrfAppSessionCache == NULL )
396 osrfAppSessionCache = osrfNewHash();
397 if( osrfHashGet( osrfAppSessionCache, session->session_id ) )
398 return; // A session with this id is already in the cache. Shouldn't happen.
399 osrfHashSet( osrfAppSessionCache, session, session->session_id );
404 @brief Create an osrfAppSession for a client.
405 @param remote_service Name of the service to which to connect
406 @return Pointer to the new osrfAppSession if successful, or NULL upon error.
408 Allocate memory for an osrfAppSession, and initialize it as follows:
410 - For talking with Jabber, grab an existing transport_client. It must have been
411 already set up by a prior call to osrfSystemBootstrapClientResc().
412 - Build a Jabber ID for addressing the service.
413 - Build a session ID based on a fine-grained timestamp and a process ID. This ID is
414 intended to be unique across the system, but uniqueness is not strictly guaranteed.
415 - Initialize various other bits and scraps.
416 - Add the session to the global session cache.
418 Do @em not connect to the service at this point.
420 osrfAppSession* osrfAppSessionClientInit( const char* remote_service ) {
422 if (!remote_service) {
423 osrfLogWarning( OSRF_LOG_MARK, "No remote service specified in osrfAppSessionClientInit");
427 osrfAppSession* session = safe_malloc(sizeof(osrfAppSession));
429 // Grab an existing transport_client for talking with Jabber
430 session->transport_handle = osrfSystemGetTransportClient();
431 if( session->transport_handle == NULL ) {
432 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service 'client'");
437 // Get a list of domain names from the config settings;
438 // ignore all but the first one in the list.
439 osrfStringArray* arr = osrfNewStringArray(8);
440 osrfConfigGetValueList(NULL, arr, "/domain");
441 const char* domain = osrfStringArrayGetString(arr, 0);
443 osrfLogWarning( OSRF_LOG_MARK, "No domains specified in the OpenSRF config file");
445 osrfStringArrayFree(arr);
449 // Get a router name from the config settings.
450 char* router_name = osrfConfigGetValue(NULL, "/router_name");
452 osrfLogWarning( OSRF_LOG_MARK, "No router name specified in the OpenSRF config file");
454 osrfStringArrayFree(arr);
458 char target_buf[512];
459 target_buf[ 0 ] = '\0';
461 // Using the router name, domain, and service name,
462 // build a Jabber ID for addressing the service.
463 int len = snprintf( target_buf, sizeof(target_buf), "%s@%s/%s",
464 router_name ? router_name : "(null)",
465 domain ? domain : "(null)",
466 remote_service ? remote_service : "(null)" );
467 osrfStringArrayFree(arr);
470 if( len >= sizeof( target_buf ) ) {
471 osrfLogWarning( OSRF_LOG_MARK, "Buffer overflow for remote_id");
476 session->remote_id = strdup(target_buf);
477 session->orig_remote_id = strdup(session->remote_id);
478 session->remote_service = strdup(remote_service);
479 session->session_locale = NULL;
480 session->transport_error = 0;
482 session->outbuf = NULL; // Not used by client
484 #ifdef ASSUME_STATELESS
485 session->stateless = 1;
486 osrfLogDebug( OSRF_LOG_MARK, "%s session is stateless", remote_service );
488 session->stateless = 0;
489 osrfLogDebug( OSRF_LOG_MARK, "%s session is NOT stateless", remote_service );
492 /* build a chunky, random session id */
495 snprintf(id, sizeof(id), "%f.%d%ld", get_timestamp_millis(), (int)time(NULL), (long) getpid());
496 session->session_id = strdup(id);
497 osrfLogDebug( OSRF_LOG_MARK, "Building a new client session with id [%s] [%s]",
498 session->remote_service, session->session_id );
500 session->thread_trace = 0;
501 session->state = OSRF_SESSION_DISCONNECTED;
502 session->type = OSRF_SESSION_CLIENT;
504 session->userData = NULL;
505 session->userDataFree = NULL;
507 // Initialize the hash table
509 for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
510 session->request_hash[ i ] = NULL;
512 _osrf_app_session_push_session( session );
517 @brief Create an osrfAppSession for a server.
518 @param session_id The session ID. In practice this comes from the thread member of
519 the transport message from the client.
520 @param our_app The name of the service being provided.
521 @param remote_id Jabber ID of the client.
522 @return Pointer to the newly created osrfAppSession if successful, or NULL upon failure.
524 If there is already a session with the specified id, report an error. Otherwise:
526 - Allocate memory for an osrfAppSession.
527 - For talking with Jabber, grab an existing transport_client. It should have been
528 already set up by a prior call to osrfSystemBootstrapClientResc().
529 - Install a copy of the @a our_app string as remote_service.
530 - Install copies of the @a remote_id string as remote_id and orig_remote_id.
531 - Initialize various other bits and scraps.
532 - Add the session to the global session cache.
534 Do @em not respond to the client at this point.
536 osrfAppSession* osrf_app_server_session_init(
537 const char* session_id, const char* our_app, const char* remote_id ) {
539 osrfLogDebug( OSRF_LOG_MARK, "Initing server session with session id %s, service %s,"
540 " and remote_id %s", session_id, our_app, remote_id );
542 osrfAppSession* session = osrf_app_session_find_session( session_id );
544 osrfLogWarning( OSRF_LOG_MARK, "App session already exists for session id %s",
549 session = safe_malloc(sizeof(osrfAppSession));
551 // Grab an existing transport_client for talking with Jabber
552 session->transport_handle = osrfSystemGetTransportClient();
553 if( session->transport_handle == NULL ) {
554 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service '%s'", our_app );
559 // Decide from a config setting whether the session is stateless or not. However
560 // this determination is pointless because it will immediately be overruled according
561 // to the compile-time macro ASSUME_STATELESS.
563 char* statel = osrf_settings_host_value("/apps/%s/stateless", our_app );
565 stateless = atoi( statel );
568 session->remote_id = strdup(remote_id);
569 session->orig_remote_id = strdup(remote_id);
570 session->session_id = strdup(session_id);
571 session->remote_service = strdup(our_app);
572 session->stateless = stateless;
574 #ifdef ASSUME_STATELESS
575 session->stateless = 1;
577 session->stateless = 0;
580 session->thread_trace = 0;
581 session->state = OSRF_SESSION_DISCONNECTED;
582 session->type = OSRF_SESSION_SERVER;
583 session->session_locale = NULL;
585 session->userData = NULL;
586 session->userDataFree = NULL;
587 session->transport_error = 0;
589 // Initialize the hash table
591 for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
592 session->request_hash[ i ] = NULL;
595 session->outbuf = buffer_init( 4096 );
597 _osrf_app_session_push_session( session );
602 @brief Create a REQUEST message, send it, and save it for future reference.
603 @param session Pointer to the current session, which has the addressing information.
604 @param params One way of specifying the parameters for the method.
605 @param method_name The name of the method to be called.
606 @param protocol Protocol.
607 @param param_strings Another way of specifying the parameters for the method.
608 @return The request ID of the resulting REQUEST message, or -1 upon error.
610 DEPRECATED. Use osrfAppSessionSendRequest() instead. It is identical except that it
611 doesn't use the param_strings argument, which is redundant, confusing, and unused.
613 If @a params is non-NULL, use it to specify the parameters to the method. Otherwise
614 use @a param_strings.
616 If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
617 parameter. If @a params points to any other kind of jsonObject, pass it as a single
620 If @a params is NULL, and @a param_strings is not NULL, then each pointer in the
621 osrfStringArray must point to a JSON string encoding a parameter. Pass them.
623 At this writing, all calls to this function use @a params to pass parameters, rather than
626 This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
628 int osrfAppSessionMakeRequest(
629 osrfAppSession* session, const jsonObject* params,
630 const char* method_name, int protocol, osrfStringArray* param_strings ) {
632 osrfLogWarning( OSRF_LOG_MARK, "Function osrfAppSessionMakeRequest() is deprecasted; "
633 "call osrfAppSessionSendRequest() instead" );
634 return osrfAppSessionMakeLocaleRequest( session, params,
635 method_name, protocol, param_strings, NULL );
639 @brief Create a REQUEST message, send it, and save it for future reference.
640 @param session Pointer to the current session, which has the addressing information.
641 @param params One way of specifying the parameters for the method.
642 @param method_name The name of the method to be called.
643 @param protocol Protocol.
644 @return The request ID of the resulting REQUEST message, or -1 upon error.
646 If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
647 parameter. If @a params points to any other kind of jsonObject, pass it as a single
650 This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
652 int osrfAppSessionSendRequest( osrfAppSession* session, const jsonObject* params,
653 const char* method_name, int protocol ) {
655 return osrfAppSessionMakeLocaleRequest( session, params,
656 method_name, protocol, NULL, NULL );
660 @brief Create a REQUEST message, send it, and save it for future reference.
661 @param session Pointer to the current session, which has the addressing information.
662 @param params One way of specifying the parameters for the method.
663 @param method_name The name of the method to be called.
664 @param protocol Protocol.
665 @param param_strings Another way of specifying the parameters for the method.
666 @param locale Pointer to a locale string.
667 @return The request ID of the resulting REQUEST message, or -1 upon error.
669 See the discussion of osrfAppSessionSendRequest(), which at this writing is the only
670 place that calls this function, except for the similar but deprecated function
671 osrfAppSessionMakeRequest().
673 At this writing, the @a param_strings and @a locale parameters are always NULL.
675 static int osrfAppSessionMakeLocaleRequest(
676 osrfAppSession* session, const jsonObject* params, const char* method_name,
677 int protocol, osrfStringArray* param_strings, char* locale ) {
679 if(session == NULL) return -1;
683 osrfMessage* req_msg = osrf_message_init( REQUEST, ++(session->thread_trace), protocol );
684 osrf_message_set_method(req_msg, method_name);
687 osrf_message_set_locale(req_msg, locale);
688 } else if (session->session_locale) {
689 osrf_message_set_locale(req_msg, session->session_locale);
693 osrf_message_set_params(req_msg, params);
699 for(i = 0; i!= param_strings->size ; i++ ) {
700 osrf_message_add_param(req_msg,
701 osrfStringArrayGetString(param_strings,i));
706 osrfAppRequest* req = _osrf_app_request_init( session, req_msg );
707 if(_osrf_app_session_send( session, req_msg ) ) {
708 osrfLogWarning( OSRF_LOG_MARK, "Error sending request message [%d]",
709 session->thread_trace );
710 _osrf_app_request_free(req);
714 osrfLogDebug( OSRF_LOG_MARK, "Pushing [%d] onto request queue for session [%s] [%s]",
715 req->request_id, session->remote_service, session->session_id );
716 add_app_request( session, req );
717 return req->request_id;
721 @brief Mark an osrfAppRequest (identified by session and ID) as complete.
722 @param session Pointer to the osrfAppSession that owns the request.
723 @param request_id Request ID of the osrfAppRequest.
725 void osrf_app_session_set_complete( osrfAppSession* session, int request_id ) {
729 osrfAppRequest* req = find_app_request( session, request_id );
735 @brief Determine whether a osrfAppRequest, identified by session and ID, is complete.
736 @param session Pointer to the osrfAppSession that owns the request.
737 @param request_id Request ID of the osrfAppRequest.
738 @return Non-zero if the request is complete; zero if it isn't, or if it can't be found.
740 int osrf_app_session_request_complete( const osrfAppSession* session, int request_id ) {
744 osrfAppRequest* req = find_app_request( session, request_id );
746 return req->complete;
752 @brief Reset the remote ID of a session to its original remote ID.
753 @param session Pointer to the osrfAppSession to be reset.
755 void osrf_app_session_reset_remote( osrfAppSession* session ){
759 osrfLogDebug( OSRF_LOG_MARK, "App Session [%s] [%s] resetting remote id to %s",
760 session->remote_service, session->session_id, session->orig_remote_id );
762 osrf_app_session_set_remote( session, session->orig_remote_id );
766 @brief Set a session's remote ID to a specified value.
767 @param session Pointer to the osrfAppSession whose remote ID is to be set.
768 @param remote_id Pointer to the new remote id.
770 void osrf_app_session_set_remote( osrfAppSession* session, const char* remote_id ) {
771 if( session == NULL || remote_id == NULL )
774 if( session->remote_id ) {
775 if( strlen(session->remote_id) >= strlen(remote_id) ) {
776 // There's enough room; just copy it
777 strcpy(session->remote_id, remote_id);
779 free(session->remote_id );
780 session->remote_id = strdup( remote_id );
783 session->remote_id = strdup( remote_id );
787 @brief Append an osrfMessage to the list of responses to an osrfAppRequest.
788 @param session Pointer to the osrfAppSession that owns the request.
789 @param msg Pointer to the osrfMessage to be added.
791 The thread_trace member of the osrfMessage is the request_id of the osrfAppRequest.
792 Find the corresponding request in the session and append the osrfMessage to its list.
794 void osrf_app_session_push_queue( osrfAppSession* session, osrfMessage* msg ) {
795 if( session && msg ) {
796 osrfAppRequest* req = find_app_request( session, msg->thread_trace );
798 _osrf_app_request_push_queue( req, msg );
803 @brief Connect to the remote service.
804 @param session Pointer to the osrfAppSession for the service.
805 @return 1 if successful, or 0 if not.
807 If already connected, exit immediately, reporting success. Otherwise, build a CONNECT
808 message and send it to the service. Wait for up to five seconds for an acknowledgement.
810 The timeout value is currently hard-coded. Perhaps it should be configurable.
812 int osrfAppSessionConnect( osrfAppSession* session ) {
817 if(session->state == OSRF_SESSION_CONNECTED) {
821 int timeout = 5; /* XXX CONFIG VALUE */
823 osrfLogDebug( OSRF_LOG_MARK, "AppSession connecting to %s", session->remote_id );
825 /* defaulting to protocol 1 for now */
826 osrfMessage* con_msg = osrf_message_init( CONNECT, session->thread_trace, 1 );
828 // Address this message to the router
829 osrf_app_session_reset_remote( session );
830 session->state = OSRF_SESSION_CONNECTING;
831 int ret = _osrf_app_session_send( session, con_msg );
832 osrfMessageFree(con_msg);
836 time_t start = time(NULL);
837 time_t remaining = (time_t) timeout;
839 // Wait for the acknowledgement. We look for it repeatedly because, under the covers,
840 // we may receive and process messages other than the one we're looking for.
841 while( session->state != OSRF_SESSION_CONNECTED && remaining >= 0 ) {
842 osrf_app_session_queue_wait( session, remaining, NULL );
843 if(session->transport_error) {
844 osrfLogError(OSRF_LOG_MARK, "cannot communicate with %s", session->remote_service);
847 remaining -= (int) (time(NULL) - start);
850 if(session->state == OSRF_SESSION_CONNECTED)
851 osrfLogDebug( OSRF_LOG_MARK, " * Connected Successfully to %s", session->remote_service );
853 if(session->state != OSRF_SESSION_CONNECTED)
860 @brief Disconnect from the remote service. No response is expected.
861 @param session Pointer to the osrfAppSession to be disconnected.
862 @return 1 in all cases.
864 If we're already disconnected, return immediately without doing anything. Likewise if
865 we have a stateless session and we're in the process of connecting. Otherwise, send a
866 DISCONNECT message to the service.
868 int osrf_app_session_disconnect( osrfAppSession* session){
872 if(session->state == OSRF_SESSION_DISCONNECTED)
875 if(session->stateless && session->state != OSRF_SESSION_CONNECTED) {
876 osrfLogDebug( OSRF_LOG_MARK,
877 "Exiting disconnect on stateless session %s",
878 session->session_id);
882 osrfLogDebug(OSRF_LOG_MARK, "AppSession disconnecting from %s", session->remote_id );
884 osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
885 _osrf_app_session_send( session, dis_msg );
886 session->state = OSRF_SESSION_DISCONNECTED;
888 osrfMessageFree( dis_msg );
889 osrf_app_session_reset_remote( session );
894 @brief Resend a request message, as specified by session and request id.
895 @param session Pointer to the osrfAppSession.
896 @param req_id Request ID for the request to be resent.
897 @return Zero if successful, or if the specified request cannot be found; 1 if the
898 request is already complete, or if the attempt to resend the message fails.
900 The choice of return codes may seem seem capricious, but at this writing nothing
901 pays any attention to the return code anyway.
903 int osrf_app_session_request_resend( osrfAppSession* session, int req_id ) {
904 osrfAppRequest* req = find_app_request( session, req_id );
909 } else if(!req->complete) {
910 osrfLogDebug( OSRF_LOG_MARK, "Resending request [%d]", req->request_id );
911 rc = _osrf_app_session_send( req->session, req->payload );
920 @brief Send one or more osrfMessages to the remote service or client.
921 @param session Pointer to the osrfAppSession responsible for sending the message(s).
922 @param msgs Pointer to an array of pointers to osrfMessages.
923 @param size How many messages to send.
924 @return 0 upon success, or -1 upon failure.
926 static int osrfAppSessionSendBatch( osrfAppSession* session, osrfMessage* msgs[], int size ) {
928 if( !(session && msgs && size > 0) ) return -1;
931 osrfMessage* msg = msgs[0];
935 // First grab and process any input messages, for any app session. This gives us
936 // a chance to see any CONNECT or DISCONNECT messages that may have arrived. We
937 // may also see some unrelated messages, but we have to process those sooner or
938 // later anyway, so we might as well do it now.
939 osrf_app_session_queue_wait( session, 0, NULL );
941 if(session->state != OSRF_SESSION_CONNECTED) {
943 if(session->stateless) { /* stateless session always send to the root listener */
944 osrf_app_session_reset_remote(session);
948 /* do an auto-connect if necessary */
949 if( ! session->stateless &&
950 (msg->m_type != CONNECT) &&
951 (msg->m_type != DISCONNECT) &&
952 (session->state != OSRF_SESSION_CONNECTED) ) {
954 if(!osrfAppSessionConnect( session ))
961 // Translate the collection of osrfMessages into a JSON array
962 char* string = osrfMessageSerializeBatch(msgs, size);
964 // Send the JSON as the payload of a transport_message
966 retval = osrfSendTransportPayload( session, string );
974 @brief Wrap a given string in a transport message and send it.
975 @param session Pointer to the osrfAppSession responsible for sending the message(s).
976 @param payload A string to be sent via Jabber.
977 @return 0 upon success, or -1 upon failure.
979 In practice the payload is normally a JSON string, but this function assumes nothing
982 int osrfSendTransportPayload( osrfAppSession* session, const char* payload ) {
983 transport_message* t_msg = message_init(
984 payload, "", session->session_id, session->remote_id, NULL );
985 message_set_osrf_xid( t_msg, osrfLogGetXid() );
987 int retval = client_send_message( session->transport_handle, t_msg );
989 osrfLogError( OSRF_LOG_MARK, "client_send_message failed" );
991 osrfLogInfo(OSRF_LOG_MARK, "[%s] sent %d bytes of data to %s",
992 session->remote_service, strlen( payload ), t_msg->recipient );
994 osrfLogDebug( OSRF_LOG_MARK, "Sent: %s", payload );
996 message_free( t_msg );
1001 @brief Send a single osrfMessage to the remote service or client.
1002 @param session Pointer to the osrfAppSession.
1003 @param msg Pointer to the osrfMessage to be sent.
1004 @return zero upon success, or 1 upon failure.
1006 A thin wrapper. Create an array of one element, and pass it to osrfAppSessionSendBatch().
1008 static int _osrf_app_session_send( osrfAppSession* session, osrfMessage* msg ){
1009 if( !(session && msg) )
1013 return - osrfAppSessionSendBatch( session, a, 1 );
1018 @brief Wait for any input messages to arrive, and process them as needed.
1019 @param session Pointer to the osrfAppSession whose transport_session we will use.
1020 @param timeout How many seconds to wait for the first input message.
1021 @param recvd Pointer to an boolean int. If you receive at least one message, set the boolean
1022 to true; otherwise set it to false.
1023 @return 0 upon success (even if a timeout occurs), or -1 upon failure.
1025 A thin wrapper for osrf_stack_process(). The timeout applies only to the first
1026 message; process subsequent messages if they are available, but don't wait for them.
1028 The first parameter identifies an osrfApp session, but all we really use it for is to
1029 get a pointer to the transport_session. Typically, a given process opens only a single
1030 transport_session (to talk to the Jabber server), and all app sessions in that process
1031 use the same transport_session.
1033 Hence this function indiscriminately waits for input messages for all osrfAppSessions
1034 tied to the same Jabber session, not just the one specified.
1036 Dispatch each message to the appropriate processing routine, depending on its type
1037 and contents, and on whether we're acting as a client or as a server for that message.
1038 For example, a response to a request may be appended to the input queue of the
1039 relevant request. A server session receiving a REQUEST message may execute the
1040 requested method. And so forth.
1042 int osrf_app_session_queue_wait( osrfAppSession* session, int timeout, int* recvd ){
1043 if(session == NULL) return 0;
1044 osrfLogDebug(OSRF_LOG_MARK, "AppSession in queue_wait with timeout %d", timeout );
1045 return osrf_stack_process(session->transport_handle, timeout, recvd);
1049 @brief Shut down and destroy an osrfAppSession.
1050 @param session Pointer to the osrfAppSession to be destroyed.
1052 If this is a client session, send a DISCONNECT message.
1054 Remove the session from the global session cache.
1056 Free all associated resources, including any pending osrfAppRequests.
1058 void osrfAppSessionFree( osrfAppSession* session ){
1059 if(session == NULL) return;
1063 osrfLogDebug(OSRF_LOG_MARK, "AppSession [%s] [%s] destroying self and deleting requests",
1064 session->remote_service, session->session_id );
1065 /* disconnect if we're a client */
1066 if(session->type == OSRF_SESSION_CLIENT
1067 && session->state != OSRF_SESSION_DISCONNECTED ) {
1068 osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
1069 _osrf_app_session_send( session, dis_msg );
1070 osrfMessageFree(dis_msg);
1073 /* Remove self from the global session cache */
1075 osrfHashRemove( osrfAppSessionCache, session->session_id );
1077 /* Free the memory */
1079 if( session->userDataFree && session->userData )
1080 session->userDataFree(session->userData);
1082 if(session->session_locale)
1083 free(session->session_locale);
1085 free(session->remote_id);
1086 free(session->orig_remote_id);
1087 free(session->session_id);
1088 free(session->remote_service);
1090 // Free the request hash
1092 for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i ) {
1093 osrfAppRequest* app = session->request_hash[ i ];
1095 osrfAppRequest* next = app->next;
1096 _osrf_app_request_free( app );
1101 if( session->outbuf )
1102 buffer_free( session->outbuf );
1108 @brief Wait for a response to a given request, subject to a timeout.
1109 @param session Pointer to the osrfAppSession that owns the request.
1110 @param req_id Request ID for the request.
1111 @param timeout How many seconds to wait.
1112 @return A pointer to the received osrfMessage if one arrives; otherwise NULL.
1114 A thin wrapper. Given a session and a request ID, look up the corresponding request
1115 and pass it to _osrf_app_request_recv().
1117 osrfMessage* osrfAppSessionRequestRecv(
1118 osrfAppSession* session, int req_id, int timeout ) {
1119 if(req_id < 0 || session == NULL)
1121 osrfAppRequest* req = find_app_request( session, req_id );
1122 return _osrf_app_request_recv( req, timeout );
1126 @brief In response to a specified request, send a payload of data to a client.
1127 @param ses Pointer to the osrfAppSession that owns the request.
1128 @param requestId Request ID of the osrfAppRequest.
1129 @param data Pointer to a jsonObject containing the data payload.
1130 @return 0 upon success, or -1 upon failure.
1132 Translate the jsonObject to a JSON string, and send it wrapped in a RESULT message.
1134 The only failure detected is if either of the two pointer parameters is NULL.
1136 int osrfAppRequestRespond( osrfAppSession* ses, int requestId, const jsonObject* data ) {
1137 if( !ses || ! data )
1140 osrfMessage* msg = osrf_message_init( RESULT, requestId, 1 );
1141 osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
1142 char* json = jsonObjectToJSON( data );
1144 osrf_message_set_result_content( msg, json );
1145 _osrf_app_session_send( ses, msg );
1148 osrfMessageFree( msg );
1155 @brief Send one or two messages to a client in response to a specified request.
1156 @param ses Pointer to the osrfAppSession that owns the request.
1157 @param requestId Request ID of the osrfAppRequest.
1158 @param data Pointer to a jsonObject containing the data payload.
1159 @return Zero in all cases.
1161 If the @a data parameter is not NULL, translate the jsonObject into a JSON string, and
1162 incorporate that string into a RESULT message as as the payload . Also build a STATUS
1163 message indicating that the response is complete. Send both messages bundled together
1164 in the same transport_message.
1166 If the @a data parameter is NULL, send only a STATUS message indicating that the response
1169 int osrfAppRequestRespondComplete(
1170 osrfAppSession* ses, int requestId, const jsonObject* data ) {
1172 osrfMessage* status = osrf_message_init( STATUS, requestId, 1);
1173 osrf_message_set_status_info( status, "osrfConnectStatus", "Request Complete",
1174 OSRF_STATUS_COMPLETE );
1177 osrfMessage* payload = osrf_message_init( RESULT, requestId, 1 );
1178 osrf_message_set_status_info( payload, NULL, "OK", OSRF_STATUS_OK );
1180 char* json = jsonObjectToJSON( data );
1181 osrf_message_set_result_content( payload, json );
1188 osrfAppSessionSendBatch( ses, ms, 2 );
1190 osrfMessageFree( payload );
1192 osrfAppSessionSendBatch( ses, &status, 1 );
1195 osrfMessageFree( status );
1201 @brief Send a STATUS message, for a specified request, back to the client.
1202 @param ses Pointer to the osrfAppSession connected to the client.
1203 @param type A numeric code denoting the status.
1204 @param name A string naming the status.
1205 @param reqId The request ID of the request.
1206 @param message A brief message describing the status.
1207 @return 0 upon success, or -1 upon failure.
1209 The only detected failure is when the @a ses parameter is NULL.
1211 int osrfAppSessionStatus( osrfAppSession* ses, int type,
1212 const char* name, int reqId, const char* message ) {
1215 osrfMessage* msg = osrf_message_init( STATUS, reqId, 1);
1216 osrf_message_set_status_info( msg, name, message, type );
1217 _osrf_app_session_send( ses, msg );
1218 osrfMessageFree( msg );
1225 @brief Free the global session cache.
1227 Note that the osrfHash that implements the global session cache does @em not have a
1228 callback function installed for freeing its cargo. As a result, any remaining
1229 osrfAppSessions are leaked, along with all the osrfAppRequests and osrfMessages they
1232 void osrfAppSessionCleanup( void ) {
1233 osrfHashFree(osrfAppSessionCache);
1234 osrfAppSessionCache = NULL;
1238 @brief Arrange for immediate termination of the process.
1239 @param ses Pointer to the current osrfAppSession.
1241 Typical use case: a server drone loses its database connection, thereby becoming useless.
1242 It terminates so that it will not receive further requests, being unable to service them.
1244 void osrfAppSessionPanic( osrfAppSession* ses ) {