From 2c8a81381e301c7191a527efdd26ed2e57bf3487 Mon Sep 17 00:00:00 2001 From: randomdan Date: Fri, 29 Jun 2012 22:25:31 +0000 Subject: Add support for client side caching and revalidation (HTTP If-Modified-Since, must-revalidate and 304 Not Modified) --- project2/cgi/cgiAppEngine.cpp | 34 +--------------------------------- project2/cgi/cgiEnvironment.cpp | 6 ++++++ project2/cgi/cgiEnvironment.h | 1 + project2/cgi/cgiResult.h | 20 ++++++++++++++++++++ project2/cgi/cgiResultStatic.cpp | 22 ++++++++++++++++++++++ project2/cgi/cgiResultWritable.cpp | 14 ++++++++++++++ project2/cgi/cgiStageCacheHit.cpp | 4 +--- project2/cgi/cgiStagePresent.cpp | 6 +++++- project2/cgi/testCgi.cpp | 1 + project2/common/presenterCache.h | 1 + project2/common/transform.h | 1 + project2/files/presenterCache.cpp | 6 ++++++ 12 files changed, 79 insertions(+), 37 deletions(-) create mode 100644 project2/cgi/cgiResult.h create mode 100644 project2/cgi/cgiResultStatic.cpp create mode 100644 project2/cgi/cgiResultWritable.cpp diff --git a/project2/cgi/cgiAppEngine.cpp b/project2/cgi/cgiAppEngine.cpp index cc7a3aa..d9948e4 100644 --- a/project2/cgi/cgiAppEngine.cpp +++ b/project2/cgi/cgiAppEngine.cpp @@ -1,5 +1,6 @@ #include #include "cgiAppEngine.h" +#include "cgiResult.h" #include #include #include "cgiEnvironment.h" @@ -41,39 +42,6 @@ CgiApplicationEngine::env() const return _env; } -class CgiResult : public TransformChainLink { - public: - CgiResult(CgiApplicationEngine::HttpHeaderPtr & h, std::ostream & s, const std::string & e) : - header(h), - stream(s), - encoding(e) { - } - CgiApplicationEngine::HttpHeaderPtr header; - std::ostream & stream; - const std::string encoding; -}; - -class WritableToCgiResult : public TransformImpl { - public: - void transform(const WritableContent * wc, CgiResult * cr) const { - cr->header->addHeader("Content-Type", Glib::ustring::compose("%1; charset=%2", wc->getContentType(), cr->encoding)); - cr->header->render(cr->stream); - wc->writeTo(cr->stream, cr->encoding); - } -}; -DECLARE_TRANSFORM(WritableToCgiResult); - -class StaticToCgiResult : public TransformImpl { - public: - void transform(const StaticContent * sc, CgiResult * cr) const { - cr->header->addHeader("Content-Type", Glib::ustring::compose("%1; charset=%2", sc->getContentType(), sc->getEncoding())); - cr->header->addHeader("Content-Length", Glib::ustring::compose("%1", sc->getSizeInBytes())); - cr->header->render(cr->stream); - sc->writeTo(cr->stream); - } -}; -DECLARE_TRANSFORM(StaticToCgiResult); - TransformSourcePtr finalTransformSource(TransformSourcePtr ts) { diff --git a/project2/cgi/cgiEnvironment.cpp b/project2/cgi/cgiEnvironment.cpp index a5d4af2..725199b 100644 --- a/project2/cgi/cgiEnvironment.cpp +++ b/project2/cgi/cgiEnvironment.cpp @@ -6,6 +6,7 @@ #include #include #include +#include std::vector makeVector(const boost::filesystem::path & y) @@ -113,6 +114,11 @@ CgiEnvironment::platform() const return hpi->derivedPlatform(); } +time_t +CgiEnvironment::getRequestModifiedSince() const +{ + return curl_getdate(cgienv->getenv("HTTP_IF_MODIFIED_SINCE").c_str(), NULL); +} Glib::ustring CgiEnvironment::getParamUri(unsigned int p) const diff --git a/project2/cgi/cgiEnvironment.h b/project2/cgi/cgiEnvironment.h index be6732c..5aded69 100644 --- a/project2/cgi/cgiEnvironment.h +++ b/project2/cgi/cgiEnvironment.h @@ -37,6 +37,7 @@ class CgiEnvironment : public Environment { std::string getScriptName() const; std::string getRedirectURL() const; std::string getRequestMethod() const; + time_t getRequestModifiedSince() const; std::string getCookieValue(const std::string & name) const; void setCGICC(const cgicc::Cgicc *, const CgiEnvInput * cgienv); diff --git a/project2/cgi/cgiResult.h b/project2/cgi/cgiResult.h new file mode 100644 index 0000000..35165c8 --- /dev/null +++ b/project2/cgi/cgiResult.h @@ -0,0 +1,20 @@ +#ifndef CGIRESULT_H +#define CGIRESULT_H + +#include "transform.h" +#include "cgiAppEngine.h" + +class CgiResult : public TransformChainLink { + public: + CgiResult(CgiApplicationEngine::HttpHeaderPtr & h, std::ostream & s, const std::string & e) : + header(h), + stream(s), + encoding(e) { + } + CgiApplicationEngine::HttpHeaderPtr header; + std::ostream & stream; + const std::string encoding; +}; + +#endif + diff --git a/project2/cgi/cgiResultStatic.cpp b/project2/cgi/cgiResultStatic.cpp new file mode 100644 index 0000000..542212e --- /dev/null +++ b/project2/cgi/cgiResultStatic.cpp @@ -0,0 +1,22 @@ +#include +#include +#include "cgiResult.h" + +class StaticToCgiResult : public TransformImpl { + public: + void transform(const StaticContent * sc, CgiResult * cr) const { + cr->header->addHeader("Content-Type", Glib::ustring::compose("%1; charset=%2", sc->getContentType(), sc->getEncoding())); + cr->header->addHeader("Content-Length", Glib::ustring::compose("%1", sc->getSizeInBytes())); + char buf[100]; + struct tm stm; + time_t mtime = sc->getModifiedTime(); + gmtime_r(&mtime, &stm); + strftime(buf, sizeof(buf), "%a, %d %b %Y %T %z", &stm); + cr->header->addHeader("Last-Modified", buf); + cr->header->addHeader("Cache-Control", "must-revalidate"); + cr->header->render(cr->stream); + sc->writeTo(cr->stream); + } +}; +DECLARE_TRANSFORM(StaticToCgiResult); + diff --git a/project2/cgi/cgiResultWritable.cpp b/project2/cgi/cgiResultWritable.cpp new file mode 100644 index 0000000..ea17d3b --- /dev/null +++ b/project2/cgi/cgiResultWritable.cpp @@ -0,0 +1,14 @@ +#include +#include "cgiResult.h" + +class WritableToCgiResult : public TransformImpl { + public: + void transform(const WritableContent * wc, CgiResult * cr) const { + cr->header->addHeader("Content-Type", Glib::ustring::compose("%1; charset=%2", wc->getContentType(), cr->encoding)); + cr->header->addHeader("Cache-control", "no-cache"); + cr->header->render(cr->stream); + wc->writeTo(cr->stream, cr->encoding); + } +}; +DECLARE_TRANSFORM(WritableToCgiResult); + diff --git a/project2/cgi/cgiStageCacheHit.cpp b/project2/cgi/cgiStageCacheHit.cpp index 8d854b6..d398e8f 100644 --- a/project2/cgi/cgiStageCacheHit.cpp +++ b/project2/cgi/cgiStageCacheHit.cpp @@ -20,8 +20,6 @@ CgiApplicationEngine::CacheHitStage::run() CgiApplicationEngine::HttpHeaderPtr CgiApplicationEngine::CacheHitStage::getHeader() const { - Project2HttpHeader * header = new Project2HttpHeader("200 OK"); - header->addHeader("Cache-control", "no-cache"); - return HttpHeaderPtr(header); + return HttpHeaderPtr(new Project2HttpHeader("200 OK")); } diff --git a/project2/cgi/cgiStagePresent.cpp b/project2/cgi/cgiStagePresent.cpp index b6bb11e..d997e1d 100644 --- a/project2/cgi/cgiStagePresent.cpp +++ b/project2/cgi/cgiStagePresent.cpp @@ -22,8 +22,13 @@ CgiApplicationEngine::PresentStage::run() { runChecks(); PresenterCaches backFill; + time_t reqMS = this->env()->getRequestModifiedSince(); BOOST_FOREACH(const PresenterCachePtr & pc, caches) { if (pc->check(root->script->modifiedTime())) { + if (reqMS >= pc->getModifiedTime()) { + header = HttpHeaderPtr(new Project2HttpHeader("304 Not Modified")); + return NextStage(NULL, this, NULL, NULL); + } CacheHitStage * chs = new CacheHitStage(root, pc); chs->caches = backFill; return NextStage(NULL, chs, pc, NULL); @@ -63,7 +68,6 @@ CgiApplicationEngine::ResponseStage::ResponseStage(ScriptNodePtr r) : CgiApplicationEngine::HttpHeaderPtr CgiApplicationEngine::PresentStage::getHeader() const { - header->addHeader("Cache-control", "no-cache"); return header; } diff --git a/project2/cgi/testCgi.cpp b/project2/cgi/testCgi.cpp index 8a4ccb6..fb480a4 100644 --- a/project2/cgi/testCgi.cpp +++ b/project2/cgi/testCgi.cpp @@ -56,6 +56,7 @@ class TestInput : public cgicc::CgiInput, public CgiEnvInput { TESTOPT("HTTP_REFERER", "", "Referrer") TESTOPT("HTTP_COOKIE", "", "Cookie") TESTOPT("HTTPS", "No", "HTTPS?") + TESTOPT("HTTP_IF_MODIFIED_SINCE", "", "Client cached copy timestamp") ("runCount", Options::value(&runCount, 1), "Repeat run this many times") ; FileOptions fo(".testCgi.settings"); diff --git a/project2/common/presenterCache.h b/project2/common/presenterCache.h index 63ff9f0..891535c 100644 --- a/project2/common/presenterCache.h +++ b/project2/common/presenterCache.h @@ -9,6 +9,7 @@ class PresenterCache : public SourceObject, public virtual TransformSource, publ public: PresenterCache(ScriptNodePtr); virtual bool check(time_t scriptMtime) const = 0; + virtual time_t getModifiedTime() const = 0; virtual std::ostream & writeCache(const std::string & ct, const std::string & encoding) = 0; virtual void flushCache(); diff --git a/project2/common/transform.h b/project2/common/transform.h index 973f4f0..2061374 100644 --- a/project2/common/transform.h +++ b/project2/common/transform.h @@ -82,6 +82,7 @@ class StaticContent { public: virtual Glib::ustring getContentType() const = 0; virtual Glib::ustring getEncoding() const = 0; + virtual time_t getModifiedTime() const = 0; virtual size_t getSizeInBytes() const = 0; virtual void writeTo(std::ostream &) const = 0; }; diff --git a/project2/files/presenterCache.cpp b/project2/files/presenterCache.cpp index 9c8ace2..d475824 100644 --- a/project2/files/presenterCache.cpp +++ b/project2/files/presenterCache.cpp @@ -93,6 +93,12 @@ class FilePresenterCache : public PresenterCache, public StaticContent, public S { return getXAttr("user.content-type"); } + time_t getModifiedTime() const + { + struct stat st; + safesys(-1, fstat(*myCache, &st)); + return st.st_mtime; + } Glib::ustring getXAttr(const char * attr) const { char buf[BUFSIZ]; -- cgit v1.2.3