/* * Copyright 2017 Sony Corporation */ #include #include "Poco/Exception.h" #include "Poco/File.h" #include "Poco/FileStream.h" #include "Poco/Timestamp.h" #include "easyhttpcpp/common/CoreLogger.h" #include "easyhttpcpp/common/FileUtil.h" #include "easyhttpcpp/HttpException.h" #include "HttpCacheInfo.h" #include "HttpCacheMetadata.h" #include "HttpFileCache.h" #include "HttpInternalConstants.h" #include "HttpLruCacheStrategy.h" #include "HttpUtil.h" using easyhttpcpp::common::ByteArrayBuffer; using easyhttpcpp::common::Cache; using easyhttpcpp::common::CacheMetadata; using easyhttpcpp::common::CacheInfoWithDataSize; using easyhttpcpp::common::CacheStrategy; using easyhttpcpp::common::FileUtil; using easyhttpcpp::db::SqlDatabaseCorruptException; using easyhttpcpp::db::SqlException; namespace easyhttpcpp { static const std::string Tag = "HttpFileCache"; HttpFileCache::HttpFileCache(const Poco::Path& cacheRootDir, size_t maxSize) : m_cacheInitialized(false), m_cacheRootDir(cacheRootDir.absolute()), m_maxSize(maxSize) { Poco::Path databasePath = m_cacheRootDir; databasePath.append(Poco::Path(HttpInternalConstants::Database::FileName)); m_pMetadataDb = new HttpCacheDatabase(new HttpCacheDatabaseOpenHelper(databasePath)); m_lruCacheStrategy = new HttpLruCacheStrategy(m_maxSize); m_lruCacheStrategy->setListener(this); } HttpFileCache::~HttpFileCache() { m_pMetadataDb->closeSqliteSession(); } bool HttpFileCache::getMetadata(const std::string& key, CacheMetadata::Ptr& pCacheMetadata) { Poco::FastMutex::ScopedLock lock(m_instanceMutex); try { initializeCache(); EASYHTTPCPP_LOG_D(Tag, "getMetadata key=%s", key.c_str()); CacheInfoWithDataSize::Ptr pCacheInfo = m_lruCacheStrategy->get(key); if (!pCacheInfo) { EASYHTTPCPP_LOG_D(Tag, "getMetadata : [%s] not found in cache.", key.c_str()); return false; } HttpCacheInfo* pHttpCacheInfo = static_cast (pCacheInfo.get()); if (pHttpCacheInfo->isReservedRemove()) { EASYHTTPCPP_LOG_D(Tag, "getMetadata : [%s] is reserving delete.", key.c_str()); return false; } HttpCacheMetadata::Ptr pHttpCacheMetadata = m_pMetadataDb->getMetadata(key); if (!pHttpCacheMetadata) { EASYHTTPCPP_LOG_D(Tag, "getMetadata : [%s] can not get from database.", key.c_str()); return false; } if (!m_pMetadataDb->updateLastAccessedSec(key)) { EASYHTTPCPP_LOG_D(Tag, "getMetadata : [%s] can not update last accessed sec. ignored.", key.c_str()); } pCacheMetadata = pHttpCacheMetadata; EASYHTTPCPP_LOG_D(Tag, "getMetadata : [%s] succeeded.", key.c_str()); return true; } catch (const SqlDatabaseCorruptException& e) { deleteCorruptedCacheFile(__func__, e); return false; } catch (const SqlException& e) { EASYHTTPCPP_LOG_D(Tag, "getMetadata : database error occurred. Details: %s", e.getMessage().c_str()); return false; } } bool HttpFileCache::getData(const std::string& key, std::istream*& pStream) { Poco::FastMutex::ScopedLock lock(m_instanceMutex); try { initializeCache(); CacheInfoWithDataSize::Ptr pCacheInfo = m_lruCacheStrategy->get(key); if (!pCacheInfo) { EASYHTTPCPP_LOG_D(Tag, "getData : [%s] not found in cache.", key.c_str()); return false; } HttpCacheInfo* pHttpCacheInfo = static_cast (pCacheInfo.get()); if (pHttpCacheInfo->isReservedRemove()) { EASYHTTPCPP_LOG_D(Tag, "getData : [%s] is reserving delete.", key.c_str()); return false; } pStream = createStreamFromCache(key); if (pStream == NULL) { EASYHTTPCPP_LOG_D(Tag, "getData : [%s] can not create stream.", key.c_str()); return false; } pHttpCacheInfo->addDataRef(); m_lruCacheStrategy->update(key, pCacheInfo); EASYHTTPCPP_LOG_D(Tag, "getData : [%s] succeeded.", key.c_str()); return true; } catch (const SqlDatabaseCorruptException& e) { deleteCorruptedCacheFile(__func__, e); return false; } catch (const SqlException& e) { EASYHTTPCPP_LOG_D(Tag, "getMetadata : database error occurred. Details: %s", e.getMessage().c_str()); return false; } } bool HttpFileCache::get(const std::string& key, CacheMetadata::Ptr& pCacheMetadata, std::istream*& pStream) { Poco::FastMutex::ScopedLock lock(m_instanceMutex); try{ initializeCache(); // TODO: when m_reservedRemove is true, LRU list changed by LruCacheStrategy::get. // It is better to think of a good way. CacheInfoWithDataSize::Ptr pCacheInfo = m_lruCacheStrategy->get(key); if (!pCacheInfo) { EASYHTTPCPP_LOG_D(Tag, "get : [%s] not found in cache.", key.c_str()); return false; } HttpCacheInfo* pHttpCacheInfo = static_cast (pCacheInfo.get()); if (pHttpCacheInfo->isReservedRemove()) { EASYHTTPCPP_LOG_D(Tag, "get : [%s] is reserving delete.", key.c_str()); return false; } HttpCacheMetadata::Ptr pHttpCacheMetadata = m_pMetadataDb->getMetadata(key); if (!pHttpCacheMetadata) { EASYHTTPCPP_LOG_D(Tag, "get : [%s] can not get from database.", key.c_str()); return false; } if (!m_pMetadataDb->updateLastAccessedSec(key)) { EASYHTTPCPP_LOG_D(Tag, "get : [%s] can not update last accessed sec. ignored.", key.c_str()); } pStream = createStreamFromCache(key); if (pStream == NULL) { EASYHTTPCPP_LOG_D(Tag, "getData : [%s] can not create stream.", key.c_str()); return false; } pCacheMetadata = pHttpCacheMetadata; pHttpCacheInfo->addDataRef(); m_lruCacheStrategy->update(key, pCacheInfo); EASYHTTPCPP_LOG_D(Tag, "get : [%s] succeeded.", key.c_str()); return true; } catch (const SqlDatabaseCorruptException& e) { deleteCorruptedCacheFile(__func__, e); return false; } catch (const SqlException& e) { EASYHTTPCPP_LOG_D(Tag, "get : database error occurred. Details: %s", e.getMessage().c_str()); return false; } } bool HttpFileCache::putMetadata(const std::string& key, CacheMetadata::Ptr pCacheMetadata) { Poco::FastMutex::ScopedLock lock(m_instanceMutex); try { initializeCache(); CacheInfoWithDataSize::Ptr pCacheInfo = m_lruCacheStrategy->get(key); if (pCacheInfo) { HttpCacheInfo* pHttpCacheInfo = static_cast (pCacheInfo.get()); if (pHttpCacheInfo->isReservedRemove()) { EASYHTTPCPP_LOG_D(Tag, "putMetadata : [%s] is reserving delete.", key.c_str()); return false; } if (pHttpCacheInfo->getDataRefCount() != 0) { EASYHTTPCPP_LOG_D(Tag, "putMetadata : [%s] dataRefCount is not 0.", key.c_str()); return false; } } else { EASYHTTPCPP_LOG_D(Tag, "putMetadata : not found in cache [%s]", key.c_str()); return false; } m_pMetadataDb->updateMetadata(key, pCacheMetadata.unsafeCast()); return true; } catch (const SqlDatabaseCorruptException& e) { deleteCorruptedCacheFile(__func__, e); return false; } catch (const SqlException& e) { EASYHTTPCPP_LOG_D(Tag, "putMetadata : database error occurred. Details: %s", e.getMessage().c_str()); return false; } } bool HttpFileCache::put(const std::string& key, CacheMetadata::Ptr pCacheMetadata, const std::string& path) { Poco::FastMutex::ScopedLock lock(m_instanceMutex); HttpCacheMetadata::Ptr pHttpCacheMetadata = pCacheMetadata.unsafeCast(); try { initializeCache(); CacheInfoWithDataSize::Ptr pOldCacheInfo = m_lruCacheStrategy->get(key); if (pOldCacheInfo) { HttpCacheInfo* pHttpCacheInfo = static_cast (pOldCacheInfo.get()); if (pHttpCacheInfo->isReservedRemove()) { EASYHTTPCPP_LOG_D(Tag, "put : [%s] is reserving delete.", key.c_str()); return false; } if (pHttpCacheInfo->getDataRefCount() != 0) { EASYHTTPCPP_LOG_D(Tag, "put : [%s] dataRefCount is not 0.", key.c_str()); return false; } // remove old cache removeInternal(key); } if (!m_lruCacheStrategy->makeSpace(pHttpCacheMetadata->getResponseBodySize())) { EASYHTTPCPP_LOG_D(Tag, "put : can not make cache space."); return false; } } catch (const SqlDatabaseCorruptException& e) { deleteCorruptedCacheFile(__func__, e); // continue put processing. } catch (const SqlException& e) { EASYHTTPCPP_LOG_D(Tag, "put : database error occurred. Details: %s", e.getMessage().c_str()); return false; } CacheInfoWithDataSize::Ptr pNewCacheInfo = new HttpCacheInfo(key, pHttpCacheMetadata->getResponseBodySize()); Poco::File tempFile(path); std::string targetFilename = makeCachedFilename(key); Poco::File cacheFile(targetFilename); if (!FileUtil::moveFile(tempFile, cacheFile)) { EASYHTTPCPP_LOG_D(Tag, "can not move cache file. [%s] -> [%s]", path.c_str(), targetFilename.c_str()); return false; } try { initializeCache(); m_pMetadataDb->updateMetadata(key, pHttpCacheMetadata); } catch (const SqlDatabaseCorruptException& e) { deleteCorruptedCacheFile(__func__, e); if (!FileUtil::removeFileIfPresent(cacheFile)) { EASYHTTPCPP_LOG_D(Tag, "can not remove cache file. [%s]", targetFilename.c_str()); } return false; } catch (const SqlException& e) { EASYHTTPCPP_LOG_D(Tag, "put : [%s] update database failed.", key.c_str()); if (!FileUtil::removeFileIfPresent(cacheFile)) { EASYHTTPCPP_LOG_D(Tag, "can not remove cache file. [%s]", targetFilename.c_str()); } return false; } EASYHTTPCPP_LOG_D(Tag, "update key=%s", key.c_str()); // update never fails, because already make free space. m_lruCacheStrategy->update(key, pNewCacheInfo); return true; } bool HttpFileCache::put(const std::string& key, CacheMetadata::Ptr pCacheMetadata, Poco::SharedPtr pData) { EASYHTTPCPP_LOG_D(Tag, "put : [%s] ByteArrayBuffer is not supported.", key.c_str()); return false; } bool HttpFileCache::remove(const std::string& key) { Poco::FastMutex::ScopedLock lock(m_instanceMutex); try { initializeCache(); if (removeInternal(key)) { EASYHTTPCPP_LOG_D(Tag, "remove : [%s] succeeded.", key.c_str()); return true; } else { EASYHTTPCPP_LOG_D(Tag, "remove : [%s] succeeded.", key.c_str()); return false; } } catch (const SqlDatabaseCorruptException& e) { deleteCorruptedCacheFile(__func__, e); return false; } catch (const SqlException& e) { EASYHTTPCPP_LOG_D(Tag, "remove : database error occurred. Details: %s", e.getMessage().c_str()); return false; } } void HttpFileCache::releaseData(const std::string& key) { Poco::FastMutex::ScopedLock lock(m_instanceMutex); CacheInfoWithDataSize::Ptr pCacheInfo = m_lruCacheStrategy->get(key); if (!pCacheInfo) { EASYHTTPCPP_LOG_D(Tag, "releaseData : [%s] not found in cache.", key.c_str()); return; } HttpCacheInfo* pHttpCacheInfo = static_cast (pCacheInfo.get()); if (pHttpCacheInfo->getDataRefCount() == 0) { EASYHTTPCPP_LOG_D(Tag, "releaseData : [%s] dataRefCount is already 0.", key.c_str()); } else { pHttpCacheInfo->releaseDataRef(); try { m_lruCacheStrategy->update(key, pCacheInfo); } catch (const SqlDatabaseCorruptException& e) { deleteCorruptedCacheFile(__func__, e); } catch (const SqlException& e) { EASYHTTPCPP_LOG_D(Tag, "remove : database error occurred. Details: %s", e.getMessage().c_str()); } } EASYHTTPCPP_LOG_D(Tag, "releaseData : [%s] succeeded.", key.c_str()); } bool HttpFileCache::purge(bool mayDeleteIfBusy) { Poco::FastMutex::ScopedLock lock(m_instanceMutex); try { initializeCache(); bool ret = m_lruCacheStrategy->clear(mayDeleteIfBusy); // delete database file if HttpLruCacheStrategy::clear returns true // because if false returned, cache file is now reading. if (ret) { deleteCacheFile(); } return ret; } catch (const SqlDatabaseCorruptException& e) { deleteCorruptedCacheFile(__func__, e); // purge is successful. return true; } catch (const SqlException& e) { EASYHTTPCPP_LOG_D(Tag, "purge : database error occurred. Details: %s", e.getMessage().c_str()); return false; } } void HttpFileCache::deleteCacheFile() { // session should be closed before remove the data. m_pMetadataDb->closeSqliteSession(); m_pMetadataDb->deleteDatabaseFile(); } std::istream* HttpFileCache::createStreamFromCache(const std::string& key) { std::string filePath = makeCachedFilename(key); Poco::File file(filePath); try { if (!file.exists()) { EASYHTTPCPP_LOG_D(Tag, "createStreamFromCache: cached response body file is not found.[%s]", filePath.c_str()); return NULL; } } catch (const Poco::Exception& e) { EASYHTTPCPP_LOG_D(Tag, "createStreamFromCache: Poco::Exception. [%s]", e.message().c_str()); return NULL; } catch (const std::exception& e) { EASYHTTPCPP_LOG_D(Tag, "createStreamFromCache: std::exception. [%s]", e.what()); return NULL; } try { Poco::FileInputStream* pIfStream = new Poco::FileInputStream(filePath, std::ios_base::in | std::ios_base::binary); return pIfStream; } catch (const Poco::Exception& e) { EASYHTTPCPP_LOG_D(Tag, "createStreamFromCache: can not open cached response body [%s]", e.message().c_str()); return NULL; } } size_t HttpFileCache::getSize() { Poco::FastMutex::ScopedLock lock(m_instanceMutex); try { initializeCache(); } catch (const SqlDatabaseCorruptException& e) { deleteCorruptedCacheFile(__func__, e); throw HttpExecutionException("Cache database is corrupted. Check getCause() for details.", e); } catch (const SqlException& e) { EASYHTTPCPP_LOG_D(Tag, "getSize : database error occurred. Details: %s", e.getMessage().c_str()); throw HttpExecutionException("Cache database error occured. Check getCause() for details.", e); } return m_lruCacheStrategy->getTotalSize(); } bool HttpFileCache::onAdd(const std::string& key, CacheInfoWithDataSize::Ptr value) { return true; } bool HttpFileCache::onUpdate(const std::string& key, CacheInfoWithDataSize::Ptr value) { return true; } bool HttpFileCache::onRemove(const std::string& key) { try { if (!m_pMetadataDb->deleteMetadata(key)) { EASYHTTPCPP_LOG_D(Tag, "onRemove : Failed to delete from database. key=[%s]", key.c_str()); return false; } else { EASYHTTPCPP_LOG_D(Tag, "onRemove : delete from database. key=[%s]", key.c_str()); } } catch (const SqlDatabaseCorruptException& e) { EASYHTTPCPP_LOG_D(Tag, "onRemove : cache database is corrupted. Details: %s", e.getMessage().c_str()); throw; } catch (const SqlException& e) { EASYHTTPCPP_LOG_D(Tag, "onRemove : deleteMetadata failed. Details: %s", e.getMessage().c_str()); return false; } Poco::File file(makeCachedFilename(key)); if (!FileUtil::removeFileIfPresent(file)) { EASYHTTPCPP_LOG_D(Tag, "Failed to remove cached file. [%s]", file.path().c_str()); return false; } return true; } bool HttpFileCache::onGet(const std::string& key, CacheInfoWithDataSize::Ptr value) { return true; } bool HttpFileCache::onEnumerate(const HttpCacheEnumerationListener::EnumerationParam& param) { if (m_lruCacheStrategy->makeSpace(param.m_responseBodySize)) { CacheInfoWithDataSize::Ptr pCacheInfo = new HttpCacheInfo(param.m_key, param.m_responseBodySize); m_lruCacheStrategy->add(param.m_key, pCacheInfo); return true; } else { EASYHTTPCPP_LOG_D(Tag, "onEnumerate: cannot make cache space"); return false; } } bool HttpFileCache::removeInternal(const std::string& key) { return m_lruCacheStrategy->remove(key); } std::string HttpFileCache::makeCachedFilename(const std::string& key) { return HttpUtil::makeCachedResponseBodyFilename(m_cacheRootDir, key); } void HttpFileCache::initializeCache() { if (m_cacheInitialized) { return; } Poco::File file(m_cacheRootDir); if (!FileUtil::createDirsIfAbsent(file)) { EASYHTTPCPP_LOG_D(Tag, "Create cache root directory failed."); return; } // initialize date base m_pMetadataDb->enumerate(this); m_cacheInitialized = true; } void HttpFileCache::deleteCorruptedCacheFile(const std::string& funcName, const easyhttpcpp::db::SqlException& e) { EASYHTTPCPP_LOG_W(Tag, "%s: cache database got corrupted.", funcName.c_str()); EASYHTTPCPP_LOG_D(Tag, "%s: cache database got corrupted. Details: %s", funcName.c_str(), e.getMessage().c_str()); m_cacheInitialized = false; // reset LRU cache m_lruCacheStrategy->reset(); // delete cache database deleteCacheFile(); // delete cache response body Poco::File cacheDir(m_cacheRootDir); std::vector fileLists; cacheDir.list(fileLists); for (size_t i = 0; i < fileLists.size(); i++) { FileUtil::removeFileIfPresent(fileLists[i]); } EASYHTTPCPP_LOG_W(Tag, "%s: Deleted the cache database and cached files because the Cache database was corrupted.", funcName.c_str()); } } /* namespace easyhttpcpp */