2021-01-10 08:35:42 -06:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
|
|
/*
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <chrono>
|
|
|
|
#include <condition_variable>
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include <Poco/Net/HTTPClientSession.h>
|
|
|
|
#include <Poco/Net/HTTPResponse.h>
|
|
|
|
#include <Poco/StreamCopier.h>
|
|
|
|
|
|
|
|
#include <mutex>
|
|
|
|
#include <string>
|
|
|
|
#include <test/lokassert.hpp>
|
|
|
|
|
|
|
|
#if ENABLE_SSL
|
|
|
|
#include "Ssl.hpp"
|
|
|
|
#include <net/SslSocket.hpp>
|
|
|
|
#endif
|
|
|
|
#include <net/HttpRequest.hpp>
|
|
|
|
#include <FileUtil.hpp>
|
|
|
|
#include <Util.hpp>
|
2021-03-10 10:20:42 -06:00
|
|
|
#include <helpers.hpp>
|
2021-01-10 08:35:42 -06:00
|
|
|
|
|
|
|
/// http::Request unit-tests.
|
2021-01-26 09:07:46 -06:00
|
|
|
/// FIXME: use loopback and avoid depending on external services.
|
|
|
|
/// Currently we need to rely on external services to validate
|
|
|
|
/// the implementation.
|
2021-01-10 08:35:42 -06:00
|
|
|
class HttpRequestTests final : public CPPUNIT_NS::TestFixture
|
|
|
|
{
|
|
|
|
CPPUNIT_TEST_SUITE(HttpRequestTests);
|
|
|
|
|
2021-02-20 15:16:18 -06:00
|
|
|
CPPUNIT_TEST(testInvalidURI);
|
2021-01-10 08:35:42 -06:00
|
|
|
CPPUNIT_TEST(testSimpleGet);
|
|
|
|
CPPUNIT_TEST(testSimpleGetSync);
|
2021-01-29 07:35:03 -06:00
|
|
|
// CPPUNIT_TEST(test500GetStatuses); // Slow.
|
|
|
|
// CPPUNIT_TEST(testSimplePost);
|
2021-01-10 08:35:42 -06:00
|
|
|
CPPUNIT_TEST(testTimeout);
|
|
|
|
CPPUNIT_TEST(testOnFinished_Complete);
|
|
|
|
CPPUNIT_TEST(testOnFinished_Timeout);
|
|
|
|
|
|
|
|
CPPUNIT_TEST_SUITE_END();
|
|
|
|
|
2021-02-20 15:16:18 -06:00
|
|
|
void testInvalidURI();
|
2021-01-10 08:35:42 -06:00
|
|
|
void testSimpleGet();
|
|
|
|
void testSimpleGetSync();
|
|
|
|
void test500GetStatuses();
|
|
|
|
void testSimplePost();
|
|
|
|
void testTimeout();
|
|
|
|
void testOnFinished_Complete();
|
|
|
|
void testOnFinished_Timeout();
|
|
|
|
};
|
|
|
|
|
2021-02-20 15:16:18 -06:00
|
|
|
void HttpRequestTests::testInvalidURI()
|
|
|
|
{
|
|
|
|
const char* Host = "";
|
|
|
|
const char* URL = "/";
|
|
|
|
|
|
|
|
http::Request httpRequest(URL);
|
|
|
|
|
|
|
|
auto httpSession = http::Session::createHttp(Host);
|
|
|
|
httpSession->setTimeout(std::chrono::seconds(1));
|
|
|
|
LOK_ASSERT(httpSession->syncRequest(httpRequest) == false);
|
|
|
|
|
|
|
|
const std::shared_ptr<const http::Response> httpResponse = httpSession->response();
|
|
|
|
LOK_ASSERT(httpResponse->done() == false);
|
|
|
|
LOK_ASSERT(httpResponse->state() != http::Response::State::Complete);
|
|
|
|
LOK_ASSERT(httpResponse->statusLine().statusCode() != Poco::Net::HTTPResponse::HTTP_OK);
|
2021-03-14 09:06:44 -05:00
|
|
|
LOK_ASSERT_EQUAL(0, httpResponse->statusLine().statusCode());
|
2021-02-20 15:16:18 -06:00
|
|
|
LOK_ASSERT(httpResponse->statusLine().statusCategory()
|
|
|
|
== http::StatusLine::StatusCodeClass::Invalid);
|
|
|
|
LOK_ASSERT(httpResponse->getBody().empty());
|
|
|
|
}
|
|
|
|
|
2021-01-10 08:35:42 -06:00
|
|
|
void HttpRequestTests::testSimpleGet()
|
|
|
|
{
|
|
|
|
const char* Host = "example.com";
|
|
|
|
const char* URL = "/";
|
|
|
|
|
|
|
|
// Start the polling thread.
|
2021-03-14 11:32:14 -05:00
|
|
|
SocketPoll pollThread("HttpAsyncReqPoll");
|
2021-01-10 08:35:42 -06:00
|
|
|
pollThread.startThread();
|
|
|
|
|
|
|
|
http::Request httpRequest(URL);
|
|
|
|
|
|
|
|
static constexpr http::Session::Protocol Protocols[]
|
|
|
|
= { http::Session::Protocol::HttpUnencrypted, http::Session::Protocol::HttpSsl };
|
|
|
|
for (const http::Session::Protocol protocol : Protocols)
|
|
|
|
{
|
|
|
|
if (protocol == http::Session::Protocol::HttpSsl)
|
|
|
|
{
|
2021-01-26 06:02:09 -06:00
|
|
|
#if ENABLE_SSL
|
2021-01-10 08:35:42 -06:00
|
|
|
if (!SslContext::isInitialized())
|
|
|
|
#endif
|
|
|
|
continue; // Skip SSL, it's not enabled.
|
|
|
|
}
|
|
|
|
|
|
|
|
auto httpSession = http::Session::create(Host, protocol);
|
|
|
|
|
|
|
|
std::condition_variable cv;
|
|
|
|
std::mutex mutex;
|
|
|
|
bool timedout = true;
|
|
|
|
httpSession->setFinishedHandler([&](const std::shared_ptr<http::Session>&) {
|
|
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
|
|
timedout = false;
|
|
|
|
cv.notify_all();
|
|
|
|
});
|
|
|
|
|
|
|
|
httpSession->asyncRequest(httpRequest, pollThread);
|
|
|
|
|
|
|
|
// Use Poco to get the same URL in parallel.
|
2021-03-10 10:20:42 -06:00
|
|
|
const bool secure = (protocol == http::Session::Protocol::HttpSsl);
|
|
|
|
const int port = (protocol == http::Session::Protocol::HttpSsl ? 443 : 80);
|
|
|
|
const auto pocoResponse = helpers::pocoGet(secure, Host, port, URL);
|
2021-01-10 08:35:42 -06:00
|
|
|
|
|
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
|
|
cv.wait_for(lock, std::chrono::seconds(1));
|
|
|
|
|
|
|
|
const std::shared_ptr<const http::Response> httpResponse = httpSession->response();
|
|
|
|
|
|
|
|
LOK_ASSERT_EQUAL_MESSAGE("Timed out waiting for the onFinished handler", false, timedout);
|
|
|
|
LOK_ASSERT(httpResponse->state() == http::Response::State::Complete);
|
|
|
|
LOK_ASSERT(!httpResponse->statusLine().httpVersion().empty());
|
|
|
|
LOK_ASSERT(!httpResponse->statusLine().reasonPhrase().empty());
|
2021-03-14 09:06:44 -05:00
|
|
|
LOK_ASSERT_EQUAL(200, httpResponse->statusLine().statusCode());
|
2021-01-10 08:35:42 -06:00
|
|
|
LOK_ASSERT(httpResponse->statusLine().statusCategory()
|
|
|
|
== http::StatusLine::StatusCodeClass::Successful);
|
|
|
|
|
2021-03-14 15:01:46 -05:00
|
|
|
LOK_ASSERT_EQUAL(pocoResponse.second, httpResponse->getBody());
|
2021-01-10 08:35:42 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
pollThread.joinThread();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpRequestTests::testSimpleGetSync()
|
|
|
|
{
|
|
|
|
const char* Host = "www.example.com";
|
|
|
|
const char* URL = "/";
|
2021-03-10 10:20:42 -06:00
|
|
|
const bool secure = false;
|
|
|
|
const int port = 80;
|
2021-01-10 08:35:42 -06:00
|
|
|
|
2021-03-10 10:20:42 -06:00
|
|
|
const auto pocoResponse = helpers::pocoGet(secure, Host, port, URL);
|
2021-01-10 08:35:42 -06:00
|
|
|
|
|
|
|
http::Request httpRequest(URL);
|
|
|
|
|
|
|
|
auto httpSession = http::Session::createHttp(Host);
|
|
|
|
httpSession->setTimeout(std::chrono::seconds(1));
|
|
|
|
LOK_ASSERT(httpSession->syncRequest(httpRequest));
|
|
|
|
LOK_ASSERT(httpSession->syncRequest(httpRequest)); // Second request.
|
|
|
|
|
|
|
|
const std::shared_ptr<const http::Response> httpResponse = httpSession->response();
|
|
|
|
LOK_ASSERT(httpResponse->done());
|
|
|
|
LOK_ASSERT(httpResponse->state() == http::Response::State::Complete);
|
|
|
|
LOK_ASSERT(!httpResponse->statusLine().httpVersion().empty());
|
|
|
|
LOK_ASSERT(!httpResponse->statusLine().reasonPhrase().empty());
|
2021-03-14 09:06:44 -05:00
|
|
|
LOK_ASSERT_EQUAL(200, httpResponse->statusLine().statusCode());
|
2021-01-10 08:35:42 -06:00
|
|
|
LOK_ASSERT(httpResponse->statusLine().statusCategory()
|
|
|
|
== http::StatusLine::StatusCodeClass::Successful);
|
2021-03-14 15:01:46 -05:00
|
|
|
LOK_ASSERT_EQUAL(std::string("HTTP/1.1"), httpResponse->statusLine().httpVersion());
|
|
|
|
LOK_ASSERT_EQUAL(std::string("OK"), httpResponse->statusLine().reasonPhrase());
|
2021-01-10 08:35:42 -06:00
|
|
|
|
2021-03-14 15:01:46 -05:00
|
|
|
LOK_ASSERT_EQUAL(pocoResponse.second, httpResponse->getBody());
|
2021-01-10 08:35:42 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static void compare(const Poco::Net::HTTPResponse& pocoResponse, const std::string& pocoBody,
|
|
|
|
const http::Response& httpResponse)
|
|
|
|
{
|
2021-03-15 22:53:45 -05:00
|
|
|
LOK_ASSERT_EQUAL_MESSAGE("Response state", httpResponse.state(),
|
|
|
|
http::Response::State::Complete);
|
2021-01-10 08:35:42 -06:00
|
|
|
LOK_ASSERT(!httpResponse.statusLine().httpVersion().empty());
|
|
|
|
LOK_ASSERT(!httpResponse.statusLine().reasonPhrase().empty());
|
|
|
|
|
2021-03-15 22:53:45 -05:00
|
|
|
LOK_ASSERT_EQUAL_MESSAGE("Body", pocoBody, httpResponse.getBody());
|
2021-01-10 08:35:42 -06:00
|
|
|
|
2021-03-15 22:53:45 -05:00
|
|
|
LOK_ASSERT_EQUAL_MESSAGE("Status Code", static_cast<int>(pocoResponse.getStatus()),
|
|
|
|
httpResponse.statusLine().statusCode());
|
|
|
|
LOK_ASSERT_EQUAL_MESSAGE("Reason Phrase", pocoResponse.getReason(),
|
|
|
|
httpResponse.statusLine().reasonPhrase());
|
2021-01-10 08:35:42 -06:00
|
|
|
|
2021-03-15 22:53:45 -05:00
|
|
|
LOK_ASSERT_EQUAL_MESSAGE("hasContentLength", pocoResponse.hasContentLength(),
|
|
|
|
httpResponse.header().hasContentLength());
|
2021-01-10 08:35:42 -06:00
|
|
|
if (pocoResponse.hasContentLength())
|
2021-03-15 22:53:45 -05:00
|
|
|
LOK_ASSERT_EQUAL_MESSAGE("ContentLength", pocoResponse.getContentLength(),
|
|
|
|
httpResponse.header().getContentLength());
|
2021-01-10 08:35:42 -06:00
|
|
|
}
|
|
|
|
|
2021-01-29 07:35:03 -06:00
|
|
|
/// This test requests specific *reponse* codes from
|
|
|
|
/// the server to test the handling of all possible
|
|
|
|
/// response status codes.
|
|
|
|
/// It exercises a few hundred requests/responses.
|
2021-01-10 08:35:42 -06:00
|
|
|
void HttpRequestTests::test500GetStatuses()
|
|
|
|
{
|
2021-03-10 10:20:42 -06:00
|
|
|
constexpr auto Host = "httpbin.org";
|
|
|
|
constexpr bool secure = false;
|
|
|
|
constexpr int port = 80;
|
|
|
|
|
2021-01-10 08:35:42 -06:00
|
|
|
// Start the polling thread.
|
2021-03-14 11:32:14 -05:00
|
|
|
SocketPoll pollThread("HttpAsyncReqPoll");
|
2021-01-10 08:35:42 -06:00
|
|
|
pollThread.startThread();
|
|
|
|
|
2021-03-10 10:20:42 -06:00
|
|
|
auto httpSession = http::Session::createHttp(Host);
|
2021-01-10 08:35:42 -06:00
|
|
|
httpSession->setTimeout(std::chrono::seconds(1));
|
|
|
|
|
|
|
|
std::condition_variable cv;
|
|
|
|
std::mutex mutex;
|
|
|
|
bool timedout = true;
|
|
|
|
httpSession->setFinishedHandler([&](const std::shared_ptr<http::Session>&) {
|
|
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
|
|
timedout = false;
|
|
|
|
cv.notify_all();
|
|
|
|
});
|
|
|
|
|
|
|
|
http::StatusLine::StatusCodeClass statusCodeClasses[]
|
|
|
|
= { http::StatusLine::StatusCodeClass::Informational,
|
|
|
|
http::StatusLine::StatusCodeClass::Successful,
|
|
|
|
http::StatusLine::StatusCodeClass::Redirection,
|
|
|
|
http::StatusLine::StatusCodeClass::Client_Error,
|
|
|
|
http::StatusLine::StatusCodeClass::Server_Error };
|
|
|
|
int curStatusCodeClass = -1;
|
2021-01-28 04:34:05 -06:00
|
|
|
for (int statusCode = 100; statusCode < 512; ++statusCode)
|
2021-01-10 08:35:42 -06:00
|
|
|
{
|
|
|
|
const std::string url = "/status/" + std::to_string(statusCode);
|
2021-03-10 10:20:42 -06:00
|
|
|
|
|
|
|
http::Request httpRequest;
|
2021-01-10 08:35:42 -06:00
|
|
|
httpRequest.setUrl(url);
|
|
|
|
|
|
|
|
timedout = true; // Assume we timed out until we prove otherwise.
|
|
|
|
|
|
|
|
httpSession->asyncRequest(httpRequest, pollThread);
|
|
|
|
|
|
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
|
|
cv.wait_for(lock, std::chrono::seconds(1));
|
|
|
|
|
|
|
|
const std::shared_ptr<const http::Response> httpResponse = httpSession->response();
|
|
|
|
LOK_ASSERT(httpResponse->state() == http::Response::State::Complete);
|
|
|
|
LOK_ASSERT(!httpResponse->statusLine().httpVersion().empty());
|
|
|
|
LOK_ASSERT(!httpResponse->statusLine().reasonPhrase().empty());
|
|
|
|
|
|
|
|
if (statusCode % 100 == 0)
|
|
|
|
++curStatusCodeClass;
|
|
|
|
LOK_ASSERT(httpResponse->statusLine().statusCategory()
|
|
|
|
== statusCodeClasses[curStatusCodeClass]);
|
|
|
|
|
2021-03-14 09:06:44 -05:00
|
|
|
LOK_ASSERT_EQUAL(statusCode, httpResponse->statusLine().statusCode());
|
2021-01-10 08:35:42 -06:00
|
|
|
|
|
|
|
if (httpResponse->statusLine().statusCategory()
|
|
|
|
!= http::StatusLine::StatusCodeClass::Informational)
|
|
|
|
{
|
2021-03-10 10:20:42 -06:00
|
|
|
// Get via Poco in parallel.
|
|
|
|
const auto pocoResponse = helpers::pocoGet(secure, Host, port, url);
|
2021-01-10 08:35:42 -06:00
|
|
|
compare(*pocoResponse.first, pocoResponse.second, *httpResponse);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pollThread.joinThread();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpRequestTests::testSimplePost()
|
|
|
|
{
|
|
|
|
const std::string Host = "httpbin.org";
|
|
|
|
const char* URL = "/post";
|
|
|
|
|
|
|
|
// Start the polling thread.
|
2021-03-14 11:32:14 -05:00
|
|
|
SocketPoll pollThread("HttpAsyncReqPoll");
|
2021-01-10 08:35:42 -06:00
|
|
|
pollThread.startThread();
|
|
|
|
|
|
|
|
http::Request httpRequest(URL, http::Request::VERB_POST);
|
|
|
|
|
|
|
|
// Write the test data to file.
|
|
|
|
const char data[] = "abcd-qwerty!!!";
|
|
|
|
const std::string path = FileUtil::getSysTempDirectoryPath() + "/test_http_post";
|
|
|
|
std::ofstream ofs(path, std::ios::binary);
|
|
|
|
ofs.write(data, sizeof(data) - 1); // Don't write the terminating null.
|
|
|
|
ofs.close();
|
|
|
|
|
|
|
|
httpRequest.setBodyFile(path);
|
|
|
|
|
|
|
|
auto httpSession = http::Session::createHttp(Host);
|
|
|
|
httpSession->setTimeout(std::chrono::seconds(1));
|
|
|
|
|
|
|
|
std::condition_variable cv;
|
|
|
|
std::mutex mutex;
|
|
|
|
bool timedout = true;
|
|
|
|
httpSession->setFinishedHandler([&](const std::shared_ptr<http::Session>&) {
|
|
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
|
|
timedout = false;
|
|
|
|
cv.notify_all();
|
|
|
|
});
|
|
|
|
|
|
|
|
httpSession->asyncRequest(httpRequest, pollThread);
|
|
|
|
|
|
|
|
std::unique_lock<std::mutex> lock(mutex);
|
|
|
|
cv.wait_for(lock, std::chrono::seconds(1));
|
|
|
|
|
|
|
|
const std::shared_ptr<const http::Response> httpResponse = httpSession->response();
|
|
|
|
LOK_ASSERT(httpResponse->state() == http::Response::State::Complete);
|
|
|
|
LOK_ASSERT(!httpResponse->statusLine().httpVersion().empty());
|
|
|
|
LOK_ASSERT(!httpResponse->statusLine().reasonPhrase().empty());
|
2021-03-14 09:06:44 -05:00
|
|
|
LOK_ASSERT_EQUAL(200, httpResponse->statusLine().statusCode());
|
2021-01-10 08:35:42 -06:00
|
|
|
LOK_ASSERT(httpResponse->statusLine().statusCategory()
|
|
|
|
== http::StatusLine::StatusCodeClass::Successful);
|
|
|
|
|
|
|
|
const std::string body = httpResponse->getBody();
|
|
|
|
LOK_ASSERT(!body.empty());
|
|
|
|
std::cerr << "[" << body << "]\n";
|
|
|
|
LOK_ASSERT(body.find(data) != std::string::npos);
|
|
|
|
|
|
|
|
pollThread.joinThread();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpRequestTests::testTimeout()
|
|
|
|
{
|
|
|
|
const char* Host = "www.example.com";
|
|
|
|
const char* URL = "/";
|
|
|
|
|
|
|
|
http::Request httpRequest(URL);
|
|
|
|
|
|
|
|
auto httpSession = http::Session::createHttp(Host);
|
|
|
|
|
|
|
|
httpSession->setTimeout(std::chrono::milliseconds(1)); // Very short interval.
|
|
|
|
|
|
|
|
LOK_ASSERT(!httpSession->syncRequest(httpRequest)); // Must fail to complete.
|
|
|
|
|
|
|
|
const std::shared_ptr<const http::Response> httpResponse = httpSession->response();
|
|
|
|
LOK_ASSERT(httpResponse->done());
|
|
|
|
LOK_ASSERT(httpResponse->state() == http::Response::State::Timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpRequestTests::testOnFinished_Complete()
|
|
|
|
{
|
|
|
|
const char* Host = "www.example.com";
|
|
|
|
const char* URL = "/";
|
|
|
|
|
|
|
|
http::Request httpRequest(URL);
|
|
|
|
|
|
|
|
auto httpSession = http::Session::createHttp(Host);
|
|
|
|
|
|
|
|
bool completed = false;
|
|
|
|
httpSession->setFinishedHandler([&](const std::shared_ptr<http::Session>& session) {
|
|
|
|
LOK_ASSERT(session->response()->done());
|
|
|
|
LOK_ASSERT(session->response()->state() == http::Response::State::Complete);
|
|
|
|
completed = true;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
|
|
|
LOK_ASSERT(httpSession->syncRequest(httpRequest));
|
|
|
|
|
|
|
|
const std::shared_ptr<const http::Response> httpResponse = httpSession->response();
|
|
|
|
LOK_ASSERT(completed);
|
|
|
|
LOK_ASSERT(httpResponse->done());
|
|
|
|
LOK_ASSERT(httpResponse->state() == http::Response::State::Complete);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpRequestTests::testOnFinished_Timeout()
|
|
|
|
{
|
|
|
|
const char* Host = "www.example.com";
|
|
|
|
const char* URL = "/";
|
|
|
|
|
|
|
|
http::Request httpRequest(URL);
|
|
|
|
|
|
|
|
auto httpSession = http::Session::createHttp(Host);
|
|
|
|
|
|
|
|
httpSession->setTimeout(std::chrono::milliseconds(1)); // Very short interval.
|
|
|
|
|
|
|
|
bool completed = false;
|
|
|
|
httpSession->setFinishedHandler([&](const std::shared_ptr<http::Session>& session) {
|
|
|
|
LOK_ASSERT(session->response()->done());
|
|
|
|
LOK_ASSERT(session->response()->state() == http::Response::State::Timeout);
|
|
|
|
completed = true;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
|
|
|
LOK_ASSERT(!httpSession->syncRequest(httpRequest));
|
|
|
|
|
|
|
|
const std::shared_ptr<const http::Response> httpResponse = httpSession->response();
|
|
|
|
LOK_ASSERT(completed);
|
|
|
|
LOK_ASSERT(httpResponse->done());
|
|
|
|
LOK_ASSERT(httpResponse->state() == http::Response::State::Timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
CPPUNIT_TEST_SUITE_REGISTRATION(HttpRequestTests);
|
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|