src/cgi/cgiapp.cpp

Go to the documentation of this file.
00001 /*  $Id: cgiapp.cpp 174216 2009-10-26 14:10:52Z grichenk $
00002 * ===========================================================================
00003 *
00004 *                            PUBLIC DOMAIN NOTICE
00005 *               National Center for Biotechnology Information
00006 *
00007 *  This software/database is a "United States Government Work" under the
00008 *  terms of the United States Copyright Act.  It was written as part of
00009 *  the author's official duties as a United States Government employee and
00010 *  thus cannot be copyrighted.  This software/database is freely available
00011 *  to the public for use. The National Library of Medicine and the U.S.
00012 *  Government have not placed any restriction on its use or reproduction.
00013 *
00014 *  Although all reasonable efforts have been taken to ensure the accuracy
00015 *  and reliability of the software and data, the NLM and the U.S.
00016 *  Government do not and cannot warrant the performance or results that
00017 *  may be obtained by using this software or data. The NLM and the U.S.
00018 *  Government disclaim all warranties, express or implied, including
00019 *  warranties of performance, merchantability or fitness for any particular
00020 *  purpose.
00021 *
00022 *  Please cite the author in any work or product based on this material.
00023 *
00024 * ===========================================================================
00025 *
00026 * Author: Eugene Vasilchenko, Denis Vakatov, Anatoliy Kuznetsov
00027 *
00028 * File Description:
00029 *   Definition CGI application class and its context class.
00030 */
00031 
00032 #include <ncbi_pch.hpp>
00033 #include <corelib/ncbienv.hpp>
00034 #include <corelib/rwstream.hpp>
00035 #include <corelib/stream_utils.hpp>
00036 #include <corelib/ncbi_system.hpp> // for SuppressSystemMessageBox
00037 #include <corelib/rwstream.hpp>
00038 #include <corelib/ncbi_safe_static.hpp>
00039 #include <corelib/request_ctx.hpp>
00040 #include <corelib/ncbi_strings.h>
00041 
00042 #include <util/multi_writer.hpp>
00043 #include <util/cache/cache_ref.hpp>
00044 
00045 #include <cgi/cgictx.hpp>
00046 #include <cgi/cgi_exception.hpp>
00047 #include <cgi/cgi_serial.hpp>
00048 #include <cgi/error_codes.hpp>
00049 
00050 #ifdef NCBI_OS_UNIX
00051 #  include <unistd.h>
00052 #endif
00053 
00054 
00055 #define NCBI_USE_ERRCODE_X   Cgi_Application
00056 
00057 
00058 BEGIN_NCBI_SCOPE
00059 
00060 
00061 NCBI_PARAM_DECL(bool, CGI, Print_Http_Referer);
00062 NCBI_PARAM_DEF_EX(bool, CGI, Print_Http_Referer, true, eParam_NoThread,
00063                   CGI_PRINT_HTTP_REFERER);
00064 static NCBI_PARAM_TYPE(CGI, Print_Http_Referer) s_PrintRefererParam;
00065 
00066 
00067 NCBI_PARAM_DECL(bool, CGI, Print_User_Agent);
00068 NCBI_PARAM_DEF_EX(bool, CGI, Print_User_Agent, true, eParam_NoThread,
00069                   CGI_PRINT_USER_AGENT);
00070 static NCBI_PARAM_TYPE(CGI, Print_User_Agent) s_PrintUserAgentParam;
00071 
00072 
00073 NCBI_PARAM_DECL(bool, CGI, Print_Self_Url);
00074 NCBI_PARAM_DEF_EX(bool, CGI, Print_Self_Url, true, eParam_NoThread,
00075                   CGI_PRINT_SELF_URL);
00076 static NCBI_PARAM_TYPE(CGI, Print_Self_Url) s_PrintSelfUrlParam;
00077 
00078 
00079 ///////////////////////////////////////////////////////
00080 // IO streams with byte counting for CGI applications
00081 //
00082 
00083 
00084 class CCGIStreamReader : public IReader
00085 {
00086 public:
00087     CCGIStreamReader(istream& is) : m_IStr(is) { }
00088 
00089     virtual ERW_Result Read(void*   buf,
00090                             size_t  count,
00091                             size_t* bytes_read = 0);
00092     virtual ERW_Result PendingCount(size_t* count)
00093     { return eRW_NotImplemented; }
00094 
00095 protected:
00096     istream& m_IStr;
00097 };
00098 
00099 
00100 ERW_Result CCGIStreamReader::Read(void*   buf,
00101                                   size_t  count,
00102                                   size_t* bytes_read)
00103 {
00104     streamsize x_read = CStreamUtils::Readsome(m_IStr, (char*)buf, count);
00105     ERW_Result result;
00106     if (x_read < 0) {
00107         result = eRW_Error;
00108     }
00109     else if (x_read > 0) {
00110         result = eRW_Success;
00111     }
00112     else {
00113         result = eRW_Eof;
00114     }
00115     if (bytes_read) {
00116         *bytes_read = x_read < 0 ? 0 : x_read;
00117     }
00118     return result;
00119 }
00120 
00121 
00122 class CCGIStreamWriter : public IWriter
00123 {
00124 public:
00125     CCGIStreamWriter(ostream& os) : m_OStr(os) { }
00126 
00127     virtual ERW_Result Write(const void* buf,
00128                              size_t      count,
00129                              size_t*     bytes_written = 0);
00130 
00131     virtual ERW_Result Flush(void)
00132     { return m_OStr.flush() ? eRW_Success : eRW_Error; }
00133 
00134 protected:
00135     ostream& m_OStr;
00136 };
00137 
00138 
00139 ERW_Result CCGIStreamWriter::Write(const void* buf,
00140                                    size_t      count,
00141                                    size_t*     bytes_written)
00142 {
00143     ERW_Result result;
00144     if (!m_OStr.write((char*)buf, count)) {
00145         result = eRW_Error;
00146     }
00147     else {
00148         result = eRW_Success;
00149     }
00150     if (bytes_written) {
00151         *bytes_written = result == eRW_Success ? count : 0;
00152     }
00153     return result;
00154 }
00155 
00156 
00157 ///////////////////////////////////////////////////////
00158 // CCgiApplication
00159 //
00160 
00161 
00162 CCgiApplication* CCgiApplication::Instance(void)
00163 {
00164     return dynamic_cast<CCgiApplication*> (CParent::Instance());
00165 }
00166 
00167 
00168 int CCgiApplication::Run(void)
00169 {
00170     // Value to return from this method Run()
00171     int result;
00172 
00173     // Try to run as a Fast-CGI loop
00174     if ( x_RunFastCGI(&result) ) {
00175         return result;
00176     }
00177 
00178     /// Run as a plain CGI application
00179 
00180     // Make sure to restore old diagnostic state after the Run()
00181     CDiagRestorer diag_restorer;
00182 
00183     // Compose diagnostics prefix
00184 #if defined(NCBI_OS_UNIX)
00185     PushDiagPostPrefix(NStr::IntToString(getpid()).c_str());
00186 #endif
00187     PushDiagPostPrefix(GetEnvironment().Get(m_DiagPrefixEnv).c_str());
00188 
00189     // Timing
00190     CTime start_time(CTime::eCurrent);
00191 
00192     // Logging for statistics
00193     bool is_stat_log = GetConfig().GetBool("CGI", "StatLog", false,
00194                                            0, CNcbiRegistry::eReturn);
00195     bool skip_stat_log = false;
00196     auto_ptr<CCgiStatistics> stat(is_stat_log ? CreateStat() : 0);
00197 
00198     CNcbiOstream* orig_stream = NULL;
00199     //int orig_fd = -1;
00200     CNcbiStrstream result_copy;
00201     auto_ptr<CNcbiOstream> new_stream;
00202     
00203     try {
00204         _TRACE("(CGI) CCgiApplication::Run: calling ProcessRequest");
00205         GetDiagContext().SetAppState(eDiagAppState_RequestBegin);
00206 
00207         m_Context.reset( CreateContext() );
00208         ConfigureDiagnostics(*m_Context);
00209         x_AddLBCookie();
00210         try {
00211             // Print request start message
00212             x_OnEvent(eStartRequest, 0);
00213 
00214             VerifyCgiContext(*m_Context);
00215             ProcessHttpReferer();
00216             LogRequest();
00217 
00218             try {
00219                 m_Cache.reset( GetCacheStorage() );
00220             } catch( exception& ex ) {
00221                 ERR_POST_X(1, "Couldn't create cache : " << ex.what());
00222             }
00223             bool skip_process_request = false;
00224             bool caching_needed = IsCachingNeeded(m_Context->GetRequest());
00225             if (m_Cache.get() && caching_needed) {
00226                 skip_process_request = GetResultFromCache(m_Context->GetRequest(),
00227                                                            m_Context->GetResponse().out());
00228             }
00229             if (!skip_process_request) {
00230                 if( m_Cache.get() ) {
00231                     list<CNcbiOstream*> slist;
00232                     orig_stream = m_Context->GetResponse().GetOutput();
00233                     slist.push_back(orig_stream);
00234                     slist.push_back(&result_copy);
00235                     new_stream.reset(new CWStream(new CMultiWriter(slist), 0,0,
00236                                                   CRWStreambuf::fOwnWriter));
00237                     m_Context->GetResponse().SetOutput(new_stream.get());
00238                 }
00239                 GetDiagContext().SetAppState(eDiagAppState_Request);
00240                 result = ProcessRequest(*m_Context);
00241                 GetDiagContext().SetAppState(eDiagAppState_RequestEnd);
00242                 if (result != 0) {
00243                     SetHTTPStatus(500);
00244                 } else {
00245                     if (m_Cache.get()) {
00246                         m_Context->GetResponse().Flush();
00247                         if (m_IsResultReady) {
00248                             if(caching_needed)
00249                                 SaveResultToCache(m_Context->GetRequest(), result_copy);
00250                             else {
00251                                 auto_ptr<CCgiRequest> request(GetSavedRequest(m_RID));
00252                                 if (request.get()) 
00253                                     SaveResultToCache(*request, result_copy);
00254                             }
00255                         } else if (caching_needed) {
00256                             SaveRequest(m_RID, m_Context->GetRequest());
00257                         }
00258                     }
00259                 }
00260             }
00261         }
00262         catch (CCgiException& e) {
00263             if ( e.GetStatusCode() <  CCgiException::e200_Ok  ||
00264                  e.GetStatusCode() >= CCgiException::e400_BadRequest ) {
00265                 throw;
00266             }
00267             GetDiagContext().SetAppState(eDiagAppState_RequestEnd);
00268             // If for some reason exception with status 2xx was thrown,
00269             // set the result to 0, update HTTP status and continue.
00270             m_Context->GetResponse().SetStatus(e.GetStatusCode(),
00271                                                e.GetStatusMessage());
00272             result = 0;
00273         }
00274         _TRACE("CCgiApplication::Run: flushing");
00275         m_Context->GetResponse().Flush();
00276         _TRACE("CCgiApplication::Run: return " << result);
00277         x_OnEvent(result == 0 ? eSuccess : eError, result);
00278         x_OnEvent(eExit, result);
00279     }
00280     catch (exception& e) {
00281         GetDiagContext().SetAppState(eDiagAppState_RequestEnd);
00282         // Call the exception handler and set the CGI exit code
00283         result = OnException(e, NcbiCout);
00284         x_OnEvent(eException, result);
00285 
00286         // Logging
00287         {{
00288             string msg = "(CGI) CCgiApplication::ProcessRequest() failed: ";
00289             msg += e.what();
00290 
00291             if ( is_stat_log ) {
00292                 stat->Reset(start_time, result, &e);
00293                 msg = stat->Compose();
00294                 stat->Submit(msg);
00295                 skip_stat_log = true; // Don't print the same message again
00296             }
00297         }}
00298 
00299         // Exception reporting
00300         NCBI_REPORT_EXCEPTION_X(13, "(CGI) CCgiApplication::Run", e);
00301     }
00302 
00303     // Logging
00304     if ( is_stat_log  &&  !skip_stat_log ) {
00305         stat->Reset(start_time, result);
00306         string msg = stat->Compose();
00307         stat->Submit(msg);
00308     }
00309 
00310     x_OnEvent(eEndRequest, 120);
00311     x_OnEvent(eExit, result);
00312 
00313     if (orig_stream) 
00314         m_Context->GetResponse().SetOutput(NULL);
00315     return result;
00316 }
00317 
00318 
00319 const char* kExtraType_CGI = "NCBICGI";
00320 
00321 void CCgiApplication::ProcessHttpReferer(void)
00322 {
00323     // Set HTTP_REFERER
00324     CCgiContext& ctx = GetContext();
00325     string ref = ctx.GetSelfURL();
00326     if ( !ref.empty() ) {
00327         string args =
00328             ctx.GetRequest().GetProperty(eCgi_QueryString);
00329         if ( !args.empty() ) {
00330             ref += "?" + args;
00331         }
00332         GetConfig().Set("CONN", "HTTP_REFERER", ref);
00333     }
00334 }
00335 
00336 
00337 void CCgiApplication::LogRequest(void) const
00338 {
00339     const CCgiContext& ctx = GetContext();
00340     string str;
00341     if ( s_PrintSelfUrlParam.Get() ) {
00342         // Print script URL
00343         str = ctx.GetSelfURL();
00344         if ( !str.empty() ) {
00345             string args =
00346                 ctx.GetRequest().GetProperty(eCgi_QueryString);
00347             if ( !args.empty() ) {
00348                 str += "?" + args;
00349             }
00350             GetDiagContext().Extra().
00351                 //SetType(kExtraType_CGI).
00352                 Print("SELF_URL", str);
00353         }
00354     }
00355     // Print HTTP_REFERER
00356     if ( s_PrintRefererParam.Get() ) {
00357         str = ctx.GetRequest().GetProperty(eCgi_HttpReferer);
00358         if ( !str.empty() ) {
00359             GetDiagContext().Extra().
00360                 //SetType(kExtraType_CGI).
00361                 Print("HTTP_REFERER", str);
00362         }
00363     }
00364     // Print USER_AGENT
00365     if ( s_PrintUserAgentParam.Get() ) {
00366         str = ctx.GetRequest().GetProperty(eCgi_HttpUserAgent);
00367         if ( !str.empty() ) {
00368             GetDiagContext().Extra().
00369                 //SetType(kExtraType_CGI).
00370                 Print("USER_AGENT", str);
00371         }
00372     }
00373 }
00374 
00375 
00376 void CCgiApplication::SetupArgDescriptions(CArgDescriptions* arg_desc)
00377 {
00378     arg_desc->SetArgsType(CArgDescriptions::eCgiArgs);
00379 
00380     CParent::SetupArgDescriptions(arg_desc);
00381 }
00382 
00383 
00384 CCgiContext& CCgiApplication::x_GetContext( void ) const
00385 {
00386     if ( !m_Context.get() ) {
00387         ERR_POST_X(2, "CCgiApplication::GetContext: no context set");
00388         throw runtime_error("no context set");
00389     }
00390     return *m_Context;
00391 }
00392 
00393 
00394 CNcbiResource& CCgiApplication::x_GetResource( void ) const
00395 {
00396     if ( !m_Resource.get() ) {
00397         ERR_POST_X(3, "CCgiApplication::GetResource: no resource set");
00398         throw runtime_error("no resource set");
00399     }
00400     return *m_Resource;
00401 }
00402 
00403 
00404 NCBI_PARAM_DECL(bool, CGI, Merge_Log_Lines);
00405 NCBI_PARAM_DEF_EX(bool, CGI, Merge_Log_Lines, true, eParam_NoThread,
00406                   CGI_MERGE_LOG_LINES);
00407 static NCBI_PARAM_TYPE(CGI, Merge_Log_Lines) s_MergeLogLines;
00408 
00409 
00410 void CCgiApplication::Init(void)
00411 {
00412     if ( s_MergeLogLines.Get() ) {
00413         // Convert multi-line diagnostic messages into one-line ones by default.
00414         SetDiagPostFlag(eDPF_PreMergeLines);
00415         SetDiagPostFlag(eDPF_MergeLines);
00416     }
00417 
00418     CParent::Init();
00419 
00420     m_Resource.reset(LoadResource());
00421 
00422     m_DiagPrefixEnv = GetConfig().Get("CGI", "DiagPrefixEnv");
00423 }
00424 
00425 
00426 void CCgiApplication::Exit(void)
00427 {
00428     m_Resource.reset(0);
00429     CParent::Exit();
00430 }
00431 
00432 
00433 CNcbiResource* CCgiApplication::LoadResource(void)
00434 {
00435     return 0;
00436 }
00437 
00438 
00439 CCgiServerContext* CCgiApplication::LoadServerContext(CCgiContext& /*context*/)
00440 {
00441     return 0;
00442 }
00443 
00444 
00445 NCBI_PARAM_DECL(bool, CGI, Count_Transfered);
00446 NCBI_PARAM_DEF_EX(bool, CGI, Count_Transfered, false, eParam_NoThread,
00447                   CGI_COUNT_TRANSFERED);
00448 typedef NCBI_PARAM_TYPE(CGI, Count_Transfered) TCGI_Count_Transfered;
00449 
00450 
00451 CCgiContext* CCgiApplication::CreateContext
00452 (CNcbiArguments*   args,
00453  CNcbiEnvironment* env,
00454  CNcbiIstream*     inp,
00455  CNcbiOstream*     out,
00456  int               ifd,
00457  int               ofd)
00458 {
00459     int errbuf_size =
00460         GetConfig().GetInt("CGI", "RequestErrBufSize", 256, 0,
00461                            CNcbiRegistry::eReturn);
00462 
00463     if ( TCGI_Count_Transfered::GetDefault() ) {
00464         if ( !inp ) {
00465             if ( !m_InputStream.get() ) {
00466                 m_InputStream.reset(
00467                     new CRStream(new CCGIStreamReader(std::cin),
00468                                 CRWStreambuf::fOwnReader));
00469             }
00470             inp = m_InputStream.get();
00471         }
00472         if ( !out ) {
00473             if ( !m_OutputStream.get() ) {
00474                 m_OutputStream.reset(
00475                     new CWStream(new CCGIStreamWriter(std::cout),
00476                                 CRWStreambuf::fOwnWriter));
00477             }
00478             out = m_OutputStream.get();
00479             if ( m_InputStream.get() ) {
00480                 // If both streams are created by the application, tie them.
00481                 inp->tie(out);
00482             }
00483         }
00484     }
00485     return
00486         new CCgiContext(*this, args, env, inp, out, ifd, ofd,
00487                         (errbuf_size >= 0) ? (size_t) errbuf_size : 256,
00488                         m_RequestFlags);
00489 }
00490 
00491 
00492 void CCgiApplication::SetCafService(CCookieAffinity* caf)
00493 {
00494     m_Caf.reset(caf);
00495 }
00496 
00497 
00498 
00499 // Flexible diagnostics support
00500 //
00501 
00502 class CStderrDiagFactory : public CDiagFactory
00503 {
00504 public:
00505     virtual CDiagHandler* New(const string&) {
00506         return new CStreamDiagHandler(&NcbiCerr);
00507     }
00508 };
00509 
00510 
00511 class CAsBodyDiagFactory : public CDiagFactory
00512 {
00513 public:
00514     CAsBodyDiagFactory(CCgiApplication* app) : m_App(app) {}
00515     virtual CDiagHandler* New(const string&) {
00516         CCgiResponse& response = m_App->GetContext().GetResponse();
00517         CDiagHandler* result   = new CStreamDiagHandler(&response.out());
00518         if (!response.IsHeaderWritten()) {
00519             response.SetContentType("text/plain");
00520             response.WriteHeader();
00521         }
00522         response.SetOutput(0); // suppress normal output
00523         return result;
00524     }
00525 
00526 private:
00527     CCgiApplication* m_App;
00528 };
00529 
00530 
00531 CCgiApplication::CCgiApplication(void) 
00532  : m_RequestFlags(0),
00533    m_HostIP(0), 
00534    m_Iteration(0),
00535    m_ArgContextSync(false),
00536    m_IsResultReady(true),
00537    m_ShouldExit(false),
00538    m_RequestStartPrinted(false)
00539 {
00540     // CGI applications should use /log for logging by default
00541     CDiagContext::SetUseRootLog();
00542     // Disable system popup messages
00543     SuppressSystemMessageBox();
00544 
00545     // Turn on iteration number
00546     SetDiagPostFlag(eDPF_RequestId);
00547     SetDiagTraceFlag(eDPF_RequestId);
00548 
00549     SetStdioFlags(fBinaryCin | fBinaryCout);
00550     DisableArgDescriptions();
00551     RegisterDiagFactory("stderr", new CStderrDiagFactory);
00552     RegisterDiagFactory("asbody", new CAsBodyDiagFactory(this));
00553 }
00554 
00555 
00556 CCgiApplication::~CCgiApplication(void)
00557 {
00558     ITERATE (TDiagFactoryMap, it, m_DiagFactories) {
00559         delete it->second;
00560     }
00561     if ( m_HostIP )
00562         free(m_HostIP);
00563 }
00564 
00565 
00566 int CCgiApplication::OnException(exception& e, CNcbiOstream& os)
00567 {
00568     // Discriminate between different types of error
00569     string status_str = "500 Server Error";
00570     string message = "";
00571     SetHTTPStatus(500);
00572     CException* ce = dynamic_cast<CException*> (&e);
00573     if ( ce ) {
00574         message = ce->GetMsg();
00575         CCgiException* cgi_e = dynamic_cast<CCgiException*>(&e);
00576         if ( cgi_e ) {
00577             if ( cgi_e->GetStatusCode() != CCgiException::eStatusNotSet ) {
00578                 SetHTTPStatus(cgi_e->GetStatusCode());
00579                 status_str = NStr::IntToString(cgi_e->GetStatusCode()) +
00580                     " " + cgi_e->GetStatusMessage();
00581             }
00582             else {
00583                 // Convert CgiRequestException and CCgiArgsException
00584                 // to error 400
00585                 if (dynamic_cast<CCgiRequestException*> (&e)  ||
00586                     dynamic_cast<CCgiArgsException*> (&e)) {
00587                     SetHTTPStatus(400);
00588                     status_str = "400 Malformed HTTP Request";
00589                 }
00590             }
00591         }
00592     }
00593     else {
00594         message = e.what();
00595     }
00596 
00597     try {
00598         // HTTP header
00599         os << "Status: " << status_str << HTTP_EOL;
00600         os << "Content-Type: text/plain" HTTP_EOL HTTP_EOL;
00601 
00602         // Message
00603         os << "ERROR:  " << status_str << " " HTTP_EOL HTTP_EOL;
00604         os << message;
00605 
00606         if ( dynamic_cast<CArgException*> (&e) ) {
00607             string ustr;
00608             const CArgDescriptions* descr = GetArgDescriptions();
00609             if (descr) {
00610                 os << descr->PrintUsage(ustr) << HTTP_EOL HTTP_EOL;
00611             }
00612         }
00613 
00614 
00615         // Check for problems in sending the response
00616         if ( !os.good() ) {
00617             ERR_POST_X(4, "CCgiApplication::OnException() failed to send error page"
00618                           " back to the client");
00619             return -1;
00620         }
00621     }
00622     catch (exception& e) {
00623         NCBI_REPORT_EXCEPTION_X(14, "(CGI) CCgiApplication::Run", e);
00624     }
00625     return 0;
00626 }
00627 
00628 
00629 const CArgs& CCgiApplication::GetArgs(void) const
00630 {
00631     // Are there no argument descriptions or no CGI context (yet?)
00632     if (!GetArgDescriptions()  ||  !m_Context.get())
00633         return CParent::GetArgs();
00634 
00635     // Is everything already in-sync
00636     if ( m_ArgContextSync )
00637         return *m_CgiArgs;
00638 
00639     // Create CGI version of args, if necessary
00640     if ( !m_CgiArgs.get() )
00641         m_CgiArgs.reset(new CArgs());
00642 
00643     // Copy cmd-line arg values to CGI args
00644     *m_CgiArgs = CParent::GetArgs();
00645 
00646     // Add CGI parameters to the CGI version of args
00647     GetArgDescriptions()->ConvertKeys(m_CgiArgs.get(),
00648                                       GetContext().GetRequest().GetEntries(),
00649                                       true /*update=yes*/);
00650 
00651     m_ArgContextSync = true;
00652     return *m_CgiArgs;
00653 }
00654 
00655 
00656 void CCgiApplication::x_OnEvent(EEvent event, int status)
00657 {
00658     switch ( event ) {
00659     case eStartRequest:
00660         {
00661             // Set context properties
00662             const CCgiRequest& req = m_Context->GetRequest();
00663 
00664             // Print request start message
00665             if ( !CDiagContext::IsSetOldPostFormat() ) {
00666                 GetDiagContext().PrintRequestStart(req.GetCGIEntriesStr());
00667                 m_RequestStartPrinted = true;
00668             }
00669 
00670             // Set default HTTP status code (reset above by PrintRequestStart())
00671             SetHTTPStatus(200);
00672 
00673             const string& phid = CDiagContext::GetRequestContext().GetHitID();
00674             // Check if ncbi_st cookie is set
00675             const CCgiCookie* st = req.GetCookies().Find(
00676                 g_GetNcbiString(eNcbiStrings_Stat));
00677             CCgiArgs pg_info;
00678             if ( st ) {
00679                 pg_info.SetQueryString(st->GetValue());
00680             }
00681             pg_info.SetValue(g_GetNcbiString(eNcbiStrings_PHID), phid);
00682             // Log ncbi_st values
00683             CDiagContext_Extra extra = GetDiagContext().Extra();
00684             // extra.SetType("NCBICGI");
00685             ITERATE(CCgiArgs::TArgs, it, pg_info.GetArgs()) {
00686                 extra.Print(it->name, it->value);
00687             }
00688             extra.Flush();
00689             break;
00690         }
00691     case eSuccess:
00692     case eError:
00693     case eException:
00694         {
00695             CRequestContext& rctx = GetDiagContext().GetRequestContext();
00696             if ( m_InputStream.get() ) {
00697                 if ( m_InputStream->eof() ) {
00698                     m_InputStream->clear();
00699                 }
00700                 rctx.SetBytesRd(NcbiStreamposToInt8(m_InputStream->tellg()));
00701             }
00702             if ( m_OutputStream.get() ) {
00703                 rctx.SetBytesWr(NcbiStreamposToInt8(m_OutputStream->tellp()));
00704             }
00705             break;
00706         }
00707     case eEndRequest:
00708         {
00709             if ( m_RequestStartPrinted  &&
00710                 !CDiagContext::IsSetOldPostFormat() ) {
00711                 // This will also reset request context
00712                 GetDiagContext().PrintRequestStop();
00713                 m_RequestStartPrinted = false;
00714             }
00715             break;
00716         }
00717     case eExit:
00718     case eExecutable:
00719     case eWatchFile:
00720     case eExitOnFail:
00721     case eExitRequest:
00722     case eWaiting:
00723         {
00724             break;
00725         }
00726     }
00727 
00728     OnEvent(event, status);
00729 }
00730 
00731 
00732 void CCgiApplication::OnEvent(EEvent /*event*/,
00733                               int    /*exit_code*/)
00734 {
00735     return;
00736 }
00737 
00738 
00739 void CCgiApplication::RegisterDiagFactory(const string& key,
00740                                           CDiagFactory* fact)
00741 {
00742     m_DiagFactories[key] = fact;
00743 }
00744 
00745 
00746 CDiagFactory* CCgiApplication::FindDiagFactory(const string& key)
00747 {
00748     TDiagFactoryMap::const_iterator it = m_DiagFactories.find(key);
00749     if (it == m_DiagFactories.end())
00750         return 0;
00751     return it->second;
00752 }
00753 
00754 
00755 void CCgiApplication::ConfigureDiagnostics(CCgiContext& context)
00756 {
00757     // Disable for production servers?
00758     ConfigureDiagDestination(context);
00759     ConfigureDiagThreshold(context);
00760     ConfigureDiagFormat(context);
00761 }
00762 
00763 
00764 void CCgiApplication::ConfigureDiagDestination(CCgiContext& context)
00765 {
00766     const CCgiRequest& request = context.GetRequest();
00767 
00768     bool   is_set;
00769     string dest   = request.GetEntry("diag-destination", &is_set);
00770     if ( !is_set )
00771         return;
00772 
00773     SIZE_TYPE colon = dest.find(':');
00774     CDiagFactory* factory = FindDiagFactory(dest.substr(0, colon));
00775     if ( factory ) {
00776         SetDiagHandler(factory->New(dest.substr(colon + 1)));
00777     }
00778 }
00779 
00780 
00781 void CCgiApplication::ConfigureDiagThreshold(CCgiContext& context)
00782 {
00783     const CCgiRequest& request = context.GetRequest();
00784 
00785     bool   is_set;
00786     string threshold = request.GetEntry("diag-threshold", &is_set);
00787     if ( !is_set )
00788         return;
00789 
00790     if (threshold == "fatal") {
00791         SetDiagPostLevel(eDiag_Fatal);
00792     } else if (threshold == "critical") {
00793         SetDiagPostLevel(eDiag_Critical);
00794     } else if (threshold == "error") {
00795         SetDiagPostLevel(eDiag_Error);
00796     } else if (threshold == "warning") {
00797         SetDiagPostLevel(eDiag_Warning);
00798     } else if (threshold == "info") {
00799         SetDiagPostLevel(eDiag_Info);
00800     } else if (threshold == "trace") {
00801         SetDiagPostLevel(eDiag_Info);
00802         SetDiagTrace(eDT_Enable);
00803     }
00804 }
00805 
00806 
00807 void CCgiApplication::ConfigureDiagFormat(CCgiContext& context)
00808 {
00809     const CCgiRequest& request = context.GetRequest();
00810 
00811     typedef map<string, TDiagPostFlags> TFlagMap;
00812     static CSafeStaticPtr<TFlagMap> s_FlagMap;
00813     TFlagMap& flagmap = s_FlagMap.Get();
00814 
00815     TDiagPostFlags defaults = (eDPF_Prefix | eDPF_Severity
00816                                | eDPF_ErrCode | eDPF_ErrSubCode);
00817 
00818     if ( !CDiagContext::IsSetOldPostFormat() ) {
00819         defaults |= (eDPF_UID | eDPF_PID | eDPF_RequestId |
00820             eDPF_SerialNo | eDPF_ErrorID);
00821     }
00822 
00823     TDiagPostFlags new_flags = 0;
00824 
00825     bool   is_set;
00826     string format = request.GetEntry("diag-format", &is_set);
00827     if ( !is_set )
00828         return;
00829 
00830     if (flagmap.empty()) {
00831         flagmap["file"]        = eDPF_File;
00832         flagmap["path"]        = eDPF_LongFilename;
00833         flagmap["line"]        = eDPF_Line;
00834         flagmap["prefix"]      = eDPF_Prefix;
00835         flagmap["severity"]    = eDPF_Severity;
00836         flagmap["code"]        = eDPF_ErrCode;
00837         flagmap["subcode"]     = eDPF_ErrSubCode;
00838         flagmap["time"]        = eDPF_DateTime;
00839         flagmap["omitinfosev"] = eDPF_OmitInfoSev;
00840         flagmap["all"]         = eDPF_All;
00841         flagmap["trace"]       = eDPF_Trace;
00842         flagmap["log"]         = eDPF_Log;
00843         flagmap["errorid"]     = eDPF_ErrorID;
00844         flagmap["location"]    = eDPF_Location;
00845         flagmap["pid"]         = eDPF_PID;
00846         flagmap["tid"]         = eDPF_TID;
00847         flagmap["serial"]      = eDPF_SerialNo;
00848         flagmap["serial_thr"]  = eDPF_SerialNo_Thread;
00849         flagmap["iteration"]   = eDPF_RequestId;
00850         flagmap["uid"]         = eDPF_UID;
00851     }
00852     list<string> flags;
00853     NStr::Split(format, " ", flags);
00854     ITERATE(list<string>, flag, flags) {
00855         TFlagMap::const_iterator it;
00856         if ((it = flagmap.find(*flag)) != flagmap.end()) {
00857             new_flags |= it->second;
00858         } else if ((*flag)[0] == '!'
00859                    &&  ((it = flagmap.find(flag->substr(1)))
00860                         != flagmap.end())) {
00861             new_flags &= ~(it->second);
00862         } else if (*flag == "default") {
00863             new_flags |= defaults;
00864         }
00865     }
00866     SetDiagPostAllFlags(new_flags);
00867 }
00868 
00869 
00870 CCgiApplication::ELogOpt CCgiApplication::GetLogOpt() const
00871 {
00872     string log = GetConfig().Get("CGI", "Log");
00873 
00874     CCgiApplication::ELogOpt logopt = eNoLog;
00875     if ((NStr::CompareNocase(log, "On") == 0) ||
00876         (NStr::CompareNocase(log, "true") == 0)) {
00877         logopt = eLog;
00878     } else if (NStr::CompareNocase(log, "OnError") == 0) {
00879         logopt = eLogOnError;
00880     }
00881 #ifdef _DEBUG
00882     else if (NStr::CompareNocase(log, "OnDebug") == 0) {
00883         logopt = eLog;
00884     }
00885 #endif
00886 
00887     return logopt;
00888 }
00889 
00890 
00891 CCgiStatistics* CCgiApplication::CreateStat()
00892 {
00893     return new CCgiStatistics(*this);
00894 }
00895 
00896 ICgiSessionStorage* 
00897 CCgiApplication::GetSessionStorage(CCgiSessionParameters&) const 
00898 {
00899     return 0;
00900 }
00901 bool CCgiApplication::IsCachingNeeded(const CCgiRequest& request) const
00902 {
00903     return true;
00904 }
00905 
00906 ICache* CCgiApplication::GetCacheStorage() const
00907 { 
00908     return NULL;
00909 }
00910 
00911 
00912 CCgiApplication::EPreparseArgs
00913 CCgiApplication::PreparseArgs(int                argc,
00914                               const char* const* argv)
00915 {
00916     static const char* s_ArgVersion = "-version";
00917     static const char* s_ArgFullVersion = "-version-full";
00918 
00919     if (argc != 2  ||  !argv[1]) {
00920         return ePreparse_Continue;
00921     }
00922     if ( NStr::strcmp(argv[1], s_ArgVersion) == 0 ) {
00923         // Print VERSION
00924         cout << GetFullVersion().Print(GetProgramDisplayName(),
00925             CVersion::fVersionInfo | CVersion::fPackageShort);
00926         return ePreparse_Exit;
00927     }
00928     else if ( NStr::strcmp(argv[1], s_ArgFullVersion) == 0 ) {
00929         // Print full VERSION
00930         cout << GetFullVersion().Print(GetProgramDisplayName());
00931         return ePreparse_Exit;
00932     }
00933     return ePreparse_Continue;
00934 }
00935 
00936 
00937 void CCgiApplication::SetRequestId(const string& rid, bool is_done)
00938 {
00939     m_RID = rid;
00940     m_IsResultReady = is_done;
00941 }
00942 bool CCgiApplication::GetResultFromCache(const CCgiRequest& request, CNcbiOstream& os)
00943 {
00944     string checksum, content;
00945     if (!request.CalcChecksum(checksum, content))
00946         return false;
00947 
00948     try {
00949         CCacheHashedContent helper(*m_Cache);
00950         auto_ptr<IReader> reader( helper.GetHashedContent(checksum, content));
00951         if (reader.get()) {
00952             //cout << "(Read) " << checksum << " --- " << content << endl;
00953             CRStream cache_reader(reader.get());
00954             return NcbiStreamCopy(os, cache_reader);
00955         }
00956     } catch (exception& ex) {
00957         ERR_POST_X(5, "Couldn't read cached request : " << ex.what());
00958     }
00959     return false;
00960 }
00961 void CCgiApplication::SaveResultToCache(const CCgiRequest& request, CNcbiIstream& is)
00962 {
00963     string checksum, content;
00964     if ( !request.CalcChecksum(checksum, content) )
00965         return;
00966     try {
00967         CCacheHashedContent helper(*m_Cache);
00968         auto_ptr<IWriter> writer( helper.StoreHashedContent(checksum, content) );
00969         if (writer.get()) {
00970             //        cout << "(Write) : " << checksum << " --- " << content << endl;
00971             CWStream cache_writer(writer.get());
00972             NcbiStreamCopy(cache_writer, is);
00973         }
00974     } catch (exception& ex) {
00975         ERR_POST_X(6, "Couldn't cache request : " << ex.what());
00976     } 
00977 }
00978 
00979 void CCgiApplication::SaveRequest(const string& rid, const CCgiRequest& request)
00980 {    
00981     if (rid.empty())
00982         return;
00983     try {
00984         auto_ptr<IWriter> writer( m_Cache->GetWriteStream(rid, 0, "NS_JID") );
00985         if (writer.get()) {
00986             CWStream cache_stream(writer.get());            
00987             request.Serialize(cache_stream);
00988         }
00989     } catch (exception& ex) {
00990         ERR_POST_X(7, "Couldn't save request : " << ex.what());
00991     } 
00992 }
00993 CCgiRequest* CCgiApplication::GetSavedRequest(const string& rid)
00994 {
00995     if (rid.empty())
00996         return NULL;
00997     try {
00998         auto_ptr<IReader> reader(m_Cache->GetReadStream(rid, 0, "NS_JID"));
00999         if (reader.get()) {
01000             CRStream cache_stream(reader.get());
01001             auto_ptr<CCgiRequest> request(new CCgiRequest);
01002             request->Deserialize(cache_stream, 0);
01003             return request.release();
01004         }
01005     } catch (exception& ex) {
01006         ERR_POST_X(8, "Couldn't read saved request : " << ex.what());
01007     } 
01008     return NULL;
01009 }
01010 
01011 void CCgiApplication::x_AddLBCookie()
01012 {
01013     const CNcbiRegistry& reg = GetConfig();
01014 
01015     string cookie_name = GetConfig().Get("CGI-LB", "Name");
01016     if ( cookie_name.empty() )
01017         return;
01018 
01019     int life_span = reg.GetInt("CGI-LB", "LifeSpan", 0, 0,
01020                                CNcbiRegistry::eReturn);
01021 
01022     string domain = reg.GetString("CGI-LB", "Domain", ".ncbi.nlm.nih.gov");
01023 
01024     if ( domain.empty() ) {
01025         ERR_POST_X(9, "CGI-LB: 'Domain' not specified.");
01026     } else {
01027         if (domain[0] != '.') {     // domain must start with dot
01028             domain.insert(0, ".");
01029         }
01030     }
01031 
01032     string path = reg.Get("CGI-LB", "Path");
01033 
01034     bool secure = reg.GetBool("CGI-LB", "Secure", false,
01035                               0, CNcbiRegistry::eErrPost);
01036 
01037     string host;
01038 
01039     // Getting host configuration can take some time
01040     // for fast CGIs we try to avoid overhead and call it only once
01041     // m_HostIP variable keeps the cached value
01042 
01043     if ( m_HostIP ) {     // repeated call
01044         host = m_HostIP;
01045     }
01046     else {               // first time call
01047         host = reg.Get("CGI-LB", "Host");
01048         if ( host.empty() ) {
01049             if ( m_Caf.get() ) {
01050                 char  host_ip[64] = {0,};
01051                 m_Caf->GetHostIP(host_ip, sizeof(host_ip));
01052                 m_HostIP = m_Caf->Encode(host_ip, 0);
01053                 host = m_HostIP;
01054             }
01055             else {
01056                 ERR_POST_X(10, "CGI-LB: 'Host' not specified.");
01057             }
01058         }
01059     }
01060 
01061 
01062     CCgiCookie cookie(cookie_name, host, domain, path);
01063     if (life_span > 0) {
01064         CTime exp_time(CTime::eCurrent, CTime::eGmt);
01065         exp_time.AddSecond(life_span);
01066         cookie.SetExpTime(exp_time);
01067     }
01068     cookie.SetSecure(secure);
01069 
01070     GetContext().GetResponse().Cookies().Add(cookie);
01071 }
01072 
01073 
01074 void CCgiApplication::VerifyCgiContext(CCgiContext& context)
01075 {
01076     string x_moz = context.GetRequest().GetRandomProperty("X_MOZ");
01077     if ( NStr::EqualNocase(x_moz, "prefetch") ) {
01078         NCBI_EXCEPTION_VAR(ex, CCgiRequestException, eData,
01079             "Prefetch is not allowed for CGIs");
01080         ex.SetStatus(CCgiException::e403_Forbidden);
01081         ex.SetSeverity(eDiag_Info);
01082         NCBI_EXCEPTION_THROW(ex);
01083     }
01084 }
01085 
01086 
01087 void CCgiApplication::AppStart(void)
01088 {
01089     // Print application start message
01090     if ( !CDiagContext::IsSetOldPostFormat() ) {
01091         GetDiagContext().PrintStart(kEmptyStr);
01092     }
01093 }
01094 
01095 
01096 void CCgiApplication::AppStop(int exit_code)
01097 {
01098     GetDiagContext().SetExitCode(exit_code);
01099 }
01100 
01101 
01102 const char* kToolkitRcPath = "/etc/toolkitrc";
01103 const char* kWebDirToPort = "Web_dir_to_port";
01104 
01105 string CCgiApplication::GetDefaultLogPath(void) const
01106 {
01107     string log_path = "/log/";
01108 
01109     string exe_path = GetProgramExecutablePath();
01110     CNcbiIfstream is(kToolkitRcPath, ios::binary);
01111     CNcbiRegistry reg(is);
01112     list<string> entries;
01113     reg.EnumerateEntries(kWebDirToPort, &entries);
01114     size_t min_pos = exe_path.length();
01115     string web_dir;
01116     // Find the first dir name corresponding to one of the entries
01117     ITERATE(list<string>, it, entries) {
01118         if (!it->empty()  &&  (*it)[0] != '/') {
01119             // not an absolute path
01120             string mask = "/" + *it;
01121             if (mask[mask.length() - 1] != '/') {
01122                 mask += "/";
01123             }
01124             size_t pos = exe_path.find(mask);
01125             if (pos < min_pos) {
01126                 min_pos = pos;
01127                 web_dir = *it;
01128             }
01129         }
01130         else {
01131             // absolute path
01132             if (exe_path.substr(0, it->length()) == *it) {
01133                 web_dir = *it;
01134                 break;
01135             }
01136         }
01137     }
01138     if ( !web_dir.empty() ) {
01139         return log_path + reg.GetString(kWebDirToPort, web_dir, kEmptyStr);
01140     }
01141     // Could not find a valid web-dir entry, use port or 'srv'
01142     const char* port = ::getenv("SERVER_PORT");
01143     return port ? log_path + string(port) : log_path + "srv";
01144 }
01145 
01146 
01147 void CCgiApplication::SetHTTPStatus(int status)
01148 {
01149     CDiagContext::GetRequestContext().SetRequestStatus(status);
01150 }
01151 
01152 
01153 ///////////////////////////////////////////////////////
01154 // CCgiStatistics
01155 //
01156 
01157 
01158 CCgiStatistics::CCgiStatistics(CCgiApplication& cgi_app)
01159     : m_CgiApp(cgi_app), m_LogDelim(";")
01160 {
01161 }
01162 
01163 
01164 CCgiStatistics::~CCgiStatistics()
01165 {
01166 }
01167 
01168 
01169 void CCgiStatistics::Reset(const CTime& start_time,
01170                            int          result,
01171                            const std::exception*  ex)
01172 {
01173     m_StartTime = start_time;
01174     m_Result    = result;
01175     m_ErrMsg    = ex ? ex->what() : kEmptyStr;
01176 }
01177 
01178 
01179 string CCgiStatistics::Compose(void)
01180 {
01181     const CNcbiRegistry& reg = m_CgiApp.GetConfig();
01182     CTime end_time(CTime::eCurrent);
01183 
01184     // Check if it is assigned NOT to log the requests took less than
01185     // cut off time threshold
01186     TSeconds time_cutoff = reg.GetInt("CGI", "TimeStatCutOff", 0, 0,
01187                                       CNcbiRegistry::eReturn);
01188     if (time_cutoff > 0) {
01189         TSeconds diff = end_time.DiffSecond(m_StartTime);
01190         if (diff < time_cutoff) {
01191             return kEmptyStr;  // do nothing if it is a light weight request
01192         }
01193     }
01194 
01195     string msg, tmp_str;
01196 
01197     tmp_str = Compose_ProgramName();
01198     if ( !tmp_str.empty() ) {
01199         msg.append(tmp_str);
01200         msg.append(m_LogDelim);
01201     }
01202 
01203     tmp_str = Compose_Result();
01204     if ( !tmp_str.empty() ) {
01205         msg.append(tmp_str);
01206         msg.append(m_LogDelim);
01207     }
01208 
01209     bool is_timing =
01210         reg.GetBool("CGI", "TimeStamp", false, 0, CNcbiRegistry::eErrPost);
01211     if ( is_timing ) {
01212         tmp_str = Compose_Timing(end_time);
01213         if ( !tmp_str.empty() ) {
01214             msg.append(tmp_str);
01215             msg.append(m_LogDelim);
01216         }
01217     }
01218 
01219     tmp_str = Compose_Entries();
01220     if ( !tmp_str.empty() ) {
01221         msg.append(tmp_str);
01222     }
01223 
01224     tmp_str = Compose_ErrMessage();
01225     if ( !tmp_str.empty() ) {
01226         msg.append(tmp_str);
01227         msg.append(m_LogDelim);
01228     }
01229 
01230     return msg;
01231 }
01232 
01233 
01234 void CCgiStatistics::Submit(const string& message)
01235 {
01236     LOG_POST_X(11, message);
01237 }
01238 
01239 
01240 string CCgiStatistics::Compose_ProgramName(void)
01241 {
01242     return m_CgiApp.GetArguments().GetProgramName();
01243 }
01244 
01245 
01246 string CCgiStatistics::Compose_Timing(const CTime& end_time)
01247 {
01248     CTimeSpan elapsed = end_time.DiffTimeSpan(m_StartTime);
01249     return m_StartTime.AsString() + m_LogDelim + elapsed.AsString();
01250 }
01251 
01252 
01253 string CCgiStatistics::Compose_Entries(void)
01254 {
01255     const CCgiContext* ctx = m_CgiApp.m_Context.get();
01256     if ( !ctx )
01257         return kEmptyStr;
01258 
01259     const CCgiRequest& cgi_req = ctx->GetRequest();
01260 
01261     // LogArgs - list of CGI arguments to log.
01262     // Can come as list of arguments (LogArgs = param1;param2;param3),
01263     // or be supplemented with aliases (LogArgs = param1=1;param2=2;param3).
01264     // When alias is provided we use it for logging purposes (this feature
01265     // can be used to save logging space or reduce the net traffic).
01266     const CNcbiRegistry& reg = m_CgiApp.GetConfig();
01267     string log_args = reg.Get("CGI", "LogArgs");
01268     if ( log_args.empty() )
01269         return kEmptyStr;
01270 
01271     list<string> vars;
01272     NStr::Split(log_args, ",; \t", vars);
01273 
01274     string msg;
01275     ITERATE (list<string>, i, vars) {
01276         bool is_entry_found;
01277         const string& arg = *i;
01278 
01279         size_t pos = arg.find_last_of('=');
01280         if (pos == 0) {
01281             return "<misconf>" + m_LogDelim;
01282         } else if (pos != string::npos) {   // alias assigned
01283             string key = arg.substr(0, pos);
01284             const CCgiEntry& entry = cgi_req.GetEntry(key, &is_entry_found);
01285             if ( is_entry_found ) {
01286                 string alias = arg.substr(pos+1, arg.length());
01287                 msg.append(alias);
01288                 msg.append("='");
01289                 msg.append(entry.GetValue());
01290                 msg.append("'");
01291                 msg.append(m_LogDelim);
01292             }
01293         } else {
01294             const CCgiEntry& entry = cgi_req.GetEntry(arg, &is_entry_found);
01295             if ( is_entry_found ) {
01296                 msg.append(arg);
01297                 msg.append("='");
01298                 msg.append(entry.GetValue());
01299                 msg.append("'");
01300                 msg.append(m_LogDelim);
01301             }
01302         }
01303     }
01304 
01305     return msg;
01306 }
01307 
01308 
01309 string CCgiStatistics::Compose_Result(void)
01310 {
01311     return NStr::IntToString(m_Result);
01312 }
01313 
01314 
01315 string CCgiStatistics::Compose_ErrMessage(void)
01316 {
01317     return m_ErrMsg;
01318 }
01319 
01320 /////////////////////////////////////////////////////////////////////////////
01321 //  Tracking Environment
01322 
01323 NCBI_PARAM_DEF(bool, CGI, DisableTrackingCookie, false);
01324 NCBI_PARAM_DEF(string, CGI, TrackingCookieName, "ncbi_sid");
01325 NCBI_PARAM_DEF(string, CGI, TrackingCookieDomain, ".nih.gov");
01326 NCBI_PARAM_DEF(string, CGI, TrackingCookiePath, "/");
01327 
01328 
01329 END_NCBI_SCOPE
01330 
01331 

Generated on Sun Dec 6 22:21:58 2009 for NCBI C++ ToolKit by  doxygen 1.4.6
Modified on Mon Dec 07 16:20:56 2009 by modify_doxy.py rev. 173732