From a9523a398c57733cde6b736c14129288095491fa Mon Sep 17 00:00:00 2001 From: randomdan Date: Thu, 20 Mar 2014 23:49:45 +0000 Subject: Support range requests and seekable streams Untangle streams/streamPresenter/writableContent Pass the ExecContext into more bits of content interface --- project2/cgi/cgiRequestContext.cpp | 24 ++++++++++++++++++++++++ project2/cgi/cgiRequestContext.h | 2 ++ project2/cgi/cgiResultWritable.cpp | 26 +++++++++++++++++++++++++- project2/cgi/testCgi.cpp | 1 + project2/common/presenterCache.cpp | 2 +- project2/common/range.h | 22 ++++++++++++++++++++++ project2/common/stream.cpp | 22 ++++++++++++++++++++++ project2/common/stream.h | 9 ++++++++- project2/common/transform.h | 9 +++++++-- project2/json/presenter.cpp | 4 ++-- project2/json/presenter.h | 4 ++-- project2/mail/sendmailTask.cpp | 4 ++-- project2/streams/streamPresenter.cpp | 31 +++---------------------------- project2/xml/transformHtml.cpp | 4 ++-- project2/xml/transformHtml.h | 4 ++-- project2/xml/transformText.cpp | 4 ++-- project2/xml/transformText.h | 4 ++-- project2/xml/xmlPresenter.cpp | 4 ++-- project2/xml/xmlPresenter.h | 4 ++-- 19 files changed, 133 insertions(+), 51 deletions(-) create mode 100644 project2/common/range.h create mode 100644 project2/common/stream.cpp diff --git a/project2/cgi/cgiRequestContext.cpp b/project2/cgi/cgiRequestContext.cpp index d38a256..2827aa7 100644 --- a/project2/cgi/cgiRequestContext.cpp +++ b/project2/cgi/cgiRequestContext.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -120,3 +121,26 @@ CgiRequestContext::getParameter(const VariableType & p) const return cgi(p); } +boost::optional +CgiRequestContext::getRequestRange() const +{ + const auto r = cgienv.getenv("HTTP_RANGE"); + std::vector rangeParts; + boost::split(rangeParts, r, boost::is_any_of("=-"), boost::algorithm::token_compress_off); + + if (rangeParts.size() < 3 || (rangeParts[1].empty() && rangeParts[2].empty())) { + return boost::optional(); + } + RangeRequest range; + range.Unit = rangeParts[0]; + + if (!rangeParts[1].empty()) { + range.Start = boost::lexical_cast(rangeParts[1]); + } + if (!rangeParts[2].empty()) { + range.End = boost::lexical_cast(rangeParts[2]); + } + + return range; +} + diff --git a/project2/cgi/cgiRequestContext.h b/project2/cgi/cgiRequestContext.h index 57c233d..9501a20 100644 --- a/project2/cgi/cgiRequestContext.h +++ b/project2/cgi/cgiRequestContext.h @@ -6,6 +6,7 @@ #include "cgiEnvInput.h" #include "cgiRouter.h" #include "execContext.h" +#include #include #include #include @@ -40,6 +41,7 @@ class CgiRequestContext : public ExecContext { ETags getRequestETags() const; time_t getRequestModifiedSince() const; std::string getCookieValue(const std::string & name) const; + boost::optional getRequestRange() const; LazyPointer router; boost::posix_time::ptime startTime; diff --git a/project2/cgi/cgiResultWritable.cpp b/project2/cgi/cgiResultWritable.cpp index 357a57c..1364456 100644 --- a/project2/cgi/cgiResultWritable.cpp +++ b/project2/cgi/cgiResultWritable.cpp @@ -4,8 +4,32 @@ class WritableToCgiResult : public TransformImpl { public: void transform(const WritableContent * wc, CgiResult * cr, ExecContext * ec) const { - cr->header->addHeader("Content-Type", Glib::ustring::compose("%1; charset=%2", wc->getContentType(), cr->encoding)); + cr->header->addHeader("Content-Type", Glib::ustring::compose("%1; charset=%2", wc->getContentType(ec), cr->encoding)); cr->header->addHeader("Cache-control", "no-cache"); + auto size = wc->getSizeInBytes(ec); + if (size) { + auto range = wc->getRange(ec); + if (range) { + cr->header->addHeader("Status", "206 Partial Content"); + cr->header->addHeader("Content-Length", Glib::ustring::compose("%1", range->End - range->Start + 1)); + cr->header->addHeader("Content-Range", Glib::ustring::compose("%1 %2-%3/%4", + range->Unit, range->Start, range->End, *size)); + } + else { + cr->header->addHeader("Content-Length", Glib::ustring::compose("%1", *size)); + } + } + if (wc->isSeekable()) { + cr->header->addHeader("Accept-Ranges", "bytes"); + } + auto modifiedTime = wc->getModifiedTime(ec); + if (modifiedTime) { + char buf[100]; + struct tm stm; + gmtime_r(&*modifiedTime, &stm); + strftime(buf, sizeof(buf), "%a, %d %b %Y %T %z", &stm); + cr->header->addHeader("Last-Modified", buf); + } cr->header->render(cr->stream); wc->writeTo(cr->stream, cr->encoding, ec); } diff --git a/project2/cgi/testCgi.cpp b/project2/cgi/testCgi.cpp index 6644102..0c2b472 100644 --- a/project2/cgi/testCgi.cpp +++ b/project2/cgi/testCgi.cpp @@ -125,6 +125,7 @@ TESTOPT("HTTP_REFERER", "", "Referrer") TESTOPT("HTTP_COOKIE", "", "Cookie") TESTOPT("HTTPS", "No", "HTTPS?") TESTOPT("HTTP_IF_MODIFIED_SINCE", "", "Client cached copy timestamp") +TESTOPT("HTTP_RANGE", "", "Range requested by client '{unit}=[start]-[end]'") ("urlListFile", Options::value(&urlListFile, ""), "Load URL list from this file") ("runCount", Options::value(&runCount, 1), "Repeat run this many times") ("help", ShowHelpComponent::Option(), "Print usage and exit")("h") diff --git a/project2/common/presenterCache.cpp b/project2/common/presenterCache.cpp index 31fdc8f..1b56db8 100644 --- a/project2/common/presenterCache.cpp +++ b/project2/common/presenterCache.cpp @@ -16,7 +16,7 @@ PresenterCache::flushCache() class WriteToCache : public TransformImpl { public: void transform(const WritableContent * wc, PresenterCache * pc, ExecContext * ec) const { - wc->writeTo(pc->writeCache(wc->getContentType(), pc->encoding, ec), pc->encoding, ec); + wc->writeTo(pc->writeCache(wc->getContentType(ec), pc->encoding, ec), pc->encoding, ec); } }; DECLARE_TRANSFORM(WriteToCache); diff --git a/project2/common/range.h b/project2/common/range.h new file mode 100644 index 0000000..d7606b4 --- /dev/null +++ b/project2/common/range.h @@ -0,0 +1,22 @@ +#ifndef RANGE_H +#define RANGE_H + +#include + +typedef unsigned long long RangePos; +typedef boost::optional OptionalRangePos; + +struct RangeRequest { + std::string Unit; + OptionalRangePos Start; + OptionalRangePos End; +}; + +struct RangeResponse { + std::string Unit; + RangePos Start; + RangePos End; +}; + +#endif + diff --git a/project2/common/stream.cpp b/project2/common/stream.cpp new file mode 100644 index 0000000..0db9225 --- /dev/null +++ b/project2/common/stream.cpp @@ -0,0 +1,22 @@ +#include "stream.h" +#include + +Glib::ustring +Stream::getContentType(ExecContext *) const +{ + return "application/octet-stream"; +} + +size_t +Stream::write(std::ostream * os, const char * data, size_t len) +{ + os->write(data, len); + return len; +} + +void +Stream::writeTo(std::ostream & os, const std::string &, ExecContext * ec) const +{ + runStream(boost::bind(&Stream::write, &os, _1, _2), ec); +} + diff --git a/project2/common/stream.h b/project2/common/stream.h index ad3ee79..bf0dc41 100644 --- a/project2/common/stream.h +++ b/project2/common/stream.h @@ -3,15 +3,22 @@ #include "sourceObject.h" #include +#include "transform.h" class ExecContext; -class Stream : public SourceObject { +class Stream : public SourceObject, public WritableContent { public: template Stream(const X &... x) : SourceObject(x...) { } typedef boost::function Sink; virtual void runStream(const Sink &, ExecContext *) const = 0; + virtual Class getContentClass(ExecContext *) const { return ClassData; } + virtual Glib::ustring getContentType(ExecContext *) const; + virtual void writeTo(std::ostream & os, const std::string &, ExecContext * ec) const; + + protected: + static size_t write(std::ostream * os, const char * data, size_t len); }; typedef boost::intrusive_ptr StreamPtr; diff --git a/project2/common/transform.h b/project2/common/transform.h index 978e7df..7a46c7f 100644 --- a/project2/common/transform.h +++ b/project2/common/transform.h @@ -5,6 +5,7 @@ #include "intrusivePtrBase.h" #include "scriptLoader.h" #include +#include "range.h" class ExecContext; @@ -75,8 +76,12 @@ class WritableContent { ClassPretty = 2, ClassData = 3 }; - virtual Class getContentClass() const = 0; - virtual Glib::ustring getContentType() const = 0; + virtual Class getContentClass(ExecContext *) const = 0; + virtual Glib::ustring getContentType(ExecContext *) const = 0; + virtual boost::optional getSizeInBytes(ExecContext *) const { return boost::optional(); } + virtual boost::optional getModifiedTime(ExecContext *) const { return boost::optional(); } + virtual boost::optional getRange(ExecContext *) const { return boost::optional(); } + virtual bool isSeekable() const { return false; } virtual void writeTo(std::ostream &, const std::string & encoding, ExecContext *) const = 0; }; diff --git a/project2/json/presenter.cpp b/project2/json/presenter.cpp index 5b769c9..c07c71e 100644 --- a/project2/json/presenter.cpp +++ b/project2/json/presenter.cpp @@ -119,13 +119,13 @@ JsonPresenter::operator const WritableContent * () const } Glib::ustring -JsonPresenter::getContentType() const +JsonPresenter::getContentType(ExecContext *) const { return contentType; } WritableContent::Class -JsonPresenter::getContentClass() const +JsonPresenter::getContentClass(ExecContext *) const { return ClassData; } diff --git a/project2/json/presenter.h b/project2/json/presenter.h index 5ccecb2..dbb2b7a 100644 --- a/project2/json/presenter.h +++ b/project2/json/presenter.h @@ -27,8 +27,8 @@ class JsonPresenter : public MultiRowSetPresenter, public ContentPresenter, publ void finishArray(bool) const; operator const json::Object *() const; operator const WritableContent * () const; - Glib::ustring getContentType() const; - Class getContentClass() const; + Glib::ustring getContentType(ExecContext *) const; + Class getContentClass(ExecContext *) const; void writeTo(std::ostream & o, const std::string & encoding, ExecContext *) const; private: diff --git a/project2/mail/sendmailTask.cpp b/project2/mail/sendmailTask.cpp index bd4094e..3b3253a 100644 --- a/project2/mail/sendmailTask.cpp +++ b/project2/mail/sendmailTask.cpp @@ -137,10 +137,10 @@ class TransformWritableContentToEmail : public TransformImplparts.insert(new BoundaryBegin(wc->getContentType(), encoding, wc->getContentClass())); + parts->parts.insert(new BoundaryBegin(wc->getContentType(ec), encoding, wc->getContentClass(ec))); std::stringstream str; wc->writeTo(str, encoding, ec); - parts->parts.insert(new MimeContent(str.str(), wc->getContentClass())); + parts->parts.insert(new MimeContent(str.str(), wc->getContentClass(ec))); SendMailTask::MailPart::mimeIdx += 1; } void configure(ScriptNodePtr s, ExecContext * ec) diff --git a/project2/streams/streamPresenter.cpp b/project2/streams/streamPresenter.cpp index 3dde21c..229799c 100644 --- a/project2/streams/streamPresenter.cpp +++ b/project2/streams/streamPresenter.cpp @@ -3,38 +3,14 @@ #include #include -class StreamPresenter : public Presenter, public WritableContent, public SourceOf { +class StreamPresenter : public Presenter, public SourceOf { public: StreamPresenter(ScriptNodePtr e, ObjectSource os, ExecContext * ) : - Presenter(os), - contentType(e, "contenttype", "application/octet-stream") + Presenter(os) { e->script->loader.addLoadTarget(e, Storer::into(&stream)); } - WritableContent::Class getContentClass() const - { - return ClassData; - } - - Glib::ustring getContentType() const - { - return contentType(NULL); - } - - static size_t write(std::ostream * os, const char * data, size_t len) - { - os->write(data, len); - return len; - } - - void writeTo(std::ostream & os, const std::string&, ExecContext * ec) const - { - if (stream) { - stream->runStream(boost::bind(&StreamPresenter::write, &os, _1, _2), ec); - } - } - void addNewArray(const Glib::ustring&, bool) const { } @@ -61,11 +37,10 @@ class StreamPresenter : public Presenter, public WritableContent, public SourceO operator const WritableContent * () const { - return this; + return stream.get(); } private: - Variable contentType; StreamPtr stream; }; diff --git a/project2/xml/transformHtml.cpp b/project2/xml/transformHtml.cpp index adf534b..2232a4f 100644 --- a/project2/xml/transformHtml.cpp +++ b/project2/xml/transformHtml.cpp @@ -49,13 +49,13 @@ HtmlDocument::writeTo(std::ostream & o, const std::string & encoding, ExecContex } Glib::ustring -HtmlDocument::getContentType() const +HtmlDocument::getContentType(ExecContext *) const { return contentType; } WritableContent::Class -HtmlDocument::getContentClass() const { +HtmlDocument::getContentClass(ExecContext *) const { return ClassPretty; } diff --git a/project2/xml/transformHtml.h b/project2/xml/transformHtml.h index d572244..b8fa82e 100644 --- a/project2/xml/transformHtml.h +++ b/project2/xml/transformHtml.h @@ -11,8 +11,8 @@ class HtmlDocument : public SourceOf, public WritableContent, publ htmlDocPtr doc; operator const HtmlDocument * () const; operator const WritableContent * () const; - Class getContentClass() const; - Glib::ustring getContentType() const; + Class getContentClass(ExecContext *) const; + Glib::ustring getContentType(ExecContext *) const; void writeTo(std::ostream &, const std::string & encoding, ExecContext *) const; Glib::ustring contentType; Glib::ustring method; diff --git a/project2/xml/transformText.cpp b/project2/xml/transformText.cpp index f92e8ef..edf8a8b 100644 --- a/project2/xml/transformText.cpp +++ b/project2/xml/transformText.cpp @@ -26,13 +26,13 @@ TextDocument::writeTo(std::ostream & o, const std::string & encoding, ExecContex } Glib::ustring -TextDocument::getContentType() const +TextDocument::getContentType(ExecContext *) const { return contentType(NULL); } WritableContent::Class -TextDocument::getContentClass() const { +TextDocument::getContentClass(ExecContext *) const { return ClassPlain; } diff --git a/project2/xml/transformText.h b/project2/xml/transformText.h index 417ceb1..57e19f9 100644 --- a/project2/xml/transformText.h +++ b/project2/xml/transformText.h @@ -11,8 +11,8 @@ class TextDocument : public SourceOf, public WritableContent, publ Glib::ustring doc; operator const TextDocument * () const; operator const WritableContent * () const; - Class getContentClass() const; - Glib::ustring getContentType() const; + Class getContentClass(ExecContext *) const; + Glib::ustring getContentType(ExecContext *) const; void writeTo(std::ostream &, const std::string & encoding, ExecContext *) const; Variable contentType; }; diff --git a/project2/xml/xmlPresenter.cpp b/project2/xml/xmlPresenter.cpp index 10ff3e6..e7abc3a 100644 --- a/project2/xml/xmlPresenter.cpp +++ b/project2/xml/xmlPresenter.cpp @@ -250,12 +250,12 @@ XmlPresenter::finishArray(bool objects) const } Glib::ustring -XmlPresenter::getContentType() const { +XmlPresenter::getContentType(ExecContext *) const { return contentType; } WritableContent::Class -XmlPresenter::getContentClass() const { +XmlPresenter::getContentClass(ExecContext *) const { return ClassData; } diff --git a/project2/xml/xmlPresenter.h b/project2/xml/xmlPresenter.h index c94e29a..6e3dac2 100644 --- a/project2/xml/xmlPresenter.h +++ b/project2/xml/xmlPresenter.h @@ -42,8 +42,8 @@ class XmlPresenter : public Presenter, public ContentPresenter, public SourceOf< operator const xmlDoc * () const; operator const boost::shared_ptr * () const; operator const WritableContent * () const; - Class getContentClass() const; - Glib::ustring getContentType() const; + Class getContentClass(ExecContext *) const; + Glib::ustring getContentType(ExecContext *) const; void writeTo(std::ostream &, const std::string & enc, ExecContext *) const; void finalizeContent() const; void init(ExecContext *); -- cgit v1.2.3