From bf5f932ff134f042965b530fcdaf269404c0b191 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 7 Jan 2018 08:56:06 +0100 Subject: [PATCH] HTTP access enhancments Added the ability for asyn requests. Fixed a memory lead issue in WebDAV access. --- src/tl/tl/tlHttpStream.cc | 11 ++++++++- src/tl/tl/tlHttpStream.h | 32 ++++++++++++++++++++++++++ src/tl/tl/tlStream.cc | 18 ++++++++++++++- src/tl/tl/tlStream.h | 17 ++++++++++++-- src/tl/tl/tlWebDAV.cc | 2 +- src/tl/unit_tests/tlHttpStream.cc | 37 +++++++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 5 deletions(-) diff --git a/src/tl/tl/tlHttpStream.cc b/src/tl/tl/tlHttpStream.cc index 883ac7a8d..e45223a56 100644 --- a/src/tl/tl/tlHttpStream.cc +++ b/src/tl/tl/tlHttpStream.cc @@ -123,7 +123,7 @@ InputHttpStream::InputHttpStream (const std::string &url) InputHttpStream::~InputHttpStream () { - // .. nothing yet .. + // .. nothing yet .. } void @@ -166,6 +166,7 @@ InputHttpStream::finished (QNetworkReply *reply) issue_request (QUrl (redirect_target.toString ())); } else { mp_reply = reply; + m_ready (); } } @@ -205,6 +206,14 @@ InputHttpStream::issue_request (const QUrl &url) #endif } +void +InputHttpStream::send () +{ + if (mp_reply == 0) { + issue_request (QUrl (tl::to_qstring (m_url))); + } +} + size_t InputHttpStream::read (char *b, size_t n) { diff --git a/src/tl/tl/tlHttpStream.h b/src/tl/tl/tlHttpStream.h index 6063a2bae..cb82a2007 100644 --- a/src/tl/tl/tlHttpStream.h +++ b/src/tl/tl/tlHttpStream.h @@ -25,6 +25,7 @@ #define HDR_tlHttpStream #include "tlStream.h" +#include "tlEvents.h" #include #include @@ -84,6 +85,19 @@ public: */ virtual ~InputHttpStream (); + /** + * @brief Sends the request for data + * To ensure prompt delivery of data, this method can be used prior to + * "read" to trigger the download from the given URL. + * This method will return immediately. When the reply is available, + * the "ready" event will be triggered. "read" can then be used to + * read the data or - in case of an error - throw an exception. + * If "send" is not used before "read", "read" will block until data + * is available. + * If a request has already been sent, this method will do nothing. + */ + void send (); + /** * @brief Sets the request verb * The default verb is "GET" @@ -115,6 +129,23 @@ public: */ virtual size_t read (char *b, size_t n); + /** + * @brief Gets the "ready" event + * Connect to this event for the asynchroneous interface. + */ + tl::Event &ready () + { + return m_ready; + } + + /** + * @brief Gets a value indicating whether data is available + */ + bool data_available () + { + return mp_reply != 0; + } + virtual void reset (); virtual std::string source () const @@ -140,6 +171,7 @@ private: QByteArray m_data; QBuffer *mp_buffer; std::map m_headers; + tl::Event m_ready; void issue_request (const QUrl &url); }; diff --git a/src/tl/tl/tlStream.cc b/src/tl/tl/tlStream.cc index 5ee4e5a88..23def2170 100644 --- a/src/tl/tl/tlStream.cc +++ b/src/tl/tl/tlStream.cc @@ -311,6 +311,14 @@ InputStream::InputStream (InputStreamBase &delegate) mp_buffer = new char [m_bcap]; } +InputStream::InputStream (InputStreamBase *delegate) + : m_pos (0), mp_bptr (0), mp_delegate (delegate), m_owns_delegate (true), mp_inflate (0) +{ + m_bcap = 4096; // initial buffer capacity + m_blen = 0; + mp_buffer = new char [m_bcap]; +} + InputStream::InputStream (const std::string &abstract_path) : m_pos (0), mp_bptr (0), mp_delegate (0), m_owns_delegate (false), mp_inflate (0) { @@ -357,7 +365,15 @@ std::string InputStream::absolute_path (const std::string &abstract_path) InputStream::~InputStream () { if (mp_delegate && m_owns_delegate) { - delete mp_delegate; + // NOTE: HTTP stream objects should not be deleted now, since events + // may be pending that deliver the finished signal to the object. + tl::InputHttpStream *http = dynamic_cast(mp_delegate); + if (http) { + http->ready ().clear (); // avoids events from deleted streams + http->deleteLater (); + } else { + delete mp_delegate; + } mp_delegate = 0; } if (mp_inflate) { diff --git a/src/tl/tl/tlStream.h b/src/tl/tl/tlStream.h index 98f62fffb..f88fb6990 100644 --- a/src/tl/tl/tlStream.h +++ b/src/tl/tl/tlStream.h @@ -172,11 +172,16 @@ class TL_PUBLIC InputStream public: /** * @brief Default constructor - * - * This constructor takes a delegate object. + * This constructor takes a delegate object, but does not take ownership. */ InputStream (InputStreamBase &delegate); + /** + * @brief Default constructor + * This constructor takes a delegate object, and takes ownership. + */ + InputStream (InputStreamBase *delegate); + /** * @brief Opens a stream from a abstract path * @@ -305,6 +310,14 @@ public: * @brief Gets the absolute path for a given URL */ static std::string absolute_path (const std::string &path); + + /** + * @brief Gets the base reader (delegate) + */ + InputStreamBase *base () + { + return mp_delegate; + } protected: void reset_pos () diff --git a/src/tl/tl/tlWebDAV.cc b/src/tl/tl/tlWebDAV.cc index 5f9a9a53d..35a91ea08 100644 --- a/src/tl/tl/tlWebDAV.cc +++ b/src/tl/tl/tlWebDAV.cc @@ -256,7 +256,7 @@ WebDAVObject::download_item (const std::string &url) tl::InputHttpStream *http = new tl::InputHttpStream (url); // This trick allows accessing GitHub repos through their SVN API http->add_header ("User-Agent", "SVN"); - return new tl::InputStream (*http); + return new tl::InputStream (http); } bool diff --git a/src/tl/unit_tests/tlHttpStream.cc b/src/tl/unit_tests/tlHttpStream.cc index 36600c2e9..298cb60e1 100644 --- a/src/tl/unit_tests/tlHttpStream.cc +++ b/src/tl/unit_tests/tlHttpStream.cc @@ -24,6 +24,8 @@ #include "tlHttpStream.h" #include "tlUnitTest.h" +#include + static std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); static std::string test_url2 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/dir1"); @@ -73,3 +75,38 @@ TEST(2) "\n" ); } + +namespace +{ + +class Receiver : public tl::Object +{ +public: + Receiver () : flag (false) { } + void handle () { flag = true; } + bool flag; +}; + +} + +// async mode +TEST(3) +{ + tl::InputHttpStream stream (test_url1); + + Receiver r; + stream.ready ().add (&r, &Receiver::handle); + + stream.send (); + EXPECT_EQ (stream.data_available (), false); + + while (! stream.data_available ()) { + QCoreApplication::processEvents (QEventLoop::ExcludeUserInputEvents); + } + EXPECT_EQ (r.flag, true); + + char b[100]; + size_t n = stream.read (b, sizeof (b)); + std::string res (b, n); + EXPECT_EQ (res, "hello, world.\n"); +}