]> err.no Git - sope/blob - sope-appserver/mod_ngobjweb/handler.c
added strict OSX bundle dependencies
[sope] / sope-appserver / mod_ngobjweb / handler.c
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "common.h"
23
24 #define BUFSIZE   2048
25
26 extern int HEAVY_LOG;
27
28 #if WITH_LOGGING
29 static void _logTable(const char *text, apr_table_t *table);
30 #endif
31
32 static ngobjweb_dir_config *_getConfig(request_rec *r) {
33   ngobjweb_dir_config *cfg;
34
35   if (r == NULL) {
36     fprintf(stderr, "%s: missing request !\n", __PRETTY_FUNCTION__);
37     return NULL;
38   }
39   if (r->per_dir_config == NULL) {
40     ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
41                  "missing directory config in request ...");
42     return NULL;
43   }
44   
45   cfg = (ngobjweb_dir_config *)
46     ap_get_module_config(r->per_dir_config, &ngobjweb_module);
47   
48   return cfg;
49 }
50
51 static void _extractAppName(const char *uri, char *appName, int maxLen) {
52   char *tmp;
53   
54   /* extract name of application */
55   if ((tmp = index(uri + 1, '/'))) {
56     int len;
57     len = (tmp - (uri + 1));
58     strncpy(appName, (uri + 1), len);
59     appName[len] = '\0';
60   }
61   else {
62     strncpy(appName, (uri + 1), maxLen - 1);
63     appName[maxLen - 1] = '\0';
64   }
65   
66   /* cut off .woa extension from application name */
67   if ((tmp = strstr(appName, ".woa")))
68     *tmp = '\0';
69   
70   /* cut off .sky extension from application name */
71   if ((tmp = strstr(appName, ".sky")))
72     *tmp = '\0';
73 }
74
75 static void *_readRequestBody(request_rec *r, int *requestContentLength) {
76   const char *clen;
77   int  contentLength;
78   void *ptr;
79   int  readBytes, toBeRead;
80   void *requestBody;
81   
82   clen = apr_table_get(r->headers_in, "content-length");
83   contentLength = clen ? atoi(clen) : 0;
84   *requestContentLength = contentLength;
85   
86   /* no content to read ... */
87   if (contentLength == 0) return NULL;
88   
89   /* read content */
90   
91   if (HEAVY_LOG) {
92     ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server, 
93                  "going to read %i bytes from browser ...", contentLength);
94   }
95   
96   requestBody = apr_palloc(r->pool, contentLength + 2);
97
98   ptr = requestBody;
99   for (toBeRead = contentLength; toBeRead > 0;) {
100 #ifdef AP_VERSION_1
101     readBytes = ap_bread(r->connection->client, ptr, toBeRead);
102 #else
103                 ap_setup_client_block(r,REQUEST_CHUNKED_DECHUNK);
104     readBytes = ap_get_client_block(r, ptr, toBeRead);
105 #endif
106     toBeRead -= readBytes;
107     ptr += readBytes;
108     if (readBytes == 0) break;
109   }
110   ptr = NULL;
111       
112   if (toBeRead > 0) {
113     ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
114                  "couldn't read complete HTTP req body from browser "
115                  "(read %i of %i bytes)",
116                  (contentLength - toBeRead), contentLength);
117     return NULL;
118   }
119   
120   return requestBody;
121 }
122
123 static void
124 _copyHeadersToRequest(request_rec *r, apr_table_t *headers, int *contentLength)
125 {
126   const apr_array_header_t *array;
127   apr_table_entry_t  *entries;
128   int          i;
129   const char   *value;
130   
131   if (headers == NULL) return;
132   
133   value = apr_table_get(headers, "content-type");
134   if (value) r->content_type = value;
135   value = apr_table_get(headers, "content-encoding");
136   if (value) r->content_encoding = value;
137   value = apr_table_get(headers, "content-length");
138   *contentLength = value ? atoi(value) : 0;
139   
140   array   = apr_table_elts(headers);
141   entries = (apr_table_entry_t *)array->elts;
142
143   for (i = 0; i < array->nelts; i++) {
144     apr_table_entry_t *entry = &(entries[i]);
145
146     apr_table_set(r->headers_out, entry->key, entry->val);
147   }
148   // _logTable("out", r->headers_out);
149 }
150
151 static void _logInstanceAddress(request_rec *r, struct sockaddr *address,
152                                 size_t addressLen, int domain)
153 {
154   char buf[1024];
155   
156   if (!HEAVY_LOG) return;
157   
158   apr_snprintf(buf, sizeof(buf), "  => address len=%li domain=%i<", (long int) addressLen, domain);
159   switch (domain) {
160     case AF_INET: strcat(buf, "inet"); break;
161     case AF_UNIX: strcat(buf, "unix"); break;
162     default: strcat(buf, "unknown"); break;
163   }
164   strcat(buf, ">");
165   
166   if (domain == AF_UNIX) {
167     strcat(buf, " path=\"");
168     strcat(buf, ((struct sockaddr_un *)address)->sun_path);
169     strcat(buf, "\"");
170   }
171   else if (domain == AF_INET) {
172     char         *ptr = NULL;
173     int  port;
174     char sport[256];
175     
176     ptr  = inet_ntoa(((struct sockaddr_in *)address)->sin_addr);
177     port = ntohs(((struct sockaddr_in *)address)->sin_port);
178     apr_snprintf(sport, sizeof(sport), "host=\"%s\" port=%i", ptr, port);
179     strcat(buf, sport);
180   }
181   
182   ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server, buf);
183 }
184
185 static int _connectInstance(request_rec *r,
186                             int appFd, struct sockaddr *address,
187                             size_t addressLen)
188 {
189   int  result;
190   int  tryCount = 0;
191   char isConnected = 0;
192   
193   result = connect(appFd, address, addressLen);
194   if (result >= 0) return result;
195   
196   while (tryCount < 3) {
197     char *pdelay = NULL; /* pblock_findval("delay", _paras) */
198     int delay    = pdelay ? atoi(pdelay) : 3; // default: 3s
199
200     ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server,
201                  "sleeping %is ..", delay);
202 #ifdef AP_VERSION_1
203     apr_sleep(delay); /* should be in seconds for Apache 1? */
204 #else
205     apr_sleep(delay * 1000 * 1000 /* in microseconds now! */);
206 #endif
207     
208     ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server,
209                  "retry connect ..");
210     result = connect(appFd, address, addressLen);
211     
212     if (result >= 0) {
213       isConnected = 1;
214       break;
215     }
216     tryCount++;
217   }
218   
219   if (isConnected == 0) {
220     ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
221                  "connect to application instance failed, tried %i times.",
222                  tryCount);
223     close(appFd);
224     return -1;
225   }
226   return result;
227 }
228
229 static int _writeInHeaders(NGBufferedDescriptor *toApp, request_rec *r) {
230   const apr_array_header_t *array;
231   apr_table_entry_t  *entries;
232   int          i;
233   
234   if (r->headers_in == NULL) return 1;
235
236   array   = apr_table_elts(r->headers_in);
237   entries = (apr_table_entry_t *)array->elts;
238
239   for (i = 0; i < array->nelts; i++) {
240     apr_table_entry_t *entry = &(entries[i]);
241         
242     if (!NGBufferedDescriptor_writeHttpHeader(toApp,
243                                               entry->key, entry->val)) {
244       return 0;
245     }
246   }
247   return 1;
248 }
249
250 int ngobjweb_handler(request_rec *r) {
251   struct    sockaddr   *address = NULL;
252   size_t               addressLen;
253   int                  domain;
254   char                 appName[256];
255   NGBufferedDescriptor *toApp = NULL;
256   int                  appFd;
257   int                  result;
258   int                  writeError    = 0;
259   int                  contentLength = 0;
260   int                  statusCode    = 500;
261   ngobjweb_dir_config  *cfg;
262   const char           *uri;
263   unsigned             requestContentLength;
264   void                 *requestBody;
265   
266   uri = r->uri;
267   requestContentLength = 0;
268   requestBody = NULL;
269   
270 #ifndef AP_VERSION_1
271   if (r->handler == NULL)
272     return DECLINED;
273   if (strcmp(r->handler, "ngobjweb-adaptor") != 0)
274     return DECLINED;
275 #endif
276
277   if (uri == NULL)   return DECLINED;
278   if (uri[0] != '/') return DECLINED;
279   if (strstr(uri, "WebServerResources")) return DECLINED;
280
281   /* get directory configuration */
282   
283   if ((cfg = _getConfig(r))) {
284     if (cfg->appPrefix) {
285       if (HEAVY_LOG) {
286         ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server,
287                      "using prefix '%s'\n", cfg->appPrefix);
288       }
289       uri += strlen(cfg->appPrefix);
290     }
291   }
292   else {
293     return 500;
294   }
295
296   /* find app name in url */
297   _extractAppName(uri, appName, sizeof(appName));
298   
299   /* before continuing, read request body */
300   
301   requestBody = _readRequestBody(r, &contentLength);
302   requestContentLength = contentLength;
303   
304   if ((requestBody == NULL) && (contentLength > 0))
305     /* read failed, error is logged in function */
306     return 500;
307   
308   /* ask SNS for server address */
309
310   if (cfg->snsPort) {
311     address = _sendSNSQuery(r,
312                             r->the_request,
313                             apr_table_get(r->headers_in, "cookie"),
314                             &domain, &addressLen,
315                             appName,
316                             cfg);
317     if (address == NULL) {
318       /* did not find an appropriate application server */
319       ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
320                    "did not find SOPE instance using SNS.");
321       return DECLINED;
322     }
323   }
324   else if (cfg->appPort) {
325     domain = cfg->appPortDomain;
326     
327     if (cfg->appPortDomain == AF_UNIX) {
328       addressLen = sizeof(struct sockaddr_un);
329       address = apr_palloc(r->pool, sizeof(struct sockaddr_un)); 
330       memset(address, 0, sizeof(struct sockaddr_un)); 
331          
332       ((struct sockaddr_un *)address)->sun_family = AF_UNIX; 
333       strncpy(((struct sockaddr_un *)address)->sun_path, 
334               cfg->appPort, 
335               sizeof(((struct sockaddr_un *)address)->sun_path) - 1);
336     }
337     else {
338       struct sockaddr_in *snsi;
339       char *host;
340       int  port;
341       
342       host = "127.0.0.1";
343       port = atoi(cfg->appPort);
344
345       //ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server,
346       //             "appPort: '%s', cfg 0x%08X", cfg->appPort, cfg);
347       
348       addressLen = sizeof(struct sockaddr_in);
349       address = apr_palloc(r->pool, sizeof(struct sockaddr_in));
350       memset(address, 0, sizeof(struct sockaddr_in)); 
351       snsi = (struct sockaddr_in *)address; 
352          
353       snsi->sin_addr.s_addr = apr_inet_addr(host); 
354       
355       snsi->sin_family = AF_INET; 
356       snsi->sin_port   = htons((short)(port & 0xFFFF)); 
357       
358       if (snsi->sin_addr.s_addr == -1) { 
359         ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
360                      "couldn't convert snsd IP address: %s", host); 
361       } 
362       if (HEAVY_LOG && 0) { 
363         ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
364                      "connect IP address: %s", host); 
365       } 
366     }
367   }
368   else {
369     ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
370                  "neither SNS port nor app port are set for request ...");
371     return 500;
372   }
373
374   if (addressLen > 10000) {
375     ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
376                  "suspect instance port length (%li) ...", 
377                  (long int) addressLen);
378     return 500;
379   }
380   
381   _logInstanceAddress(r, address, addressLen, domain);
382   
383   /* setup connection to application server */
384   
385   if ((appFd = socket(domain, SOCK_STREAM, 0)) < 0) {
386     ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
387                  "could not create socket in domain %i.", domain);
388     return DECLINED;
389   }
390
391   if ((result = _connectInstance(r, appFd, address, addressLen)) < 0)
392     return 500;
393   
394   toApp = NGBufferedDescriptor_newWithOwnedDescriptorAndSize(appFd, 512);
395   if (toApp == NULL) {
396     close(appFd);
397     ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
398                  "could not alloc socket buffer for "
399                  "application server connection");
400     return 500;
401   }
402   
403   /* write request to application server */
404   
405   if (HEAVY_LOG) {
406     ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server, 
407                  "transfer reqline");
408   }
409
410   {
411     char *reqLine;
412     unsigned toGo;
413
414     reqLine = r->the_request;
415     toGo = reqLine ? strlen(reqLine) : 0;
416     
417     ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server,
418                  "req is %s(len=%i)", reqLine, toGo);
419
420     if (!NGBufferedDescriptor_safeWrite(toApp, reqLine,
421                                         reqLine ? strlen(reqLine) : 0)) {
422       writeError = 1;
423       goto writeErrorHandler;
424     }
425     if (!NGBufferedDescriptor_safeWrite(toApp, "\r\n", 2)) {
426       writeError = 1;
427       goto writeErrorHandler;
428     }
429   }
430
431   /* transfer headers */
432   
433   if (writeError == 0) {
434     if (HEAVY_LOG) {
435       ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server, 
436                    "transfer hdrs");
437     }
438     
439     /* extended adaptor headers */
440     {
441       char tmp[256];
442       const char *value;
443       
444       value = r->protocol;
445       value = (value != NULL) ? value : "http";
446       if (!NGBufferedDescriptor_writeHttpHeader(toApp,
447                                                 "x-webobjects-server-protocol",
448                                                 value)) {
449         writeError = 1;
450         goto writeErrorHandler;
451       }
452       
453       if ((value = r->connection->remote_ip) != NULL) {
454         if (!NGBufferedDescriptor_writeHttpHeader(toApp,
455                                                   "x-webobjects-remote-addr",
456                                                   value)) {
457           writeError = 1;
458           goto writeErrorHandler;
459         }
460       }
461       
462       value = r->connection->remote_host;
463       if (value == NULL) value = r->connection->remote_ip;
464       if (value != NULL) {
465         if (!NGBufferedDescriptor_writeHttpHeader(toApp,
466                                                   "x-webobjects-remote-host",
467                                                   value)) {
468           writeError = 1;
469           goto writeErrorHandler;
470         }
471       }
472
473 #ifdef AP_VERSION_1
474       if ((value = r->connection->ap_auth_type) != NULL) {
475 #else
476       if ((value = r->ap_auth_type) != NULL) {
477 #endif
478         if (!NGBufferedDescriptor_writeHttpHeader(toApp,
479                                                   "x-webobjects-auth-type",
480                                                   value)) {
481           writeError = 1;
482           goto writeErrorHandler;
483         }
484       }
485       
486 #ifdef AP_VERSION_1
487       if ((value = r->connection->user) != NULL) {
488 #else
489       if ((value = r->user) != NULL) {
490 #endif
491         if (!NGBufferedDescriptor_writeHttpHeader(toApp,
492                                                   "x-webobjects-remote-user",
493                                                   value)) {
494           writeError = 1;
495           goto writeErrorHandler;
496         }
497       }
498       
499       if (cfg != NULL) {
500         if (cfg->appPrefix != NULL) {
501           if (!NGBufferedDescriptor_writeHttpHeader(toApp,
502                 "x-webobjects-adaptor-prefix", cfg->appPrefix)) {
503             writeError = 1;
504             goto writeErrorHandler;
505           }
506         }
507       }
508
509       if (!NGBufferedDescriptor_writeHttpHeader(toApp,
510                                                 "x-webobjects-server-name",
511                                                 r->server->server_hostname)) {
512         writeError = 1;
513         goto writeErrorHandler;
514       }
515       
516       if (r->server->port != 0) {
517         apr_snprintf(tmp, sizeof(tmp), "%i", r->server->port);
518         if (!NGBufferedDescriptor_writeHttpHeader(toApp,
519                                                   "x-webobjects-server-port",
520                                                   tmp)) {
521           writeError = 1;
522           goto writeErrorHandler;
523         }
524       }
525
526       // TODO: this seems to be broken with some Apache's!
527       // see: http://www.mail-archive.com/modssl-users@modssl.org/msg16396.html
528       if (r->server->port != 0) {
529         apr_snprintf(tmp, sizeof(tmp), "%s://%s:%i",
530                      ap_http_method(r),
531                      r->server->server_hostname,
532                      r->server->port);
533       }
534       else {
535         apr_snprintf(tmp, sizeof(tmp), "%s://%s",
536                      ap_http_method(r), r->server->server_hostname);
537       }
538       if (!NGBufferedDescriptor_writeHttpHeader(toApp,
539                                                 "x-webobjects-server-url",
540                                                 tmp)) {
541         writeError = 1;
542         goto writeErrorHandler;
543       }
544       
545       /* SSL environment */
546       
547       if (r->subprocess_env != NULL) {
548         apr_table_t *env = r->subprocess_env;
549         const char *s;
550         
551         s = apr_table_get(env, "HTTPS");
552         if (s != NULL && strncasecmp(s, "on", 2) == 0) { // SSL is one
553           if (!NGBufferedDescriptor_writeHttpHeader(toApp,
554                                                   "x-webobjects-https-enabled",
555                                                     "1")) {
556             writeError = 1;
557             goto writeErrorHandler;
558           }
559         }
560         
561         s = apr_table_get(env, "SSL_CLIENT_CERT");
562         if (s != NULL) {
563           const apr_array_header_t *array;
564           apr_table_entry_t  *entries;
565           int          i;
566           
567           if (!NGBufferedDescriptor_writeHttpHeader(toApp,
568                                                    "x-webobjects-clients-cert",
569                                                     s)) {
570             writeError = 1;
571             goto writeErrorHandler;
572           }
573           
574           /* deliver all SSL_CLIENT_ env-vars as headers */
575           array   = apr_table_elts(env);
576           entries = (apr_table_entry_t *)array->elts;
577           for (i = 0; i < array->nelts; i++) {
578             apr_table_entry_t *entry = &(entries[i]);
579             
580             if (strncmp(entry->key, "SSL_CLIENT_", 11) != 0)
581               continue;
582             if (strncmp(entry->key, "SSL_CLIENT_CERT", 15) == 0)
583               continue; /* already avail as x-webobjects-clients-cert" */
584             
585             if (!NGBufferedDescriptor_writeHttpHeader(toApp,
586                                                       entry->key,
587                                                       entry->val)) {
588               writeError = 1;
589               goto writeErrorHandler;
590             }
591           }
592         }
593
594         /* keysize, don't know whether mapping is correct? */
595         if ((s = apr_table_get(env, "SSL_CIPHER_ALGKEYSIZE")) != NULL) {
596           if (!NGBufferedDescriptor_writeHttpHeader(toApp,
597                                         "x-webobjects-https-secret-keysize",
598                                                     s)) {
599             writeError = 1;
600             goto writeErrorHandler;
601           }
602         }
603         if ((s = apr_table_get(env, "SSL_CIPHER_USEKEYSIZE")) != NULL) {
604           if (!NGBufferedDescriptor_writeHttpHeader(toApp,
605                                                   "x-webobjects-https-keysize",
606                                                     s)) {
607             writeError = 1;
608             goto writeErrorHandler;
609           }
610         }
611       }
612     }
613     
614     /* http headers */
615     if (!_writeInHeaders(toApp, r)) {
616       writeError = 1;
617       goto writeErrorHandler;
618     }
619     
620     if (!NGBufferedDescriptor_safeWrite(toApp, "\r\n", 2)) {
621       writeError = 1;
622       goto writeErrorHandler;
623     }
624     if (!NGBufferedDescriptor_flush(toApp))
625       writeError = 1;
626   }
627
628  writeErrorHandler:
629   if (writeError == 1) {
630     if (toApp) NGBufferedDescriptor_free(toApp);
631     
632     ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
633                  "socket write error during transfer of HTTP header section");
634     return 500;
635   }
636   
637   /* transfer request body */
638   
639   if (requestContentLength > 0) {
640     if (!NGBufferedDescriptor_safeWrite(toApp,
641                                         requestBody,
642                                         requestContentLength)) {
643       if (toApp) NGBufferedDescriptor_free(toApp);
644       ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
645                    "couldn't transfer HTTP req body to app server (%i bytes)",
646                    contentLength);
647       return 500;
648     }
649     NGBufferedDescriptor_flush(toApp);
650   }
651   else {
652     if (HEAVY_LOG) {
653       ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server,
654                    "no content in request to transfer");
655     }
656   }
657   
658   /* read response line */
659   
660   if (!NGScanResponseLine(toApp, NULL, &statusCode, NULL)) {
661     if (toApp) NGBufferedDescriptor_free(toApp);
662     ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
663                  "error during reading of response line ..");
664     return 500;
665   }
666   r->status      = statusCode;
667   r->status_line = NULL;
668
669   /* process response headers */
670   {
671     apr_table_t *headers = NULL;
672     
673     if (HEAVY_LOG)
674       ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server, "scan headers");
675
676     if ((headers = NGScanHeaders(r->pool, toApp)) == NULL) {
677       ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
678                    "error during parsing of response headers ..");
679     }
680     
681     _copyHeadersToRequest(r, headers, &contentLength);
682 #ifdef AP_VERSION_1
683     ap_send_http_header(r);
684 #endif
685   }
686   
687   /* send response content */
688   
689   if (!r->header_only) {
690     if (contentLength > 0) {
691       void *buffer = NULL;
692       
693       if ((buffer = apr_pcalloc(r->pool, contentLength + 1)) == NULL) {
694         ap_log_error(__FILE__, __LINE__, APLOG_ERR, 0, r->server,
695                      "could not allocate response buffer (size=%i)",
696                      contentLength);
697       }
698
699       // read whole response
700       if (!NGBufferedDescriptor_safeRead(toApp, buffer, contentLength)) {
701         if (toApp) NGBufferedDescriptor_free(toApp);
702       }
703
704       // close connection to app
705       if (toApp) {
706         NGBufferedDescriptor_free(toApp);
707         toApp = NULL;
708       }
709
710       ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server,
711                    "send response (size=%i)",
712                    contentLength);
713       // send response to client
714       ap_rwrite(buffer, contentLength, r);
715       ap_rflush(r);
716     }
717     else if (contentLength == 0) {
718       // no content length header, read until EOF
719       unsigned char buffer[4096];
720       int result = 0;
721       int writeCount = 0;
722
723       do {
724         result = NGBufferedDescriptor_read(toApp, buffer, sizeof(buffer));
725         if (result > 0) {
726           ap_rwrite(buffer, result, r);
727           ap_rflush(r);
728           writeCount += result;
729         }
730       }
731       while (result > 0);
732
733       if (HEAVY_LOG && (writeCount > 0)) {
734         ap_log_error(__FILE__, __LINE__, APLOG_INFO, 0, r->server,
735                      "write %i bytes (without content-length header)",
736                      writeCount);
737       }
738     }
739   }
740   
741   return OK;
742 }
743
744 #if WITH_LOGGING
745 #if 0
746 static void test(void) {
747   fprintf(stderr,
748           "%s: called:\n"
749           "  app:      %s\n"
750           "  uri:      %s\n"
751           "  pathinfo: %s\n"
752           "  method:   %s\n"
753           "  protocol: %s\n"
754           "  1st:      %s\n"
755           "  host:     %s\n"
756           "  type:     %s\n"
757           "  handler:  %s\n",
758           __PRETTY_FUNCTION__,
759           appName,
760           r->uri,
761           r->path_info,
762           r->method,
763           r->protocol,
764           r->the_request,
765           apr_table_get(r->headers_in, "content-length"),
766           r->content_type,
767           r->handler
768           );
769
770   _logTable("  out", r->headers_out);
771   _logTable("  err", r->err_headers_out);
772   _logTable("  env", r->subprocess_env);
773   _logTable("  in",  r->headers_in);
774 }
775 #endif
776
777 static void _logTable(const char *text, apr_table_t *table) {
778   const apr_array_header_t *array;
779   apr_table_entry_t  *entries;
780   int          i;
781
782   if (table == NULL) {
783     fprintf(stderr, "%s: log NULL table.\n", text);
784     return;
785   }
786
787   array   = apr_table_elts(table);
788   entries = (apr_table_entry_t *)array->elts;
789
790   if (array->nelts == 0) {
791     fprintf(stderr, "%s: empty\n", text);
792     return;
793   }
794
795   for (i = 0; i < array->nelts; i++) {
796     apr_table_entry_t *entry = &(entries[i]);
797     
798     fprintf(stderr, "%s: %s: %s\n", text, entry->key, entry->val);
799   }
800 }
801 #endif