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 static char* current_ingress = NULL;
12 struct osrf_app_request_struct {
13 /** The controlling session. */
14 struct osrf_app_session_struct* session;
16 /** Request id. It is the same as the thread_trace of the REQUEST message
17 for which it was created.
20 /** True if we have received a 'request complete' message from our request. */
22 /** The original REQUEST message payload. */
24 /** Linked list of responses to the request. */
27 /** Boolean; if true, then a call that is waiting on a response will reset the
28 timeout and set this variable back to false. */
30 /** Linkage pointers for a linked list. We maintain a hash table of pending requests,
31 and each slot of the hash table is a doubly linked list. */
36 static inline unsigned int request_id_hash( int req_id );
37 static osrfAppRequest* find_app_request( const osrfAppSession* session, int req_id );
38 static void add_app_request( osrfAppSession* session, osrfAppRequest* req );
40 /* Send the given message */
41 static int _osrf_app_session_send( osrfAppSession*, osrfMessage* msg );
43 static int osrfAppSessionMakeLocaleRequest(
44 osrfAppSession* session, const jsonObject* params, const char* method_name,
45 int protocol, osrfStringArray* param_strings, char* locale );
47 /** @brief The global session cache.
49 Key: session_id. Data: osrfAppSession.
51 static osrfHash* osrfAppSessionCache = NULL;
53 // --------------------------------------------------------------------------
55 // --------------------------------------------------------------------------
58 @brief Create a new osrfAppRequest.
59 @param session Pointer to the osrfAppSession that will own the new osrfAppRequest.
60 @param msg Pointer to the osrfMessage representing the request.
61 @return Pointer to the new osrfAppRequest.
63 The calling code is responsible for freeing the osrfAppRequest by calling
64 _osrf_app_request_free().
66 static osrfAppRequest* _osrf_app_request_init(
67 osrfAppSession* session, osrfMessage* msg ) {
69 osrfAppRequest* req = safe_malloc(sizeof(osrfAppRequest));
71 req->session = session;
72 req->request_id = msg->thread_trace;
76 req->reset_timeout = 0;
85 @brief Free an osrfAppRequest and everything it owns.
86 @param req Pointer to an osrfAppRequest.
88 static void _osrf_app_request_free( osrfAppRequest * req ) {
91 osrfMessageFree( req->payload );
93 /* Free the messages in the result queue */
94 osrfMessage* next_msg;
95 while( req->result ) {
96 next_msg = req->result->next;
97 osrfMessageFree( req->result );
98 req->result = next_msg;
106 @brief Append a new message to the list of responses to a request.
107 @param req Pointer to the osrfAppRequest for the original REQUEST message.
108 @param result Pointer to an osrfMessage received in response to the request.
110 For each osrfAppRequest we maintain a linked list of response messages, and traverse
113 static void _osrf_app_request_push_queue( osrfAppRequest* req, osrfMessage* result ){
114 if(req == NULL || result == NULL)
117 osrfLogDebug( OSRF_LOG_MARK, "App Session pushing request [%d] onto request queue",
118 result->thread_trace );
119 if(req->result == NULL) {
120 req->result = result; // Add the first node
124 // Find the last node in the list, and append the new node to it
125 osrfMessage* ptr = req->result;
126 osrfMessage* ptr2 = req->result->next;
136 @brief Remove an osrfAppRequest (identified by request_id) from an osrfAppSession.
137 @param session Pointer to the osrfAppSession that owns the osrfAppRequest.
138 @param req_id request_id of the osrfAppRequest to be removed.
140 void osrf_app_session_request_finish( osrfAppSession* session, int req_id ) {
143 // Search the hash table for the request in question
144 unsigned int index = request_id_hash( req_id );
145 osrfAppRequest* old_req = session->request_hash[ index ];
147 if( old_req->request_id == req_id )
150 old_req = old_req->next;
154 // Remove the request from the doubly linked list
156 old_req->prev->next = old_req->next;
158 session->request_hash[ index ] = old_req->next;
161 old_req->next->prev = old_req->prev;
163 _osrf_app_request_free( old_req );
169 @brief Derive a hash key from a request id.
170 @param req_id The request id.
171 @return The corresponding hash key; an index into request_hash[].
173 If OSRF_REQUEST_HASH_SIZE is a power of two, then this calculation should
174 reduce to a binary AND.
176 static inline unsigned int request_id_hash( int req_id ) {
177 return ((unsigned int) req_id ) % OSRF_REQUEST_HASH_SIZE;
181 @brief Search for an osrfAppRequest in the hash table, given a request id.
182 @param session Pointer to the relevant osrfAppSession.
183 @param req_id The request_id of the osrfAppRequest being sought.
184 @return A pointer to the osrfAppRequest if found, or NULL if not.
186 static osrfAppRequest* find_app_request( const osrfAppSession* session, int req_id ) {
188 osrfAppRequest* req = session->request_hash[ request_id_hash( req_id) ];
190 if( req->request_id == req_id )
200 @brief Add an osrfAppRequest to the hash table of a given osrfAppSession.
201 @param session Pointer to the session to which the request belongs.
202 @param req Pointer to the osrfAppRequest to be stored.
204 Find the right spot in the hash table; then add the request to the linked list at that
205 spot. We just add it to the head of the list, without trying to maintain any particular
208 static void add_app_request( osrfAppSession* session, osrfAppRequest* req ) {
209 if( session && req ) {
210 unsigned int index = request_id_hash( req->request_id );
211 req->next = session->request_hash[ index ];
213 session->request_hash[ index ] = req;
218 @brief Request a reset of the timeout period for a request.
219 @param session Pointer to the relevant osrfAppSession.
220 @param req_id Request ID of the request whose timeout is to be reset.
222 This happens when a client receives a STATUS message with a status code
223 OSRF_STATUS_CONTINUE; in effect the server is asking for more time.
225 The request to be reset is identified by the combination of session and request id.
227 void osrf_app_session_request_reset_timeout( osrfAppSession* session, int req_id ) {
230 osrfLogDebug( OSRF_LOG_MARK, "Resetting request timeout %d", req_id );
231 osrfAppRequest* req = find_app_request( session, req_id );
233 req->reset_timeout = 1;
237 @brief Fetch the next response message to a given previous request, subject to a timeout.
238 @param req Pointer to the osrfAppRequest representing the request.
239 @param timeout Maxmimum time to wait, in seconds.
241 @return Pointer to the next osrfMessage for this request, if one is available, or if it
242 becomes available before the end of the timeout; otherwise NULL;
244 If there is already a message available in the input queue for this request, dequeue and
245 return it immediately. Otherwise wait up to timeout seconds until you either get an
246 input message for the specified request, run out of time, or encounter an error.
248 If the only message we receive for this request is a STATUS message with a status code
249 OSRF_STATUS_COMPLETE, then return NULL. That means that the server has nothing further
250 to send in response to this request.
252 You may also receive other messages for other requests, and other sessions. These other
253 messages will be wholly or partially processed behind the scenes while you wait for the
256 static osrfMessage* _osrf_app_request_recv( osrfAppRequest* req, int timeout ) {
258 if(req == NULL) return NULL;
260 if( req->result != NULL ) {
261 /* Dequeue the next message in the list */
262 osrfMessage* tmp_msg = req->result;
263 req->result = req->result->next;
267 time_t start = time(NULL);
268 time_t remaining = (time_t) timeout;
270 // Wait repeatedly for input messages until you either receive one for the request
271 // you're interested in, run out of time, or encounter an error.
272 // Wait repeatedly because you may also receive messages for other requests, or for
273 // other sessions, and process them behind the scenes. These are not the messages
274 // you're looking for.
275 while( remaining >= 0 ) {
276 /* tell the session to wait for stuff */
277 osrfLogDebug( OSRF_LOG_MARK, "In app_request receive with remaining time [%d]",
281 osrf_app_session_queue_wait( req->session, 0, NULL );
282 if(req->session->transport_error) {
283 osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
287 if( req->result != NULL ) { /* if we received any results for this request */
288 /* dequeue the first message in the list */
289 osrfLogDebug( OSRF_LOG_MARK, "app_request_recv received a message, returning it" );
290 osrfMessage* ret_msg = req->result;
291 req->result = ret_msg->next;
292 if (ret_msg->sender_locale)
293 osrf_app_session_set_locale(req->session, ret_msg->sender_locale);
301 osrf_app_session_queue_wait( req->session, (int) remaining, NULL );
303 if(req->session->transport_error) {
304 osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
308 if( req->result != NULL ) { /* if we received any results for this request */
309 /* dequeue the first message in the list */
310 osrfLogDebug( OSRF_LOG_MARK, "app_request_recv received a message, returning it");
311 osrfMessage* ret_msg = req->result;
312 req->result = ret_msg->next;
313 if (ret_msg->sender_locale)
314 osrf_app_session_set_locale(req->session, ret_msg->sender_locale);
322 // Determine how much time is left
323 if(req->reset_timeout) {
324 // We got a reprieve. This happens when a client receives a STATUS message
325 // with a status code OSRF_STATUS_CONTINUE. We restart the timer from the
326 // beginning -- but only once. We reset reset_timeout to zero. so that a
327 // second attempted reprieve will allow, at most, only one more second.
328 remaining = (time_t) timeout;
329 req->reset_timeout = 0;
330 osrfLogDebug( OSRF_LOG_MARK, "Received a timeout reset");
332 remaining -= (int) (time(NULL) - start);
336 // Timeout exhausted; no messages for the request in question
337 char* paramString = jsonObjectToJSON(req->payload->_params);
338 osrfLogInfo( OSRF_LOG_MARK, "Returning NULL from app_request_recv after timeout: %s %s",
339 req->payload->method_name, paramString);
345 // --------------------------------------------------------------------------
347 // --------------------------------------------------------------------------
350 @brief Install a copy of a locale string in a specified session.
351 @param session Pointer to the osrfAppSession in which the locale is to be installed.
352 @param locale The locale string to be copied and installed.
353 @return A pointer to the installed copy of the locale string.
355 char* osrf_app_session_set_locale( osrfAppSession* session, const char* locale ) {
356 if (!session || !locale)
359 if(session->session_locale) {
360 if( strlen(session->session_locale) >= strlen(locale) ) {
361 /* There's room available; just copy */
362 strcpy(session->session_locale, locale);
364 free(session->session_locale);
365 session->session_locale = strdup( locale );
368 session->session_locale = strdup( locale );
371 return session->session_locale;
375 @brief Install a copy of a TZ string in a specified session.
376 @param session Pointer to the osrfAppSession in which the TZ is to be installed.
377 @param TZ The TZ string to be copied and installed.
378 @return A pointer to the installed copy of the TZ string.
380 char* osrf_app_session_set_tz( osrfAppSession* session, const char* tz ) {
384 if(session->session_tz) {
385 if( strlen(session->session_tz) >= strlen(tz) ) {
386 /* There's room available; just copy */
387 strcpy(session->session_tz, tz);
389 free(session->session_tz);
390 session->session_tz = strdup( tz );
393 session->session_tz = strdup( tz );
396 return session->session_tz;
400 @brief Install a copy of a ingress string as the new default.
401 @param session Pointer to the new strdup'ed default_ingress
402 @param ingress The ingress string to be copied and installed.
404 char* osrfAppSessionSetIngress(const char* ingress) {
405 if (!ingress) return NULL;
407 free(current_ingress);
408 return current_ingress = strdup(ingress);
412 @brief Returns the current ingress value
413 @return A pointer to the installed copy of the ingress string
415 const char* osrfAppSessionGetIngress() {
416 return current_ingress;
420 @brief Find the osrfAppSession for a given session id.
421 @param session_id The session id to look for.
422 @return Pointer to the corresponding osrfAppSession if found, or NULL if not.
424 Search the global session cache for the specified session id.
426 osrfAppSession* osrf_app_session_find_session( const char* session_id ) {
428 return osrfHashGet( osrfAppSessionCache, session_id );
434 @brief Add a session to the global session cache, keyed by session id.
435 @param session Pointer to the osrfAppSession to be added.
437 If a cache doesn't exist yet, create one. It's an osrfHash using session ids for the
438 key and osrfAppSessions for the data.
440 static void _osrf_app_session_push_session( osrfAppSession* session ) {
442 if( osrfAppSessionCache == NULL )
443 osrfAppSessionCache = osrfNewHash();
444 if( osrfHashGet( osrfAppSessionCache, session->session_id ) )
445 return; // A session with this id is already in the cache. Shouldn't happen.
446 osrfHashSet( osrfAppSessionCache, session, session->session_id );
451 @brief Create an osrfAppSession for a client.
452 @param remote_service Name of the service to which to connect
453 @return Pointer to the new osrfAppSession if successful, or NULL upon error.
455 Allocate memory for an osrfAppSession, and initialize it as follows:
457 - For talking with Jabber, grab an existing transport_client. It must have been
458 already set up by a prior call to osrfSystemBootstrapClientResc().
459 - Build a Jabber ID for addressing the service.
460 - Build a session ID based on a fine-grained timestamp and a process ID. This ID is
461 intended to be unique across the system, but uniqueness is not strictly guaranteed.
462 - Initialize various other bits and scraps.
463 - Add the session to the global session cache.
465 Do @em not connect to the service at this point.
467 osrfAppSession* osrfAppSessionClientInit( const char* remote_service ) {
469 if (!remote_service) {
470 osrfLogWarning( OSRF_LOG_MARK, "No remote service specified in osrfAppSessionClientInit");
474 osrfAppSession* session = safe_malloc(sizeof(osrfAppSession));
476 // Grab an existing transport_client for talking with Jabber
477 session->transport_handle = osrfSystemGetTransportClient();
478 if( session->transport_handle == NULL ) {
479 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service 'client'");
484 // Get a list of domain names from the config settings;
485 // ignore all but the first one in the list.
486 osrfStringArray* arr = osrfNewStringArray(8);
487 osrfConfigGetValueList(NULL, arr, "/domain");
488 const char* domain = osrfStringArrayGetString(arr, 0);
490 osrfLogWarning( OSRF_LOG_MARK, "No domains specified in the OpenSRF config file");
492 osrfStringArrayFree(arr);
496 // Get a router name from the config settings.
497 char* router_name = osrfConfigGetValue(NULL, "/router_name");
499 osrfLogWarning( OSRF_LOG_MARK, "No router name specified in the OpenSRF config file");
501 osrfStringArrayFree(arr);
505 char target_buf[512];
506 target_buf[ 0 ] = '\0';
508 // Using the router name, domain, and service name,
509 // build a Jabber ID for addressing the service.
510 int len = snprintf( target_buf, sizeof(target_buf), "%s@%s/%s",
511 router_name ? router_name : "(null)",
512 domain ? domain : "(null)",
513 remote_service ? remote_service : "(null)" );
514 osrfStringArrayFree(arr);
517 if( len >= sizeof( target_buf ) ) {
518 osrfLogWarning( OSRF_LOG_MARK, "Buffer overflow for remote_id");
523 session->remote_id = strdup(target_buf);
524 session->orig_remote_id = strdup(session->remote_id);
525 session->remote_service = strdup(remote_service);
526 session->session_locale = NULL;
527 session->session_tz = NULL;
528 session->transport_error = 0;
530 session->outbuf = NULL; // Not used by client
532 #ifdef ASSUME_STATELESS
533 session->stateless = 1;
534 osrfLogDebug( OSRF_LOG_MARK, "%s session is stateless", remote_service );
536 session->stateless = 0;
537 osrfLogDebug( OSRF_LOG_MARK, "%s session is NOT stateless", remote_service );
540 /* build a chunky, random session id */
543 snprintf(id, sizeof(id), "%f.%d%ld", get_timestamp_millis(), (int)time(NULL), (long) getpid());
544 session->session_id = strdup(id);
545 osrfLogDebug( OSRF_LOG_MARK, "Building a new client session with id [%s] [%s]",
546 session->remote_service, session->session_id );
548 session->thread_trace = 0;
549 session->state = OSRF_SESSION_DISCONNECTED;
550 session->type = OSRF_SESSION_CLIENT;
552 session->userData = NULL;
553 session->userDataFree = NULL;
555 // Initialize the hash table
557 for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
558 session->request_hash[ i ] = NULL;
560 _osrf_app_session_push_session( session );
565 @brief Create an osrfAppSession for a server.
566 @param session_id The session ID. In practice this comes from the thread member of
567 the transport message from the client.
568 @param our_app The name of the service being provided.
569 @param remote_id Jabber ID of the client.
570 @return Pointer to the newly created osrfAppSession if successful, or NULL upon failure.
572 If there is already a session with the specified id, report an error. Otherwise:
574 - Allocate memory for an osrfAppSession.
575 - For talking with Jabber, grab an existing transport_client. It should have been
576 already set up by a prior call to osrfSystemBootstrapClientResc().
577 - Install a copy of the @a our_app string as remote_service.
578 - Install copies of the @a remote_id string as remote_id and orig_remote_id.
579 - Initialize various other bits and scraps.
580 - Add the session to the global session cache.
582 Do @em not respond to the client at this point.
584 osrfAppSession* osrf_app_server_session_init(
585 const char* session_id, const char* our_app, const char* remote_id ) {
587 osrfLogDebug( OSRF_LOG_MARK, "Initing server session with session id %s, service %s,"
588 " and remote_id %s", session_id, our_app, remote_id );
590 osrfAppSession* session = osrf_app_session_find_session( session_id );
592 osrfLogWarning( OSRF_LOG_MARK, "App session already exists for session id %s",
597 session = safe_malloc(sizeof(osrfAppSession));
599 // Grab an existing transport_client for talking with Jabber
600 session->transport_handle = osrfSystemGetTransportClient();
601 if( session->transport_handle == NULL ) {
602 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service '%s'", our_app );
607 // Decide from a config setting whether the session is stateless or not. However
608 // this determination is pointless because it will immediately be overruled according
609 // to the compile-time macro ASSUME_STATELESS.
611 char* statel = osrf_settings_host_value("/apps/%s/stateless", our_app );
613 stateless = atoi( statel );
616 session->remote_id = strdup(remote_id);
617 session->orig_remote_id = strdup(remote_id);
618 session->session_id = strdup(session_id);
619 session->remote_service = strdup(our_app);
620 session->stateless = stateless;
622 #ifdef ASSUME_STATELESS
623 session->stateless = 1;
625 session->stateless = 0;
628 session->thread_trace = 0;
629 session->state = OSRF_SESSION_DISCONNECTED;
630 session->type = OSRF_SESSION_SERVER;
631 session->session_locale = NULL;
632 session->session_tz = NULL;
634 session->userData = NULL;
635 session->userDataFree = NULL;
636 session->transport_error = 0;
638 // Initialize the hash table
640 for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
641 session->request_hash[ i ] = NULL;
644 session->outbuf = buffer_init( 4096 );
646 _osrf_app_session_push_session( session );
651 @brief Create a REQUEST message, send it, and save it for future reference.
652 @param session Pointer to the current session, which has the addressing information.
653 @param params One way of specifying the parameters for the method.
654 @param method_name The name of the method to be called.
655 @param protocol Protocol.
656 @param param_strings Another way of specifying the parameters for the method.
657 @return The request ID of the resulting REQUEST message, or -1 upon error.
659 DEPRECATED. Use osrfAppSessionSendRequest() instead. It is identical except that it
660 doesn't use the param_strings argument, which is redundant, confusing, and unused.
662 If @a params is non-NULL, use it to specify the parameters to the method. Otherwise
663 use @a param_strings.
665 If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
666 parameter. If @a params points to any other kind of jsonObject, pass it as a single
669 If @a params is NULL, and @a param_strings is not NULL, then each pointer in the
670 osrfStringArray must point to a JSON string encoding a parameter. Pass them.
672 At this writing, all calls to this function use @a params to pass parameters, rather than
675 This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
677 int osrfAppSessionMakeRequest(
678 osrfAppSession* session, const jsonObject* params,
679 const char* method_name, int protocol, osrfStringArray* param_strings ) {
681 osrfLogWarning( OSRF_LOG_MARK, "Function osrfAppSessionMakeRequest() is deprecated; "
682 "call osrfAppSessionSendRequest() instead" );
683 return osrfAppSessionMakeLocaleRequest( session, params,
684 method_name, protocol, param_strings, NULL );
688 @brief Create a REQUEST message, send it, and save it for future reference.
689 @param session Pointer to the current session, which has the addressing information.
690 @param params One way of specifying the parameters for the method.
691 @param method_name The name of the method to be called.
692 @param protocol Protocol.
693 @return The request ID of the resulting REQUEST message, or -1 upon error.
695 If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
696 parameter. If @a params points to any other kind of jsonObject, pass it as a single
699 This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
701 int osrfAppSessionSendRequest( osrfAppSession* session, const jsonObject* params,
702 const char* method_name, int protocol ) {
704 return osrfAppSessionMakeLocaleRequest( session, params,
705 method_name, protocol, NULL, NULL );
709 @brief Create a REQUEST message, send it, and save it for future reference.
710 @param session Pointer to the current session, which has the addressing information.
711 @param params One way of specifying the parameters for the method.
712 @param method_name The name of the method to be called.
713 @param protocol Protocol.
714 @param param_strings Another way of specifying the parameters for the method.
715 @param locale Pointer to a locale string.
716 @return The request ID of the resulting REQUEST message, or -1 upon error.
718 See the discussion of osrfAppSessionSendRequest(), which at this writing is the only
719 place that calls this function, except for the similar but deprecated function
720 osrfAppSessionMakeRequest().
722 At this writing, the @a param_strings and @a locale parameters are always NULL.
724 static int osrfAppSessionMakeLocaleRequest(
725 osrfAppSession* session, const jsonObject* params, const char* method_name,
726 int protocol, osrfStringArray* param_strings, char* locale ) {
728 if(session == NULL) return -1;
732 osrfMessage* req_msg = osrf_message_init( REQUEST, ++(session->thread_trace), protocol );
733 osrf_message_set_method(req_msg, method_name);
736 osrf_message_set_locale(req_msg, locale);
737 } else if (session->session_locale) {
738 osrf_message_set_locale(req_msg, session->session_locale);
741 osrf_message_set_tz(req_msg, session->session_tz);
743 if (!current_ingress)
744 osrfAppSessionSetIngress("opensrf");
745 osrfMessageSetIngress(req_msg, current_ingress);
748 osrf_message_set_params(req_msg, params);
754 for(i = 0; i!= param_strings->size ; i++ ) {
755 osrf_message_add_param(req_msg,
756 osrfStringArrayGetString(param_strings,i));
761 osrfAppRequest* req = _osrf_app_request_init( session, req_msg );
762 if(_osrf_app_session_send( session, req_msg ) ) {
763 osrfLogWarning( OSRF_LOG_MARK, "Error sending request message [%d]",
764 session->thread_trace );
765 _osrf_app_request_free(req);
769 osrfLogDebug( OSRF_LOG_MARK, "Pushing [%d] onto request queue for session [%s] [%s]",
770 req->request_id, session->remote_service, session->session_id );
771 add_app_request( session, req );
772 return req->request_id;
776 @brief Mark an osrfAppRequest (identified by session and ID) as complete.
777 @param session Pointer to the osrfAppSession that owns the request.
778 @param request_id Request ID of the osrfAppRequest.
780 void osrf_app_session_set_complete( osrfAppSession* session, int request_id ) {
784 osrfAppRequest* req = find_app_request( session, request_id );
790 @brief Determine whether a osrfAppRequest, identified by session and ID, is complete.
791 @param session Pointer to the osrfAppSession that owns the request.
792 @param request_id Request ID of the osrfAppRequest.
793 @return Non-zero if the request is complete; zero if it isn't, or if it can't be found.
795 int osrf_app_session_request_complete( const osrfAppSession* session, int request_id ) {
799 osrfAppRequest* req = find_app_request( session, request_id );
801 return req->complete;
807 @brief Reset the remote ID of a session to its original remote ID.
808 @param session Pointer to the osrfAppSession to be reset.
810 void osrf_app_session_reset_remote( osrfAppSession* session ){
814 osrfLogDebug( OSRF_LOG_MARK, "App Session [%s] [%s] resetting remote id to %s",
815 session->remote_service, session->session_id, session->orig_remote_id );
817 osrf_app_session_set_remote( session, session->orig_remote_id );
821 @brief Set a session's remote ID to a specified value.
822 @param session Pointer to the osrfAppSession whose remote ID is to be set.
823 @param remote_id Pointer to the new remote id.
825 void osrf_app_session_set_remote( osrfAppSession* session, const char* remote_id ) {
826 if( session == NULL || remote_id == NULL )
829 if( session->remote_id ) {
830 if( strlen(session->remote_id) >= strlen(remote_id) ) {
831 // There's enough room; just copy it
832 strcpy(session->remote_id, remote_id);
834 free(session->remote_id );
835 session->remote_id = strdup( remote_id );
838 session->remote_id = strdup( remote_id );
842 @brief Append an osrfMessage to the list of responses to an osrfAppRequest.
843 @param session Pointer to the osrfAppSession that owns the request.
844 @param msg Pointer to the osrfMessage to be added.
846 The thread_trace member of the osrfMessage is the request_id of the osrfAppRequest.
847 Find the corresponding request in the session and append the osrfMessage to its list.
849 void osrf_app_session_push_queue( osrfAppSession* session, osrfMessage* msg ) {
850 if( session && msg ) {
851 osrfAppRequest* req = find_app_request( session, msg->thread_trace );
853 _osrf_app_request_push_queue( req, msg );
858 @brief Connect to the remote service.
859 @param session Pointer to the osrfAppSession for the service.
860 @return 1 if successful, or 0 if not.
862 If already connected, exit immediately, reporting success. Otherwise, build a CONNECT
863 message and send it to the service. Wait for up to five seconds for an acknowledgement.
865 The timeout value is currently hard-coded. Perhaps it should be configurable.
867 int osrfAppSessionConnect( osrfAppSession* session ) {
872 if(session->state == OSRF_SESSION_CONNECTED) {
876 int timeout = 5; /* XXX CONFIG VALUE */
878 osrfLogDebug( OSRF_LOG_MARK, "AppSession connecting to %s", session->remote_id );
880 /* defaulting to protocol 1 for now */
881 osrfMessage* con_msg = osrf_message_init( CONNECT, session->thread_trace, 1 );
883 // Address this message to the router
884 osrf_app_session_reset_remote( session );
885 session->state = OSRF_SESSION_CONNECTING;
886 int ret = _osrf_app_session_send( session, con_msg );
887 osrfMessageFree(con_msg);
891 time_t start = time(NULL);
892 time_t remaining = (time_t) timeout;
894 // Wait for the acknowledgement. We look for it repeatedly because, under the covers,
895 // we may receive and process messages other than the one we're looking for.
896 while( session->state != OSRF_SESSION_CONNECTED && remaining >= 0 ) {
897 osrf_app_session_queue_wait( session, remaining, NULL );
898 if(session->transport_error) {
899 osrfLogError(OSRF_LOG_MARK, "cannot communicate with %s", session->remote_service);
902 remaining -= (int) (time(NULL) - start);
905 if(session->state == OSRF_SESSION_CONNECTED)
906 osrfLogDebug( OSRF_LOG_MARK, " * Connected Successfully to %s", session->remote_service );
908 if(session->state != OSRF_SESSION_CONNECTED)
915 @brief Disconnect from the remote service. No response is expected.
916 @param session Pointer to the osrfAppSession to be disconnected.
917 @return 1 in all cases.
919 If we're already disconnected, return immediately without doing anything. Likewise if
920 we have a stateless session and we're in the process of connecting. Otherwise, send a
921 DISCONNECT message to the service.
923 int osrf_app_session_disconnect( osrfAppSession* session){
927 if(session->state == OSRF_SESSION_DISCONNECTED)
930 if(session->stateless && session->state != OSRF_SESSION_CONNECTED) {
931 osrfLogDebug( OSRF_LOG_MARK,
932 "Exiting disconnect on stateless session %s",
933 session->session_id);
937 osrfLogDebug(OSRF_LOG_MARK, "AppSession disconnecting from %s", session->remote_id );
939 osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
940 _osrf_app_session_send( session, dis_msg );
941 session->state = OSRF_SESSION_DISCONNECTED;
943 osrfMessageFree( dis_msg );
944 osrf_app_session_reset_remote( session );
949 @brief Resend a request message, as specified by session and request id.
950 @param session Pointer to the osrfAppSession.
951 @param req_id Request ID for the request to be resent.
952 @return Zero if successful, or if the specified request cannot be found; 1 if the
953 request is already complete, or if the attempt to resend the message fails.
955 The choice of return codes may seem seem capricious, but at this writing nothing
956 pays any attention to the return code anyway.
958 int osrf_app_session_request_resend( osrfAppSession* session, int req_id ) {
959 osrfAppRequest* req = find_app_request( session, req_id );
964 } else if(!req->complete) {
965 osrfLogDebug( OSRF_LOG_MARK, "Resending request [%d]", req->request_id );
966 rc = _osrf_app_session_send( req->session, req->payload );
975 @brief Send one or more osrfMessages to the remote service or client.
976 @param session Pointer to the osrfAppSession responsible for sending the message(s).
977 @param msgs Pointer to an array of pointers to osrfMessages.
978 @param size How many messages to send.
979 @return 0 upon success, or -1 upon failure.
981 static int osrfAppSessionSendBatch( osrfAppSession* session, osrfMessage* msgs[], int size ) {
983 if( !(session && msgs && size > 0) ) return -1;
986 osrfMessage* msg = msgs[0];
990 // First grab and process any input messages, for any app session. This gives us
991 // a chance to see any CONNECT or DISCONNECT messages that may have arrived. We
992 // may also see some unrelated messages, but we have to process those sooner or
993 // later anyway, so we might as well do it now.
994 osrf_app_session_queue_wait( session, 0, NULL );
996 if(session->state != OSRF_SESSION_CONNECTED) {
998 if(session->stateless) { /* stateless session always send to the root listener */
999 osrf_app_session_reset_remote(session);
1003 /* do an auto-connect if necessary */
1004 if( ! session->stateless &&
1005 (msg->m_type != CONNECT) &&
1006 (msg->m_type != DISCONNECT) &&
1007 (session->state != OSRF_SESSION_CONNECTED) ) {
1009 if(!osrfAppSessionConnect( session ))
1016 // Translate the collection of osrfMessages into a JSON array
1017 char* string = osrfMessageSerializeBatch(msgs, size);
1019 // Send the JSON as the payload of a transport_message
1021 retval = osrfSendTransportPayload( session, string );
1029 @brief Wrap a given string in a transport message and send it.
1030 @param session Pointer to the osrfAppSession responsible for sending the message(s).
1031 @param payload A string to be sent via Jabber.
1032 @return 0 upon success, or -1 upon failure.
1034 In practice the payload is normally a JSON string, but this function assumes nothing
1037 int osrfSendTransportPayload( osrfAppSession* session, const char* payload ) {
1038 transport_message* t_msg = message_init(
1039 payload, "", session->session_id, session->remote_id, NULL );
1040 message_set_osrf_xid( t_msg, osrfLogGetXid() );
1042 int retval = client_send_message( session->transport_handle, t_msg );
1044 osrfLogError( OSRF_LOG_MARK, "client_send_message failed, exit()ing immediately" );
1048 osrfLogInfo(OSRF_LOG_MARK, "[%s] sent %d bytes of data to %s",
1049 session->remote_service, strlen( payload ), t_msg->recipient );
1051 osrfLogDebug( OSRF_LOG_MARK, "Sent: %s", payload );
1053 message_free( t_msg );
1058 @brief Send a single osrfMessage to the remote service or client.
1059 @param session Pointer to the osrfAppSession.
1060 @param msg Pointer to the osrfMessage to be sent.
1061 @return zero upon success, or 1 upon failure.
1063 A thin wrapper. Create an array of one element, and pass it to osrfAppSessionSendBatch().
1065 static int _osrf_app_session_send( osrfAppSession* session, osrfMessage* msg ){
1066 if( !(session && msg) )
1070 return - osrfAppSessionSendBatch( session, a, 1 );
1075 @brief Wait for any input messages to arrive, and process them as needed.
1076 @param session Pointer to the osrfAppSession whose transport_session we will use.
1077 @param timeout How many seconds to wait for the first input message.
1078 @param recvd Pointer to an boolean int. If you receive at least one message, set the boolean
1079 to true; otherwise set it to false.
1080 @return 0 upon success (even if a timeout occurs), or -1 upon failure.
1082 A thin wrapper for osrf_stack_process(). The timeout applies only to the first
1083 message; process subsequent messages if they are available, but don't wait for them.
1085 The first parameter identifies an osrfApp session, but all we really use it for is to
1086 get a pointer to the transport_session. Typically, a given process opens only a single
1087 transport_session (to talk to the Jabber server), and all app sessions in that process
1088 use the same transport_session.
1090 Hence this function indiscriminately waits for input messages for all osrfAppSessions
1091 tied to the same Jabber session, not just the one specified.
1093 Dispatch each message to the appropriate processing routine, depending on its type
1094 and contents, and on whether we're acting as a client or as a server for that message.
1095 For example, a response to a request may be appended to the input queue of the
1096 relevant request. A server session receiving a REQUEST message may execute the
1097 requested method. And so forth.
1099 int osrf_app_session_queue_wait( osrfAppSession* session, int timeout, int* recvd ){
1100 if(session == NULL) return 0;
1101 osrfLogDebug(OSRF_LOG_MARK, "AppSession in queue_wait with timeout %d", timeout );
1102 return osrf_stack_process(session->transport_handle, timeout, recvd);
1106 @brief Shut down and destroy an osrfAppSession.
1107 @param session Pointer to the osrfAppSession to be destroyed.
1109 If this is a client session, send a DISCONNECT message.
1111 Remove the session from the global session cache.
1113 Free all associated resources, including any pending osrfAppRequests.
1115 void osrfAppSessionFree( osrfAppSession* session ){
1116 if(session == NULL) return;
1120 osrfLogDebug(OSRF_LOG_MARK, "AppSession [%s] [%s] destroying self and deleting requests",
1121 session->remote_service, session->session_id );
1122 /* disconnect if we're a client */
1123 if(session->type == OSRF_SESSION_CLIENT
1124 && session->state != OSRF_SESSION_DISCONNECTED ) {
1125 osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
1126 _osrf_app_session_send( session, dis_msg );
1127 osrfMessageFree(dis_msg);
1130 /* Remove self from the global session cache */
1132 osrfHashRemove( osrfAppSessionCache, session->session_id );
1134 /* Free the memory */
1136 if( session->userDataFree && session->userData )
1137 session->userDataFree(session->userData);
1139 if(session->session_locale)
1140 free(session->session_locale);
1142 if(session->session_tz)
1143 free(session->session_tz);
1145 free(session->remote_id);
1146 free(session->orig_remote_id);
1147 free(session->session_id);
1148 free(session->remote_service);
1150 // Free the request hash
1152 for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i ) {
1153 osrfAppRequest* app = session->request_hash[ i ];
1155 osrfAppRequest* next = app->next;
1156 _osrf_app_request_free( app );
1161 if( session->outbuf )
1162 buffer_free( session->outbuf );
1168 @brief Wait for a response to a given request, subject to a timeout.
1169 @param session Pointer to the osrfAppSession that owns the request.
1170 @param req_id Request ID for the request.
1171 @param timeout How many seconds to wait.
1172 @return A pointer to the received osrfMessage if one arrives; otherwise NULL.
1174 A thin wrapper. Given a session and a request ID, look up the corresponding request
1175 and pass it to _osrf_app_request_recv().
1177 osrfMessage* osrfAppSessionRequestRecv(
1178 osrfAppSession* session, int req_id, int timeout ) {
1179 if(req_id < 0 || session == NULL)
1181 osrfAppRequest* req = find_app_request( session, req_id );
1182 return _osrf_app_request_recv( req, timeout );
1186 @brief In response to a specified request, send a payload of data to a client.
1187 @param ses Pointer to the osrfAppSession that owns the request.
1188 @param requestId Request ID of the osrfAppRequest.
1189 @param data Pointer to a jsonObject containing the data payload.
1190 @return 0 upon success, or -1 upon failure.
1192 Translate the jsonObject to a JSON string, and send it wrapped in a RESULT message.
1194 The only failure detected is if either of the two pointer parameters is NULL.
1196 int osrfAppRequestRespond( osrfAppSession* ses, int requestId, const jsonObject* data ) {
1197 if( !ses || ! data )
1200 osrfMessage* msg = osrf_message_init( RESULT, requestId, 1 );
1201 osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
1202 char* json = jsonObjectToJSON( data );
1204 osrf_message_set_result_content( msg, json );
1205 _osrf_app_session_send( ses, msg );
1208 osrfMessageFree( msg );
1215 @brief Send one or two messages to a client in response to a specified request.
1216 @param ses Pointer to the osrfAppSession that owns the request.
1217 @param requestId Request ID of the osrfAppRequest.
1218 @param data Pointer to a jsonObject containing the data payload.
1219 @return Zero in all cases.
1221 If the @a data parameter is not NULL, translate the jsonObject into a JSON string, and
1222 incorporate that string into a RESULT message as as the payload . Also build a STATUS
1223 message indicating that the response is complete. Send both messages bundled together
1224 in the same transport_message.
1226 If the @a data parameter is NULL, send only a STATUS message indicating that the response
1229 int osrfAppRequestRespondComplete(
1230 osrfAppSession* ses, int requestId, const jsonObject* data ) {
1232 osrfMessage* status = osrf_message_init( STATUS, requestId, 1);
1233 osrf_message_set_status_info( status, "osrfConnectStatus", "Request Complete",
1234 OSRF_STATUS_COMPLETE );
1237 osrfMessage* payload = osrf_message_init( RESULT, requestId, 1 );
1238 osrf_message_set_status_info( payload, NULL, "OK", OSRF_STATUS_OK );
1240 char* json = jsonObjectToJSON( data );
1241 osrf_message_set_result_content( payload, json );
1248 osrfAppSessionSendBatch( ses, ms, 2 );
1250 osrfMessageFree( payload );
1252 osrfAppSessionSendBatch( ses, &status, 1 );
1255 osrfMessageFree( status );
1261 @brief Send a STATUS message, for a specified request, back to the client.
1262 @param ses Pointer to the osrfAppSession connected to the client.
1263 @param type A numeric code denoting the status.
1264 @param name A string naming the status.
1265 @param reqId The request ID of the request.
1266 @param message A brief message describing the status.
1267 @return 0 upon success, or -1 upon failure.
1269 The only detected failure is when the @a ses parameter is NULL.
1271 int osrfAppSessionStatus( osrfAppSession* ses, int type,
1272 const char* name, int reqId, const char* message ) {
1275 osrfMessage* msg = osrf_message_init( STATUS, reqId, 1);
1276 osrf_message_set_status_info( msg, name, message, type );
1277 _osrf_app_session_send( ses, msg );
1278 osrfMessageFree( msg );
1285 @brief Free the global session cache.
1287 Note that the osrfHash that implements the global session cache does @em not have a
1288 callback function installed for freeing its cargo. As a result, any remaining
1289 osrfAppSessions are leaked, along with all the osrfAppRequests and osrfMessages they
1292 void osrfAppSessionCleanup( void ) {
1293 osrfHashFree(osrfAppSessionCache);
1294 osrfAppSessionCache = NULL;
1298 @brief Arrange for immediate termination of the process.
1299 @param ses Pointer to the current osrfAppSession.
1301 Typical use case: a server drone loses its database connection, thereby becoming useless.
1302 It terminates so that it will not receive further requests, being unable to service them.
1304 void osrfAppSessionPanic( osrfAppSession* ses ) {