CPPSERV


Home Projects Jobs Clientele Contact
CPPSERV Documentation Download TODO Mailing lists Bug tracker News RSS Feed Browse source

appcontext.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002  *   Copyright (C) 2007 by Ilya A. Volynets-Evenbakh                       *
00003  *   ilya@total-knowledge.com                                              *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU General Public License as published by  *
00007  *   the Free Software Foundation; either version 2 of the License, or     *
00008  *   (at your option) any later version.                                   *
00009  *                                                                         *
00010  *   This program is distributed in the hope that it will be useful,       *
00011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00013  *   GNU General Public License for more details.                          *
00014  *                                                                         *
00015  *   You should have received a copy of the GNU General Public License     *
00016  *   along with this program; if not, write to the                         *
00017  *   Free Software Foundation, Inc.,                                       *
00018  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
00019  ***************************************************************************/
00020 
00021 #include <serverconfig/serverconfig.h>
00022 #include <requesthandler.h>
00023 #include <servlet/IllegalArgumentException.h>
00024 #include <sptk4/CThread.h>
00025 #include <sptk4/CBuffer.h>
00026 #include <sptk4/CBase64.h>
00027 #include <sptk4/CGuard.h>
00028 #include <dlfcn.h>
00029 #include <fcntl.h>
00030 
00031 using namespace container;
00032 namespace container {
00033 namespace serverconfig {
00034 
00035 // Helper class to remove servlets from the servlet map
00036 class RHServletRemover
00037 {
00038 public:
00039     void operator()(const std::string& path)
00040     {
00041         RequestHandler::removeServlet(path);
00042     }
00043 };
00044 
00045 // Helper class template to iterate over all servlets in the map
00046 template<class T>
00047  class SMFunctor
00048 {
00049 private:
00050     T* m_inst;
00051     typedef void (T::*F)(const std::string&);
00052     F m_f;
00053 public:
00054     SMFunctor(T* inst, F f): m_inst(inst), m_f(f){}
00055     void operator()(const std::string& path)
00056     {
00057         (m_inst->*m_f)(path);
00058     }
00059 };
00060 
00061 AppContext::AppContext(const ConfigNode& node, Context* parent)
00062     : Context(parent->getServerConfig(), parent)
00063     , servlet::ServletContext()
00064     , m_sessionTimeout(600) // five minutes
00065     , m_uploadDir("/tmp") //hardcoded default
00066     , m_cache(false) // Don't let proxies cache anything generated by CPPSERV by default
00067 #ifdef HAVE_LIBMAGIC
00068     , m_mime_cookie(0)
00069 #endif
00070 {
00071     m_paramregistry.getParamList(getUnsetParams());
00072     util::param_t attrs = node.getAttrs();
00073     util::param_t::const_iterator cacheIt = attrs.find("allow_cacheing");
00074     if(cacheIt != attrs.end())
00075     {
00076         std::string cacheVal(cacheIt->second);
00077         std::transform(cacheVal.begin(), cacheVal.end(), cacheVal.begin(), ::tolower);
00078         if(cacheVal == "true")
00079             m_cache=true;
00080     }
00081 }
00082 
00083 bool AppContext::onSetParam(const ConfigNode& node)
00084 {
00085     return m_paramregistry.setParam(node, this);
00086 }
00087 
00088 AppContext::~AppContext()
00089 {
00090     if(m_cleaner) {
00091         m_cleaner->stop();
00092         delete m_cleaner;
00093     }
00094     killAllSessions();
00095     std::string empty;
00096     destroyServlets(); // Calls destroy() on all servlets, before unloading them
00097     RHServletRemover rh;
00098     SMFunctor<AppContext> unl(this, &AppContext::unloadServlet);
00099     // This will block till all requests being processed by this servlet
00100     // are done. Alternatively we may want to kill all processes that are
00101     // in this servlet. Maybe there should be some timeout
00102     m_maptop.forEachServletPath(rh, empty);
00103     m_maptop.forEachServletPath(unl, empty);
00104 #ifdef HAVE_LIBMAGIC
00105     if(m_mime_cookie)
00106         ::magic_close(m_mime_cookie);
00107 #endif
00108 }
00109 
00110 void AppContext::registerContexts(ContextRegistry& reg)
00111 {
00112     reg.registerContext("servlet",serverconfig::ServletContext::contextCreator, 0);
00113     reg.registerContext("csp",CSPContext::contextCreator, 0);
00114 }
00115 
00116 void AppContext::registerParams(ParamRegistry<AppContext>& reg)
00117 {
00118     reg.registerParam("sessiontimeout",&AppContext::setSessionTimeout,PARAM_INHERITABLE|PARAM_SINGLE_OF_TYPE);
00119     reg.registerParam("upload_dir",&AppContext::setUploadDir,PARAM_INHERITABLE|PARAM_SINGLE_OF_TYPE);
00120     reg.registerParam("parameter",&AppContext::addInitParam,0);
00121     reg.registerParam("max_request_size", &Context::setIgnore,PARAM_INHERITABLE|PARAM_SINGLE_OF_TYPE);
00122     reg.registerParam("max_file_size", &Context::setIgnore,PARAM_INHERITABLE|PARAM_SINGLE_OF_TYPE);
00123     reg.registerParam("uri_base", &AppContext::setUriBase,PARAM_INHERITABLE|PARAM_REQUIRED|PARAM_SINGLE_OF_TYPE);
00124     reg.registerParam("phys_base", &AppContext::setPhysBase,PARAM_INHERITABLE|PARAM_SINGLE_OF_TYPE);
00125     reg.registerParam("default_mime_type", &AppContext::setDefaultMimeType,PARAM_SINGLE_OF_TYPE);
00126     reg.registerParam("default_character_encoding", &AppContext::setDefaultCharacterEncoding,PARAM_SINGLE_OF_TYPE);
00127 }
00128 
00129 bool AppContext::setSessionTimeout(const ConfigNode& val)
00130 {
00131     util::param_t attrs = val.getAttrs();
00132     util::param_t::const_iterator value = attrs.find("value");
00133     if(attrs.end() == value) {
00134         std::cerr<<"Value missing for session timeout paramter"<<std::endl;
00135         return false;
00136     }
00137     m_sessionTimeout=::atoi(value->second.c_str());
00138     return true;
00139 }
00140 
00141 bool AppContext::setUploadDir(const ConfigNode& val)
00142 {
00143     return setString(val, m_uploadDir);
00144 }
00145 
00146 bool AppContext::setPhysBase(const ConfigNode& val)
00147 {
00148     return setString(val, m_physbase);
00149 }
00150 
00151 bool AppContext::setUriBase(const ConfigNode& val)
00152 {
00153     return setString(val, m_uribase);
00154 }
00155 
00156 bool AppContext::setDefaultMimeType(const ConfigNode& val)
00157 {
00158     return setString(val, m_mime);
00159 }
00160 
00161 bool AppContext::setDefaultCharacterEncoding(const ConfigNode& val)
00162 {
00163     return setString(val, m_enc);
00164 }
00165 
00166 bool AppContext::addInitParam(const ConfigNode& val)
00167 {
00168     util::param_t::const_iterator name=val.getAttrs().find("name");
00169     util::param_t::const_iterator value=val.getAttrs().find("value");
00170     if(value == val.getAttrs().end() || name == val.getAttrs().end()) {
00171         std::cerr<<"Application paramter is missing name or value"<<std::endl;
00172         return false;
00173     }
00174     m_init_params.push_back(std::pair<std::string,std::string>(name->second,value->second));
00175     return true;
00176 }
00177 
00178 bool AppContext::onPreComplete()
00179 {
00180     // URI base MUST end with '/'
00181     if(m_uribase.empty())
00182         m_uribase="/";
00183     else if(*m_uribase.rbegin()!='/')
00184         m_uribase+='/';
00185     m_sessionCookieName=makeSName(getName());
00186     if(m_mime.empty())
00187         m_mime="text/html";
00188     if(m_enc.empty())
00189         m_enc="utf8";
00190     for(util::pairlist_t::iterator it=m_init_params.begin(); it!=m_init_params.end(); it++) {
00191         addInitParam(it->first,it->second);
00192     }
00193     getGlobalContext()->registerApp(getName(), this);
00194     return true;
00195 }
00196 
00197 bool AppContext::onPostComplete()
00198 {
00199     m_fileSaveTemplate=m_uploadDir+'/'+"cppserv.tmpXXXXXX";
00200     initServlets();
00201     m_cleaner=new SessionCleaner(this,m_sessionTimeout/2+1); // +1, so that it doesn't become 0...
00202     m_cleaner->run();
00203     return true;
00204 }
00205 
00206 ParamRegistry<AppContext> AppContext::m_paramregistry(AppContext::registerParams);
00207 ContextRegistry AppContext::m_contextregistry(AppContext::registerContexts);
00208 
00209 ServletConfigImpl* AppContext::addServlet(const std::string& path, const std::string& name, const std::string& dso, bool hidden, size_t maxRequestSize, size_t maxFileSize)
00210 {
00211     std::string fullpath("/");
00212     if(!getServletContextName().empty()) { // avoid starting with "//"
00213         fullpath.append(getServletContextName());
00214         fullpath+='/';
00215     }
00216     fullpath.append(path);
00217     ServletDesc* desc = getDesc(fullpath);
00218     if(desc) { // Another servlet is already configured for the path in this app...
00219         std::cerr<<"Path "<<fullpath<<" is already used"<<std::endl;
00220         return 0;
00221     }
00222     void* dsoHandle=dlopen(dso.c_str(),RTLD_LAZY);
00223     if(!dsoHandle) {
00224         std::cerr<<"Error loading library \""<<dso<<"\": "<<dlerror()<<std::endl;
00225         return 0;
00226     }
00227     servletcreatefunc_t createFunc=(servletcreatefunc_t)dlsym(dsoHandle,(name + "_createServlet").c_str());
00228     if(!createFunc) {
00229         std::cerr<<"Could not locate servlet "<<name<<" in the library "<<dso<<": "<<dlerror()<<std::endl;
00230         return 0;
00231     }
00232     ServletConfigImpl* config=new ServletConfigImpl(this, name);
00233     desc=new ServletDesc(createFunc(), config, dsoHandle, fullpath, maxRequestSize, maxFileSize, m_mime, m_enc, m_cache);
00234     m_maptop[fullpath]=desc;
00235     if(!hidden) {
00236         if(!RequestHandler::addServlet(fullpath,getServletContainer(fullpath))) { // Another such URL exists globally
00237             unloadServlet(fullpath);
00238             delServlet(fullpath);
00239             return 0;
00240         }
00241     }
00242     return config;
00243 }
00244 
00245 AppContext::ServletDesc* AppContext::getDesc(const std::string& path)
00246 {
00247     return m_maptop.getServletDesc(path);
00248 }
00249 
00250 void AppContext::delServlet(const std::string& path)
00251 {
00252     m_maptop.removeDesc(path);
00253 }
00254 
00255 ServletContainer* AppContext::getServletContainer(const std::string& path)
00256 {
00257     ServletDesc* desc = getDesc(path);
00258     if(!desc)
00259         return 0;
00260     return &(desc->m_cont);
00261 }
00262 
00263 void AppContext::splitServPath(const std::string& path, std::string& dir, std::string& name)
00264 {
00265     std::string::size_type slash = path.rfind('/');
00266     if(slash == std::string::npos) {
00267         dir = "/";
00268         name = path;
00269     } else {
00270         dir.assign(path.substr(0, slash));
00271         name.assign(path.substr(slash + 1));
00272     }
00273 }
00274 
00275 void AppContext::initServlet(const std::string& path)
00276 {
00277     ServletContainer* cont=getServletContainer(path);
00278     cont->init();
00279 }
00280 
00281 void AppContext::destroyServlet(const std::string& path)
00282 {
00283     ServletContainer* cont=getServletContainer(path);
00284     cont->destroy();
00285 }
00286 
00293 void AppContext::unloadServlet(const std::string& path)
00294 {
00295     ServletContainer* cont=getServletContainer(path);
00296     servlet::ServletConfig* conf=cont->getConfig();
00297     ServletDesc* d=getDesc(path);
00298     void* dsoHandle=d->m_h;
00299     delete d; // this will delete ServletContainer, which will in turn delete servlet object
00300     delete conf;
00301     ::dlclose(dsoHandle);
00302 }
00303 
00304 void AppContext::initServlets()
00305 {
00306     std::string empty;
00307     SMFunctor<AppContext> init(this, &AppContext::initServlet);
00308     m_maptop.forEachServletPath(init, empty);
00309 }
00310 
00311 void AppContext::destroyServlets()
00312 {
00313     std::string empty;
00314     SMFunctor<AppContext> destroy(this, &AppContext::destroyServlet);
00315     m_maptop.forEachServletPath(destroy, empty);
00316 }
00317 
00318 
00327 container::HttpSessionImpl* AppContext::getSession(const std::string& sid, bool create)
00328 {
00329     sptk::CGuard guard(m_sessionLock);
00330     if(sid.empty()) {
00331         container::HttpSessionImpl *s = 0;
00332         if(create)
00333             s = newSession();
00334         return s;
00335     }
00336     sessionlist_t::iterator s=m_sessions.find(sid);
00337     if(s==m_sessions.end()||!s->second->validP()){
00338         HttpSessionImpl* s = 0;
00339         if(create)
00340             s = newSession();
00341         return s;
00342     }
00343     s->second->notNew();
00344     return s->second;
00345 }
00346 
00347 
00352 container::HttpSessionImpl* AppContext::newSession()
00353 {
00354     std::string sid=container::util::getRandomString(10);
00355     HttpSessionImpl* s=new HttpSessionImpl(*this,sid,m_sessionTimeout);
00356     m_sessions[sid]=s;
00357     return s;
00358 }
00359 
00360 
00366 void AppContext::killSession(const std::string& sid)
00367 {
00368     sessionlist_t::iterator s=m_sessions.find(sid);
00369     if(s==m_sessions.end()){
00370         return;
00371     }
00372     delete s->second;
00373     m_sessions.erase(sid);
00374 }
00375 
00376 
00380 void AppContext::cleanSessions()
00381 {
00382     sptk::CGuard guard(m_sessionLock);
00383     sessionlist_t::iterator cur=m_sessions.begin();
00384     while(cur!=m_sessions.end()){
00385         if(!cur->second->validP()){
00386             std::string sid = cur->first;
00387             cur++;
00388             killSession(sid);
00389         } else {
00390             cur++;
00391         }
00392     }
00393 }
00398 AppContext::SessionCleaner::SessionCleaner(AppContext* ctx,int freq)
00399     : sptk::CThread("SessionCleaner",true)
00400     , m_running(true)
00401     , m_freq(freq)
00402     , m_ctx(ctx)
00403 {
00404     if(::pipe(m_trigger)==-1) {
00405         char errbuf[1024];
00406         const char* err = ::strerror_r(errno, errbuf, sizeof(errbuf));
00407         std::cerr<<"Error setting up session cleaner trigger: "<<err<<std::endl;
00408         ::exit(1);
00409     }
00410     if(::fcntl(m_trigger[0],F_SETFL,O_NONBLOCK)<0) {
00411         char errbuf[1024];
00412         const char* err = ::strerror_r(errno, errbuf, sizeof(errbuf));
00413         std::cerr<<"Error setting trigger reader to non-blocking mode: "
00414             <<err<<std::endl;
00415         ::exit(1);
00416     }
00417 }
00421 AppContext::SessionCleaner::~SessionCleaner()
00422 {
00423     ::close(m_trigger[0]);
00424     ::close(m_trigger[1]);
00425 }
00426 void AppContext::SessionCleaner::threadFunction()
00427 {
00428     while(m_running) {
00429         m_ctx->cleanSessions();
00430         struct timeval timeout={m_freq,0};
00431         fd_set rfd;
00432         FD_ZERO(&rfd);
00433         FD_SET(m_trigger[0],&rfd);
00434         int r=::select(m_trigger[0]+1,&rfd,0,0,&timeout);
00435         if(r){
00436             char buf[4];
00437             while(::read(m_trigger[0],buf,sizeof(buf))>0){;}
00438         }
00439     }
00440 }
00447 void AppContext::SessionCleaner::stop()
00448 {
00449     m_running=false;
00450     this->wakeUp();
00451     this->terminate();
00452 }
00453 
00459 bool AppContext::isSessionValid(const std::string& sid)
00460 {
00461     sessionlist_t::iterator s=m_sessions.find(sid);
00462     return s!=m_sessions.end()&&s->second->validP();
00463 }
00464 
00465 
00469 void AppContext::addInitParam(const std::string& name, const std::string& value)
00470 {
00471     m_params[name]=value;
00472 }
00473 
00478 std::string AppContext::getServletContextName() const
00479 {
00480     return getName();
00481 }
00482 
00483 void AppContext::log(const std::string& msg) const
00484 {
00485     std::cerr<<msg<<std::endl;
00486 }
00487 
00488 void AppContext::log(const std::string& message, const std::exception& e) const 
00489 {
00490     log(message);
00491     std::cerr<<e.what()<<std::endl;
00492 }
00493 
00494 
00499 void AppContext::SessionCleaner::wakeUp()
00500 {
00501     char c='1';
00502     ::write(m_trigger[1],&c,1);
00503 }
00504 
00505 
00512 void AppContext::killAllSessions()
00513 {
00514     sptk::CGuard guard(m_sessionLock);
00515     sessionlist_t::iterator cur=m_sessions.begin();
00516     while(cur!=m_sessions.end()){
00517         delete cur->second;
00518         cur++;
00519     }
00520     m_sessions.clear();
00521 }
00522 
00523 std::string AppContext::makeSName(const std::string& name)
00524 {
00525     sptk::CBuffer in(name.c_str());
00526     sptk::CBuffer out(name.length()+5);
00527     std::string ret="CPPSERV";
00528     ::memset(out.data(), 0, out.size());
00529     sptk::CBase64::encode(out,in);
00530     ret+=out.data();
00531     std::transform(ret.begin(),ret.end(),ret.begin(),container::util::TrEq());
00532     return ret;
00533 }
00534 
00535 
00536 servlet::ServletContext* AppContext::getContext(const std::string& uripath)
00537 {
00538     std::string uribase = getUriBase();
00539     if(uripath.find(uribase) != 0)
00540         return 0;
00541     std::string contextName = uripath.substr(uribase.length());
00542     return getServerConfig().getApp(contextName);
00543 }
00544 int AppContext::getMajorVersion()
00545 {
00546     return VERSION_MAJOR;
00547 }
00548 int AppContext::getMinorVersion()
00549 {
00550     return VERSION_MINOR;
00551 }
00552 std::string AppContext::getMimeType(const std::string& file)
00553 {
00554     std::string ret;
00555 #ifdef HAVE_LIBMAGIC
00556     if(!m_mime_cookie)
00557     {
00558         m_mime_cookie=::magic_open(MAGIC_SYMLINK|MAGIC_MIME_TYPE|MAGIC_ERROR);
00559         if(!m_mime_cookie)
00560         {
00561             std::cerr<<"Error initializing MIME magic library"<<std::endl;
00562             return ret;
00563         }
00564         if(::magic_load(m_mime_cookie, 0)!=0) // Fail!
00565         {
00566             std::cerr<<"Error loading MIME magic database: "<<::magic_error(m_mime_cookie)<<std::endl;
00567             return ret;
00568         }
00569     }
00570     const char* cstr_ret = ::magic_file(m_mime_cookie, file.c_str());
00571     if(!cstr_ret)
00572     {
00573         std::cerr<<"Error determining MIME type of file \""<<file<<"\": "<<::magic_error(m_mime_cookie)<<std::endl;
00574         return ret;
00575     }
00576     ret = cstr_ret;
00577 #endif
00578     return ret;
00579 }
00580 std::auto_ptr< std::set<std::string> > AppContext::getResourcePaths(const std::string& path)
00581 {
00582     std::auto_ptr< std::set<std::string> > ret(new std::set<std::string>);
00583     // For now we can only return list of available Servlets
00584     // Which means our implementation is not really complete
00585     servletmap_t* sp = m_maptop.getPath(path, false);
00586     if(!sp)
00587         return ret;
00588     std::insert_iterator<std::set<std::string> > ii(*ret, ret->begin());
00589     std::transform(sp->getSubpaths().begin(), sp->getSubpaths().end(), ii, __gnu_cxx::select1st<std::pair<std::string, ServletMap<ServletDesc>*> >());
00590     std::transform(sp->getServlets().begin(), sp->getServlets().end(), ii, __gnu_cxx::select1st<std::pair<std::string, ServletDesc*> >());
00591     return ret;
00592 }
00593 std::string AppContext::getResource(const std::string& /*path*/)
00594 {
00595     //throw servlet::ServletException("Method unimplemented");
00596     return "";
00597 }
00598 std::istream& AppContext::getResourceAsStream(const std::string& /*path*/)
00599 {
00600     throw servlet::ServletException("Method unimplemented");
00601 }
00602 servlet::RequestDispatcher* AppContext::getRequestDispatcher(const std::string& path)
00603 {
00604     if(path[0]!='/')
00605         throw servlet::IllegalArgumentException();
00606     std::string name(path.substr(1));
00607     return getServletContainer(name);
00608 }
00609 servlet::RequestDispatcher* AppContext::getNamedDispatcher(const std::string& name)
00610 {
00611     return getServletContainer(name);
00612 }
00613 std::string AppContext::getRealPath(const std::string& path)
00614 {
00615     //TODO: use TR2 filesystem interface, when available
00616     //for now completely non-portable
00617     std::string ret = m_physbase;
00618     if(path.empty())
00619         return "";
00620     if(path[0]!='/')
00621         ret += '/';
00622     ret+=path;
00623     return ret;
00624 }
00625 std::string AppContext::getServerInfo()
00626 {
00627     return "CPPSERV/" CPPSERV_VERSION;
00628 }
00629 std::string AppContext::getInitParameter(const std::string& name)
00630 {
00631     param_t::const_iterator iRet = m_params.find(name);
00632     if(iRet == m_params.end())
00633         return "";
00634     else
00635         return iRet->second;
00636 }
00637 std::auto_ptr< std::vector<std::string> > AppContext::getInitParameterNames()
00638 {
00639     return util::getMapKeyNames(m_params);
00640 }
00641 boost::shared_ptr<void> AppContext::getAttribute(const std::string& name)
00642 {
00643     attr_t::const_iterator iRet = m_attrs.find(name);
00644     if(iRet == m_attrs.end())
00645         return boost::shared_ptr<void>();
00646     else
00647         return iRet->second;
00648 }
00649 std::auto_ptr< std::vector<std::string> > AppContext::getAttributeNames()
00650 {
00651     return util::getMapKeyNames(m_attrs);
00652 }
00653 void AppContext::setAttribute(const std::string& name, boost::shared_ptr<void> object)
00654 {
00655     m_attrs[name] = object;
00656 }
00657 void AppContext::removeAttribute(const std::string& name)
00658 {
00659     attr_t::iterator it=m_attrs.find(name);
00660     if(it!=m_attrs.end())
00661         m_attrs.erase(it);
00662 }
00663 bool AppContext::hasAttribute(const std::string& name) const
00664 {
00665     attr_t::const_iterator it=m_attrs.find(name);
00666     return it!=m_attrs.end();
00667 }
00668 
00669 }
00670 }

SourceForge.net Logo