[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/sony/easyhttpcpp/master/src/HttpFileCache.cpp [Back]  [Original]

/*
 * 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 */

Web Proxy Viewer  |  New URL  |  Original Page