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 /** Buffer used to collect partial response messages */
28 growing_buffer* part_response_buffer;
30 /** Boolean; if true, then a call that is waiting on a response will reset the
31 timeout and set this variable back to false. */
33 /** Linkage pointers for a linked list. We maintain a hash table of pending requests,
34 and each slot of the hash table is a doubly linked list. */
39 static inline unsigned int request_id_hash( int req_id );
40 static osrfAppRequest* find_app_request( const osrfAppSession* session, int req_id );
41 static void add_app_request( osrfAppSession* session, osrfAppRequest* req );
43 /* Send the given message */
44 static int _osrf_app_session_send( osrfAppSession*, osrfMessage* msg );
46 static int osrfAppSessionMakeLocaleRequest(
47 osrfAppSession* session, const jsonObject* params, const char* method_name,
48 int protocol, osrfStringArray* param_strings, char* locale );
50 /** @brief The global session cache.
52 Key: session_id. Data: osrfAppSession.
54 static osrfHash* osrfAppSessionCache = NULL;
56 // --------------------------------------------------------------------------
58 // --------------------------------------------------------------------------
61 @brief Create a new osrfAppRequest.
62 @param session Pointer to the osrfAppSession that will own the new osrfAppRequest.
63 @param msg Pointer to the osrfMessage representing the request.
64 @return Pointer to the new osrfAppRequest.
66 The calling code is responsible for freeing the osrfAppRequest by calling
67 _osrf_app_request_free().
69 static osrfAppRequest* _osrf_app_request_init(
70 osrfAppSession* session, osrfMessage* msg ) {
72 osrfAppRequest* req = safe_malloc(sizeof(osrfAppRequest));
74 req->session = session;
75 req->request_id = msg->thread_trace;
79 req->reset_timeout = 0;
82 req->part_response_buffer = NULL;
89 @brief Free an osrfAppRequest and everything it owns.
90 @param req Pointer to an osrfAppRequest.
92 static void _osrf_app_request_free( osrfAppRequest * req ) {
95 osrfMessageFree( req->payload );
97 /* Free the messages in the result queue */
98 osrfMessage* next_msg;
99 while( req->result ) {
100 next_msg = req->result->next;
101 osrfMessageFree( req->result );
102 req->result = next_msg;
105 if (req->part_response_buffer)
106 osrf_buffer_free(req->part_response_buffer);
113 @brief Append a new message to the list of responses to a request.
114 @param req Pointer to the osrfAppRequest for the original REQUEST message.
115 @param result Pointer to an osrfMessage received in response to the request.
117 For each osrfAppRequest we maintain a linked list of response messages, and traverse
120 static void _osrf_app_request_push_queue( osrfAppRequest* req, osrfMessage* result ){
121 if(req == NULL || result == NULL)
124 if (result->status_code == OSRF_STATUS_PARTIAL) {
125 osrfLogDebug(OSRF_LOG_MARK, "received partial message response");
127 if (!req->part_response_buffer) {
128 // assume the max_chunk_size of the server matches ours for
129 // buffer initialization, since the setting will usually be
130 // a site-wide value.
131 req->part_response_buffer = osrf_buffer_init(OSRF_MSG_CHUNK_SIZE + 1);
134 const char* partial = jsonObjectGetString(result->_result_content);
136 if (partial != NULL) {
137 osrfLogDebug(OSRF_LOG_MARK,
138 "adding %d bytes to response buffer", strlen(partial));
140 // add the partial contents of the message to the buffer
141 osrf_buffer_add(req->part_response_buffer, partial);
144 // all done. req and result are freed by the caller
147 } else if (result->status_code == OSRF_STATUS_NOCONTENT) {
148 if (req->part_response_buffer && req->part_response_buffer->n_used) {
150 // part_response_buffer contains a stitched-together JSON string
151 osrfLogDebug(OSRF_LOG_MARK,
152 "partial response complete, parsing %d bytes",
153 req->part_response_buffer->n_used);
155 // coerce the partial-complete response into a standard RESULT.
156 osrf_message_set_status_info(result, NULL, "OK", OSRF_STATUS_OK);
158 // use the stitched-together JSON string as the result conten
159 osrf_message_set_result_content(
160 result, req->part_response_buffer->buf);
162 // free string, keep the buffer
163 osrf_buffer_reset(req->part_response_buffer);
166 osrfLogDebug(OSRF_LOG_MARK,
167 "Received OSRF_STATUS_NOCONTENT with no preceeding content");
172 osrfLogDebug( OSRF_LOG_MARK, "App Session pushing request [%d] onto request queue",
173 result->thread_trace );
175 if(req->result == NULL) {
176 req->result = result; // Add the first node
180 // Find the last node in the list, and append the new node to it
181 osrfMessage* ptr = req->result;
182 osrfMessage* ptr2 = req->result->next;
192 @brief Remove an osrfAppRequest (identified by request_id) from an osrfAppSession.
193 @param session Pointer to the osrfAppSession that owns the osrfAppRequest.
194 @param req_id request_id of the osrfAppRequest to be removed.
196 void osrf_app_session_request_finish( osrfAppSession* session, int req_id ) {
199 // Search the hash table for the request in question
200 unsigned int index = request_id_hash( req_id );
201 osrfAppRequest* old_req = session->request_hash[ index ];
203 if( old_req->request_id == req_id )
206 old_req = old_req->next;
210 // Remove the request from the doubly linked list
212 old_req->prev->next = old_req->next;
214 session->request_hash[ index ] = old_req->next;
217 old_req->next->prev = old_req->prev;
219 _osrf_app_request_free( old_req );
225 @brief Derive a hash key from a request id.
226 @param req_id The request id.
227 @return The corresponding hash key; an index into request_hash[].
229 If OSRF_REQUEST_HASH_SIZE is a power of two, then this calculation should
230 reduce to a binary AND.
232 static inline unsigned int request_id_hash( int req_id ) {
233 return ((unsigned int) req_id ) % OSRF_REQUEST_HASH_SIZE;
237 @brief Search for an osrfAppRequest in the hash table, given a request id.
238 @param session Pointer to the relevant osrfAppSession.
239 @param req_id The request_id of the osrfAppRequest being sought.
240 @return A pointer to the osrfAppRequest if found, or NULL if not.
242 static osrfAppRequest* find_app_request( const osrfAppSession* session, int req_id ) {
244 osrfAppRequest* req = session->request_hash[ request_id_hash( req_id) ];
246 if( req->request_id == req_id )
256 @brief Add an osrfAppRequest to the hash table of a given osrfAppSession.
257 @param session Pointer to the session to which the request belongs.
258 @param req Pointer to the osrfAppRequest to be stored.
260 Find the right spot in the hash table; then add the request to the linked list at that
261 spot. We just add it to the head of the list, without trying to maintain any particular
264 static void add_app_request( osrfAppSession* session, osrfAppRequest* req ) {
265 if( session && req ) {
266 unsigned int index = request_id_hash( req->request_id );
267 req->next = session->request_hash[ index ];
269 session->request_hash[ index ] = req;
274 @brief Request a reset of the timeout period for a request.
275 @param session Pointer to the relevant osrfAppSession.
276 @param req_id Request ID of the request whose timeout is to be reset.
278 This happens when a client receives a STATUS message with a status code
279 OSRF_STATUS_CONTINUE; in effect the server is asking for more time.
281 The request to be reset is identified by the combination of session and request id.
283 void osrf_app_session_request_reset_timeout( osrfAppSession* session, int req_id ) {
286 osrfLogDebug( OSRF_LOG_MARK, "Resetting request timeout %d", req_id );
287 osrfAppRequest* req = find_app_request( session, req_id );
289 req->reset_timeout = 1;
293 @brief Fetch the next response message to a given previous request, subject to a timeout.
294 @param req Pointer to the osrfAppRequest representing the request.
295 @param timeout Maxmimum time to wait, in seconds.
297 @return Pointer to the next osrfMessage for this request, if one is available, or if it
298 becomes available before the end of the timeout; otherwise NULL;
300 If there is already a message available in the input queue for this request, dequeue and
301 return it immediately. Otherwise wait up to timeout seconds until you either get an
302 input message for the specified request, run out of time, or encounter an error.
304 If the only message we receive for this request is a STATUS message with a status code
305 OSRF_STATUS_COMPLETE, then return NULL. That means that the server has nothing further
306 to send in response to this request.
308 You may also receive other messages for other requests, and other sessions. These other
309 messages will be wholly or partially processed behind the scenes while you wait for the
312 static osrfMessage* _osrf_app_request_recv( osrfAppRequest* req, int timeout ) {
314 if(req == NULL) return NULL;
316 if( req->result != NULL ) {
317 /* Dequeue the next message in the list */
318 osrfMessage* tmp_msg = req->result;
319 req->result = req->result->next;
323 time_t start = time(NULL);
324 time_t remaining = (time_t) timeout;
326 // Wait repeatedly for input messages until you either receive one for the request
327 // you're interested in, run out of time, or encounter an error.
328 // Wait repeatedly because you may also receive messages for other requests, or for
329 // other sessions, and process them behind the scenes. These are not the messages
330 // you're looking for.
331 while( remaining >= 0 ) {
332 /* tell the session to wait for stuff */
333 osrfLogDebug( OSRF_LOG_MARK, "In app_request receive with remaining time [%d]",
337 osrf_app_session_queue_wait( req->session, 0, NULL );
338 if(req->session->transport_error) {
339 osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
343 if( req->result != NULL ) { /* if we received any results for this request */
344 /* dequeue the first message in the list */
345 osrfLogDebug( OSRF_LOG_MARK, "app_request_recv received a message, returning it" );
346 osrfMessage* ret_msg = req->result;
347 req->result = ret_msg->next;
348 if (ret_msg->sender_locale)
349 osrf_app_session_set_locale(req->session, ret_msg->sender_locale);
357 osrf_app_session_queue_wait( req->session, (int) remaining, NULL );
359 if(req->session->transport_error) {
360 osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
364 if( req->result != NULL ) { /* if we received any results for this request */
365 /* dequeue the first message in the list */
366 osrfLogDebug( OSRF_LOG_MARK, "app_request_recv received a message, returning it");
367 osrfMessage* ret_msg = req->result;
368 req->result = ret_msg->next;
369 if (ret_msg->sender_locale)
370 osrf_app_session_set_locale(req->session, ret_msg->sender_locale);
378 // Determine how much time is left
379 if(req->reset_timeout) {
380 // We got a reprieve. This happens when a client receives a STATUS message
381 // with a status code OSRF_STATUS_CONTINUE. We restart the timer from the
382 // beginning -- but only once. We reset reset_timeout to zero. so that a
383 // second attempted reprieve will allow, at most, only one more second.
384 remaining = (time_t) timeout;
385 req->reset_timeout = 0;
386 osrfLogDebug( OSRF_LOG_MARK, "Received a timeout reset");
388 // Subtract the amount of time taken during this loop
389 // iteration, not the combined time of all iterations.
390 time_t tmp = time(NULL);
391 remaining -= (int) (tmp - start);
396 // Timeout exhausted; no messages for the request in question
397 char* paramString = jsonObjectToJSON(req->payload->_params);
398 osrfLogInfo( OSRF_LOG_MARK, "Returning NULL from app_request_recv after timeout: %s %s",
399 req->payload->method_name, paramString);
405 // --------------------------------------------------------------------------
407 // --------------------------------------------------------------------------
410 @brief Install a copy of a locale string in a specified session.
411 @param session Pointer to the osrfAppSession in which the locale is to be installed.
412 @param locale The locale string to be copied and installed.
413 @return A pointer to the installed copy of the locale string.
415 char* osrf_app_session_set_locale( osrfAppSession* session, const char* locale ) {
416 if (!session || !locale)
419 if(session->session_locale) {
420 if( strlen(session->session_locale) >= strlen(locale) ) {
421 /* There's room available; just copy */
422 strcpy(session->session_locale, locale);
424 free(session->session_locale);
425 session->session_locale = strdup( locale );
428 session->session_locale = strdup( locale );
431 return session->session_locale;
435 @brief Install a copy of a TZ string in a specified session.
436 @param session Pointer to the osrfAppSession in which the TZ is to be installed.
437 @param TZ The TZ string to be copied and installed.
438 @return A pointer to the installed copy of the TZ string.
440 char* osrf_app_session_set_tz( osrfAppSession* session, const char* tz ) {
444 if(session->session_tz) {
445 if( strlen(session->session_tz) >= strlen(tz) ) {
446 /* There's room available; just copy */
447 strcpy(session->session_tz, tz);
449 free(session->session_tz);
450 session->session_tz = strdup( tz );
453 session->session_tz = strdup( tz );
456 return session->session_tz;
460 @brief Install a copy of a ingress string as the new default.
461 @param session Pointer to the new strdup'ed default_ingress
462 @param ingress The ingress string to be copied and installed.
464 char* osrfAppSessionSetIngress(const char* ingress) {
465 if (!ingress) return NULL;
467 free(current_ingress);
468 return current_ingress = strdup(ingress);
472 @brief Returns the current ingress value
473 @return A pointer to the installed copy of the ingress string
475 const char* osrfAppSessionGetIngress() {
476 return current_ingress;
480 @brief Find the osrfAppSession for a given session id.
481 @param session_id The session id to look for.
482 @return Pointer to the corresponding osrfAppSession if found, or NULL if not.
484 Search the global session cache for the specified session id.
486 osrfAppSession* osrf_app_session_find_session( const char* session_id ) {
488 return osrfHashGet( osrfAppSessionCache, session_id );
494 @brief Add a session to the global session cache, keyed by session id.
495 @param session Pointer to the osrfAppSession to be added.
497 If a cache doesn't exist yet, create one. It's an osrfHash using session ids for the
498 key and osrfAppSessions for the data.
500 static void _osrf_app_session_push_session( osrfAppSession* session ) {
502 if( osrfAppSessionCache == NULL )
503 osrfAppSessionCache = osrfNewHash();
504 if( osrfHashGet( osrfAppSessionCache, session->session_id ) )
505 return; // A session with this id is already in the cache. Shouldn't happen.
506 osrfHashSet( osrfAppSessionCache, session, session->session_id );
511 @brief Create an osrfAppSession for a client.
512 @param remote_service Name of the service to which to connect
513 @return Pointer to the new osrfAppSession if successful, or NULL upon error.
515 Allocate memory for an osrfAppSession, and initialize it as follows:
517 - For talking with Jabber, grab an existing transport_client. It must have been
518 already set up by a prior call to osrfSystemBootstrapClientResc().
519 - Build a Jabber ID for addressing the service.
520 - Build a session ID based on a fine-grained timestamp and a process ID. This ID is
521 intended to be unique across the system, but uniqueness is not strictly guaranteed.
522 - Initialize various other bits and scraps.
523 - Add the session to the global session cache.
525 Do @em not connect to the service at this point.
527 osrfAppSession* osrfAppSessionClientInit( const char* remote_service ) {
529 if (!remote_service) {
530 osrfLogWarning( OSRF_LOG_MARK, "No remote service specified in osrfAppSessionClientInit");
534 osrfAppSession* session = safe_malloc(sizeof(osrfAppSession));
536 // Grab an existing transport_client for talking with Jabber
537 session->transport_handle = osrfSystemGetTransportClient();
538 if( session->transport_handle == NULL ) {
539 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service 'client'");
544 // Get a list of domain names from the config settings;
545 // ignore all but the first one in the list.
546 osrfStringArray* arr = osrfNewStringArray(8);
547 osrfConfigGetValueList(NULL, arr, "/domain");
548 const char* domain = osrfStringArrayGetString(arr, 0);
550 osrfLogWarning( OSRF_LOG_MARK, "No domains specified in the OpenSRF config file");
552 osrfStringArrayFree(arr);
556 // Get a router name from the config settings.
557 char* router_name = osrfConfigGetValue(NULL, "/router_name");
559 osrfLogWarning( OSRF_LOG_MARK, "No router name specified in the OpenSRF config file");
561 osrfStringArrayFree(arr);
565 char target_buf[512];
566 target_buf[ 0 ] = '\0';
568 // Using the router name, domain, and service name,
569 // build a Jabber ID for addressing the service.
570 int len = snprintf( target_buf, sizeof(target_buf), "%s@%s/%s",
571 router_name ? router_name : "(null)",
572 domain ? domain : "(null)",
573 remote_service ? remote_service : "(null)" );
574 osrfStringArrayFree(arr);
577 if( len >= sizeof( target_buf ) ) {
578 osrfLogWarning( OSRF_LOG_MARK, "Buffer overflow for remote_id");
583 session->remote_id = strdup(target_buf);
584 session->orig_remote_id = strdup(session->remote_id);
585 session->remote_service = strdup(remote_service);
586 session->session_locale = NULL;
587 session->session_tz = NULL;
588 session->transport_error = 0;
590 session->outbuf = NULL; // Not used by client
592 #ifdef ASSUME_STATELESS
593 session->stateless = 1;
594 osrfLogDebug( OSRF_LOG_MARK, "%s session is stateless", remote_service );
596 session->stateless = 0;
597 osrfLogDebug( OSRF_LOG_MARK, "%s session is NOT stateless", remote_service );
600 /* build a chunky, random session id */
603 snprintf(id, sizeof(id), "%f.%d%ld", get_timestamp_millis(), (int)time(NULL), (long) getpid());
604 session->session_id = strdup(id);
605 osrfLogDebug( OSRF_LOG_MARK, "Building a new client session with id [%s] [%s]",
606 session->remote_service, session->session_id );
608 session->thread_trace = 0;
609 session->state = OSRF_SESSION_DISCONNECTED;
610 session->type = OSRF_SESSION_CLIENT;
612 session->userData = NULL;
613 session->userDataFree = NULL;
615 // Initialize the hash table
617 for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
618 session->request_hash[ i ] = NULL;
620 _osrf_app_session_push_session( session );
625 @brief Create an osrfAppSession for a server.
626 @param session_id The session ID. In practice this comes from the thread member of
627 the transport message from the client.
628 @param our_app The name of the service being provided.
629 @param remote_id Jabber ID of the client.
630 @return Pointer to the newly created osrfAppSession if successful, or NULL upon failure.
632 If there is already a session with the specified id, report an error. Otherwise:
634 - Allocate memory for an osrfAppSession.
635 - For talking with Jabber, grab an existing transport_client. It should have been
636 already set up by a prior call to osrfSystemBootstrapClientResc().
637 - Install a copy of the @a our_app string as remote_service.
638 - Install copies of the @a remote_id string as remote_id and orig_remote_id.
639 - Initialize various other bits and scraps.
640 - Add the session to the global session cache.
642 Do @em not respond to the client at this point.
644 osrfAppSession* osrf_app_server_session_init(
645 const char* session_id, const char* our_app, const char* remote_id ) {
647 osrfLogDebug( OSRF_LOG_MARK, "Initing server session with session id %s, service %s,"
648 " and remote_id %s", session_id, our_app, remote_id );
650 osrfAppSession* session = osrf_app_session_find_session( session_id );
652 osrfLogWarning( OSRF_LOG_MARK, "App session already exists for session id %s",
657 session = safe_malloc(sizeof(osrfAppSession));
659 // Grab an existing transport_client for talking with Jabber
660 session->transport_handle = osrfSystemGetTransportClient();
661 if( session->transport_handle == NULL ) {
662 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service '%s'", our_app );
667 // Decide from a config setting whether the session is stateless or not. However
668 // this determination is pointless because it will immediately be overruled according
669 // to the compile-time macro ASSUME_STATELESS.
671 char* statel = osrf_settings_host_value("/apps/%s/stateless", our_app );
673 stateless = atoi( statel );
676 session->remote_id = strdup(remote_id);
677 session->orig_remote_id = strdup(remote_id);
678 session->session_id = strdup(session_id);
679 session->remote_service = strdup(our_app);
680 session->stateless = stateless;
682 #ifdef ASSUME_STATELESS
683 session->stateless = 1;
685 session->stateless = 0;
688 session->thread_trace = 0;
689 session->state = OSRF_SESSION_DISCONNECTED;
690 session->type = OSRF_SESSION_SERVER;
691 session->session_locale = NULL;
692 session->session_tz = NULL;
694 session->userData = NULL;
695 session->userDataFree = NULL;
696 session->transport_error = 0;
698 // Initialize the hash table
700 for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
701 session->request_hash[ i ] = NULL;
704 session->outbuf = osrf_buffer_init( 4096 );
706 _osrf_app_session_push_session( session );
711 @brief Create a REQUEST message, send it, and save it for future reference.
712 @param session Pointer to the current session, which has the addressing information.
713 @param params One way of specifying the parameters for the method.
714 @param method_name The name of the method to be called.
715 @param protocol Protocol.
716 @param param_strings Another way of specifying the parameters for the method.
717 @return The request ID of the resulting REQUEST message, or -1 upon error.
719 DEPRECATED. Use osrfAppSessionSendRequest() instead. It is identical except that it
720 doesn't use the param_strings argument, which is redundant, confusing, and unused.
722 If @a params is non-NULL, use it to specify the parameters to the method. Otherwise
723 use @a param_strings.
725 If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
726 parameter. If @a params points to any other kind of jsonObject, pass it as a single
729 If @a params is NULL, and @a param_strings is not NULL, then each pointer in the
730 osrfStringArray must point to a JSON string encoding a parameter. Pass them.
732 At this writing, all calls to this function use @a params to pass parameters, rather than
735 This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
737 int osrfAppSessionMakeRequest(
738 osrfAppSession* session, const jsonObject* params,
739 const char* method_name, int protocol, osrfStringArray* param_strings ) {
741 osrfLogWarning( OSRF_LOG_MARK, "Function osrfAppSessionMakeRequest() is deprecated; "
742 "call osrfAppSessionSendRequest() instead" );
743 return osrfAppSessionMakeLocaleRequest( session, params,
744 method_name, protocol, param_strings, NULL );
748 @brief Create a REQUEST message, send it, and save it for future reference.
749 @param session Pointer to the current session, which has the addressing information.
750 @param params One way of specifying the parameters for the method.
751 @param method_name The name of the method to be called.
752 @param protocol Protocol.
753 @return The request ID of the resulting REQUEST message, or -1 upon error.
755 If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
756 parameter. If @a params points to any other kind of jsonObject, pass it as a single
759 This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
761 int osrfAppSessionSendRequest( osrfAppSession* session, const jsonObject* params,
762 const char* method_name, int protocol ) {
764 return osrfAppSessionMakeLocaleRequest( session, params,
765 method_name, protocol, NULL, NULL );
769 @brief Create a REQUEST message, send it, and save it for future reference.
770 @param session Pointer to the current session, which has the addressing information.
771 @param params One way of specifying the parameters for the method.
772 @param method_name The name of the method to be called.
773 @param protocol Protocol.
774 @param param_strings Another way of specifying the parameters for the method.
775 @param locale Pointer to a locale string.
776 @return The request ID of the resulting REQUEST message, or -1 upon error.
778 See the discussion of osrfAppSessionSendRequest(), which at this writing is the only
779 place that calls this function, except for the similar but deprecated function
780 osrfAppSessionMakeRequest().
782 At this writing, the @a param_strings and @a locale parameters are always NULL.
784 static int osrfAppSessionMakeLocaleRequest(
785 osrfAppSession* session, const jsonObject* params, const char* method_name,
786 int protocol, osrfStringArray* param_strings, char* locale ) {
788 if(session == NULL) return -1;
792 osrfMessage* req_msg = osrf_message_init( REQUEST, ++(session->thread_trace), protocol );
793 osrf_message_set_method(req_msg, method_name);
796 osrf_message_set_locale(req_msg, locale);
797 } else if (session->session_locale) {
798 osrf_message_set_locale(req_msg, session->session_locale);
801 osrf_message_set_tz(req_msg, session->session_tz);
803 if (!current_ingress)
804 osrfAppSessionSetIngress("opensrf");
805 osrfMessageSetIngress(req_msg, current_ingress);
808 osrf_message_set_params(req_msg, params);
814 for(i = 0; i!= param_strings->size ; i++ ) {
815 osrf_message_add_param(req_msg,
816 osrfStringArrayGetString(param_strings,i));
821 osrfAppRequest* req = _osrf_app_request_init( session, req_msg );
822 if(_osrf_app_session_send( session, req_msg ) ) {
823 osrfLogWarning( OSRF_LOG_MARK, "Error sending request message [%d]",
824 session->thread_trace );
825 _osrf_app_request_free(req);
829 osrfLogDebug( OSRF_LOG_MARK, "Pushing [%d] onto request queue for session [%s] [%s]",
830 req->request_id, session->remote_service, session->session_id );
831 add_app_request( session, req );
832 return req->request_id;
836 @brief Mark an osrfAppRequest (identified by session and ID) as complete.
837 @param session Pointer to the osrfAppSession that owns the request.
838 @param request_id Request ID of the osrfAppRequest.
840 void osrf_app_session_set_complete( osrfAppSession* session, int request_id ) {
844 osrfAppRequest* req = find_app_request( session, request_id );
850 @brief Determine whether a osrfAppRequest, identified by session and ID, is complete.
851 @param session Pointer to the osrfAppSession that owns the request.
852 @param request_id Request ID of the osrfAppRequest.
853 @return Non-zero if the request is complete; zero if it isn't, or if it can't be found.
855 int osrf_app_session_request_complete( const osrfAppSession* session, int request_id ) {
859 osrfAppRequest* req = find_app_request( session, request_id );
861 return req->complete;
867 @brief Reset the remote ID of a session to its original remote ID.
868 @param session Pointer to the osrfAppSession to be reset.
870 void osrf_app_session_reset_remote( osrfAppSession* session ){
874 osrfLogDebug( OSRF_LOG_MARK, "App Session [%s] [%s] resetting remote id to %s",
875 session->remote_service, session->session_id, session->orig_remote_id );
877 osrf_app_session_set_remote( session, session->orig_remote_id );
881 @brief Set a session's remote ID to a specified value.
882 @param session Pointer to the osrfAppSession whose remote ID is to be set.
883 @param remote_id Pointer to the new remote id.
885 void osrf_app_session_set_remote( osrfAppSession* session, const char* remote_id ) {
886 if( session == NULL || remote_id == NULL )
889 if( session->remote_id ) {
890 if( strlen(session->remote_id) >= strlen(remote_id) ) {
891 // There's enough room; just copy it
892 strcpy(session->remote_id, remote_id);
894 free(session->remote_id );
895 session->remote_id = strdup( remote_id );
898 session->remote_id = strdup( remote_id );
902 @brief Append an osrfMessage to the list of responses to an osrfAppRequest.
903 @param session Pointer to the osrfAppSession that owns the request.
904 @param msg Pointer to the osrfMessage to be added.
906 The thread_trace member of the osrfMessage is the request_id of the osrfAppRequest.
907 Find the corresponding request in the session and append the osrfMessage to its list.
909 void osrf_app_session_push_queue( osrfAppSession* session, osrfMessage* msg ) {
910 if( session && msg ) {
911 osrfAppRequest* req = find_app_request( session, msg->thread_trace );
913 _osrf_app_request_push_queue( req, msg );
918 @brief Connect to the remote service.
919 @param session Pointer to the osrfAppSession for the service.
920 @return 1 if successful, or 0 if not.
922 If already connected, exit immediately, reporting success. Otherwise, build a CONNECT
923 message and send it to the service. Wait for up to five seconds for an acknowledgement.
925 The timeout value is currently hard-coded. Perhaps it should be configurable.
927 int osrfAppSessionConnect( osrfAppSession* session ) {
932 if(session->state == OSRF_SESSION_CONNECTED) {
936 int timeout = 5; /* XXX CONFIG VALUE */
938 osrfLogDebug( OSRF_LOG_MARK, "AppSession connecting to %s", session->remote_id );
940 /* defaulting to protocol 1 for now */
941 osrfMessage* con_msg = osrf_message_init( CONNECT, session->thread_trace, 1 );
943 // Address this message to the router
944 osrf_app_session_reset_remote( session );
945 session->state = OSRF_SESSION_CONNECTING;
946 int ret = _osrf_app_session_send( session, con_msg );
947 osrfMessageFree(con_msg);
951 time_t start = time(NULL);
952 time_t remaining = (time_t) timeout;
954 // Wait for the acknowledgement. We look for it repeatedly because, under the covers,
955 // we may receive and process messages other than the one we're looking for.
956 while( session->state != OSRF_SESSION_CONNECTED && remaining >= 0 ) {
957 osrf_app_session_queue_wait( session, remaining, NULL );
958 if(session->transport_error) {
959 osrfLogError(OSRF_LOG_MARK, "cannot communicate with %s", session->remote_service);
962 remaining -= (int) (time(NULL) - start);
965 if(session->state == OSRF_SESSION_CONNECTED)
966 osrfLogDebug( OSRF_LOG_MARK, " * Connected Successfully to %s", session->remote_service );
968 if(session->state != OSRF_SESSION_CONNECTED)
975 @brief Disconnect from the remote service. No response is expected.
976 @param session Pointer to the osrfAppSession to be disconnected.
977 @return 1 in all cases.
979 If we're already disconnected, return immediately without doing anything. Likewise if
980 we have a stateless session and we're in the process of connecting. Otherwise, send a
981 DISCONNECT message to the service.
983 int osrf_app_session_disconnect( osrfAppSession* session){
987 if(session->state == OSRF_SESSION_DISCONNECTED)
990 if(session->stateless && session->state != OSRF_SESSION_CONNECTED) {
991 osrfLogDebug( OSRF_LOG_MARK,
992 "Exiting disconnect on stateless session %s",
993 session->session_id);
997 osrfLogDebug(OSRF_LOG_MARK, "AppSession disconnecting from %s", session->remote_id );
999 osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
1000 _osrf_app_session_send( session, dis_msg );
1001 session->state = OSRF_SESSION_DISCONNECTED;
1003 osrfMessageFree( dis_msg );
1004 osrf_app_session_reset_remote( session );
1009 @brief Resend a request message, as specified by session and request id.
1010 @param session Pointer to the osrfAppSession.
1011 @param req_id Request ID for the request to be resent.
1012 @return Zero if successful, or if the specified request cannot be found; 1 if the
1013 request is already complete, or if the attempt to resend the message fails.
1015 The choice of return codes may seem seem capricious, but at this writing nothing
1016 pays any attention to the return code anyway.
1018 int osrf_app_session_request_resend( osrfAppSession* session, int req_id ) {
1019 osrfAppRequest* req = find_app_request( session, req_id );
1024 } else if(!req->complete) {
1025 osrfLogDebug( OSRF_LOG_MARK, "Resending request [%d]", req->request_id );
1026 rc = _osrf_app_session_send( req->session, req->payload );
1035 @brief Send one or more osrfMessages to the remote service or client.
1036 @param session Pointer to the osrfAppSession responsible for sending the message(s).
1037 @param msgs Pointer to an array of pointers to osrfMessages.
1038 @param size How many messages to send.
1039 @return 0 upon success, or -1 upon failure.
1041 static int osrfAppSessionSendBatch( osrfAppSession* session, osrfMessage* msgs[], int size ) {
1043 if( !(session && msgs && size > 0) ) return -1;
1046 osrfMessage* msg = msgs[0];
1050 // First grab and process any input messages, for any app session. This gives us
1051 // a chance to see any CONNECT or DISCONNECT messages that may have arrived. We
1052 // may also see some unrelated messages, but we have to process those sooner or
1053 // later anyway, so we might as well do it now.
1054 osrf_app_session_queue_wait( session, 0, NULL );
1056 if(session->state != OSRF_SESSION_CONNECTED) {
1058 if(session->stateless) { /* stateless session always send to the root listener */
1059 osrf_app_session_reset_remote(session);
1063 /* do an auto-connect if necessary */
1064 if( ! session->stateless &&
1065 (msg->m_type != CONNECT) &&
1066 (msg->m_type != DISCONNECT) &&
1067 (session->state != OSRF_SESSION_CONNECTED) ) {
1069 if(!osrfAppSessionConnect( session ))
1076 // Translate the collection of osrfMessages into a JSON array
1077 char* string = osrfMessageSerializeBatch(msgs, size);
1079 // Send the JSON as the payload of a transport_message
1081 retval = osrfSendTransportPayload( session, string );
1089 @brief Split a given string into one or more transport result messages and send it
1090 @param session Pointer to the osrfAppSession responsible for sending the message(s).
1091 @param request_id Request ID of the osrfAppRequest.
1092 @param payload A string to be sent via Jabber.
1093 @param payload_size length of payload
1094 @param chunk_size chunk_size to use
1096 @return 0 upon success, or -1 upon failure.
1098 int osrfSendChunkedResult(
1099 osrfAppSession* session, int request_id, const char* payload,
1100 size_t payload_size, size_t chunk_size ) {
1104 for (i = 0; i < payload_size; i += chunk_size) {
1105 osrfMessage* msg = osrf_message_init(RESULT, request_id, 1);
1106 osrf_message_set_status_info(msg,
1107 "osrfResultPartial",
1112 // see how long this chunk is. If this is the last
1113 // chunk, it will likely be less than chunk_size
1114 int partial_size = strlen(&payload[i]);
1115 if (partial_size > chunk_size)
1116 partial_size = chunk_size;
1118 // substr(data, i, partial_size)
1119 char partial_buf[partial_size + 1];
1120 memcpy(partial_buf, &payload[i], partial_size);
1121 partial_buf[partial_size] = '\0';
1123 // package the partial chunk as a JSON string object
1124 jsonObject* partial_obj = jsonNewObject(partial_buf);
1125 osrf_message_set_result(msg, partial_obj);
1126 jsonObjectFree(partial_obj);
1128 // package the osrf message within an array then
1129 // serialize to json for delivery
1130 jsonObject* arr = jsonNewObject(NULL);
1132 // msg json freed when arr is freed
1133 jsonObjectPush(arr, osrfMessageToJSON(msg));
1134 char* json = jsonObjectToJSON(arr);
1136 osrfSendTransportPayload(session, json);
1137 osrfMessageFree(msg);
1138 jsonObjectFree(arr);
1142 // all chunks sent; send the final partial-complete msg
1143 osrfMessage* msg = osrf_message_init(RESULT, request_id, 1);
1144 osrf_message_set_status_info(msg,
1145 "osrfResultPartialComplete",
1146 "Partial Response Finalized",
1147 OSRF_STATUS_NOCONTENT
1150 jsonObject* arr = jsonNewObject(NULL);
1151 jsonObjectPush(arr, osrfMessageToJSON(msg));
1152 char* json = jsonObjectToJSON(arr);
1153 osrfSendTransportPayload(session, json);
1154 osrfMessageFree(msg);
1155 jsonObjectFree(arr);
1162 @brief Wrap a given string in a transport message and send it.
1163 @param session Pointer to the osrfAppSession responsible for sending the message(s).
1164 @param payload A string to be sent via Jabber.
1165 @return 0 upon success, or -1 upon failure.
1167 In practice the payload is normally a JSON string, but this function assumes nothing
1170 int osrfSendTransportPayload( osrfAppSession* session, const char* payload ) {
1171 transport_message* t_msg = message_init(
1172 payload, "", session->session_id, session->remote_id, NULL );
1173 message_set_osrf_xid( t_msg, osrfLogGetXid() );
1175 int retval = client_send_message( session->transport_handle, t_msg );
1177 osrfLogError( OSRF_LOG_MARK, "client_send_message failed, exit()ing immediately" );
1181 osrfLogInfo(OSRF_LOG_MARK, "[%s] sent %d bytes of data to %s",
1182 session->remote_service, strlen( payload ), t_msg->recipient );
1184 osrfLogDebug( OSRF_LOG_MARK, "Sent: %s", payload );
1186 message_free( t_msg );
1191 @brief Send a single osrfMessage to the remote service or client.
1192 @param session Pointer to the osrfAppSession.
1193 @param msg Pointer to the osrfMessage to be sent.
1194 @return zero upon success, or 1 upon failure.
1196 A thin wrapper. Create an array of one element, and pass it to osrfAppSessionSendBatch().
1198 static int _osrf_app_session_send( osrfAppSession* session, osrfMessage* msg ){
1199 if( !(session && msg) )
1203 return - osrfAppSessionSendBatch( session, a, 1 );
1208 @brief Wait for any input messages to arrive, and process them as needed.
1209 @param session Pointer to the osrfAppSession whose transport_session we will use.
1210 @param timeout How many seconds to wait for the first input message.
1211 @param recvd Pointer to an boolean int. If you receive at least one message, set the boolean
1212 to true; otherwise set it to false.
1213 @return 0 upon success (even if a timeout occurs), or -1 upon failure.
1215 A thin wrapper for osrf_stack_process(). The timeout applies only to the first
1216 message; process subsequent messages if they are available, but don't wait for them.
1218 The first parameter identifies an osrfApp session, but all we really use it for is to
1219 get a pointer to the transport_session. Typically, a given process opens only a single
1220 transport_session (to talk to the Jabber server), and all app sessions in that process
1221 use the same transport_session.
1223 Hence this function indiscriminately waits for input messages for all osrfAppSessions
1224 tied to the same Jabber session, not just the one specified.
1226 Dispatch each message to the appropriate processing routine, depending on its type
1227 and contents, and on whether we're acting as a client or as a server for that message.
1228 For example, a response to a request may be appended to the input queue of the
1229 relevant request. A server session receiving a REQUEST message may execute the
1230 requested method. And so forth.
1232 int osrf_app_session_queue_wait( osrfAppSession* session, int timeout, int* recvd ){
1233 if(session == NULL) return 0;
1234 osrfLogDebug(OSRF_LOG_MARK, "AppSession in queue_wait with timeout %d", timeout );
1235 return osrf_stack_process(session->transport_handle, timeout, recvd);
1239 @brief Shut down and destroy an osrfAppSession.
1240 @param session Pointer to the osrfAppSession to be destroyed.
1242 If this is a client session, send a DISCONNECT message.
1244 Remove the session from the global session cache.
1246 Free all associated resources, including any pending osrfAppRequests.
1248 void osrfAppSessionFree( osrfAppSession* session ){
1249 if(session == NULL) return;
1253 osrfLogDebug(OSRF_LOG_MARK, "AppSession [%s] [%s] destroying self and deleting requests",
1254 session->remote_service, session->session_id );
1255 /* disconnect if we're a client */
1256 if(session->type == OSRF_SESSION_CLIENT
1257 && session->state != OSRF_SESSION_DISCONNECTED ) {
1258 osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
1259 _osrf_app_session_send( session, dis_msg );
1260 osrfMessageFree(dis_msg);
1263 /* Remove self from the global session cache */
1265 osrfHashRemove( osrfAppSessionCache, session->session_id );
1267 /* Free the memory */
1269 if( session->userDataFree && session->userData )
1270 session->userDataFree(session->userData);
1272 if(session->session_locale)
1273 free(session->session_locale);
1275 if(session->session_tz)
1276 free(session->session_tz);
1278 free(session->remote_id);
1279 free(session->orig_remote_id);
1280 free(session->session_id);
1281 free(session->remote_service);
1283 // Free the request hash
1285 for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i ) {
1286 osrfAppRequest* app = session->request_hash[ i ];
1288 osrfAppRequest* next = app->next;
1289 _osrf_app_request_free( app );
1294 if( session->outbuf )
1295 osrf_buffer_free( session->outbuf );
1301 @brief Wait for a response to a given request, subject to a timeout.
1302 @param session Pointer to the osrfAppSession that owns the request.
1303 @param req_id Request ID for the request.
1304 @param timeout How many seconds to wait.
1305 @return A pointer to the received osrfMessage if one arrives; otherwise NULL.
1307 A thin wrapper. Given a session and a request ID, look up the corresponding request
1308 and pass it to _osrf_app_request_recv().
1310 osrfMessage* osrfAppSessionRequestRecv(
1311 osrfAppSession* session, int req_id, int timeout ) {
1312 if(req_id < 0 || session == NULL)
1314 osrfAppRequest* req = find_app_request( session, req_id );
1315 return _osrf_app_request_recv( req, timeout );
1319 @brief In response to a specified request, send a payload of data to a client.
1320 @param ses Pointer to the osrfAppSession that owns the request.
1321 @param requestId Request ID of the osrfAppRequest.
1322 @param data Pointer to a jsonObject containing the data payload.
1323 @return 0 upon success, or -1 upon failure.
1325 Translate the jsonObject to a JSON string, and send it wrapped in a RESULT message.
1327 The only failure detected is if either of the two pointer parameters is NULL.
1329 int osrfAppRequestRespond( osrfAppSession* ses, int requestId, const jsonObject* data ) {
1330 if( !ses || ! data )
1333 osrfMessage* msg = osrf_message_init( RESULT, requestId, 1 );
1334 osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
1335 char* json = jsonObjectToJSON( data );
1337 osrf_message_set_result_content( msg, json );
1338 _osrf_app_session_send( ses, msg );
1341 osrfMessageFree( msg );
1348 @brief Send one or two messages to a client in response to a specified request.
1349 @param ses Pointer to the osrfAppSession that owns the request.
1350 @param requestId Request ID of the osrfAppRequest.
1351 @param data Pointer to a jsonObject containing the data payload.
1352 @return Zero in all cases.
1354 If the @a data parameter is not NULL, translate the jsonObject into a JSON string, and
1355 incorporate that string into a RESULT message as as the payload . Also build a STATUS
1356 message indicating that the response is complete. Send both messages bundled together
1357 in the same transport_message.
1359 If the @a data parameter is NULL, send only a STATUS message indicating that the response
1362 int osrfAppRequestRespondComplete(
1363 osrfAppSession* ses, int requestId, const jsonObject* data ) {
1365 osrfMessage* status = osrf_message_init( STATUS, requestId, 1);
1366 osrf_message_set_status_info( status, "osrfConnectStatus", "Request Complete",
1367 OSRF_STATUS_COMPLETE );
1370 char* json = jsonObjectToJSON(data);
1371 size_t raw_size = strlen(json);
1372 size_t extra_size = osrfXmlEscapingLength(json);
1373 size_t data_size = raw_size + extra_size;
1374 size_t chunk_size = OSRF_MSG_CHUNK_SIZE;
1376 if (data_size > chunk_size) // calculate an escape-scaled chunk size
1377 chunk_size = ((double)raw_size / (double)data_size) * (double)chunk_size;
1379 if (chunk_size > 0 && chunk_size < raw_size) {
1380 // chunking -- response message exceeds max message size.
1381 // break it up into chunks for partial delivery
1383 osrfSendChunkedResult(ses, requestId, json, raw_size, chunk_size);
1384 osrfAppSessionSendBatch( ses, &status, 1 );
1387 // message doesn't need to be chunked
1388 osrfMessage* payload = osrf_message_init( RESULT, requestId, 1 );
1389 osrf_message_set_status_info( payload, NULL, "OK", OSRF_STATUS_OK );
1391 osrf_message_set_result_content( payload, json );
1397 osrfAppSessionSendBatch( ses, ms, 2 );
1399 osrfMessageFree( payload );
1405 osrfAppSessionSendBatch( ses, &status, 1 );
1408 osrfMessageFree( status );
1414 @brief Send a STATUS message, for a specified request, back to the client.
1415 @param ses Pointer to the osrfAppSession connected to the client.
1416 @param type A numeric code denoting the status.
1417 @param name A string naming the status.
1418 @param reqId The request ID of the request.
1419 @param message A brief message describing the status.
1420 @return 0 upon success, or -1 upon failure.
1422 The only detected failure is when the @a ses parameter is NULL.
1424 int osrfAppSessionStatus( osrfAppSession* ses, int type,
1425 const char* name, int reqId, const char* message ) {
1428 osrfMessage* msg = osrf_message_init( STATUS, reqId, 1);
1429 osrf_message_set_status_info( msg, name, message, type );
1430 _osrf_app_session_send( ses, msg );
1431 osrfMessageFree( msg );
1438 @brief Free the global session cache.
1440 Note that the osrfHash that implements the global session cache does @em not have a
1441 callback function installed for freeing its cargo. As a result, any remaining
1442 osrfAppSessions are leaked, along with all the osrfAppRequests and osrfMessages they
1445 void osrfAppSessionCleanup( void ) {
1446 osrfHashFree(osrfAppSessionCache);
1447 osrfAppSessionCache = NULL;
1451 @brief Arrange for immediate termination of the process.
1452 @param ses Pointer to the current osrfAppSession.
1454 Typical use case: a server drone loses its database connection, thereby becoming useless.
1455 It terminates so that it will not receive further requests, being unable to service them.
1457 void osrfAppSessionPanic( osrfAppSession* ses ) {