Fix LP#1883169 by using growing_buffer
[OpenSRF.git] / src / gateway / osrf_json_gateway.c
1 #include "apachetools.h"
2 #include "opensrf/osrf_app_session.h"
3 #include "opensrf/osrf_system.h"
4 #include "opensrf/osrfConfig.h"
5 #include <opensrf/osrf_json.h>
6 #include <opensrf/osrf_json_xml.h>
7 #include <opensrf/osrf_legacy_json.h>
8 #include <opensrf/string_array.h>
9 #include <sys/time.h>
10 #include <sys/resource.h>
11 #include <unistd.h>
12 #include <strings.h>
13
14
15 #define MODULE_NAME "osrf_json_gateway_module"
16 #define GATEWAY_CONFIG "OSRFGatewayConfig"
17 #define DEFAULT_LOCALE "OSRFDefaultLocale"
18 #define CONFIG_CONTEXT "gateway"
19 #define JSON_PROTOCOL "OSRFGatewayLegacyJSON"
20 #define GATEWAY_USE_LEGACY_JSON 0
21
22 typedef struct {
23         int legacyJSON;
24 } osrf_json_gateway_dir_config;
25
26
27 module AP_MODULE_DECLARE_DATA osrf_json_gateway_module;
28
29 char* osrf_json_default_locale = "en-US";
30 char* osrf_json_gateway_config_file = NULL;
31 int bootstrapped = 0;
32 int numserved = 0;
33 osrfStringArray* allowedOrigins = NULL;
34
35 static const char* osrf_json_gateway_set_default_locale(cmd_parms *parms,
36                 void *config, const char *arg) {
37         if (arg)
38                 osrf_json_default_locale = (char*) arg;
39         return NULL;
40 }
41
42 static const char* osrf_json_gateway_set_config(cmd_parms *parms, void *config, const char *arg) {
43         osrf_json_gateway_config_file = (char*) arg;
44         return NULL;
45 }
46
47 static const char* osrf_json_gateway_set_json_proto(cmd_parms *parms, void *config, const char *arg) {
48         osrf_json_gateway_dir_config* cfg = (osrf_json_gateway_dir_config*) config;
49         cfg->legacyJSON = (!strcasecmp((char*) arg, "true")) ? 1 : 0;
50         return NULL;
51 }
52
53 /* tell apache about our commands */
54 static const command_rec osrf_json_gateway_cmds[] = {
55         AP_INIT_TAKE1( GATEWAY_CONFIG, osrf_json_gateway_set_config,
56                         NULL, RSRC_CONF, "osrf json gateway config file"),
57         AP_INIT_TAKE1( DEFAULT_LOCALE, osrf_json_gateway_set_default_locale,
58                         NULL, RSRC_CONF, "osrf json gateway default locale"),
59         AP_INIT_TAKE1( JSON_PROTOCOL, osrf_json_gateway_set_json_proto,
60                         NULL, ACCESS_CONF, "osrf json gateway config file"),
61         {NULL}
62 };
63
64
65 static void* osrf_json_gateway_create_dir_config( apr_pool_t* p, char* dir) {
66         osrf_json_gateway_dir_config* cfg = (osrf_json_gateway_dir_config*)
67                         apr_palloc(p, sizeof(osrf_json_gateway_dir_config));
68         cfg->legacyJSON = GATEWAY_USE_LEGACY_JSON;
69         return (void*) cfg;
70 }
71
72 static apr_status_t child_exit(void* data) {
73         osrfLogInfo(OSRF_LOG_MARK, "Disconnecting on child cleanup...");
74         osrf_system_shutdown();
75         return OK;
76 }
77
78 static void osrf_json_gateway_child_init(apr_pool_t *p, server_rec *s) {
79
80         char* cfg = osrf_json_gateway_config_file;
81         char buf[32];
82         int t = time(NULL);
83         snprintf(buf, sizeof(buf), "%d", t);
84
85         if( ! osrfSystemBootstrapClientResc( cfg, CONFIG_CONTEXT, buf ) ) {
86                 ap_log_error( APLOG_MARK, APLOG_ERR, 0, s,
87                         "Unable to Bootstrap OpenSRF Client with config %s..", cfg);
88                 return;
89         }
90
91         allowedOrigins = osrfNewStringArray(4);
92         osrfConfigGetValueList(NULL, allowedOrigins, "/cross_origin/origin");
93
94         bootstrapped = 1;
95         osrfLogInfo(OSRF_LOG_MARK, "Bootstrapping gateway child for requests");
96
97         // when this pool is cleaned up, it means the child
98         // process is going away.  register some cleanup code
99         // XXX causes us to disconnect even for clone()'d process cleanup (as in mod_cgi)
100         //apr_pool_cleanup_register(p, NULL, child_exit, apr_pool_cleanup_null);
101 }
102
103 static int osrf_json_gateway_method_handler (request_rec *r) {
104
105         /* make sure we're needed first thing*/
106         if (strcmp(r->handler, MODULE_NAME )) return DECLINED;
107
108         crossOriginHeaders(r, allowedOrigins);
109
110         osrf_json_gateway_dir_config* dir_conf =
111                 ap_get_module_config(r->per_dir_config, &osrf_json_gateway_module);
112
113
114         /* provide 2 different JSON parsers and serializers to support legacy JSON */
115         jsonObject* (*parseJSONFunc) (const char*) = legacy_jsonParseString;
116         char* (*jsonToStringFunc) (const jsonObject*) = legacy_jsonObjectToJSON;
117
118         if(dir_conf->legacyJSON) {
119                 ap_log_rerror( APLOG_MARK, APLOG_DEBUG, 0, r, "Using legacy JSON");
120
121         } else {
122                 parseJSONFunc = jsonParse;
123                 jsonToStringFunc = jsonObjectToJSON;
124         }
125
126
127         osrfLogDebug(OSRF_LOG_MARK, "osrf gateway: entered request handler");
128
129         /* verify we are connected */
130         if( !bootstrapped || !osrfSystemGetTransportClient()) {
131                 ap_log_rerror( APLOG_MARK, APLOG_ERR, 0, r, "Cannot process request "
132                                 "because the OpenSRF JSON gateway has not been bootstrapped...");
133                 usleep( 100000 ); /* 100 milliseconds */
134                 exit(1);
135         }
136
137         osrfLogSetAppname("osrf_json_gw");
138         osrfAppSessionSetIngress("gateway-v1");
139
140         char* osrf_locale   = NULL;
141         char* param_locale  = NULL;  /* locale for this call */
142         char* service       = NULL;  /* service to connect to */
143         char* method        = NULL;  /* method to perform */
144         char* format        = NULL;  /* method to perform */
145         char* a_l           = NULL;  /* request api level */
146         char* input_format  = NULL;  /* POST data format, defaults to 'format' */
147         int   isXML         = 0;
148         int   api_level     = 1;
149
150         r->allowed |= (AP_METHOD_BIT << M_GET);
151         r->allowed |= (AP_METHOD_BIT << M_POST);
152
153         osrfLogDebug(OSRF_LOG_MARK, "osrf gateway: parsing URL params");
154         osrfStringArray* mparams = NULL;
155         osrfStringArray* params  = apacheParseParms(r); /* free me */
156         param_locale             = apacheGetFirstParamValue( params, "locale" );
157         service                  = apacheGetFirstParamValue( params, "service" );
158         method                   = apacheGetFirstParamValue( params, "method" );
159         format                   = apacheGetFirstParamValue( params, "format" );
160         input_format             = apacheGetFirstParamValue( params, "input_format" );
161         a_l                      = apacheGetFirstParamValue( params, "api_level" );
162         mparams                  = apacheGetParamValues( params, "param" ); /* free me */
163
164         if(format == NULL)
165                 format = strdup( "json" );
166         if(input_format == NULL)
167                 input_format = strdup( format );
168
169         /* set the user defined timeout value */
170         int timeout = 60;
171         char* tout = apacheGetFirstParamValue( params, "timeout" ); /* request timeout in seconds */
172         if( tout ) {
173                 timeout = atoi(tout);
174                 osrfLogDebug(OSRF_LOG_MARK, "Client supplied timeout of %d", timeout);
175                 free( tout );
176         }
177
178         if (a_l) {
179                 api_level = atoi(a_l);
180                 free( a_l );
181         }
182
183         if (!strcasecmp(format, "xml")) {
184                 isXML = 1;
185                 ap_set_content_type(r, "application/xml");
186         } else {
187                 ap_set_content_type(r, "text/plain");
188         }
189
190         free( format );
191         int ret = OK;
192
193         /* ----------------------------------------------------------------- */
194         /* Grab the requested locale using the Accept-Language header*/
195
196
197         if ( !param_locale ) {
198                 if ( apr_table_get(r->headers_in, "X-OpenSRF-Language") ) {
199                         param_locale = strdup( apr_table_get(r->headers_in, "X-OpenSRF-Language") );
200                 } else if ( apr_table_get(r->headers_in, "Accept-Language") ) {
201                         param_locale = strdup( apr_table_get(r->headers_in, "Accept-Language") );
202                 }
203         }
204
205
206         if (param_locale) {
207                 growing_buffer* osrf_locale_buf = buffer_init(16);
208                 if (index(param_locale, ',')) {
209                         int ind = index(param_locale, ',') - param_locale;
210                         int i;
211                         for ( i = 0; i < ind && i < 128; i++ )
212                                 buffer_add_char( osrf_locale_buf, param_locale[i] );
213                 } else {
214                         buffer_add( osrf_locale_buf, param_locale );
215                 }
216
217                 free(param_locale);
218                 osrf_locale = buffer_release( osrf_locale_buf );
219         } else {
220                 osrf_locale = strdup( osrf_json_default_locale );
221         }
222         /* ----------------------------------------------------------------- */
223
224
225         if(!(service && method)) {
226
227                 osrfLogError(OSRF_LOG_MARK,
228                         "Service [%s] not found or not allowed", service);
229                 ret = HTTP_NOT_FOUND;
230
231         } else {
232
233                 /* This will log all heaers to the apache error log
234                 const apr_array_header_t* arr = apr_table_elts(r->headers_in);
235                 const void* ptr;
236
237                 while( (ptr = apr_array_pop(arr)) ) {
238                         apr_table_entry_t* e = (apr_table_entry_t*) ptr;
239                         fprintf(stderr, "Table entry: %s : %s\n", e->key, e->val );
240                 }
241                 fflush(stderr);
242                 */
243
244                 osrfAppSession* session = osrfAppSessionClientInit(service);
245                 osrf_app_session_set_locale(session, osrf_locale);
246
247                 double starttime = get_timestamp_millis();
248                 int req_id = -1;
249
250                 if(!strcasecmp(input_format, "json")) {
251                         jsonObject * arr = jsonNewObject(NULL);
252
253                         const char* str;
254                         int i = 0;
255
256                         while( (str = osrfStringArrayGetString(mparams, i++)) )
257                                 jsonObjectPush(arr, parseJSONFunc(str));
258
259                         req_id = osrfAppSessionSendRequest( session, arr, method, api_level );
260                         jsonObjectFree(arr);
261                 } else {
262
263                         /**
264                         * If we receive XML method params, convert each param to a JSON object
265                         * and pass the array of JSON object params to the method */
266                         if(!strcasecmp(input_format, "xml")) {
267                                 jsonObject* jsonParams = jsonNewObject(NULL);
268
269                                 const char* str;
270                                 int i = 0;
271                                 while( (str = osrfStringArrayGetString(mparams, i++)) ) {
272                                         jsonObjectPush(jsonParams, jsonXMLToJSONObject(str));
273                                 }
274
275                                 req_id = osrfAppSessionSendRequest( session, jsonParams, method, api_level );
276                                 jsonObjectFree(jsonParams);
277                         }
278                 }
279
280
281                 if( req_id == -1 ) {
282                         osrfLogError(OSRF_LOG_MARK, "I am unable to communicate with opensrf..going away...");
283                         osrfAppSessionFree(session);
284                         /* we don't want to spawn an intense re-forking storm
285                          * if there is no jabber server.. so give it some time before we die */
286                         usleep( 100000 ); /* 100 milliseconds */
287                         exit(1);
288                 }
289
290
291                 /* ----------------------------------------------------------------- */
292                 /* log all requests to the activity log */
293                 const char* authtoken = apr_table_get(r->headers_in, "X-OILS-Authtoken");
294                 if(!authtoken) authtoken = "";
295                 growing_buffer* act = buffer_init(128);
296 #ifdef APACHE_MIN_24
297                 buffer_fadd(act, "[%s] [%s] [%s] %s %s", r->connection->client_ip,
298                         authtoken, osrf_locale, service, method );
299 #else
300                 buffer_fadd(act, "[%s] [%s] [%s] %s %s", r->connection->remote_ip,
301                         authtoken, osrf_locale, service, method );
302 #endif
303
304                 const char* str; int i = 0;
305                 int redact_params = 0;
306                 while( (str = osrfStringArrayGetString(log_protect_arr, i++)) ) {
307                         //osrfLogInternal(OSRF_LOG_MARK, "Checking for log protection [%s]", str);
308                         if(!strncmp(method, str, strlen(str))) {
309                                 redact_params = 1;
310                                 break;
311                         }
312                 }
313                 if(redact_params) {
314                         OSRF_BUFFER_ADD(act, " **PARAMS REDACTED**");
315                 } else {
316                         i = 0;
317                         while( (str = osrfStringArrayGetString(mparams, i++)) ) {
318                                 if( i == 1 ) {
319                                         OSRF_BUFFER_ADD(act, " ");
320                                         OSRF_BUFFER_ADD(act, str);
321                                 } else {
322                                         OSRF_BUFFER_ADD(act, ", ");
323                                         OSRF_BUFFER_ADD(act, str);
324                                 }
325                         }
326                 }
327
328                 osrfLogActivity( OSRF_LOG_MARK, "%s", act->buf );
329                 buffer_free(act);
330                 /* ----------------------------------------------------------------- */
331
332
333                 osrfMessage* omsg = NULL;
334
335                 int statuscode = 200;
336
337                 /* kick off the object */
338                 if (isXML)
339                         ap_rputs( "<response xmlns=\"http://opensrf.org/-/namespaces/gateway/v1\"><payload>",
340                                 r );
341                 else
342                         ap_rputs("{\"payload\":[", r);
343
344                 int morethan1       = 0;
345                 char* statusname    = NULL;
346                 char* statustext    = NULL;
347                 char* output        = NULL;
348
349                 while((omsg = osrfAppSessionRequestRecv( session, req_id, timeout ))) {
350
351                         statuscode = omsg->status_code;
352                         const jsonObject* res;
353
354                         if( ( res = osrfMessageGetResult(omsg)) ) {
355
356                                 if (isXML) {
357                                         output = jsonObjectToXML( res );
358                                 } else {
359                                         output = jsonToStringFunc( res );
360                                         if( morethan1 ) ap_rputs(",", r); /* comma between JSON array items */
361                                 }
362                                 ap_rputs(output, r);
363                                 free(output);
364                                 morethan1 = 1;
365
366                         } else {
367
368                                 if( statuscode > 299 ) { /* the request returned a low level error */
369                                         statusname = omsg->status_name ? strdup(omsg->status_name)
370                                                 : strdup("Unknown Error");
371                                         statustext = omsg->status_text ? strdup(omsg->status_text) 
372                                                 : strdup("No Error Message");
373                                         osrfLogError( OSRF_LOG_MARK,  "Gateway received error: %s", statustext );
374                                 }
375                         }
376
377                         osrfMessageFree(omsg);
378                         if(statusname) break;
379                 }
380
381                 double duration = get_timestamp_millis() - starttime;
382                 osrfLogDebug(OSRF_LOG_MARK, "gateway request took %f seconds", duration);
383
384
385                 if (isXML)
386                         ap_rputs("</payload>", r);
387                 else
388                         ap_rputs("]",r); /* finish off the payload array */
389
390                 if(statusname) {
391
392                         /* add a debug field if the request died */
393                         ap_log_rerror( APLOG_MARK, APLOG_INFO, 0, r,
394                                         "OpenSRF JSON Request returned error: %s -> %s", statusname, statustext );
395                         growing_buffer* buf = buffer_init(512);
396
397                         if (isXML)
398                                 buffer_fadd(buf, "<debug>\"%s : %s\"</debug>", statusname, statustext);
399
400                         else {
401                                 buffer_fadd(buf, "%s : %s", statusname, statustext);
402                                 jsonObject* tmp = jsonNewObject(buf->buf);
403                                 char* j = jsonToStringFunc(tmp);
404                                 buffer_reset(buf);
405                                 buffer_fadd(buf, ",\"debug\": %s", j);
406                                 free(j);
407                                 jsonObjectFree(tmp);
408                         }
409
410                         ap_rputs(buf->buf, r);
411
412                         buffer_free(buf);
413                         free(statusname);
414                         free(statustext);
415                 }
416
417                 /* insert the status code */
418                 char buf[32];
419
420                 if (isXML)
421                         snprintf(buf, sizeof(buf), "<status>%d</status>", statuscode );
422                 else
423                         snprintf(buf, sizeof(buf), ",\"status\":%d", statuscode );
424
425                 ap_rputs( buf, r );
426
427                 if (isXML)
428                         ap_rputs("</response>", r);
429                 else
430                         ap_rputs( "}", r ); /* finish off the object */
431
432                 osrfAppSessionFree(session);
433         }
434
435         osrfLogInfo(OSRF_LOG_MARK, "Completed processing service=%s, method=%s", service, method);
436         osrfStringArrayFree(params);
437         osrfStringArrayFree(mparams);
438         free( osrf_locale );
439         free( input_format );
440         free( method );
441         free( service );
442
443         osrfLogDebug(OSRF_LOG_MARK, "Gateway served %d requests", ++numserved);
444         osrfLogClearXid();
445
446         return ret;
447 }
448
449
450
451 static void osrf_json_gateway_register_hooks (apr_pool_t *p) {
452         ap_hook_handler(osrf_json_gateway_method_handler, NULL, NULL, APR_HOOK_MIDDLE);
453         ap_hook_child_init(osrf_json_gateway_child_init,NULL,NULL,APR_HOOK_MIDDLE);
454 }
455
456
457 module AP_MODULE_DECLARE_DATA osrf_json_gateway_module = {
458         STANDARD20_MODULE_STUFF,
459         osrf_json_gateway_create_dir_config,
460         NULL,
461         NULL,
462         NULL,
463         osrf_json_gateway_cmds,
464         osrf_json_gateway_register_hooks,
465 };