Feature #7598 » 0001-Add-framework-for-weakSessionId-support.patch
| src/Wt/Test/WTestEnvironment.C | ||
|---|---|---|
| void WTestEnvironment::init(EntryPointType type) | ||
| { | ||
|   session_ = new WebSession(controller_, "testwtd", type, "", 0, this); | ||
|   session_ = new WebSession(controller_, "testwtd", "weaktestwtd", type, "", 0, this); | ||
|   theSession_.reset(session_); | ||
| #ifndef WT_TARGET_JAVA | ||
| src/web/WebController.C | ||
|---|---|---|
|       for (SessionMap::iterator i = sessions_.begin(); i != sessions_.end(); | ||
| 	   ++i) | ||
| 	sessionList.push_back(i->second); | ||
| 	sessionList.push_back(i->session()); | ||
|       sessions_.clear(); | ||
| ... | ... | |
| #endif | ||
|   std::vector<std::string> sessionIds; | ||
|   for (SessionMap::const_iterator i = sessions_.begin(); i != sessions_.end(); ++i) { | ||
|     sessionIds.push_back(i->first); | ||
|     sessionIds.push_back(i->sessionId()); | ||
|   } | ||
|   return sessionIds; | ||
| } | ||
| ... | ... | |
| #endif // WT_THREADED | ||
|     for (SessionMap::iterator i = sessions_.begin(); i != sessions_.end();) { | ||
|       std::shared_ptr<WebSession> session = i->second; | ||
|       std::shared_ptr<WebSession> session = i->session(); | ||
|       int diff = session->expireTime() - now; | ||
| ... | ... | |
|   return result; | ||
| } | ||
| /* | ||
|  * addSession is called from WTestEnvironment w/hard-coded sessionId and weakSessionId | ||
|  * ...so we can avoid checking that these ids are distinct from one another | ||
|  */ | ||
| void WebController::addSession(const std::shared_ptr<WebSession>& session) | ||
| { | ||
| #ifdef WT_THREADED | ||
|   std::unique_lock<std::recursive_mutex> lock(mutex_); | ||
| #endif // WT_THREADED | ||
|   sessions_[session->sessionId()] = session; | ||
|   sessions_.insert(SessionEntry(session->sessionId(), session->weakSessionId(), session)); | ||
| } | ||
| void WebController::removeSession(const std::string& sessionId) | ||
| ... | ... | |
|   SessionMap::iterator i = sessions_.find(sessionId); | ||
|   if (i != sessions_.end()) { | ||
|     ++zombieSessions_; | ||
|     if (i->second->env().ajax()) | ||
|     if (i->session()->env().ajax()) | ||
|       --ajaxSessions_; | ||
|     else | ||
|       --plainHtmlSessions_; | ||
| ... | ... | |
|       return false; | ||
|     std::string sessionId = *wtdE; | ||
|     /* | ||
|      * This could be a weak sessionId, but we need to post event to | ||
|      * a normal sessionId. So, look it up as a weakSessionId and replace | ||
|      * with the corresponding sessionId, if found... | ||
|      */ | ||
|     { | ||
| #ifdef WT_THREADED | ||
|       std::unique_lock<std::recursive_mutex> lock(mutex_); | ||
|       SessionMap::index<WeakSessionIdTag>::type::iterator iw = | ||
|         sessions_.get<WeakSessionIdTag>().find(sessionId); | ||
|       if (iw != sessions_.get<WeakSessionIdTag>().end()) { | ||
|         sessionId = iw->sessionId(); | ||
|       } | ||
| #endif // WT_THREADED | ||
|     } | ||
|     ApplicationEvent event(sessionId, | ||
| 			   std::bind(&WebController::updateResourceProgress, | ||
| ... | ... | |
|     SessionMap::iterator i = sessions_.find(event.sessionId); | ||
|     if (i != sessions_.end() && !i->second->dead()) | ||
|       session = i->second; | ||
|     if (i != sessions_.end() && !i->session()->dead()) | ||
|       session = i->session(); | ||
|   } | ||
|   if (!session) { | ||
| ... | ... | |
|     return; | ||
|   } | ||
|   std::string sessionId; | ||
|   std::string sessionId, weakSessionId; | ||
|   /* | ||
|    * Get session from request. | ||
| ... | ... | |
|     SessionMap::iterator i = sessions_.find(sessionId); | ||
|     // Not a valid regular sessionId, perhaps it is a weakSessionId (used for resources)? | ||
|     if (i == sessions_.end()) { | ||
|       SessionMap::index<WeakSessionIdTag>::type::iterator iw = | ||
|         sessions_.get<WeakSessionIdTag>().find(sessionId); | ||
|       // if matched a weakSessionId, set i iterator to point at SessionElement | ||
|       if (iw != sessions_.get<WeakSessionIdTag>().end()) { | ||
|         i = sessions_.project<SessionIdTag>(iw); | ||
|       } | ||
|     } | ||
|     Configuration::SessionTracking sessionTracking = configuration().sessionTracking(); | ||
|     if (i == sessions_.end() || i->second->dead() || | ||
|     if (i == sessions_.end() || i->session()->dead() || | ||
|         (sessionTracking == Configuration::Combined && | ||
| 	 (multiSessionCookie.empty() || multiSessionCookie != i->second->multiSessionId()))) { | ||
| 	 (multiSessionCookie.empty() || multiSessionCookie != i->session()->multiSessionId()))) { | ||
|       try { | ||
|         if (sessionTracking == Configuration::Combined && | ||
|             i != sessions_.end() && !i->second->dead()) { | ||
|             i != sessions_.end() && !i->session()->dead()) { | ||
|           if (!request->headerValue("Cookie")) { | ||
|             LOG_ERROR_S(&server_, "Valid session id: " << sessionId << ", but " | ||
|                         "no cookie received (expecting multi session cookie)"); | ||
| ... | ... | |
|         } | ||
| 	if (singleSessionId_.empty()) { | ||
| 	  do { | ||
| 	    sessionId = conf_.generateSessionId(); | ||
| 	    if (!conf_.registerSessionId(std::string(), sessionId)) | ||
| 	      sessionId.clear(); | ||
| 	  } while (sessionId.empty()); | ||
|           if (!findAvailableSessionIds("", sessionId, weakSessionId)) { | ||
|             LOG_ERROR_S(&server_, "Could not allocate session ids. Consider increasing session-id-length"); | ||
|             request->setStatus(500); | ||
|             request->flush(WebResponse::ResponseState::ResponseDone); | ||
|             return; | ||
|           } | ||
| 	} | ||
| 	std::string favicon = request->entryPoint_->favicon(); | ||
| 	if (favicon.empty()) | ||
| 	  conf_.readConfigurationProperty("favicon", favicon); | ||
| 	session.reset(new WebSession(this, sessionId, | ||
| 	session.reset(new WebSession(this, sessionId, weakSessionId, | ||
| 				     request->entryPoint_->type(), | ||
| 				     favicon, request)); | ||
| ... | ... | |
| 			     + " Path=" + session->env().deploymentPath() | ||
| 			     + "; httponly;" + (session->env().urlScheme() == "https" ? " secure;" : "")); | ||
| 	sessions_[sessionId] = session; | ||
|         sessions_.insert(SessionEntry(sessionId, weakSessionId, session)); | ||
| 	++plainHtmlSessions_; | ||
|       } catch (std::exception& e) { | ||
| 	LOG_ERROR_S(&server_, "could not create new session: " << e.what()); | ||
| ... | ... | |
| 	return; | ||
|       } | ||
|     } else { | ||
|       session = i->second; | ||
|       session = i->session(); | ||
|     } | ||
|   } | ||
| ... | ... | |
|   return conf_.matchEntryPoint(scriptName, pathInfo, false); | ||
| } | ||
| /* | ||
|  * If either sessionId or WeakSessionId in/out parameter is empty, | ||
|  * replace it with a newly generated sessionId -- if one is available. | ||
|  * For a sessionId to be considered available, it must be: | ||
|  * | ||
|  * 1. unused in the SessionIdTag index to sessions_ | ||
|  * 2. unused in the WeakSessionIdTag index to sessions_ | ||
|  * | ||
|  * For the SessionId, the registerSessionId method must also | ||
|  * succeed (used by FCGI connector). | ||
|  * | ||
|  * Return true on success. | ||
|  * | ||
|  * NOTE: The WebController mutex_ must be held when calling this method | ||
|  */ | ||
| bool WebController::findAvailableSessionIds(std::string registrationOldId, | ||
|                                             std::string& sessionId, | ||
|                                             std::string& weakSessionId) | ||
| { | ||
|   // With default length session ids, the likelihood of any retries is very small | ||
|   for (int retry = 0; weakSessionId.empty() && retry < 10; ++retry) { | ||
|     weakSessionId = conf_.generateSessionId(); | ||
|     if (sessions_.get<WeakSessionIdTag>().find(weakSessionId) != | ||
|         sessions_.get<WeakSessionIdTag>().end()) { | ||
|       weakSessionId.clear(); | ||
|       continue; | ||
|     } | ||
|     if (sessions_.find(weakSessionId) != sessions_.end()) | ||
|       weakSessionId.clear(); | ||
|   } | ||
|   for (int retry = 0; sessionId.empty() && retry < 10; ++retry) { | ||
|     sessionId = conf_.generateSessionId(); | ||
|     if (sessionId == weakSessionId) { | ||
|       sessionId.clear(); | ||
|       continue; | ||
|     } | ||
|     if (sessions_.get<WeakSessionIdTag>().find(sessionId) != | ||
|         sessions_.get<WeakSessionIdTag>().end()) { | ||
|       sessionId.clear(); | ||
|       continue; | ||
|     } | ||
|     if (sessions_.find(sessionId) != sessions_.end()) { | ||
|       sessionId.clear(); | ||
|       continue; | ||
|     } | ||
|     if (!conf_.registerSessionId(registrationOldId, sessionId)) | ||
|       sessionId.clear(); | ||
|   } | ||
|   return !sessionId.empty() && !weakSessionId.empty(); | ||
| } | ||
| std::string | ||
| WebController::generateNewSessionId(const std::shared_ptr<WebSession>& session) | ||
| { | ||
| ... | ... | |
|   std::unique_lock<std::recursive_mutex> lock(mutex_); | ||
| #endif // WT_THREADED   | ||
|   std::string newSessionId; | ||
|   do { | ||
|     newSessionId = conf_.generateSessionId(); | ||
|     if (!conf_.registerSessionId(session->sessionId(), newSessionId)) | ||
|       newSessionId.clear(); | ||
|   } while (newSessionId.empty()); | ||
|   std::string newSessionId, newWeakSessionId; | ||
|   if (!findAvailableSessionIds(session->sessionId(), newSessionId, newWeakSessionId)) { | ||
|     throw WException( | ||
|       "generateNewSessionId: out of session ids. Consider increasing session-id-length"); | ||
|   } | ||
|   sessions_[newSessionId] = session; | ||
|   sessions_.insert(SessionEntry(newSessionId, newWeakSessionId, session)); | ||
|   SessionMap::iterator i = sessions_.find(session->sessionId()); | ||
|   sessions_.erase(i); | ||
| src/web/WebController.h | ||
|---|---|---|
| #include <set> | ||
| #include <map> | ||
| #include <boost/multi_index_container.hpp> | ||
| #include <boost/multi_index/hashed_index.hpp> | ||
| #include <boost/multi_index/ordered_index.hpp> | ||
| #include <boost/multi_index/member.hpp> | ||
| #include <Wt/WDllDefs.h> | ||
| #include <Wt/WServer.h> | ||
| #include <Wt/WSocketNotifier.h> | ||
| ... | ... | |
|   std::string switchSession(WebSession *session, | ||
| 			    const std::string& newSessionId); | ||
|   bool findAvailableSessionIds (std::string registrationOldId, | ||
|                                 std::string& sessionId, | ||
|                                 std::string& weakSessionId); | ||
|   std::string generateNewSessionId(const std::shared_ptr<WebSession>& session); | ||
| private: | ||
| ... | ... | |
| #endif // WT_THREADED | ||
|   std::set<std::string> uploadProgressUrls_; | ||
|   typedef std::map<std::string, std::shared_ptr<WebSession> > SessionMap; | ||
|   class SessionMap; | ||
|   class SessionEntry { | ||
|   public: | ||
|     SessionEntry(std::string sessionId, std::string weakSessionId, std::shared_ptr<WebSession> session) : | ||
|       sessionId_(sessionId), weakSessionId_(weakSessionId), session_(session) {} | ||
|     std::string sessionId() const { return sessionId_; } | ||
|     std::string weakSessionId() const { return weakSessionId_; } | ||
|     std::shared_ptr<WebSession> session() const { return session_; } | ||
|   private: | ||
|     std::string sessionId_; | ||
|     std::string weakSessionId_; | ||
|     std::shared_ptr<WebSession> session_; | ||
|   friend class SessionMap; | ||
|   }; | ||
|   struct SessionIdTag { }; | ||
|   struct WeakSessionIdTag { }; | ||
|   class SessionMap : public boost::multi_index_container< | ||
|     SessionEntry, | ||
|     boost::multi_index::indexed_by< | ||
|       boost::multi_index::ordered_unique< | ||
|         boost::multi_index::tag<SessionIdTag>, | ||
|         boost::multi_index::member<SessionEntry, std::string, &SessionEntry::sessionId_> | ||
|       >, | ||
|       boost::multi_index::hashed_unique< | ||
|         boost::multi_index::tag<WeakSessionIdTag>, | ||
|         boost::multi_index::member<SessionEntry, std::string, &SessionEntry::weakSessionId_> | ||
|       > | ||
|     > | ||
|   >{ }; | ||
|   SessionMap sessions_; | ||
| #ifdef WT_THREADED | ||
| src/web/WebSession.C | ||
|---|---|---|
| WebSession::WebSession(WebController *controller, | ||
| 		       const std::string& sessionId, | ||
| 		       const std::string& weakSessionId, | ||
| 		       EntryPointType type, | ||
| 		       const std::string& favicon, | ||
|                        const WebRequest *request, | ||
| ... | ... | |
|     favicon_(favicon), | ||
|     state_(State::JustCreated), | ||
|     sessionId_(sessionId), | ||
|     weakSessionId_(weakSessionId), | ||
|     sessionIdChanged_(false), | ||
|     sessionIdCookieChanged_(false), | ||
|     sessionIdInUrl_(false), | ||
| ... | ... | |
|   } | ||
| #ifndef WT_TARGET_JAVA | ||
|   LOG_INFO("session created (#sessions = " << | ||
| 	   (controller_->sessionCount() + 1) << ")"); | ||
|   LOG_INFO("session created (#sessions = " << (controller_->sessionCount() + 1) | ||
|            << "), sessionId: " << sessionId_ << ", weakSessionId: " << weakSessionId_); | ||
|   expire_ = Time() + 60*1000; | ||
| #endif // WT_TARGET_JAVA | ||
| ... | ... | |
|   } | ||
| } | ||
| std::string WebSession::sessionQuery() const | ||
| std::string WebSession::sessionQuery(SessionOption sessionOption) const | ||
| { | ||
|   std::string result ="?wtd=" + DomElement::urlEncodeS(sessionId_); | ||
|   const std::string& sessionId = | ||
|     (sessionOption == SessionOption::UseSessionId ?  sessionId_ : weakSessionId_); | ||
|   std::string result ="?wtd=" + DomElement::urlEncodeS(sessionId); | ||
|   if (type() == EntryPointType::WidgetSet) | ||
|     result += "&wtt=widgetset"; | ||
|   return result; | ||
| ... | ... | |
|   } | ||
| } | ||
| std::string WebSession::mostRelativeUrl(const std::string& internalPath) const | ||
| std::string WebSession::mostRelativeUrl(const std::string& internalPath, | ||
|                                         SessionOption sessionOption) const | ||
| { | ||
|   return appendSessionQuery(bookmarkUrl(internalPath)); | ||
|   return appendSessionQuery(bookmarkUrl(internalPath), sessionOption); | ||
| } | ||
| std::string WebSession::appendSessionQuery(const std::string& url) const | ||
| std::string WebSession::appendSessionQuery(const std::string& url, | ||
|                                            SessionOption sessionOption) const | ||
| { | ||
|   std::string result = url; | ||
|  | ||
| ... | ... | |
|   std::size_t questionPos = result.find('?'); | ||
|   if (questionPos == std::string::npos) | ||
|     result += sessionQuery(); | ||
|     result += sessionQuery(sessionOption); | ||
|   else if (questionPos == result.length() - 1) | ||
|     result += sessionQuery().substr(1); | ||
|     result += sessionQuery(sessionOption).substr(1); | ||
|   else | ||
|     result += '&' + sessionQuery().substr(1); | ||
|     result += '&' + sessionQuery(sessionOption).substr(1); | ||
| #ifndef WT_TARGET_JAVA | ||
|   return result; | ||
| ... | ... | |
|   sessionId_ = controller_->generateNewSessionId(shared_from_this()); | ||
|   sessionIdChanged_ = true; | ||
|   LOG_INFO("new session id for " << oldId); | ||
|   LOG_INFO("new session id for: " << oldId << ", new weak session id: " << weakSessionId_); | ||
|   if (!useUrlRewriting()) { | ||
|     std::string cookieName = env_->deploymentPath(); | ||
| src/web/WebSession.h | ||
|---|---|---|
|     Dead | ||
|   }; | ||
|   WebSession(WebController *controller, const std::string& sessionId, | ||
|   WebSession(WebController *controller, | ||
| 	     const std::string& sessionId, const std::string& weakSessionId, | ||
| 	     EntryPointType type, const std::string& favicon, | ||
| 	     const WebRequest *request, WEnvironment *env = nullptr); | ||
|   ~WebSession(); | ||
| ... | ... | |
|   std::string docType() const; | ||
|   std::string sessionId() const { return sessionId_; } | ||
|   std::string weakSessionId() const { return weakSessionId_; } | ||
|   std::string multiSessionId() const { return multiSessionId_; } | ||
|   void setMultiSessionId(const std::string &multiSessionId); | ||
| ... | ... | |
|   void setPagePathInfo(const std::string& path); | ||
|   std::string pagePathInfo() const { return pagePathInfo_; } | ||
|   enum class SessionOption { | ||
|     UseSessionId, | ||
|     UseWeakSessionId | ||
|   }; | ||
|   //    (http://www.bigapp.com/myapp/app.wt) ?wtd=ABCD | ||
|   // or (http://www.bigapp.com/myapp/) app.wt/path?wtd=ABCD | ||
|   std::string mostRelativeUrl(const std::string& internalPath = std::string()) | ||
|     const; | ||
|   std::string mostRelativeUrl(const std::string& internalPath = std::string(), | ||
|     SessionOption sessionOption = SessionOption::UseSessionId) const; | ||
|   std::string appendInternalPath(const std::string& url, | ||
| 				 const std::string& internalPath) const; | ||
|   std::string appendSessionQuery(const std::string& url) const; | ||
|   std::string appendSessionQuery(const std::string& url, | ||
|     SessionOption sessionOption = SessionOption::UseSessionId) const; | ||
|   std::string ajaxCanonicalUrl(const WebResponse& request) const; | ||
| ... | ... | |
|   std::string favicon_; | ||
|   State state_; | ||
|   std::string sessionId_, sessionIdCookie_, multiSessionId_; | ||
|   std::string sessionId_, weakSessionId_, sessionIdCookie_, multiSessionId_; | ||
|   bool sessionIdChanged_, sessionIdCookieChanged_, sessionIdInUrl_; | ||
|   WebController *controller_; | ||
| ... | ... | |
|   void init(const WebRequest& request); | ||
|   bool start(WebResponse *response); | ||
|   std::string sessionQuery() const; | ||
|   std::string sessionQuery( | ||
|           SessionOption sessionOption = SessionOption::UseSessionId) const; | ||
|   void flushBootStyleResponse(); | ||
|   void changeInternalPath(const std::string& path, WebResponse *response); | ||