source: trunk/gsdl/src/recpt/cgiwrapper.cpp@ 12008

Last change on this file since 12008 was 11998, checked in by davidb, 18 years ago

First cut at 'The Depositor' -- Greenstone support for institutional
repositories

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 21.7 KB
Line 
1/**********************************************************************
2 *
3 * cgiwrapper.cpp -- output pages using the cgi protocol
4 * Copyright (C) 1999 The New Zealand Digital Library Project
5 *
6 * A component of the Greenstone digital library software
7 * from the New Zealand Digital Library Project at the
8 * University of Waikato, New Zealand.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 *********************************************************************/
25
26#include "gsdlconf.h"
27#include "cgiwrapper.h"
28#include "recptconfig.h"
29#include "fileutil.h"
30#include <stdlib.h>
31#include <assert.h>
32
33#if defined(GSDL_USE_OBJECTSPACE)
34# include <ospace/std/iostream>
35# include <ospace/std/fstream>
36#elif defined(GSDL_USE_IOS_H)
37# include <iostream.h>
38# include <fstream.h>
39#else
40# include <iostream>
41# include <fstream>
42#endif
43
44#include "cgicc/Cgicc.h"
45
46using namespace cgicc;
47extern Cgicc* formData;
48
49
50#ifdef USE_FASTCGI
51#include "fcgiapp.h"
52#endif
53
54
55#ifdef USE_FASTCGI
56// used to output the text from receptionist
57class fcgistreambuf : public streambuf {
58public:
59 fcgistreambuf ();
60 int sync ();
61 int overflow (int ch);
62 int underflow () {return EOF;}
63
64 void fcgisbreset() {fcgx_stream = NULL; other_ostream = NULL;};
65 void set_fcgx_stream(FCGX_Stream *newone) {fcgx_stream=newone;};
66 void set_other_ostream(ostream *newone) {other_ostream=newone;};
67
68private:
69 FCGX_Stream *fcgx_stream;
70 ostream *other_ostream;
71};
72
73fcgistreambuf::fcgistreambuf() {
74 fcgisbreset();
75 if (base() == ebuf()) allocate();
76 setp (base(), ebuf());
77};
78
79int fcgistreambuf::sync () {
80 if ((fcgx_stream != NULL) &&
81 (FCGX_PutStr (pbase(), out_waiting(), fcgx_stream) < 0)) {
82 fcgx_stream = NULL;
83 }
84
85 if (other_ostream != NULL) {
86 char *thepbase=pbase();
87 for (int i=0;i<out_waiting();++i) (*other_ostream).put(thepbase[i]);
88 }
89
90 setp (pbase(), epptr());
91
92 return 0;
93}
94
95int fcgistreambuf::overflow (int ch) {
96 if (sync () == EOF) return EOF;
97 if (ch != EOF) sputc (ch);
98 return 0;
99}
100
101#endif
102
103static void format_error_string (text_t &errorpage, const text_t &errortext, bool debug) {
104
105 errorpage.clear();
106
107 if (debug) {
108 errorpage += "\n";
109 errorpage += "ERROR: " + errortext;
110 errorpage += "\n";
111
112 } else {
113
114 errorpage += "Content-type: text/html\n\n";
115
116 errorpage += "<html>\n";
117 errorpage += "<head>\n";
118 errorpage += "<title>Error</title>\n";
119 errorpage += "</head>\n";
120 errorpage += "<body>\n";
121 errorpage += "<h2>Oops!</h2>\n";
122 errorpage += errortext;
123 errorpage += "</body>\n";
124 errorpage += "</html>\n";
125 }
126}
127
128static void page_errorcollect (const text_t &gsdlhome, text_t &errorpage, bool debug) {
129
130 text_t collectdir = filename_cat (gsdlhome, "collect");
131
132 text_t errortext = "No valid collections were found: Check that your collect directory\n";
133 errortext += "(" + collectdir + ") is readable and contains at least one valid collection.\n";
134 errortext += "Note that modelcol is NOT a valid collection.\n";
135 errortext += "If the path to your collect directory is wrong edit the 'gsdlhome' field\n";
136 errortext += "in your gsdlsite.cfg configuration file.\n";
137
138 format_error_string (errorpage, errortext, debug);
139}
140
141static void page_errorsitecfg (text_t &errorpage, bool debug, int mode) {
142
143 text_t errortext;
144
145 if (mode == 0) {
146 errortext += "The gsdlsite.cfg configuration file could not be found. This\n";
147 errortext += "file should contain configuration information relating to this\n";
148 errortext += "site's setup.\n";
149
150 } else if (mode == 1) {
151 errortext += "The gsdlsite.cfg configuration file does not contain a valid\n";
152 errortext += "gsdlhome entry.\n";
153 }
154
155 if (debug) {
156 errortext += "gsdlsite.cfg should reside in the directory from which the\n";
157 errortext += "library executable was run.\n";
158 } else {
159 errortext += "gsdlsite.cfg should reside in the same directory as the library\n";
160 errortext += "executable file.\n";
161 }
162
163 format_error_string (errorpage, errortext, debug);
164}
165
166
167static void page_errormaincfg (const text_t &gsdlhome, const text_t &collection,
168 bool debug, text_t &errorpage) {
169
170 text_t errortext;
171
172 if (collection.empty()) {
173 text_t main_cfg_file = filename_cat (gsdlhome, "etc", "main.cfg");
174 errortext += "The main.cfg configuration file could not be found. This file\n";
175 errortext += "should contain configuration information relating to the\n";
176 errortext += "setup of the interface. As this receptionist is not being run\n";
177 errortext += "in collection specific mode the file should reside at\n";
178 errortext += main_cfg_file + ".\n";
179 } else {
180 text_t collect_cfg_file = filename_cat (gsdlhome, "collect", collection, "etc", "collect.cfg");
181 text_t main_collect_cfg_file = filename_cat (gsdlhome, "etc", "collect.cfg");
182 text_t main_cfg_file = filename_cat (gsdlhome, "etc", "main.cfg");
183 errortext += "Either the collect.cfg or main.cfg configuration file could\n";
184 errortext += "not be found. This file should contain configuration information\n";
185 errortext += "relating to the setup of the interface. As this receptionist is\n";
186 errortext += "being run in collection specific mode the file should reside\n";
187 errortext += "at either " + collect_cfg_file + ",\n";
188 errortext += main_collect_cfg_file + " or " + main_cfg_file + ".\n";
189 }
190
191 format_error_string (errorpage, errortext, debug);
192}
193
194
195static void page_errorinit (const text_t &gsdlhome, bool debug, text_t &errorpage) {
196
197 text_t errortext = "An error occurred during the initialisation of the Greenstone Digital\n";
198 errortext += "Library software. It is likely that the software has not been setup\n";
199 errortext += "correctly.\n";
200
201 text_t error_file = filename_cat (gsdlhome, "etc", "error.txt");
202 char *efile = error_file.getcstr();
203 ifstream errin (efile);
204 delete []efile;
205 if (errin) {
206 errortext += "The error log, " + error_file + ", contains the\n";
207 errortext += "following information:\n\n";
208 if (!debug) errortext += "<pre>\n";
209
210 char c;
211 errin.get(c);
212 while (!errin.eof ()) {
213 errortext.push_back(c);
214 errin.get(c);
215 }
216
217 if (!debug) errortext += "</pre>\n";
218
219 errin.close();
220
221 } else {
222 errortext += "Please consult " + error_file + " for more information.\n";
223 }
224
225 format_error_string (errorpage, errortext, debug);
226}
227
228static void page_errorparseargs (const text_t &gsdlhome, bool debug, text_t &errorpage) {
229
230 text_t errortext = "An error occurred during the parsing of the cgi arguments.\n";
231
232 text_t error_file = filename_cat (gsdlhome, "etc", "error.txt");
233 char *efile = error_file.getcstr();
234 ifstream errin (efile);
235 delete []efile;
236 if (errin) {
237 errortext += "The error log, " + error_file + ", contains the\n";
238 errortext += "following information:\n\n";
239 if (!debug) errortext += "<pre>\n";
240
241 char c;
242 errin.get(c);
243 while (!errin.eof ()) {
244 errortext.push_back(c);
245 errin.get(c);
246 }
247 if (!debug) errortext += "</pre>\n";
248 errin.close();
249
250 } else {
251 errortext += "Please consult " + error_file + " for more information.\n";
252 }
253
254 format_error_string (errorpage, errortext, debug);
255}
256
257static void page_errorcgipage (const text_t &gsdlhome, bool debug, text_t &errorpage) {
258
259 text_t errortext = "An error occurred during the construction of the cgi page.\n";
260
261 text_t error_file = filename_cat (gsdlhome, "etc", "error.txt");
262 char *efile = error_file.getcstr();
263 ifstream errin (efile);
264 delete []efile;
265 if (errin) {
266 errortext += "The error log, " + error_file + ", contains the\n";
267 errortext += "following information:\n\n";
268 if (!debug) errortext += "<pre>\n";
269
270 char c;
271 errin.get(c);
272 while (!errin.eof ()) {
273 errortext.push_back(c);
274 errin.get(c);
275 }
276 if (!debug) errortext += "</pre>\n";
277 errin.close();
278
279 } else {
280 errortext += "Please consult " + error_file + " for more information.\n";
281 }
282
283 format_error_string (errorpage, errortext, debug);
284}
285
286static void print_debug_info (receptionist &recpt) {
287
288 outconvertclass text_t2ascii;
289 const recptconf &configinfo = recpt.get_configinfo ();
290 text_t etc_dir = filename_cat (configinfo.gsdlhome, "etc");
291
292 cout << "\n";
293 cout << text_t2ascii
294 << "------------------------------------------------------------\n"
295 << "Configuration and initialization completed successfully.\n"
296 << " Note that more debug information may be available in the\n"
297 << " initialization and error log error.txt in " << etc_dir << ".\n"
298 << "------------------------------------------------------------\n\n";
299
300 bool colspec = false;
301 if (configinfo.collection.empty()) {
302 cout << "Receptionist is running in \"general\" (i.e. not \"collection\n"
303 << "specific\") mode.\n";
304 } else {
305 cout << text_t2ascii
306 << "Receptionist is running in \"collection specific\" mode.\n"
307 << " collection=" << configinfo.collection << "\n"
308 << " collection directory=" << configinfo.collectdir << "\n";
309 colspec = true;
310 }
311
312 cout << text_t2ascii << "gsdlhome=" << configinfo.gsdlhome << "\n";
313 if (!configinfo.gdbmhome.empty())
314 cout << text_t2ascii << "gdbmhome=" << configinfo.gdbmhome << "\n";
315 cout << text_t2ascii << "httpprefix=" << configinfo.httpprefix << "\n";
316 cout << text_t2ascii << "httpimg=" << configinfo.httpimg << "\n";
317 cout << text_t2ascii << "gwcgi=" << configinfo.gwcgi << "\n"
318 << " Note that unless gwcgi has been set from a configuration\n"
319 << " file it is dependent on environment variables set by your\n"
320 << " webserver. Therefore it may not have the same value when run\n"
321 << " from the command line as it would be when run from your\n"
322 << " web server.\n";
323 if (configinfo.usecookies)
324 cout << "cookies are enabled\n";
325 else
326 cout << "cookies are disabled\n";
327 if (configinfo.logcgiargs)
328 cout << "logging is enabled\n";
329 else
330 cout << "logging is disabled\n";
331 cout << "------------------------------------------------------------\n\n";
332
333 text_tset::const_iterator this_mfile = configinfo.macrofiles.begin();
334 text_tset::const_iterator end_mfile = configinfo.macrofiles.end();
335 cout << "Macro Files:\n"
336 << "------------\n";
337 text_t mfile;
338 bool found;
339 while (this_mfile != end_mfile) {
340 cout << text_t2ascii << *this_mfile;
341 int spaces = (22 - (*this_mfile).size());
342 if (spaces < 2) spaces = 2;
343 text_t outspaces;
344 for (int i = 0; i < spaces; ++i) outspaces.push_back (' ');
345 cout << text_t2ascii << outspaces;
346
347 found = false;
348 if (colspec) {
349 // collection specific - try collectdir/macros first
350 mfile = filename_cat (configinfo.collectdir, "macros", *this_mfile);
351 if (file_exists (mfile)) {
352 cout << text_t2ascii << "found (" << mfile << ")\n";
353 found = true;
354 }
355 }
356
357 if (!found) {
358 // try main macro directory
359 mfile = filename_cat (configinfo.gsdlhome, "macros", *this_mfile);
360 if (file_exists (mfile)) {
361 cout << text_t2ascii << "found (" << mfile << ")\n";
362 found = true;
363 }
364 }
365
366 if (!found)
367 cout << text_t2ascii << "NOT FOUND\n";
368
369 ++this_mfile;
370 }
371
372 cout << "------------------------------------------------------------\n\n"
373 << "Collections:\n"
374 << "------------\n"
375 << " Note that collections will only appear as \"running\" if\n"
376 << " their build.cfg files exist, are readable, contain a valid\n"
377 << " builddate field (i.e. > 0), and are in the collection's\n"
378 << " index directory (i.e. NOT the building directory)\n\n";
379
380 recptprotolistclass *protos = recpt.get_recptprotolist_ptr();
381 recptprotolistclass::iterator rprotolist_here = protos->begin();
382 recptprotolistclass::iterator rprotolist_end = protos->end();
383
384 bool is_z3950 = false;
385 bool found_valid_col = false;
386
387
388 while (rprotolist_here != rprotolist_end) {
389 comerror_t err;
390 if ((*rprotolist_here).p == NULL) continue;
391 else if (is_z3950==false &&
392 (*rprotolist_here).p->get_protocol_name(err) == "z3950proto") {
393 cout << "\nZ39.50 Servers: (always public)\n"
394 << "---------------\n";
395 is_z3950=true;
396 }
397
398 text_tarray collist;
399 (*rprotolist_here).p->get_collection_list (collist, err, cerr);
400 if (err == noError) {
401 text_tarray::iterator collist_here = collist.begin();
402 text_tarray::iterator collist_end = collist.end();
403
404 while (collist_here != collist_end) {
405
406 cout << text_t2ascii << *collist_here;
407
408 int spaces = (22 - (*collist_here).size());
409 if (spaces < 2) spaces = 2;
410 text_t outspaces;
411 for (int i = 0; i < spaces; ++i) outspaces.push_back (' ');
412 cout << text_t2ascii << outspaces;
413
414 ColInfoResponse_t *cinfo = recpt.get_collectinfo_ptr ((*rprotolist_here).p, *collist_here, cerr);
415 if (cinfo != NULL) {
416 if (cinfo->isPublic) cout << "public ";
417 else cout << "private";
418
419 if (cinfo->buildDate > 0) {
420 cout << " running ";
421 found_valid_col = true;
422 } else {
423 cout << " not running";
424 }
425 }
426
427 cout << "\n";
428
429 ++collist_here;
430 }
431 }
432 is_z3950=false;
433 ++rprotolist_here;
434 } // end of while loop
435
436 if (!found_valid_col) {
437 cout << "WARNING: No \"running\" collections were found. You need to\n";
438 cout << " build one of the above collections\n";
439 }
440
441 cout << "\n------------------------------------------------------------\n";
442 cout << "------------------------------------------------------------\n\n";
443 cout << "receptionist running in command line debug mode\n";
444 cout << "enter cgi arguments as name=value pairs (e.g. 'a=p&p=home'):\n";
445
446}
447
448// cgiwrapper does everything necessary to output a page
449// using the cgi protocol. If this is being run for a particular
450// collection then "collection" should be set, otherwise it
451// should equal "".
452void cgiwrapper (receptionist &recpt, text_t collection) {
453
454 int numrequests = 0;
455 bool debug = false;
456 const recptconf &configinfo = recpt.get_configinfo ();
457
458 // find out whether this is being run as a cgi-script
459 // or a fastcgi script
460#ifdef USE_FASTCGI
461 fcgistreambuf outbuf;
462 int isfastcgi = !FCGX_IsCGI();
463 FCGX_Stream *fcgiin, *fcgiout, *fcgierr;
464 FCGX_ParamArray fcgienvp;
465#else
466 int isfastcgi = 0;
467#endif
468
469 // get the query string if it is not being run as a fastcgi
470 // script
471 text_t argstr = g_EmptyText;
472 cgiargsclass args;
473 char *aURIStr;
474 if (!isfastcgi) {
475
476
477 // Iterate through the vector, and synthesis argstr
478 bool first = true;
479
480 for(const_form_iterator iter = formData->getElements().begin();
481 iter != formData->getElements().end();
482 ++iter) {
483
484 const char* name_cstr = iter->getName().c_str();
485 const char* value_cstr = iter->getValue().c_str();
486 if (first) {
487
488 argstr = name_cstr;
489 argstr.append("=");
490 argstr.append(value_cstr);
491 first = false;
492 }
493 else {
494 argstr.append("&");
495 argstr.append(name_cstr);
496 argstr.append("=");
497 argstr.append(value_cstr);
498 }
499 }
500 }
501
502 if (debug) {
503 cout << "Configuring Greenstone...\n";
504 cout << flush;
505 }
506
507 // init stuff - we can't output error pages directly with
508 // fastcgi so the pages are stored until we can output them
509 text_t errorpage;
510 outconvertclass text_t2ascii;
511
512 // set defaults
513 int maxrequests = 10000;
514 recpt.configure ("collection", collection);
515 recpt.configure ("httpimg", "/gsdl/images");
516 char *script_name = getenv("SCRIPT_NAME");
517 if (script_name != NULL) recpt.configure("gwcgi", script_name);
518 else recpt.configure("gwcgi", "/gsdl");
519
520 // read in the configuration files.
521 text_t gsdlhome;
522 if (!site_cfg_read (recpt, gsdlhome, maxrequests)) {
523 // couldn't find the site configuration file
524 page_errorsitecfg (errorpage, debug, 0);
525 } else if (gsdlhome.empty()) {
526 // no gsdlhome in gsdlsite.cfg
527 page_errorsitecfg (errorpage, debug, 1);
528 } else if (!directory_exists(gsdlhome)) {
529 // gsdlhome not a valid directory
530 page_errorsitecfg (errorpage, debug, 1);
531 } else if (!main_cfg_read (recpt, gsdlhome, collection)) {
532 // couldn't find the main configuration file
533 page_errormaincfg (gsdlhome, collection, debug, errorpage);
534 } else if (configinfo.collectinfo.empty() && false) { // commented out for corba
535 // don't have any collections
536 page_errorcollect (gsdlhome, errorpage, debug);
537 }
538
539 if (errorpage.empty()) {
540
541 // initialise the library software
542 if (debug) {
543 cout << "Initializing...\n";
544 cout << flush;
545 }
546
547 text_t error_file = filename_cat (gsdlhome, "etc", "error.txt");
548 char *eout = error_file.getcstr();
549 ofstream errout (eout, ios::app);
550 delete []eout;
551 if (!recpt.init(errout)) {
552 // an error occurred during the initialisation
553 errout.close();
554 page_errorinit(gsdlhome, debug, errorpage);
555 }
556 errout.close();
557 }
558
559 if (debug && errorpage.empty()) {
560 // get query string from command line
561 print_debug_info (recpt);
562 char cinURIStr[1024];
563 cin.get(cinURIStr, 1024);
564 argstr = cinURIStr;
565 }
566
567 // cgi scripts only deal with one request
568 if (!isfastcgi) maxrequests = 1;
569
570 // Page-request loop. If this is not being run as a fastcgi
571 // process then only one request will be processed and then
572 // the process will exit.
573 while (numrequests < maxrequests) {
574#ifdef USE_FASTCGI
575 if (isfastcgi) {
576 if (FCGX_Accept(&fcgiin, &fcgiout, &fcgierr, &fcgienvp) < 0) break;
577
578 char *request_method_str = FCGX_GetParam ("REQUEST_METHOD", fcgienvp);
579 char *content_length_str = FCGX_GetParam ("CONTENT_LENGTH", fcgienvp);
580
581 if (request_method_str != NULL && strcmp(request_method_str, "POST") == 0 &&
582 content_length_str != NULL) {
583 // POST form data
584 int content_length = text_t(content_length_str).getint();
585 if (content_length > 0) {
586 argstr.clear();
587 int c;
588 do {
589 c = FCGX_GetChar (fcgiin);
590 if (c < 0) break;
591 argstr.push_back (c);
592 --content_length;
593 } while (content_length > 0);
594 }
595
596 } else {
597 // GET form data
598 aURIStr = FCGX_GetParam("QUERY_STRING", fcgienvp);
599 if (aURIStr != NULL) argstr = aURIStr;
600 else argstr = g_EmptyText;
601 }
602 }
603#endif
604
605 // get output streams ready
606#ifdef USE_FASTCGI
607 outbuf.fcgisbreset ();
608 if (isfastcgi) outbuf.set_fcgx_stream (fcgiout);
609 else outbuf.set_other_ostream (&cout);
610 ostream pageout (&outbuf);
611#else
612#define pageout cout
613#endif
614
615 // if using fastcgi we'll load environment into a map,
616 // otherwise simply pass empty map (can't get environment
617 // variables using getenv() while using FCGX versions
618 // of fastcgi - at least I can't ;-) - Stefan)
619 text_tmap fastcgienv;
620#ifdef USE_FASTCGI
621 if (isfastcgi) {
622 for(; *fcgienvp != NULL; ++fcgienvp) {
623 text_t fvalue = *fcgienvp;
624 text_t::const_iterator begin = fvalue.begin();
625 text_t::const_iterator end = fvalue.end();
626 text_t::const_iterator equals_sign = findchar (begin, end, '=');
627 if (equals_sign != end)
628 fastcgienv[substr(begin, equals_sign)] = substr(equals_sign+1, end);
629 }
630 }
631#endif
632
633 // temporarily need to configure gwcgi here when using fastcgi as I can't
634 // get it to pass the SCRIPT_NAME environment variable to the initial
635 // environment (if anyone can work out how to do this using the apache
636 // server, let me know). Note that this overrides the gwcgi field in
637 // site.cfg (which it shouldn't do) but I can't at present set gwcgi
638 // from site.cfg as I have old receptionists laying around that wouldn't
639 // appreciate it. The following 5 lines of code should be deleted once
640 // I either a: get the server to pass SCRIPT_NAME at initialization
641 // time or b: convert all the collections using old receptionists over
642 // to this version and uncomment gwcgi in the site.cfg file -- Stefan.
643#ifdef USE_FASTCGI
644 if (isfastcgi) {
645 recpt.configure("gwcgi", fastcgienv["SCRIPT_NAME"]);
646 }
647#endif
648
649
650 // if there has been no error so far, perform the production of the
651 // output page
652 if (errorpage.empty()) {
653 text_t error_file = filename_cat (gsdlhome, "etc", "error.txt");
654 char *eout = error_file.getcstr();
655 ofstream errout (eout, ios::app);
656 delete []eout;
657
658#if defined(__WIN32__) && defined(GSDL_USE_IOS_H)
659 // old Windows compilers (VC++4.2)
660 cerr = errout;
661#else
662 // can't do this anymore according to c++ standard...
663 // cerr = errout;
664 // ... but can do this instead
665 streambuf* errbuf = cerr.rdbuf(errout.rdbuf());
666#endif
667
668 // parse the cgi arguments and produce the resulting page if there
669 // has been no errors so far
670 if (!recpt.parse_cgi_args (argstr, args, errout, fastcgienv)) {
671 errout.close ();
672 page_errorparseargs(gsdlhome, debug, errorpage);
673 } else {
674 // produce the output page
675
676 if (!recpt.produce_cgi_page (args, pageout, errout, fastcgienv)) {
677 errout.close ();
678 page_errorcgipage(gsdlhome, debug, errorpage);
679 }
680 recpt.log_cgi_args (args, errout, fastcgienv);
681 errout.close ();
682 }
683
684#if !defined(__WIN32__) || !defined(GSDL_USE_IOS_H)
685 // restore the cerr buffer
686 cerr.rdbuf(errbuf);
687#endif
688 }
689
690 // there was an error, output the error page
691 if (!errorpage.empty()) {
692 pageout << text_t2ascii << errorpage;
693 errorpage.clear();
694 numrequests = maxrequests; // make this the last page
695 }
696 pageout << flush;
697
698 // finish with the output streams
699#ifdef USE_FASTCGI
700 if (isfastcgi) FCGX_Finish();
701#endif
702
703 ++numrequests;
704 }
705
706 return;
707}
Note: See TracBrowser for help on using the repository browser.