]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5448] Implemented method indicating whether HTTP connection is persistent.
authorMarcin Siodelski <marcin@isc.org>
Tue, 12 Dec 2017 09:29:09 +0000 (10:29 +0100)
committerMarcin Siodelski <marcin@isc.org>
Tue, 12 Dec 2017 09:29:09 +0000 (10:29 +0100)
src/lib/http/http_types.h
src/lib/http/request.cc
src/lib/http/request.h
src/lib/http/tests/request_unittests.cc

index 285096643c9209244c8332022661311671c0c7c3..68d1c0952b3cd4e48133608e2413a3b9770ab24f 100644 (file)
@@ -44,6 +44,30 @@ struct HttpVersion {
     bool operator!=(const HttpVersion& rhs) const {
         return (!operator==(rhs));
     }
+
+    /// @name Methods returning @c HttpVersion object encapsulating typical
+    /// HTTP version numbers.
+    //@{
+
+    /// @brief HTTP version 1.0.
+    static const HttpVersion& HTTP_10() {
+        static HttpVersion ver(1, 0);
+        return (ver);
+    };
+
+    /// @brief HTTP version 1.1.
+    static const HttpVersion& HTTP_11() {
+        static HttpVersion ver(1, 1);
+        return (ver);
+    }
+
+    /// @brief HTTP version 2.0.
+    static const HttpVersion& HTTP_20() {
+        static HttpVersion ver(2, 0);
+        return (ver);
+    }
+
+    //@}
 };
 
 } // end of namespace isc::http
index b54a35ad4d70e55d705f71d8c15875aea7bb769a..74b5d18759b1bdc85a52d8e5a0f6b7c9be27111c 100644 (file)
@@ -158,6 +158,20 @@ HttpRequest::getHttpVersion() const {
 
 HttpHeaderPtr
 HttpRequest::getHeader(const std::string& header_name) const {
+    HttpHeaderPtr http_header = getHeaderSafe(header_name);
+
+    // No such header.
+    if (!http_header) {
+        isc_throw(HttpRequestNonExistingHeader, header_name << " HTTP header"
+                  " not found in the request");
+    }
+
+    // Header found.
+    return (http_header);
+}
+
+HttpHeaderPtr
+HttpRequest::getHeaderSafe(const std::string& header_name) const {
     checkCreated();
 
     HttpHeader hdr(header_name);
@@ -166,9 +180,8 @@ HttpRequest::getHeader(const std::string& header_name) const {
         return (header_it->second);
     }
 
-    // No such header.
-    isc_throw(HttpRequestNonExistingHeader, header_name << " HTTP header"
-              " not found in the request");
+    // Header not found. Return null pointer.
+    return (HttpHeaderPtr());
 }
 
 std::string
@@ -195,7 +208,16 @@ HttpRequest::getBody() const {
 
 bool
 HttpRequest::isPersistent() const {
-    return (false);
+    HttpHeaderPtr conn = getHeaderSafe("connection");
+    std::string conn_value;
+    if (conn) {
+        conn_value = conn->getLowerCaseValue();
+    }
+
+    HttpVersion ver = getHttpVersion();
+
+    return (((ver == HttpVersion::HTTP_10()) && (conn_value == "keep-alive")) ||
+            ((HttpVersion::HTTP_10() < ver) && (conn_value.empty() || (conn_value != "close"))));
 }
 
 void
index cb7178efdce8749bf72142697a398e601854f5b0..afd13c7514e028fc552f23b5903cccedc7cfc863 100644 (file)
@@ -183,8 +183,26 @@ public:
     /// @brief Returns object encapsulating HTTP header.
     ///
     /// @param header_name HTTP header name.
+    ///
+    /// @return Non-null pointer to the header.
+    /// @throw HttpRequestNonExistingHeader if header with the specified name
+    /// doesn't exist.
+    /// @throw HttpRequestError if the request hasn't been created.
     HttpHeaderPtr getHeader(const std::string& header_name) const;
 
+    /// @brief Returns object encapsulating HTTP header.
+    ///
+    /// This variant doesn't throw an exception if the header doesn't exist.
+    /// It will throw if the request hasn't been created using @c create()
+    /// method.
+    ///
+    /// @param header_name HTTP header name.
+    ///
+    /// @return Pointer to the specified header, or null if such header doesn't
+    /// exist.
+    /// @throw HttpRequestError if the request hasn't been created.
+    HttpHeaderPtr getHeaderSafe(const std::string& header_name) const;
+
     /// @brief Returns a value of the specified HTTP header.
     ///
     /// @param header_name Name of the HTTP header.
index ae6bbf20f95aee31e520f927359c0592f210e360..fc65b443727305d4532ab9ad71af6cff9b6faccc 100644 (file)
@@ -7,6 +7,7 @@
 #include <config.h>
 
 #include <http/request.h>
+#include <http/http_header.h>
 #include <http/http_types.h>
 #include <http/tests/request_test.h>
 #include <boost/lexical_cast.hpp>
@@ -18,7 +19,51 @@ using namespace isc::http::test;
 
 namespace {
 
-typedef HttpRequestTestBase<HttpRequest> HttpRequestTest;
+class HttpRequestTest : public HttpRequestTestBase<HttpRequest> {
+public:
+
+    /// @brief Tests connection persistence for the given HTTP version
+    /// and header value.
+    ///
+    /// This method creates a dummy HTTP request and sets the specified
+    /// version and header. Next, it returns the value if @c isPersistent
+    /// method for this request. The unit test verifies this value for
+    /// correctness.
+    ///
+    /// @param http_version HTTP version.
+    /// @param http_header HTTP header to be included in the request. If
+    /// the header has an empty value, it is not included.
+    ///
+    /// @return true if request indicates that connection is to be
+    /// persistent.
+    bool isPersistent(const HttpVersion& http_version,
+                      const HttpHeader& http_header = HttpHeader("Connection")) {
+        try {
+            // We need to add some JSON body.
+            std::string json_body = "{ \"param1\": \"foo\" }";
+
+            // Set method, path, version and content length.
+            setContextBasics("POST", "/isc/org", http_version);
+            addHeaderToContext("Content-Length", json_body.length());
+
+            // If additional header has been specified (typically "Connection"),
+            // include it.
+            if (!http_header.getValue().empty()) {
+                addHeaderToContext(http_header.getName(), http_header.getValue());
+            }
+            // Attach JSON body.
+            request_.context()->body_ = json_body;
+            request_.create();
+
+        } catch (...) {
+            ADD_FAILURE() << "failed to create HTTP request while testing"
+                " connection persistence";
+        }
+
+        return (request_.isPersistent());
+    }
+
+};
 
 TEST_F(HttpRequestTest, minimal) {
     setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
@@ -155,4 +200,30 @@ TEST_F(HttpRequestTest, requiresBody) {
     EXPECT_TRUE(request_.requiresBody());
 }
 
+TEST_F(HttpRequestTest, isPersistentHttp10) {
+    // In HTTP 1.0 the connection is by default non-persistent.
+    EXPECT_FALSE(isPersistent(HttpVersion(1, 0)));
+}
+
+TEST_F(HttpRequestTest, isPersistentHttp11) {
+    // In HTTP 1.1 the connection is by default persistent.
+    EXPECT_TRUE(isPersistent(HttpVersion(1, 1)));
+}
+
+TEST_F(HttpRequestTest, isPersistentHttp10KeepAlive) {
+    // In HTTP 1.0 the client indicates that the connection is desired to be
+    // persistent by including "Connection: keep-alive" header.
+    EXPECT_TRUE(
+        isPersistent(HttpVersion(1, 0), HttpHeader("Connection", "Keep-alive"))
+    );
+}
+
+TEST_F(HttpRequestTest, isPersistentHttp11Close) {
+    // In HTTP 1.1 the client would include "Connection: close" header if it
+    // desires to close the connection.
+    EXPECT_FALSE(
+        isPersistent(HttpVersion(1, 1), HttpHeader("Connection", "close"))
+    );
+}
+
 }