From 81b9dc7be371d6d3e186a92c27f486ef2d74e2ea Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 3 Sep 2021 15:49:09 -0700 Subject: [PATCH 01/47] Added HttpResource skeleton --- vnext/Shared/Modules/HttpModule.cpp | 59 ++++++++++++++++++++++++++++ vnext/Shared/Modules/HttpModule.h | 49 +++++++++++++++++++++++ vnext/Shared/Shared.vcxitems | 4 +- vnext/Shared/Shared.vcxitems.filters | 6 +++ 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 vnext/Shared/Modules/HttpModule.cpp create mode 100644 vnext/Shared/Modules/HttpModule.h diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp new file mode 100644 index 00000000000..770033178e3 --- /dev/null +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" + +#include "HttpModule.h" + +using folly::dynamic; +using std::shared_ptr; +using std::string; +using std::weak_ptr; + +namespace { +constexpr char moduleName[] = "Networking"; +} + +namespace Microsoft::React { + +HttpModule::HttpModule() {} + +#pragma region CxxModule + +string HttpModule::getName() /*override*/ { + return moduleName; +} + +std::map HttpModule::getConstants() { + return {}; +} + +// clang-format off +std::vector HttpModule::getMethods() { + return + { + { + "sendRequest", + [](dynamic args, Callback cb) + { + } + }, + { + "abortRequest", + [](dynamic args) + { + } + }, + { + "clearCookies", + [](dynamic args) + { + } + } + }; +} +// clang-format on + +#pragma endregion CxxModule + +} diff --git a/vnext/Shared/Modules/HttpModule.h b/vnext/Shared/Modules/HttpModule.h new file mode 100644 index 00000000000..14b415b5056 --- /dev/null +++ b/vnext/Shared/Modules/HttpModule.h @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include "IHttpResource.h" + +namespace Microsoft::React { + +/// +/// Realizes NativeModules projection. +/// See src\Libraries\Network\RCTNetworkingWinShared.js +/// +class HttpModule : public facebook::xplat::module::CxxModule { +public: + enum MethodId { + SendRequest = 0, AbortRequest = 1, ClearCookies = 2, LAST = ClearCookies + }; + + HttpModule(); + + #pragma region CxxModule + + /// + /// + /// + std::string getName() override; + + /// + /// + /// + std::map getConstants() override; + + /// + /// + /// + /// See See react-native/Libraries/WebSocket/WebSocket.js + std::vector getMethods() override; + + #pragma endregion CxxModule + + private: + /// + /// Notifies an event to the current React Instance. + /// + void SendEvent(std::string &&eventName, folly::dynamic &¶meters); +}; +} diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index 08ffe489ed4..f8e0951610e 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -29,7 +29,7 @@ true - + true @@ -51,6 +51,7 @@ true + @@ -88,6 +89,7 @@ + diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index dda2f33eda2..ffdf3e27b23 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -130,6 +130,9 @@ Source Files + + Source Files\Modules + @@ -378,6 +381,9 @@ Header Files + + Header Files\Modules + From d33fb9b6047b397a0401091e6df4c6d7321c32b3 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sun, 5 Sep 2021 00:55:23 -0700 Subject: [PATCH 02/47] Import WINRT namespaces --- vnext/Shared/Modules/HttpModule.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 770033178e3..1b4e3c1f742 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -5,6 +5,14 @@ #include "HttpModule.h" +#include + +// Windows API +#include +#include + +using namespace winrt::Windows::Web::Http; + using folly::dynamic; using std::shared_ptr; using std::string; @@ -55,5 +63,4 @@ std::vector HttpModule::getMethods() // clang-format on #pragma endregion CxxModule - } From 9441a36bdc90b768a6c2a9c215ae6765f116bf97 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 2 Feb 2022 01:11:26 -0800 Subject: [PATCH 03/47] Use custom struct instead of folly:dynamic in IHttpResource --- vnext/Desktop.DLL/react-native-win32.x64.def | 2 +- .../HttpResourceIntegrationTests.cpp | 5 +-- vnext/Desktop/HttpResource.cpp | 40 +++++++++++-------- vnext/Desktop/HttpResource.h | 8 ++-- vnext/Shared/IHttpResource.h | 18 +++++++-- 5 files changed, 44 insertions(+), 29 deletions(-) diff --git a/vnext/Desktop.DLL/react-native-win32.x64.def b/vnext/Desktop.DLL/react-native-win32.x64.def index cf4f1b73be0..e0509e4e5d5 100644 --- a/vnext/Desktop.DLL/react-native-win32.x64.def +++ b/vnext/Desktop.DLL/react-native-win32.x64.def @@ -59,7 +59,7 @@ EXPORTS ?GetRuntimeOptionBool@React@Microsoft@@YA?B_NAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z ?GetRuntimeOptionInt@React@Microsoft@@YA?BHAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z ?makeChakraRuntime@JSI@Microsoft@@YA?AV?$unique_ptr@VRuntime@jsi@facebook@@U?$default_delete@VRuntime@jsi@facebook@@@std@@@std@@$$QEAUChakraRuntimeArgs@12@@Z -?Make@IHttpResource@React@Microsoft@@SA?AV?$unique_ptr@UIHttpResource@React@Microsoft@@U?$default_delete@UIHttpResource@React@Microsoft@@@std@@@std@@XZ +?Make@IHttpResource@React@Microsoft@@SA?AV?$shared_ptr@UIHttpResource@React@Microsoft@@@std@@XZ ?CreateTimingModule@react@facebook@@YA?AV?$unique_ptr@VCxxModule@module@xplat@facebook@@U?$default_delete@VCxxModule@module@xplat@facebook@@@std@@@std@@AEBV?$shared_ptr@VMessageQueueThread@react@facebook@@@4@@Z ??0WebSocketModule@React@Microsoft@@QEAA@XZ ??0NetworkingModule@React@Microsoft@@QEAA@XZ diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index 8952be7798c..5d16350afed 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -5,7 +5,6 @@ #include using namespace Microsoft::React; -using namespace folly; using namespace Microsoft::VisualStudio::CppUnitTestFramework; using std::string; @@ -28,7 +27,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { rc->SetOnResponse([&received](const string &message) { received = true; }); rc->SetOnError([&error](const string &message) { error = message; }); - rc->SendRequest("GET", "http://localhost:8081/debugger-ui", {}, dynamic(), "text", false, 1000, [](int64_t) {}); + rc->SendRequest("GET", "http://localhost:8081/debugger-ui", {}, {}, "text", false, 1000, [](int64_t) {}); Assert::IsTrue(sent); Assert::IsTrue(received); @@ -40,7 +39,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { string error; rc->SetOnError([&error](const string &message) { error = message; }); - rc->SendRequest("GET", "http://nonexistinghost", {}, dynamic(), "text", false, 1000, [](int64_t) {}); + rc->SendRequest("GET", "http://nonexistinghost", {}, {}, "text", false, 1000, [](int64_t) {}); Assert::AreEqual(string("No such host is known"), error); } diff --git a/vnext/Desktop/HttpResource.cpp b/vnext/Desktop/HttpResource.cpp index ac687526cf4..ab3a1a77b3e 100644 --- a/vnext/Desktop/HttpResource.cpp +++ b/vnext/Desktop/HttpResource.cpp @@ -12,15 +12,13 @@ using namespace boost::asio::ip; using namespace boost::beast::http; -using folly::dynamic; -using std::make_unique; +using std::shared_ptr; using std::string; using std::unique_ptr; using boostecr = boost::system::error_code const &; namespace Microsoft::React { -namespace Experimental { #pragma region HttpResource members HttpResource::HttpResource() noexcept : m_resolver{m_context}, m_socket{m_context} {} @@ -29,7 +27,7 @@ void HttpResource::SendRequest( const string &method, const string &urlString, const Headers &headers, - dynamic bodyData, + BodyData&& bodyData, const string &responseType, bool useIncrementalUpdates, int64_t timeout, @@ -43,7 +41,7 @@ void HttpResource::SendRequest( // Validate verb. unique_ptr url; try { - url = make_unique(string{urlString}); + url = std::make_unique(string{urlString}); } catch (...) { m_errorHandler("Malformed URL"); return; @@ -60,14 +58,24 @@ void HttpResource::SendRequest( req.set(header.first, header.second); } - if (!bodyData.empty()) { - if (!bodyData["string"].empty()) { - } else if (!bodyData["base64"].empty()) { - } else if (!bodyData["uri"].empty()) { - assert(false); // Not implemented. - } else { - // Empty request - } + //if (!bodyData.empty()) { + // if (!bodyData["string"].empty()) { + // } else if (!bodyData["base64"].empty()) { + // } else if (!bodyData["uri"].empty()) { + // assert(false); // Not implemented. + // } else { + // // Empty request + // } + //} + + if (bodyData.Type == BodyData::Type::String) { + + } else if (bodyData.Type == BodyData::Type::Base64) { + + } else if (bodyData.Type == BodyData::Type::Uri) { + + } else { + } m_context.restart(); @@ -157,12 +165,10 @@ void HttpResource::SetOnError(std::function &&handler #pragma endregion HttpResource members -} // namespace Experimental - #pragma region IHttpResource static members -/*static*/ unique_ptr IHttpResource::Make() noexcept { - return unique_ptr(new Experimental::HttpResource()); +/*static*/ shared_ptr IHttpResource::Make() noexcept { + return nullptr; } #pragma endregion IHttpResource static members diff --git a/vnext/Desktop/HttpResource.h b/vnext/Desktop/HttpResource.h index 49ed1aed222..bed8266d58c 100644 --- a/vnext/Desktop/HttpResource.h +++ b/vnext/Desktop/HttpResource.h @@ -10,9 +10,9 @@ #include #include -namespace Microsoft::React::Experimental { +namespace Microsoft::React { -class HttpResource : public IHttpResource { +class HttpResource : public IHttpResource, public std::enable_shared_from_this { boost::asio::io_context m_context; boost::asio::ip::tcp::resolver m_resolver; boost::asio::ip::tcp::socket m_socket; @@ -35,7 +35,7 @@ class HttpResource : public IHttpResource { const std::string &method, const std::string &url, const Headers &headers, - folly::dynamic bodyData, + BodyData&& bodyData, const std::string &responseType, bool useIncrementalUpdates, std::int64_t timeout, @@ -50,4 +50,4 @@ class HttpResource : public IHttpResource { #pragma endregion }; -} // namespace Microsoft::React::Experimental +} // namespace Microsoft::React diff --git a/vnext/Shared/IHttpResource.h b/vnext/Shared/IHttpResource.h index df2eec48d5a..34ec343f45d 100644 --- a/vnext/Shared/IHttpResource.h +++ b/vnext/Shared/IHttpResource.h @@ -3,14 +3,24 @@ #pragma once -#include +// Standard Libryary +#include +#include +#include +#include namespace Microsoft::React { + struct IHttpResource { - typedef std::map Headers; + typedef std::unordered_map Headers; + + struct BodyData { + enum class Type : size_t { Empty, String, Base64, Uri } Type = Type::Empty; + std::string Data; + }; - static std::unique_ptr Make() noexcept; + static std::shared_ptr Make() noexcept; virtual ~IHttpResource() noexcept {} @@ -18,7 +28,7 @@ struct IHttpResource { const std::string &method, const std::string &url, const Headers &headers, - folly::dynamic bodyData, // ISS:2365799 - Make non-folly. + BodyData&& bodyData, const std::string &responseType, bool useIncrementalUpdates, std::int64_t timeout, From 13f5fa8b855991d8914c27796318576accfce0ed Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 2 Feb 2022 01:27:46 -0800 Subject: [PATCH 04/47] Drop factory method from Beast HTTP resource --- vnext/Desktop/HttpResource.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/vnext/Desktop/HttpResource.cpp b/vnext/Desktop/HttpResource.cpp index ab3a1a77b3e..d528048bd96 100644 --- a/vnext/Desktop/HttpResource.cpp +++ b/vnext/Desktop/HttpResource.cpp @@ -165,12 +165,4 @@ void HttpResource::SetOnError(std::function &&handler #pragma endregion HttpResource members -#pragma region IHttpResource static members - -/*static*/ shared_ptr IHttpResource::Make() noexcept { - return nullptr; -} - -#pragma endregion IHttpResource static members - } // namespace Microsoft::React From 7a9373f45eeb71b39e0db20259ff847604f2a87f Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 2 Feb 2022 02:03:55 -0800 Subject: [PATCH 05/47] Move WinRT conversion utils into header --- vnext/Shared/Utils/WinRTConversions.h | 55 +++++++++++++ vnext/Shared/WinRTWebSocketResource.cpp | 103 ++++++++++++------------ 2 files changed, 107 insertions(+), 51 deletions(-) create mode 100644 vnext/Shared/Utils/WinRTConversions.h diff --git a/vnext/Shared/Utils/WinRTConversions.h b/vnext/Shared/Utils/WinRTConversions.h new file mode 100644 index 00000000000..1e8526836d8 --- /dev/null +++ b/vnext/Shared/Utils/WinRTConversions.h @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include + +// Windows API +#include + +// Standard Library +#include + +namespace Microsoft::React::Utilities { + +/// +/// Implements an awaiter for Mso::DispatchQueue +/// +auto resume_in_queue(const Mso::DispatchQueue &queue) noexcept { + struct awaitable { + awaitable(const Mso::DispatchQueue &queue) noexcept : m_queue{queue} {} + + bool await_ready() const noexcept { + return false; + } + + void await_resume() const noexcept {} + + void await_suspend(std::experimental::coroutine_handle<> resume) noexcept { + m_callback = [context = resume.address()]() noexcept { + std::experimental::coroutine_handle<>::from_address(context)(); + }; + m_queue.Post(std::move(m_callback)); + } + + private: + Mso::DispatchQueue m_queue; + Mso::VoidFunctor m_callback; + }; + + return awaitable{queue}; +} // resume_in_queue + +std::string HResultToString(winrt::hresult_error const &e) { + std::stringstream stream; + stream << "[0x" << std::hex << e.code() << "] " << winrt::to_string(e.message()); + + return stream.str(); +} + +std::string HResultToString(winrt::hresult &&result) { + return HResultToString(winrt::hresult_error(std::move(result), winrt::hresult_error::from_abi)); +} + +}// namespace diff --git a/vnext/Shared/WinRTWebSocketResource.cpp b/vnext/Shared/WinRTWebSocketResource.cpp index 7985e01ed00..138582d2b11 100644 --- a/vnext/Shared/WinRTWebSocketResource.cpp +++ b/vnext/Shared/WinRTWebSocketResource.cpp @@ -5,6 +5,7 @@ #include #include +#include // Windows API #include @@ -43,47 +44,47 @@ using winrt::Windows::Storage::Streams::IDataReader; using winrt::Windows::Storage::Streams::IDataWriter; using winrt::Windows::Storage::Streams::UnicodeEncoding; -namespace { -/// -/// Implements an awaiter for Mso::DispatchQueue -/// -auto resume_in_queue(const Mso::DispatchQueue &queue) noexcept { - struct awaitable { - awaitable(const Mso::DispatchQueue &queue) noexcept : m_queue{queue} {} - - bool await_ready() const noexcept { - return false; - } - - void await_resume() const noexcept {} - - void await_suspend(std::experimental::coroutine_handle<> resume) noexcept { - m_callback = [context = resume.address()]() noexcept { - std::experimental::coroutine_handle<>::from_address(context)(); - }; - m_queue.Post(std::move(m_callback)); - } - - private: - Mso::DispatchQueue m_queue; - Mso::VoidFunctor m_callback; - }; - - return awaitable{queue}; -} // resume_in_queue - -string HResultToString(hresult_error const &e) { - std::stringstream stream; - stream << "[0x" << std::hex << e.code() << "] " << winrt::to_string(e.message()); - - return stream.str(); -} - -string HResultToString(hresult &&result) { - return HResultToString(hresult_error(std::move(result), hresult_error::from_abi)); -} - -} // namespace +//namespace { +///// +///// Implements an awaiter for Mso::DispatchQueue +///// +//auto resume_in_queue(const Mso::DispatchQueue &queue) noexcept { +// struct awaitable { +// awaitable(const Mso::DispatchQueue &queue) noexcept : m_queue{queue} {} +// +// bool await_ready() const noexcept { +// return false; +// } +// +// void await_resume() const noexcept {} +// +// void await_suspend(std::experimental::coroutine_handle<> resume) noexcept { +// m_callback = [context = resume.address()]() noexcept { +// std::experimental::coroutine_handle<>::from_address(context)(); +// }; +// m_queue.Post(std::move(m_callback)); +// } +// +// private: +// Mso::DispatchQueue m_queue; +// Mso::VoidFunctor m_callback; +// }; +// +// return awaitable{queue}; +//} // resume_in_queue +// +//string HResultToString(hresult_error const &e) { +// std::stringstream stream; +// stream << "[0x" << std::hex << e.code() << "] " << winrt::to_string(e.message()); +// +// return stream.str(); +//} +// +//string HResultToString(hresult &&result) { +// return HResultToString(hresult_error(std::move(result), hresult_error::from_abi)); +//} +// +//} // namespace namespace Microsoft::React { @@ -134,12 +135,12 @@ IAsyncAction WinRTWebSocketResource::PerformConnect(Uri &&uri) noexcept { } } else { if (self->m_errorHandler) { - self->m_errorHandler({HResultToString(std::move(result)), ErrorType::Connection}); + self->m_errorHandler({Utilities::HResultToString(std::move(result)), ErrorType::Connection}); } } } catch (hresult_error const &e) { if (self->m_errorHandler) { - self->m_errorHandler({HResultToString(e), ErrorType::Connection}); + self->m_errorHandler({Utilities::HResultToString(e), ErrorType::Connection}); } } @@ -180,12 +181,12 @@ fire_and_forget WinRTWebSocketResource::PerformPing() noexcept { } } else { if (self->m_errorHandler) { - self->m_errorHandler({HResultToString(std::move(result)), ErrorType::Ping}); + self->m_errorHandler({Utilities::HResultToString(std::move(result)), ErrorType::Ping}); } } } catch (hresult_error const &e) { if (self->m_errorHandler) { - self->m_errorHandler({HResultToString(e), ErrorType::Ping}); + self->m_errorHandler({Utilities::HResultToString(e), ErrorType::Ping}); } } } @@ -201,7 +202,7 @@ fire_and_forget WinRTWebSocketResource::PerformWrite(string &&message, bool isBi co_await resume_on_signal(self->m_connectPerformed.get()); // Ensure connection attempt has finished - co_await resume_in_queue(self->m_dispatchQueue); // Ensure writes happen sequentially + co_await Utilities::resume_in_queue(self->m_dispatchQueue); // Ensure writes happen sequentially if (self->m_readyState != ReadyState::Open) { self = nullptr; @@ -246,7 +247,7 @@ fire_and_forget WinRTWebSocketResource::PerformWrite(string &&message, bool isBi } } else { if (self->m_errorHandler) { - self->m_errorHandler({HResultToString(std::move(result)), ErrorType::Send}); + self->m_errorHandler({Utilities::HResultToString(std::move(result)), ErrorType::Send}); } } } catch (std::exception const &e) { @@ -256,7 +257,7 @@ fire_and_forget WinRTWebSocketResource::PerformWrite(string &&message, bool isBi } catch (hresult_error const &e) { // TODO: Remove after fixing unit tests exceptions. if (self->m_errorHandler) { - self->m_errorHandler({HResultToString(e), ErrorType::Ping}); + self->m_errorHandler({Utilities::HResultToString(e), ErrorType::Ping}); } } } @@ -278,7 +279,7 @@ fire_and_forget WinRTWebSocketResource::PerformClose() noexcept { } } catch (hresult_error const &e) { if (m_errorHandler) { - m_errorHandler({HResultToString(e), ErrorType::Close}); + m_errorHandler({Utilities::HResultToString(e), ErrorType::Close}); } } @@ -322,7 +323,7 @@ void WinRTWebSocketResource::Connect(string &&url, const Protocols &protocols, c } } catch (hresult_error const &e) { if (self->m_errorHandler) { - self->m_errorHandler({HResultToString(e), ErrorType::Receive}); + self->m_errorHandler({Utilities::HResultToString(e), ErrorType::Receive}); } } }); @@ -346,7 +347,7 @@ void WinRTWebSocketResource::Connect(string &&url, const Protocols &protocols, c uri = Uri{winrt::to_hstring(url)}; } catch (hresult_error const &e) { if (m_errorHandler) { - m_errorHandler({HResultToString(e), ErrorType::Connection}); + m_errorHandler({Utilities::HResultToString(e), ErrorType::Connection}); } // Abort - Mark connection as concluded. From cc44bf08cf5d685296c1da9f837486adc03e23dd Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 2 Feb 2022 02:05:04 -0800 Subject: [PATCH 06/47] Move WinRT conversion utils into header --- vnext/Shared/WinRTWebSocketResource.cpp | 42 ------------------------- 1 file changed, 42 deletions(-) diff --git a/vnext/Shared/WinRTWebSocketResource.cpp b/vnext/Shared/WinRTWebSocketResource.cpp index 138582d2b11..44f58772330 100644 --- a/vnext/Shared/WinRTWebSocketResource.cpp +++ b/vnext/Shared/WinRTWebSocketResource.cpp @@ -44,48 +44,6 @@ using winrt::Windows::Storage::Streams::IDataReader; using winrt::Windows::Storage::Streams::IDataWriter; using winrt::Windows::Storage::Streams::UnicodeEncoding; -//namespace { -///// -///// Implements an awaiter for Mso::DispatchQueue -///// -//auto resume_in_queue(const Mso::DispatchQueue &queue) noexcept { -// struct awaitable { -// awaitable(const Mso::DispatchQueue &queue) noexcept : m_queue{queue} {} -// -// bool await_ready() const noexcept { -// return false; -// } -// -// void await_resume() const noexcept {} -// -// void await_suspend(std::experimental::coroutine_handle<> resume) noexcept { -// m_callback = [context = resume.address()]() noexcept { -// std::experimental::coroutine_handle<>::from_address(context)(); -// }; -// m_queue.Post(std::move(m_callback)); -// } -// -// private: -// Mso::DispatchQueue m_queue; -// Mso::VoidFunctor m_callback; -// }; -// -// return awaitable{queue}; -//} // resume_in_queue -// -//string HResultToString(hresult_error const &e) { -// std::stringstream stream; -// stream << "[0x" << std::hex << e.code() << "] " << winrt::to_string(e.message()); -// -// return stream.str(); -//} -// -//string HResultToString(hresult &&result) { -// return HResultToString(hresult_error(std::move(result), hresult_error::from_abi)); -//} -// -//} // namespace - namespace Microsoft::React { // private From 1224e8ae1983d844e9106c981f7036b00efb00c4 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 2 Feb 2022 03:41:10 -0800 Subject: [PATCH 07/47] Define class WinRTHttpResource --- .../HttpResourceIntegrationTests.cpp | 4 +- vnext/Desktop/HttpResource.cpp | 8 +- vnext/Desktop/HttpResource.h | 2 +- vnext/Desktop/React.Windows.Desktop.vcxproj | 9 +- .../React.Windows.Desktop.vcxproj.filters | 7 + vnext/Shared/IHttpResource.h | 9 +- vnext/Shared/Modules/HttpModule.cpp | 2 +- vnext/Shared/Modules/HttpModule.h | 14 +- vnext/Shared/Shared.vcxitems | 3 + vnext/Shared/Shared.vcxitems.filters | 9 + vnext/Shared/Utils/WinRTConversions.h | 2 +- vnext/Shared/WinRTHttpResource.cpp | 206 ++++++++++++++++++ vnext/Shared/WinRTHttpResource.h | 42 ++++ 13 files changed, 290 insertions(+), 27 deletions(-) create mode 100644 vnext/Shared/WinRTHttpResource.cpp create mode 100644 vnext/Shared/WinRTHttpResource.h diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index 5d16350afed..8b36eb40960 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -27,7 +27,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { rc->SetOnResponse([&received](const string &message) { received = true; }); rc->SetOnError([&error](const string &message) { error = message; }); - rc->SendRequest("GET", "http://localhost:8081/debugger-ui", {}, {}, "text", false, 1000, [](int64_t) {}); + rc->SendRequest("GET", "http://localhost:8081/debugger-ui", {}, {}, "text", false, 1000, false, [](int64_t) {}); Assert::IsTrue(sent); Assert::IsTrue(received); @@ -39,7 +39,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { string error; rc->SetOnError([&error](const string &message) { error = message; }); - rc->SendRequest("GET", "http://nonexistinghost", {}, {}, "text", false, 1000, [](int64_t) {}); + rc->SendRequest("GET", "http://nonexistinghost", {}, {}, "text", false, 1000, false, [](int64_t) {}); Assert::AreEqual(string("No such host is known"), error); } diff --git a/vnext/Desktop/HttpResource.cpp b/vnext/Desktop/HttpResource.cpp index d528048bd96..03c604b8a05 100644 --- a/vnext/Desktop/HttpResource.cpp +++ b/vnext/Desktop/HttpResource.cpp @@ -27,7 +27,7 @@ void HttpResource::SendRequest( const string &method, const string &urlString, const Headers &headers, - BodyData&& bodyData, + BodyData &&bodyData, const string &responseType, bool useIncrementalUpdates, int64_t timeout, @@ -58,7 +58,7 @@ void HttpResource::SendRequest( req.set(header.first, header.second); } - //if (!bodyData.empty()) { + // if (!bodyData.empty()) { // if (!bodyData["string"].empty()) { // } else if (!bodyData["base64"].empty()) { // } else if (!bodyData["uri"].empty()) { @@ -69,13 +69,9 @@ void HttpResource::SendRequest( //} if (bodyData.Type == BodyData::Type::String) { - } else if (bodyData.Type == BodyData::Type::Base64) { - } else if (bodyData.Type == BodyData::Type::Uri) { - } else { - } m_context.restart(); diff --git a/vnext/Desktop/HttpResource.h b/vnext/Desktop/HttpResource.h index bed8266d58c..9df259c7f87 100644 --- a/vnext/Desktop/HttpResource.h +++ b/vnext/Desktop/HttpResource.h @@ -35,7 +35,7 @@ class HttpResource : public IHttpResource, public std::enable_shared_from_this - + ..\Microsoft.ReactNative\QuirkSettings.idl - @@ -236,7 +235,9 @@ Create - + + true + true @@ -354,4 +355,4 @@ <_CppWinRTOriginalRootNamespace /> - \ No newline at end of file + diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters index e2abe7e0a23..f59900e15ec 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters @@ -90,6 +90,8 @@ ABI + + @@ -158,6 +160,11 @@ + + + + + diff --git a/vnext/Shared/IHttpResource.h b/vnext/Shared/IHttpResource.h index 34ec343f45d..1faeb93e35c 100644 --- a/vnext/Shared/IHttpResource.h +++ b/vnext/Shared/IHttpResource.h @@ -11,12 +11,11 @@ namespace Microsoft::React { - struct IHttpResource { typedef std::unordered_map Headers; struct BodyData { - enum class Type : size_t { Empty, String, Base64, Uri } Type = Type::Empty; + enum class Type : size_t { Empty, String, Base64, Uri, Form } Type = Type::Empty; std::string Data; }; @@ -28,12 +27,14 @@ struct IHttpResource { const std::string &method, const std::string &url, const Headers &headers, - BodyData&& bodyData, + BodyData &&bodyData, const std::string &responseType, bool useIncrementalUpdates, - std::int64_t timeout, + int64_t timeout, + bool withCredentials, std::function &&callback) noexcept = 0; virtual void AbortRequest() noexcept = 0; + virtual void ClearCookies() noexcept = 0; virtual void SetOnRequest(std::function &&handler) noexcept = 0; diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 1b4e3c1f742..d6001a4aecd 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -63,4 +63,4 @@ std::vector HttpModule::getMethods() // clang-format on #pragma endregion CxxModule -} +} // namespace Microsoft::React diff --git a/vnext/Shared/Modules/HttpModule.h b/vnext/Shared/Modules/HttpModule.h index 14b415b5056..bbd5fedcb62 100644 --- a/vnext/Shared/Modules/HttpModule.h +++ b/vnext/Shared/Modules/HttpModule.h @@ -13,14 +13,12 @@ namespace Microsoft::React { /// See src\Libraries\Network\RCTNetworkingWinShared.js /// class HttpModule : public facebook::xplat::module::CxxModule { -public: - enum MethodId { - SendRequest = 0, AbortRequest = 1, ClearCookies = 2, LAST = ClearCookies - }; + public: + enum MethodId { SendRequest = 0, AbortRequest = 1, ClearCookies = 2, LAST = ClearCookies }; HttpModule(); - #pragma region CxxModule +#pragma region CxxModule /// /// @@ -38,12 +36,12 @@ class HttpModule : public facebook::xplat::module::CxxModule { /// See See react-native/Libraries/WebSocket/WebSocket.js std::vector getMethods() override; - #pragma endregion CxxModule +#pragma endregion CxxModule - private: + private: /// /// Notifies an event to the current React Instance. /// void SendEvent(std::string &&eventName, folly::dynamic &¶meters); }; -} +} // namespace Microsoft::React diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index 6a2bd13560c..a163aaa6a33 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -65,6 +65,7 @@ true + @@ -131,8 +132,10 @@ + + diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index 560f47d7309..65fde94695d 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -136,6 +136,9 @@ Source Files\Modules + + Source Files + @@ -384,6 +387,12 @@ Header Files\Modules + + Header Files + + + Header Files\Utils + diff --git a/vnext/Shared/Utils/WinRTConversions.h b/vnext/Shared/Utils/WinRTConversions.h index 1e8526836d8..f2837aaaa9b 100644 --- a/vnext/Shared/Utils/WinRTConversions.h +++ b/vnext/Shared/Utils/WinRTConversions.h @@ -52,4 +52,4 @@ std::string HResultToString(winrt::hresult &&result) { return HResultToString(winrt::hresult_error(std::move(result), winrt::hresult_error::from_abi)); } -}// namespace +} // namespace Microsoft::React::Utilities diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp new file mode 100644 index 00000000000..5ee3119cfb9 --- /dev/null +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "WinRTHttpResource.h" + +//#include +#include +#include + +// Windows API +#include +#include +#include + +using std::function; +using std::shared_ptr; +using std::string; + +using winrt::Windows::Foundation::IAsyncOperationWithProgress; +using winrt::Windows::Foundation::Uri; +using winrt::Windows::Security::Cryptography::CryptographicBuffer; +using winrt::Windows::Storage::Streams::DataReader; +using winrt::Windows::Storage::Streams::UnicodeEncoding; +using winrt::Windows::Storage::StorageFile;// +using winrt::Windows::Web::Http::Headers::HttpMediaTypeHeaderValue; +using winrt::Windows::Web::Http::HttpBufferContent; +using winrt::Windows::Web::Http::HttpClient; +using winrt::Windows::Web::Http::HttpMethod; +using winrt::Windows::Web::Http::HttpProgress; +using winrt::Windows::Web::Http::HttpRequestMessage; +using winrt::Windows::Web::Http::HttpResponseMessage; +using winrt::Windows::Web::Http::HttpStreamContent;// +using winrt::Windows::Web::Http::HttpStringContent; +using winrt::Windows::Web::Http::IHttpContent; +using winrt::fire_and_forget; +using winrt::to_hstring; + +namespace { + +} // namespace + +namespace Microsoft::React { + +#pragma region WinRTHttpResource + + +#pragma region IHttpResource + +void WinRTHttpResource::SendRequest( + const string &method, + const string &url, + const Headers &headers, + BodyData &&bodyData, + const string &responseType, + bool useIncrementalUpdates, + int64_t timeout, + bool withCredentials, + std::function &&callback) noexcept /*override*/ { + // TODO: Mange request ID? + + // Enforce supported args + assert(responseType == "text" || responseType == "base64"); + + // TODO:Callback? + + // TODO: Use exception->hresult_error conversion + try { + HttpMethod httpMethod{to_hstring(method)}; + Uri uri{to_hstring(url)}; + HttpRequestMessage request{httpMethod, uri}; + HttpMediaTypeHeaderValue contentType{nullptr}; + string contentEncoding; + string contentLength; + + // TODO: Check casing + for (auto &header : headers) { + if (header.first == "content-type") { + bool success = HttpMediaTypeHeaderValue::TryParse(to_hstring(header.first), contentType); + // TODO: Do something with result. + } else if (header.first == "content-encoding") { + contentEncoding = header.second; + } else if (header.first == "content-length") { + contentLength = header.second; + } else if (header.first == "authorization") { + bool success = + request.Headers().TryAppendWithoutValidation(to_hstring(header.first), to_hstring(header.second)); + } else { + request.Headers().Append(to_hstring(header.first), to_hstring(header.second)); + } + } + + IHttpContent content{nullptr}; + if (BodyData::Type::String == bodyData.Type) { + content = HttpStringContent{to_hstring(bodyData.Data)}; + } else if (BodyData::Type::Base64 == bodyData.Type) { + auto buffer = CryptographicBuffer::DecodeFromBase64String(to_hstring(bodyData.Data)); + content = HttpBufferContent{buffer}; + } else if (BodyData::Type::Uri == bodyData.Type) { + auto file = StorageFile::GetFileFromApplicationUriAsync(Uri{to_hstring(bodyData.Data)}).get(); // TODO: async?? + auto stream = file.OpenReadAsync().get(); + content = HttpStreamContent{stream}; + } else if (BodyData::Type::Form == bodyData.Type) { + // TODO: Add support + } else { + // BodyData::Type::Empty + } + + if (content != nullptr) { + // TODO: Attach headers to content + request.Content(content); + } + + // TODO: PerformSendRequest + + } catch (...) { // TODO: Delcare specific exception types + // TODO: OnRequestError + } +} + +void WinRTHttpResource::AbortRequest() noexcept /*override*/ {} + +void WinRTHttpResource::ClearCookies() noexcept /*override*/ {} + +void WinRTHttpResource::SetOnRequest(function &&handler) noexcept /*override*/ {} + +void WinRTHttpResource::SetOnResponse(function &&handler) noexcept /*override*/ {} + +void WinRTHttpResource::SetOnError(function &&handler) noexcept /*override*/ { + +} + +#pragma endregion IHttpResource + +fire_and_forget WinRTHttpResource::PerformSendRequest(HttpClient client, HttpRequestMessage request, bool textResponse) noexcept { + //TODO: Set timeout? + + try { + auto async = client.SendRequestAsync(request); + //TODO: Add request? + + co_await lessthrow_await_adapter>{async}; + auto result = async.ErrorCode(); + if (result < 0) { + //TODO: OnRequestError + //TODO: Remove request + co_return; + } + + auto response = async.GetResults(); + if (response) {//TODO: check nullptr? + //TODO: OnResponseReceived + } + + //TODO: Incremental updates? + if (response && response.Content()) {//TODO: check nullptr? + auto inputStream = co_await response.Content().ReadAsInputStreamAsync(); + auto reader = DataReader{inputStream}; + + if (textResponse) { + reader.UnicodeEncoding(UnicodeEncoding::Utf8); + } + + // Only support response sizes up to 10MB. + //TODO: WHY???? + co_await reader.LoadAsync(10 * 1024 * 1024); + auto length = reader.UnconsumedBufferLength(); + + if (textResponse) { + std::vector data(length); + reader.ReadBytes(data); + string responseData = + string(Common::Utilities::CheckedReinterpretCast(data.data()), data.size()); + + //TODO: self->OnDataReceived + } else { + auto buffer = reader.ReadBuffer(length); + auto data = CryptographicBuffer::EncodeToBase64String(buffer); + auto responseData = winrt::to_string(data);//TODO: string view??? + + //TODO: self->OnDataReceived + } + + //TODO: self->OnRequestSuccess + } else { + //TODO: self->OnRequestError + } + } catch (...) { + // TODO: self->OnRequestError + //TODO: Lose generic catch + } + + //TODO: Remove request + co_return;//TODO: keep? +} + +#pragma endregion WinRTHttpResource + +#pragma region IHttpResource + +/*static*/ shared_ptr IHttpResource::Make() noexcept { + return std::make_shared(); +} + +#pragma endregion IHttpResource + +}// namespace diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h new file mode 100644 index 00000000000..8d93bb9bf79 --- /dev/null +++ b/vnext/Shared/WinRTHttpResource.h @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include + +// Windows API +#include +// Standard Library + +namespace Microsoft::React { + +class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_this { + + winrt::fire_and_forget PerformSendRequest(/*TODO: shared self?,*/ + winrt::Windows::Web::Http::HttpClient client, + winrt::Windows::Web::Http::HttpRequestMessage, + bool textResponse + /*, requestId?*/) noexcept; + +public: + void SendRequest( + const std::string &method, + const std::string &url, + const Headers &headers, + BodyData &&bodyData, + const std::string &responseType, + bool useIncrementalUpdates, + int64_t timeout, + bool withCredentials, + std::function &&callback) noexcept override; + void AbortRequest() noexcept override; + + void ClearCookies() noexcept override; + + void SetOnRequest(std::function &&handler) noexcept override; + void SetOnResponse(std::function &&handler) noexcept override; + void SetOnError(std::function &&handler) noexcept override; +}; + +} // namespace From 15ad889ee8d7e23d7b3fe29b0c696d9d96caacf1 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Thu, 3 Feb 2022 18:51:06 -0800 Subject: [PATCH 08/47] Update HttpModule --- vnext/Shared/Modules/HttpModule.cpp | 26 ++++++++++++++++++-------- vnext/Shared/Modules/HttpModule.h | 15 +++++++++++++-- vnext/Shared/WinRTHttpResource.h | 1 + 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index d6001a4aecd..0bda51c23aa 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -5,14 +5,10 @@ #include "HttpModule.h" -#include - -// Windows API -#include -#include - -using namespace winrt::Windows::Web::Http; +// React Native +#include +using facebook::react::Instance; using folly::dynamic; using std::shared_ptr; using std::string; @@ -20,11 +16,25 @@ using std::weak_ptr; namespace { constexpr char moduleName[] = "Networking"; + +//TODO: Add to shared header? (See WebSocketModule) +static void SendEvent(weak_ptr weakInstance, string &&eventName, dynamic &&args) { + if (auto instance = weakInstance.lock()) { + instance->callJSFunction("RCTDeviceEventEmitter", "emit", dynamic::array(std::move(eventName), std::move(args))); + } } +} // namespace + namespace Microsoft::React { -HttpModule::HttpModule() {} +HttpModule::HttpModule() noexcept : m_resource{IHttpResource::Make()}, m_holder{std::make_shared()} { + m_holder->Module = this; +} + +HttpModule::~HttpModule() noexcept /*override*/ { + m_holder->Module = nullptr; +} #pragma region CxxModule diff --git a/vnext/Shared/Modules/HttpModule.h b/vnext/Shared/Modules/HttpModule.h index bbd5fedcb62..dfc0a9aa674 100644 --- a/vnext/Shared/Modules/HttpModule.h +++ b/vnext/Shared/Modules/HttpModule.h @@ -3,9 +3,11 @@ #pragma once -#include #include "IHttpResource.h" +// React Native +#include + namespace Microsoft::React { /// @@ -16,7 +18,9 @@ class HttpModule : public facebook::xplat::module::CxxModule { public: enum MethodId { SendRequest = 0, AbortRequest = 1, ClearCookies = 2, LAST = ClearCookies }; - HttpModule(); + HttpModule() noexcept; + + ~HttpModule() noexcept override; #pragma region CxxModule @@ -39,6 +43,13 @@ class HttpModule : public facebook::xplat::module::CxxModule { #pragma endregion CxxModule private: + struct ModuleHolder { + HttpModule *Module{nullptr}; + }; + + std::shared_ptr m_resource; + std::shared_ptr m_holder; + /// /// Notifies an event to the current React Instance. /// diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index 8d93bb9bf79..a3e95df861e 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -13,6 +13,7 @@ namespace Microsoft::React { class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_this { + //TODO: Make non-trivial args r-value?? winrt::fire_and_forget PerformSendRequest(/*TODO: shared self?,*/ winrt::Windows::Web::Http::HttpClient client, winrt::Windows::Web::Http::HttpRequestMessage, From d9d345efd4c58eae01da8ca918ac51f8a268b3c6 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Thu, 3 Feb 2022 22:33:09 -0800 Subject: [PATCH 09/47] Define IHttpResource::Response --- vnext/Shared/IHttpResource.h | 14 +++-- vnext/Shared/Modules/HttpModule.cpp | 82 ++++++++++++++++++++++++++++- vnext/Shared/Modules/HttpModule.h | 3 +- vnext/Shared/WinRTHttpResource.cpp | 8 +-- vnext/Shared/WinRTHttpResource.h | 8 +-- 5 files changed, 102 insertions(+), 13 deletions(-) diff --git a/vnext/Shared/IHttpResource.h b/vnext/Shared/IHttpResource.h index 1faeb93e35c..1a3f1d2f3dd 100644 --- a/vnext/Shared/IHttpResource.h +++ b/vnext/Shared/IHttpResource.h @@ -19,6 +19,12 @@ struct IHttpResource { std::string Data; }; + struct Response { + int64_t StatusCode; + Headers Headers; + std::string Url; + }; + static std::shared_ptr Make() noexcept; virtual ~IHttpResource() noexcept {} @@ -37,9 +43,11 @@ struct IHttpResource { virtual void ClearCookies() noexcept = 0; - virtual void SetOnRequest(std::function &&handler) noexcept = 0; - virtual void SetOnResponse(std::function &&handler) noexcept = 0; - virtual void SetOnError(std::function &&handler) noexcept = 0; + virtual void SetOnRequest(std::function &&handler) noexcept = 0;//TODO: Keep??? + virtual void SetOnResponse(std::function &&handler) noexcept = 0; + virtual void SetOnData(std::function &&handler) noexcept = 0; + virtual void SetOnError( + std::function &&handler) noexcept = 0; }; } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 0bda51c23aa..081d04cce54 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -7,6 +7,7 @@ // React Native #include +#include using facebook::react::Instance; using folly::dynamic; @@ -15,6 +16,9 @@ using std::string; using std::weak_ptr; namespace { + +using Microsoft::React::IHttpResource; + constexpr char moduleName[] = "Networking"; //TODO: Add to shared header? (See WebSocketModule) @@ -24,11 +28,38 @@ static void SendEvent(weak_ptr weakInstance, string &&eventName, dynam } } +static shared_ptr GetOrInitHttpResource(weak_ptr weakInstance) { + auto resource = IHttpResource::Make(); + + resource->SetOnResponse([weakInstance](int64_t requestId, IHttpResource::Response &&response) { + dynamic headers = dynamic::object(); + for (auto &header : response.Headers) { + headers[header.first] = header.second; + } + + // TODO: Test response content. + dynamic args = dynamic::array( + requestId, + response.StatusCode, + headers, + response.Url + ); + + SendEvent(weakInstance, "didReceiveNetworkResponse", std::move(args)); + }); + + resource->SetOnData([weakInstance](int64_t requestId, std::string &&responseData) { + + }); + + return resource; +} + } // namespace namespace Microsoft::React { -HttpModule::HttpModule() noexcept : m_resource{IHttpResource::Make()}, m_holder{std::make_shared()} { +HttpModule::HttpModule() noexcept : m_holder{std::make_shared()} { m_holder->Module = this; } @@ -48,12 +79,59 @@ std::map HttpModule::getConstants() { // clang-format off std::vector HttpModule::getMethods() { + + auto weakHolder = weak_ptr(m_holder); + auto holder = weakHolder.lock(); + auto weakInstance = weak_ptr(holder->Module->getInstance()); + return { { "sendRequest", - [](dynamic args, Callback cb) + [weakResource = weak_ptr(m_resource)](dynamic args, Callback cb)//TODO: Check whether 'cb' is needed { + auto params = facebook::xplat::jsArgAsObject(args, 0); + auto data = params["data"]; + IHttpResource::BodyData bodyData; + if (auto resource = weakResource.lock()) + { + auto stringData = data["string"]; + if (!stringData.empty()) + { + bodyData = {IHttpResource::BodyData::Type::String, stringData.getString()}; + } + else + { + auto base64Data = data["base64"]; + if (!base64Data.empty()) + { + bodyData = {IHttpResource::BodyData::Type::Base64, base64Data.getString()}; + } + else + { + auto uriData = data["uri"]; + if (!uriData.empty()) + { + bodyData = {IHttpResource::BodyData::Type::Uri, uriData.getString()}; + } + } + } + //TODO: Support FORM data + + //TODO: Extract headers + + resource->SendRequest( + params["method"].asString(), + params["url"].asString(), + {},//headers + std::move(bodyData), + params["responseType"].asString(), + params["incrementalUpdates"].asBool(), + static_cast(params["timeout"].asDouble()), + false,//withCredentials, + {}// callback + ); + } // If resource available } }, { diff --git a/vnext/Shared/Modules/HttpModule.h b/vnext/Shared/Modules/HttpModule.h index dfc0a9aa674..7fc5f41fc3f 100644 --- a/vnext/Shared/Modules/HttpModule.h +++ b/vnext/Shared/Modules/HttpModule.h @@ -50,9 +50,10 @@ class HttpModule : public facebook::xplat::module::CxxModule { std::shared_ptr m_resource; std::shared_ptr m_holder; + //TODO: Decide whether to keep /// /// Notifies an event to the current React Instance. /// - void SendEvent(std::string &&eventName, folly::dynamic &¶meters); + void SendEvent0(std::string &&eventName, folly::dynamic &¶meters); }; } // namespace Microsoft::React diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 5ee3119cfb9..79bc4ca3213 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -121,13 +121,13 @@ void WinRTHttpResource::AbortRequest() noexcept /*override*/ {} void WinRTHttpResource::ClearCookies() noexcept /*override*/ {} -void WinRTHttpResource::SetOnRequest(function &&handler) noexcept /*override*/ {} +void WinRTHttpResource::SetOnRequest(function &&handler) noexcept /*override*/ {} -void WinRTHttpResource::SetOnResponse(function &&handler) noexcept /*override*/ {} +void WinRTHttpResource::SetOnResponse(function &&handler) noexcept /*override*/ {} -void WinRTHttpResource::SetOnError(function &&handler) noexcept /*override*/ { +void WinRTHttpResource::SetOnData(function &&handler) noexcept /*override*/ {} -} +void WinRTHttpResource::SetOnError(function &&handler) noexcept /*override*/ {} #pragma endregion IHttpResource diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index a3e95df861e..85c1ac197ef 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -35,9 +35,11 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t void ClearCookies() noexcept override; - void SetOnRequest(std::function &&handler) noexcept override; - void SetOnResponse(std::function &&handler) noexcept override; - void SetOnError(std::function &&handler) noexcept override; + void SetOnRequest(std::function &&handler) noexcept override; + void SetOnResponse(std::function &&handler) noexcept override; + void SetOnData(std::function &&handler) noexcept override; + void SetOnError( + std::function &&handler) noexcept override; }; } // namespace From 0c4624f05d28e97061e0cc5d393aad5ffb64e837 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 4 Feb 2022 00:48:53 -0800 Subject: [PATCH 10/47] Implement callback members --- .../HttpResourceIntegrationTests.cpp | 8 ++-- vnext/Shared/IHttpResource.h | 2 +- vnext/Shared/Modules/HttpModule.cpp | 15 ++++++- vnext/Shared/WinRTHttpResource.cpp | 39 +++++++++++++++---- vnext/Shared/WinRTHttpResource.h | 8 +++- 5 files changed, 56 insertions(+), 16 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index 8b36eb40960..c00ce2e7e83 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -23,9 +23,9 @@ TEST_CLASS (HttpResourceIntegrationTest) { bool sent = false; bool received = false; string error; - rc->SetOnRequest([&sent]() { sent = true; }); - rc->SetOnResponse([&received](const string &message) { received = true; }); - rc->SetOnError([&error](const string &message) { error = message; }); + //rc->SetOnRequest([&sent]() { sent = true; }); + rc->SetOnResponse([&received](int64_t, IHttpResource::Response&&) { received = true; }); + rc->SetOnError([&error](int64_t, string&& message) { error = message; }); rc->SendRequest("GET", "http://localhost:8081/debugger-ui", {}, {}, "text", false, 1000, false, [](int64_t) {}); @@ -37,7 +37,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { TEST_METHOD(RequestGetFails) { auto rc = IHttpResource::Make(); string error; - rc->SetOnError([&error](const string &message) { error = message; }); + rc->SetOnError([&error](int64_t, string &&message) { error = message; }); rc->SendRequest("GET", "http://nonexistinghost", {}, {}, "text", false, 1000, false, [](int64_t) {}); diff --git a/vnext/Shared/IHttpResource.h b/vnext/Shared/IHttpResource.h index 1a3f1d2f3dd..71b5c6ce6fe 100644 --- a/vnext/Shared/IHttpResource.h +++ b/vnext/Shared/IHttpResource.h @@ -47,7 +47,7 @@ struct IHttpResource { virtual void SetOnResponse(std::function &&handler) noexcept = 0; virtual void SetOnData(std::function &&handler) noexcept = 0; virtual void SetOnError( - std::function &&handler) noexcept = 0; + std::function &&handler) noexcept = 0; }; } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 081d04cce54..dec342d79f6 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -48,8 +48,21 @@ static shared_ptr GetOrInitHttpResource(weak_ptr weakIn SendEvent(weakInstance, "didReceiveNetworkResponse", std::move(args)); }); - resource->SetOnData([weakInstance](int64_t requestId, std::string &&responseData) { + resource->SetOnData( + [weakInstance](int64_t requestId, std::string &&responseData) { + dynamic args = dynamic::array(requestId, std::move(responseData)); + SendEvent(weakInstance, "didReceiveNetworkData", std::move(args)); + + //TODO: Move into separate method IF not executed right after onData() + SendEvent(weakInstance, "didCompleteNetworkResponse", dynamic::array(requestId)); + }); + + resource->SetOnError([weakInstance](int64_t requestId, string &&message) { + dynamic args = dynamic::array(requestId, std::move(message)); + //TODO: isTimeout errorArgs.push_back(true); + + SendEvent(weakInstance, "didCompleteNetworkResponse", std::move(args)); }); return resource; diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 79bc4ca3213..7381041ba6c 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -121,17 +121,26 @@ void WinRTHttpResource::AbortRequest() noexcept /*override*/ {} void WinRTHttpResource::ClearCookies() noexcept /*override*/ {} -void WinRTHttpResource::SetOnRequest(function &&handler) noexcept /*override*/ {} +void WinRTHttpResource::SetOnRequest(function &&handler) noexcept /*override*/ { + m_onRequest = std::move(handler); +} -void WinRTHttpResource::SetOnResponse(function &&handler) noexcept /*override*/ {} +void WinRTHttpResource::SetOnResponse(function &&handler) noexcept /*override*/ { + m_onResponse = std::move(handler); +} -void WinRTHttpResource::SetOnData(function &&handler) noexcept /*override*/ {} +void WinRTHttpResource::SetOnData(function &&handler) noexcept /*override*/ { + m_onData = std::move(handler); +} -void WinRTHttpResource::SetOnError(function &&handler) noexcept /*override*/ {} +void WinRTHttpResource::SetOnError(function &&handler) noexcept /*override*/ { + m_onError = std::move(handler); +} #pragma endregion IHttpResource fire_and_forget WinRTHttpResource::PerformSendRequest(HttpClient client, HttpRequestMessage request, bool textResponse) noexcept { + auto self = shared_from_this(); //TODO: Set timeout? try { @@ -149,6 +158,14 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpClient client, HttpReq auto response = async.GetResults(); if (response) {//TODO: check nullptr? //TODO: OnResponseReceived + if (self->m_onResponse) { + Headers headers; + for (auto header : response.Headers()) { + headers.emplace(winrt::to_string(header.Key()), winrt::to_string(header.Value())); + } + string url = winrt::to_string(response.RequestMessage().RequestUri().AbsoluteUri()); + self->m_onResponse(0 /*requestId*/, {static_cast(response.StatusCode()), std::move(headers), std::move(url)}); + } } //TODO: Incremental updates? @@ -171,18 +188,24 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpClient client, HttpReq string responseData = string(Common::Utilities::CheckedReinterpretCast(data.data()), data.size()); - //TODO: self->OnDataReceived + if (self->m_onData) { + self->m_onData(0 /*requestId*/, std::move(responseData)); + } } else { auto buffer = reader.ReadBuffer(length); auto data = CryptographicBuffer::EncodeToBase64String(buffer); auto responseData = winrt::to_string(data);//TODO: string view??? - //TODO: self->OnDataReceived + if (self->m_onData) { + self->m_onData(0 /*requestId*/, std::move(responseData)); + } } - //TODO: self->OnRequestSuccess + //TODO: self->OnRequestSuccess OR, do request success inside OnData. } else { - //TODO: self->OnRequestError + if (self->m_onError) { + self->m_onError(0 /*requestId*/, "Unhandled exception during request" /*, isTimeout*/); + } } } catch (...) { // TODO: self->OnRequestError diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index 85c1ac197ef..3a1072dbfde 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -13,6 +13,11 @@ namespace Microsoft::React { class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_this { + std::function m_onRequest; + std::function m_onResponse; + std::function m_onData; + std::function m_onError; + //TODO: Make non-trivial args r-value?? winrt::fire_and_forget PerformSendRequest(/*TODO: shared self?,*/ winrt::Windows::Web::Http::HttpClient client, @@ -32,14 +37,13 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t bool withCredentials, std::function &&callback) noexcept override; void AbortRequest() noexcept override; - void ClearCookies() noexcept override; void SetOnRequest(std::function &&handler) noexcept override; void SetOnResponse(std::function &&handler) noexcept override; void SetOnData(std::function &&handler) noexcept override; void SetOnError( - std::function &&handler) noexcept override; + std::function &&handler) noexcept override; }; } // namespace From 305c2c67135ad6aebd87478a7f23990189490880 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 5 Feb 2022 02:16:40 -0800 Subject: [PATCH 11/47] Implement AbortRequest --- vnext/Shared/IHttpResource.h | 2 +- vnext/Shared/WinRTHttpResource.cpp | 42 +++++++++++++++++++++++++++--- vnext/Shared/WinRTHttpResource.h | 20 +++++++++++++- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/vnext/Shared/IHttpResource.h b/vnext/Shared/IHttpResource.h index 71b5c6ce6fe..abdece102e7 100644 --- a/vnext/Shared/IHttpResource.h +++ b/vnext/Shared/IHttpResource.h @@ -39,7 +39,7 @@ struct IHttpResource { int64_t timeout, bool withCredentials, std::function &&callback) noexcept = 0; - virtual void AbortRequest() noexcept = 0; + virtual void AbortRequest(int64_t requestId) noexcept = 0; virtual void ClearCookies() noexcept = 0; diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 7381041ba6c..99cc5401484 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -13,6 +13,7 @@ #include using std::function; +using std::scoped_lock; using std::shared_ptr; using std::string; @@ -21,7 +22,7 @@ using winrt::Windows::Foundation::Uri; using winrt::Windows::Security::Cryptography::CryptographicBuffer; using winrt::Windows::Storage::Streams::DataReader; using winrt::Windows::Storage::Streams::UnicodeEncoding; -using winrt::Windows::Storage::StorageFile;// +using winrt::Windows::Storage::StorageFile; using winrt::Windows::Web::Http::Headers::HttpMediaTypeHeaderValue; using winrt::Windows::Web::Http::HttpBufferContent; using winrt::Windows::Web::Http::HttpClient; @@ -29,10 +30,11 @@ using winrt::Windows::Web::Http::HttpMethod; using winrt::Windows::Web::Http::HttpProgress; using winrt::Windows::Web::Http::HttpRequestMessage; using winrt::Windows::Web::Http::HttpResponseMessage; -using winrt::Windows::Web::Http::HttpStreamContent;// +using winrt::Windows::Web::Http::HttpStreamContent; using winrt::Windows::Web::Http::HttpStringContent; using winrt::Windows::Web::Http::IHttpContent; using winrt::fire_and_forget; +using winrt::hresult_error; using winrt::to_hstring; namespace { @@ -117,9 +119,29 @@ void WinRTHttpResource::SendRequest( } } -void WinRTHttpResource::AbortRequest() noexcept /*override*/ {} +void WinRTHttpResource::AbortRequest(int64_t requestId) noexcept /*override*/ { + IAsyncOperationWithProgress request{nullptr}; -void WinRTHttpResource::ClearCookies() noexcept /*override*/ {} + { + scoped_lock lock{m_mutex}; + auto iter = m_requests.find(requestId); + if (iter == std::end(m_requests)) { + return; + } + request = iter->second; + } + + try { + request.Cancel(); + } catch (hresult_error const &) { + //TODO: Propagate error? + } +} + +void WinRTHttpResource::ClearCookies() noexcept /*override*/ { + assert(false); + //NOT IMPLEMENTED +} void WinRTHttpResource::SetOnRequest(function &&handler) noexcept /*override*/ { m_onRequest = std::move(handler); @@ -139,6 +161,18 @@ void WinRTHttpResource::SetOnError(function response) noexcept { + scoped_lock lock{m_mutex}; + m_requests[requestId] = response; +} + +void WinRTHttpResource::RemoveRequest(int64_t requestId) noexcept { + scoped_lock lock{m_mutex}; + m_requests.erase(requestId); +} + fire_and_forget WinRTHttpResource::PerformSendRequest(HttpClient client, HttpRequestMessage request, bool textResponse) noexcept { auto self = shared_from_this(); //TODO: Set timeout? diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index 3a1072dbfde..2155f9a91c9 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -7,17 +7,35 @@ // Windows API #include + // Standard Library +#include namespace Microsoft::React { class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_this { + std::mutex m_mutex; + std::unordered_map< + int64_t, + winrt::Windows::Foundation::IAsyncOperationWithProgress< + winrt::Windows::Web::Http::HttpResponseMessage, + winrt::Windows::Web::Http::HttpProgress>> + m_requests; + std::function m_onRequest; std::function m_onResponse; std::function m_onData; std::function m_onError; + void AddRequest( + int64_t requestId, + winrt::Windows::Foundation::IAsyncOperationWithProgress< + winrt::Windows::Web::Http::HttpResponseMessage, + winrt::Windows::Web::Http::HttpProgress> response) noexcept; + + void RemoveRequest(int64_t requestId) noexcept; + //TODO: Make non-trivial args r-value?? winrt::fire_and_forget PerformSendRequest(/*TODO: shared self?,*/ winrt::Windows::Web::Http::HttpClient client, @@ -36,7 +54,7 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t int64_t timeout, bool withCredentials, std::function &&callback) noexcept override; - void AbortRequest() noexcept override; + void AbortRequest(int64_t requestId) noexcept override; void ClearCookies() noexcept override; void SetOnRequest(std::function &&handler) noexcept override; From 4eef0bbc7fb4bfcf730f04c7b9c45e8b2cdf007f Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 5 Feb 2022 03:11:31 -0800 Subject: [PATCH 12/47] Implement multiple request handling --- vnext/Shared/WinRTHttpResource.cpp | 83 +++++++++++++++++++----------- vnext/Shared/WinRTHttpResource.h | 22 ++++---- 2 files changed, 65 insertions(+), 40 deletions(-) diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 99cc5401484..5aef033162a 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -3,8 +3,8 @@ #include "WinRTHttpResource.h" -//#include #include +#include #include // Windows API @@ -17,7 +17,6 @@ using std::scoped_lock; using std::shared_ptr; using std::string; -using winrt::Windows::Foundation::IAsyncOperationWithProgress; using winrt::Windows::Foundation::Uri; using winrt::Windows::Security::Cryptography::CryptographicBuffer; using winrt::Windows::Storage::Streams::DataReader; @@ -27,15 +26,14 @@ using winrt::Windows::Web::Http::Headers::HttpMediaTypeHeaderValue; using winrt::Windows::Web::Http::HttpBufferContent; using winrt::Windows::Web::Http::HttpClient; using winrt::Windows::Web::Http::HttpMethod; -using winrt::Windows::Web::Http::HttpProgress; using winrt::Windows::Web::Http::HttpRequestMessage; -using winrt::Windows::Web::Http::HttpResponseMessage; using winrt::Windows::Web::Http::HttpStreamContent; using winrt::Windows::Web::Http::HttpStringContent; using winrt::Windows::Web::Http::IHttpContent; using winrt::fire_and_forget; using winrt::hresult_error; using winrt::to_hstring; +using winrt::to_string; namespace { @@ -45,6 +43,8 @@ namespace Microsoft::React { #pragma region WinRTHttpResource +//TODO: Multi-thread issues? +/*static*/ int64_t WinRTHttpResource::s_lastRequestId = 0; #pragma region IHttpResource @@ -58,7 +58,8 @@ void WinRTHttpResource::SendRequest( int64_t timeout, bool withCredentials, std::function &&callback) noexcept /*override*/ { - // TODO: Mange request ID? + + auto requestId = ++s_lastRequestId; // Enforce supported args assert(responseType == "text" || responseType == "base64"); @@ -78,7 +79,10 @@ void WinRTHttpResource::SendRequest( for (auto &header : headers) { if (header.first == "content-type") { bool success = HttpMediaTypeHeaderValue::TryParse(to_hstring(header.first), contentType); - // TODO: Do something with result. + if (m_onError) { + m_onError(requestId, "Failed to parse Content-Type"); + } + return; } else if (header.first == "content-encoding") { contentEncoding = header.second; } else if (header.first == "content-length") { @@ -86,6 +90,10 @@ void WinRTHttpResource::SendRequest( } else if (header.first == "authorization") { bool success = request.Headers().TryAppendWithoutValidation(to_hstring(header.first), to_hstring(header.second)); + if (m_onError) { + m_onError(requestId, "Failed to append Authorization"); + } + return; } else { request.Headers().Append(to_hstring(header.first), to_hstring(header.second)); } @@ -105,22 +113,38 @@ void WinRTHttpResource::SendRequest( // TODO: Add support } else { // BodyData::Type::Empty + //TODO: Error 'cause unsupported?? } if (content != nullptr) { - // TODO: Attach headers to content + // Attach content headers + if (contentType) { + content.Headers().ContentType(contentType); + } + if (!contentEncoding.empty()) { + if (!content.Headers().ContentEncoding().TryParseAdd(to_hstring(contentEncoding))) { + if (m_onError) { + m_onError(requestId, "Failed to parse Content-Encoding"); + } + return; + } + } + if (!contentLength.empty()) { + const auto contentLengthHeader = _atoi64(contentLength.c_str());//TODO: Check error? + content.Headers().ContentLength(contentLengthHeader); + } + request.Content(content); } - // TODO: PerformSendRequest - + PerformSendRequest(requestId, m_client, request, responseType == "text"); } catch (...) { // TODO: Delcare specific exception types // TODO: OnRequestError } } void WinRTHttpResource::AbortRequest(int64_t requestId) noexcept /*override*/ { - IAsyncOperationWithProgress request{nullptr}; + ResponseType request{nullptr}; { scoped_lock lock{m_mutex}; @@ -161,9 +185,7 @@ void WinRTHttpResource::SetOnError(function response) noexcept { +void WinRTHttpResource::AddRequest(int64_t requestId, ResponseType response) noexcept { scoped_lock lock{m_mutex}; m_requests[requestId] = response; } @@ -173,31 +195,32 @@ void WinRTHttpResource::RemoveRequest(int64_t requestId) noexcept { m_requests.erase(requestId); } -fire_and_forget WinRTHttpResource::PerformSendRequest(HttpClient client, HttpRequestMessage request, bool textResponse) noexcept { +fire_and_forget WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpClient client, HttpRequestMessage request, bool textResponse) noexcept { auto self = shared_from_this(); //TODO: Set timeout? try { - auto async = client.SendRequestAsync(request); - //TODO: Add request? + auto sendRequestOp = client.SendRequestAsync(request); + self->AddRequest(requestId, sendRequestOp); - co_await lessthrow_await_adapter>{async}; - auto result = async.ErrorCode(); + co_await lessthrow_await_adapter{sendRequestOp}; + auto result = sendRequestOp.ErrorCode(); if (result < 0) { - //TODO: OnRequestError - //TODO: Remove request + if (self->m_onError) { + self->m_onError(requestId, Utilities::HResultToString(std::move(result))); + } + self->RemoveRequest(requestId); co_return; } - auto response = async.GetResults(); + auto response = sendRequestOp.GetResults(); if (response) {//TODO: check nullptr? - //TODO: OnResponseReceived if (self->m_onResponse) { Headers headers; for (auto header : response.Headers()) { - headers.emplace(winrt::to_string(header.Key()), winrt::to_string(header.Value())); + headers.emplace(to_string(header.Key()), to_string(header.Value())); } - string url = winrt::to_string(response.RequestMessage().RequestUri().AbsoluteUri()); + string url = to_string(response.RequestMessage().RequestUri().AbsoluteUri()); self->m_onResponse(0 /*requestId*/, {static_cast(response.StatusCode()), std::move(headers), std::move(url)}); } } @@ -228,25 +251,27 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(HttpClient client, HttpReq } else { auto buffer = reader.ReadBuffer(length); auto data = CryptographicBuffer::EncodeToBase64String(buffer); - auto responseData = winrt::to_string(data);//TODO: string view??? + auto responseData = to_string(data);//TODO: string view??? if (self->m_onData) { self->m_onData(0 /*requestId*/, std::move(responseData)); } } - //TODO: self->OnRequestSuccess OR, do request success inside OnData. + //TODO: self->OnRequestSuccess OR, keep in self->OnData. } else { if (self->m_onError) { - self->m_onError(0 /*requestId*/, "Unhandled exception during request" /*, isTimeout*/); + self->m_onError(requestId, response == nullptr ? "request failed" : "No response content"); } } } catch (...) { - // TODO: self->OnRequestError //TODO: Lose generic catch + if (self->m_onError) { + self->m_onError(requestId, "Unhandled exception during request"); + } } - //TODO: Remove request + self->RemoveRequest(requestId); co_return;//TODO: keep? } diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index 2155f9a91c9..d4c44ed1d5e 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -15,29 +15,29 @@ namespace Microsoft::React { class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_this { - std::mutex m_mutex; - std::unordered_map< - int64_t, - winrt::Windows::Foundation::IAsyncOperationWithProgress< + typedef winrt::Windows::Foundation::IAsyncOperationWithProgress< winrt::Windows::Web::Http::HttpResponseMessage, - winrt::Windows::Web::Http::HttpProgress>> - m_requests; + winrt::Windows::Web::Http::HttpProgress> + ResponseType; + + static int64_t s_lastRequestId; + + winrt::Windows::Web::Http::HttpClient m_client; + std::mutex m_mutex; + std::unordered_map m_requests; std::function m_onRequest; std::function m_onResponse; std::function m_onData; std::function m_onError; - void AddRequest( - int64_t requestId, - winrt::Windows::Foundation::IAsyncOperationWithProgress< - winrt::Windows::Web::Http::HttpResponseMessage, - winrt::Windows::Web::Http::HttpProgress> response) noexcept; + void AddRequest(int64_t requestId, ResponseType response) noexcept; void RemoveRequest(int64_t requestId) noexcept; //TODO: Make non-trivial args r-value?? winrt::fire_and_forget PerformSendRequest(/*TODO: shared self?,*/ + int64_t requestId, winrt::Windows::Web::Http::HttpClient client, winrt::Windows::Web::Http::HttpRequestMessage, bool textResponse From 4e99d9d46d48d2f5bcbe308f26cf78372b047e90 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 5 Feb 2022 03:29:35 -0800 Subject: [PATCH 13/47] Drop client argument from PerformSendRequest --- vnext/Shared/WinRTHttpResource.cpp | 10 +++++++--- vnext/Shared/WinRTHttpResource.h | 13 ++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 5aef033162a..05682fb9eba 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -24,11 +24,11 @@ using winrt::Windows::Storage::Streams::UnicodeEncoding; using winrt::Windows::Storage::StorageFile; using winrt::Windows::Web::Http::Headers::HttpMediaTypeHeaderValue; using winrt::Windows::Web::Http::HttpBufferContent; -using winrt::Windows::Web::Http::HttpClient; using winrt::Windows::Web::Http::HttpMethod; using winrt::Windows::Web::Http::HttpRequestMessage; using winrt::Windows::Web::Http::HttpStreamContent; using winrt::Windows::Web::Http::HttpStringContent; +using winrt::Windows::Web::Http::IHttpClient; using winrt::Windows::Web::Http::IHttpContent; using winrt::fire_and_forget; using winrt::hresult_error; @@ -46,6 +46,10 @@ namespace Microsoft::React { //TODO: Multi-thread issues? /*static*/ int64_t WinRTHttpResource::s_lastRequestId = 0; +WinRTHttpResource::WinRTHttpResource(IHttpClient client) noexcept : m_client{client} {} + +WinRTHttpResource::WinRTHttpResource() noexcept : WinRTHttpResource(winrt::Windows::Web::Http::HttpClient()) {} + #pragma region IHttpResource void WinRTHttpResource::SendRequest( @@ -195,12 +199,12 @@ void WinRTHttpResource::RemoveRequest(int64_t requestId) noexcept { m_requests.erase(requestId); } -fire_and_forget WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpClient client, HttpRequestMessage request, bool textResponse) noexcept { +fire_and_forget WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage request, bool textResponse) noexcept { auto self = shared_from_this(); //TODO: Set timeout? try { - auto sendRequestOp = client.SendRequestAsync(request); + auto sendRequestOp = self->m_client.SendRequestAsync(request); self->AddRequest(requestId, sendRequestOp); co_await lessthrow_await_adapter{sendRequestOp}; diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index d4c44ed1d5e..4ed27355b8c 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -22,7 +22,7 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t static int64_t s_lastRequestId; - winrt::Windows::Web::Http::HttpClient m_client; + winrt::Windows::Web::Http::IHttpClient m_client; std::mutex m_mutex; std::unordered_map m_requests; @@ -38,12 +38,17 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t //TODO: Make non-trivial args r-value?? winrt::fire_and_forget PerformSendRequest(/*TODO: shared self?,*/ int64_t requestId, - winrt::Windows::Web::Http::HttpClient client, - winrt::Windows::Web::Http::HttpRequestMessage, + winrt::Windows::Web::Http::HttpRequestMessage request, bool textResponse /*, requestId?*/) noexcept; public: + WinRTHttpResource() noexcept; + + WinRTHttpResource(winrt::Windows::Web::Http::IHttpClient client) noexcept; + +#pragma region IHttpResource + void SendRequest( const std::string &method, const std::string &url, @@ -57,6 +62,8 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t void AbortRequest(int64_t requestId) noexcept override; void ClearCookies() noexcept override; +#pragma endregion IHttpResource + void SetOnRequest(std::function &&handler) noexcept override; void SetOnResponse(std::function &&handler) noexcept override; void SetOnData(std::function &&handler) noexcept override; From df51129be86f1eeabee47c214ef24285ff85682a Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 5 Feb 2022 03:32:07 -0800 Subject: [PATCH 14/47] Use background thread in PerformSendRequest --- vnext/Shared/WinRTHttpResource.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 05682fb9eba..0bdfea7b236 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -141,7 +141,7 @@ void WinRTHttpResource::SendRequest( request.Content(content); } - PerformSendRequest(requestId, m_client, request, responseType == "text"); + PerformSendRequest(requestId, request, responseType == "text"); } catch (...) { // TODO: Delcare specific exception types // TODO: OnRequestError } @@ -203,6 +203,9 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpReq auto self = shared_from_this(); //TODO: Set timeout? + // Ensure background thread + co_await winrt::resume_background(); + try { auto sendRequestOp = self->m_client.SendRequestAsync(request); self->AddRequest(requestId, sendRequestOp); From 9005153e5e5d0015df465cc765dc8bab8d49dcd6 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 5 Feb 2022 03:39:54 -0800 Subject: [PATCH 15/47] Add WinRTConversions.cpp --- vnext/Shared/Shared.vcxitems | 1 + vnext/Shared/Shared.vcxitems.filters | 6 ++++++ vnext/Shared/Utils/WinRTConversions.cpp | 19 +++++++++++++++++++ vnext/Shared/Utils/WinRTConversions.h | 11 ++--------- 4 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 vnext/Shared/Utils/WinRTConversions.cpp diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index a163aaa6a33..113bc3cd588 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -62,6 +62,7 @@ + true diff --git a/vnext/Shared/Shared.vcxitems.filters b/vnext/Shared/Shared.vcxitems.filters index 65fde94695d..917c8146600 100644 --- a/vnext/Shared/Shared.vcxitems.filters +++ b/vnext/Shared/Shared.vcxitems.filters @@ -139,6 +139,9 @@ Source Files + + Source Files\Utils + @@ -189,6 +192,9 @@ {1a3ad55f-1297-41b3-ba2a-0f819e69270c} + + {e78de2f1-a7e5-4a81-b69b-4a1f7fa91cde} + diff --git a/vnext/Shared/Utils/WinRTConversions.cpp b/vnext/Shared/Utils/WinRTConversions.cpp new file mode 100644 index 00000000000..403c923e331 --- /dev/null +++ b/vnext/Shared/Utils/WinRTConversions.cpp @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "WinRTConversions.h" + +namespace Microsoft::React::Utilities { + +std::string HResultToString(winrt::hresult_error const &e) { + std::stringstream stream; + stream << "[0x" << std::hex << e.code() << "] " << winrt::to_string(e.message()); + + return stream.str(); +} + +std::string HResultToString(winrt::hresult &&result) { + return HResultToString(winrt::hresult_error(std::move(result), winrt::hresult_error::from_abi)); +} + +}// namespace diff --git a/vnext/Shared/Utils/WinRTConversions.h b/vnext/Shared/Utils/WinRTConversions.h index f2837aaaa9b..7120203a1ec 100644 --- a/vnext/Shared/Utils/WinRTConversions.h +++ b/vnext/Shared/Utils/WinRTConversions.h @@ -41,15 +41,8 @@ auto resume_in_queue(const Mso::DispatchQueue &queue) noexcept { return awaitable{queue}; } // resume_in_queue -std::string HResultToString(winrt::hresult_error const &e) { - std::stringstream stream; - stream << "[0x" << std::hex << e.code() << "] " << winrt::to_string(e.message()); +std::string HResultToString(winrt::hresult_error const &e); - return stream.str(); -} - -std::string HResultToString(winrt::hresult &&result) { - return HResultToString(winrt::hresult_error(std::move(result), winrt::hresult_error::from_abi)); -} +std::string HResultToString(winrt::hresult &&result); } // namespace Microsoft::React::Utilities From 5676eb07aeb80180d8d665e74cd691c1e47af319 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 5 Feb 2022 18:59:08 -0800 Subject: [PATCH 16/47] Init resource in module --- vnext/Shared/IHttpResource.h | 2 +- vnext/Shared/Modules/HttpModule.cpp | 51 +++++++++++++++++++++++------ vnext/Shared/WinRTHttpResource.cpp | 2 +- vnext/Shared/WinRTHttpResource.h | 2 +- 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/vnext/Shared/IHttpResource.h b/vnext/Shared/IHttpResource.h index abdece102e7..8e8f6dc5f98 100644 --- a/vnext/Shared/IHttpResource.h +++ b/vnext/Shared/IHttpResource.h @@ -32,7 +32,7 @@ struct IHttpResource { virtual void SendRequest( const std::string &method, const std::string &url, - const Headers &headers, + const Headers &&headers, BodyData &&bodyData, const std::string &responseType, bool useIncrementalUpdates, diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index dec342d79f6..16768db3e4a 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -28,7 +28,7 @@ static void SendEvent(weak_ptr weakInstance, string &&eventName, dynam } } -static shared_ptr GetOrInitHttpResource(weak_ptr weakInstance) { +static shared_ptr CreateHttpResource(weak_ptr weakInstance) { auto resource = IHttpResource::Make(); resource->SetOnResponse([weakInstance](int64_t requestId, IHttpResource::Response &&response) { @@ -101,13 +101,19 @@ std::vector HttpModule::getMethods() { { "sendRequest", - [weakResource = weak_ptr(m_resource)](dynamic args, Callback cb)//TODO: Check whether 'cb' is needed + [weakHolder = weak_ptr(m_holder)](dynamic args, Callback cb)//TODO: Check whether 'cb' is needed { - auto params = facebook::xplat::jsArgAsObject(args, 0); - auto data = params["data"]; - IHttpResource::BodyData bodyData; - if (auto resource = weakResource.lock()) + auto holder = weakHolder.lock(); + if (!holder) { + return; + } + + auto resource = holder->Module->m_resource; + if (resource || (resource = CreateHttpResource(holder->Module->getInstance()))) { + IHttpResource::BodyData bodyData; + auto params = facebook::xplat::jsArgAsObject(args, 0); + auto data = params["data"]; auto stringData = data["string"]; if (!stringData.empty()) { @@ -131,12 +137,15 @@ std::vector HttpModule::getMethods() } //TODO: Support FORM data - //TODO: Extract headers + IHttpResource::Headers headers; + for (auto& header : params["headers"].items()) { + headers.emplace(header.first.getString(), header.second.getString()); + } resource->SendRequest( params["method"].asString(), params["url"].asString(), - {},//headers + std::move(headers), std::move(bodyData), params["responseType"].asString(), params["incrementalUpdates"].asBool(), @@ -149,14 +158,36 @@ std::vector HttpModule::getMethods() }, { "abortRequest", - [](dynamic args) + [weakHolder = weak_ptr(m_holder)](dynamic args) { + auto holder = weakHolder.lock(); + if (!holder) + { + return; + } + + auto resource = holder->Module->m_resource; + if (resource || (resource = CreateHttpResource(holder->Module->getInstance()))) + { + resource->AbortRequest(facebook::xplat::jsArgAsInt(args, 0)); + } } }, { "clearCookies", - [](dynamic args) + [weakHolder = weak_ptr(m_holder)](dynamic args) { + auto holder = weakHolder.lock(); + if (!holder) + { + return; + } + + auto resource = holder->Module->m_resource; + if (resource || (resource = CreateHttpResource(holder->Module->getInstance()))) + { + resource->ClearCookies(); + } } } }; diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 0bdfea7b236..9661093c6b2 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -55,7 +55,7 @@ WinRTHttpResource::WinRTHttpResource() noexcept : WinRTHttpResource(winrt::Windo void WinRTHttpResource::SendRequest( const string &method, const string &url, - const Headers &headers, + const Headers &&headers, BodyData &&bodyData, const string &responseType, bool useIncrementalUpdates, diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index 4ed27355b8c..6aed8e90acc 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -52,7 +52,7 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t void SendRequest( const std::string &method, const std::string &url, - const Headers &headers, + const Headers &&headers, BodyData &&bodyData, const std::string &responseType, bool useIncrementalUpdates, From 26a72b473d4088e558366e4e9255ae98cc841c2a Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 5 Feb 2022 20:33:02 -0800 Subject: [PATCH 17/47] Implement test RequestGetSucceeds --- .../HttpResourceIntegrationTests.cpp | 53 ++++++++++++++----- ...t.Windows.Desktop.IntegrationTests.vcxproj | 2 +- vnext/Test/HttpServer.cpp | 5 ++ vnext/Test/HttpServer.h | 3 ++ 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index c00ce2e7e83..bd5e1f8fa3b 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -1,12 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +//C4996: 'gethostbyaddr': Use getnameinfo() or GetNameInfoW() instead +#define _WINSOCK_DEPRECATED_NO_WARNINGS + +#include +#include #include #include using namespace Microsoft::React; using namespace Microsoft::VisualStudio::CppUnitTestFramework; +namespace http = boost::beast::http; + using std::string; using std::vector; @@ -17,21 +24,41 @@ TEST_CLASS (HttpResourceIntegrationTest) { } // This test always fails because the requested resource does not exist. - // TEST_METHOD(RequestGetSucceeds) { - void Disable_RequestGetSucceeds() { - auto rc = IHttpResource::Make(); - bool sent = false; - bool received = false; - string error; - //rc->SetOnRequest([&sent]() { sent = true; }); - rc->SetOnResponse([&received](int64_t, IHttpResource::Response&&) { received = true; }); - rc->SetOnError([&error](int64_t, string&& message) { error = message; }); + TEST_METHOD(RequestGetSucceeds) { + bool responded = false; + auto server = std::make_shared("127.0.0.1", static_cast(5555)); + server->SetOnRequest([&responded]() + { + responded = true; + }); + //http::response(const http::request &) + server->SetOnGet([](const http::request& request) -> http::response + { + http::response response; + response.result(http::status::ok); + + return response; + }); + server->Start(); - rc->SendRequest("GET", "http://localhost:8081/debugger-ui", {}, {}, "text", false, 1000, false, [](int64_t) {}); + auto resource = IHttpResource::Make(); + resource->SetOnResponse([](int64_t, IHttpResource::Response response) + { + auto sc = response.StatusCode; + }); + resource->SendRequest( + "GET", + "http://localhost:5555", + {}/*header*/, + {}/*bodyData*/, + "responseType", + false, + 1000/*timeout*/, + false/*withCredentials*/, + [](int64_t) {} + ); - Assert::IsTrue(sent); - Assert::IsTrue(received); - Assert::AreEqual(string(), error); + server->Stop(); } TEST_METHOD(RequestGetFails) { diff --git a/vnext/Desktop.IntegrationTests/React.Windows.Desktop.IntegrationTests.vcxproj b/vnext/Desktop.IntegrationTests/React.Windows.Desktop.IntegrationTests.vcxproj index a275b06def4..e12f8fc70cc 100644 --- a/vnext/Desktop.IntegrationTests/React.Windows.Desktop.IntegrationTests.vcxproj +++ b/vnext/Desktop.IntegrationTests/React.Windows.Desktop.IntegrationTests.vcxproj @@ -125,4 +125,4 @@ - \ No newline at end of file + diff --git a/vnext/Test/HttpServer.cpp b/vnext/Test/HttpServer.cpp index 9add04eac9b..b4a94d004f1 100644 --- a/vnext/Test/HttpServer.cpp +++ b/vnext/Test/HttpServer.cpp @@ -267,6 +267,11 @@ void HttpServer::SetOnGet( m_callbacks.OnGet = std::move(handler); } +void HttpServer::SetOnRequest(function&& handler) noexcept +{ + m_callbacks.OnRequest = std::move(handler); +} + #pragma endregion HttpServer } // namespace Microsoft::React::Test diff --git a/vnext/Test/HttpServer.h b/vnext/Test/HttpServer.h index 78c8940d0e4..bda5da21b5b 100644 --- a/vnext/Test/HttpServer.h +++ b/vnext/Test/HttpServer.h @@ -23,6 +23,7 @@ struct HttpCallbacks std::function( const boost::beast::http::request &)> OnGet; + std::function OnRequest; }; /// @@ -91,6 +92,8 @@ class HttpServer : public std::enable_shared_from_this /// void SetOnGet(std::function( const boost::beast::http::request &)> &&onGet) noexcept; + + void SetOnRequest(std::function&& handler) noexcept; }; } // namespace Microsoft::React::Test From b913c2d29cdbf23bc31970ce53ad00e365240013 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 5 Feb 2022 23:47:36 -0800 Subject: [PATCH 18/47] Complete RequestGetSucceeds --- .../HttpResourceIntegrationTests.cpp | 62 ++++++++++--------- vnext/Test/HttpServer.cpp | 6 +- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index bd5e1f8fa3b..c9e6255b62f 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -9,11 +9,15 @@ #include #include +// Standard Library +#include + using namespace Microsoft::React; using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace http = boost::beast::http; +using std::promise; using std::string; using std::vector; @@ -25,40 +29,42 @@ TEST_CLASS (HttpResourceIntegrationTest) { // This test always fails because the requested resource does not exist. TEST_METHOD(RequestGetSucceeds) { - bool responded = false; - auto server = std::make_shared("127.0.0.1", static_cast(5555)); - server->SetOnRequest([&responded]() - { - responded = true; - }); - //http::response(const http::request &) - server->SetOnGet([](const http::request& request) -> http::response + promise getPromise; + int statusCode = 0; + + // HTTP call scope { + auto server = std::make_shared("127.0.0.1", static_cast(5555)); + server->SetOnGet([](const http::request &request) -> http::response { http::response response; response.result(http::status::ok); return response; - }); - server->Start(); + }); + server->Start(); - auto resource = IHttpResource::Make(); - resource->SetOnResponse([](int64_t, IHttpResource::Response response) - { - auto sc = response.StatusCode; - }); - resource->SendRequest( - "GET", - "http://localhost:5555", - {}/*header*/, - {}/*bodyData*/, - "responseType", - false, - 1000/*timeout*/, - false/*withCredentials*/, - [](int64_t) {} - ); - - server->Stop(); + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { + statusCode = static_cast(response.StatusCode); + getPromise.set_value(); + }); + resource->SendRequest( + "GET", + "http://localhost:5555", + {} /*header*/, + {} /*bodyData*/, + "text", + false, + 1000 /*timeout*/, + false /*withCredentials*/, + [](int64_t) {}); + + server->Stop(); + } + // Synchronize response. + getPromise.get_future().wait(); + + Assert::AreEqual(200, statusCode); } TEST_METHOD(RequestGetFails) { diff --git a/vnext/Test/HttpServer.cpp b/vnext/Test/HttpServer.cpp index b4a94d004f1..2938b2d3dce 100644 --- a/vnext/Test/HttpServer.cpp +++ b/vnext/Test/HttpServer.cpp @@ -137,7 +137,10 @@ void HttpSession::OnWrite(bool /*close*/, error_code ec, size_t /*transferred*/) return; } - m_callbacks.OnResponseSent(); + if (m_callbacks.OnResponseSent) + { + m_callbacks.OnResponseSent(); + } // TODO: Re-enable when concurrent sessions are implemented. // If response indicates "Connection: close" @@ -221,6 +224,7 @@ void HttpServer::OnAccept(error_code ec, tcp::socket socket) if (ec) { // ISS:2735328 - Implement failure propagation mechanism + return; } else { From 276380ec250b51afcc1f83a51363327284619e1a Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sun, 6 Feb 2022 00:36:29 -0800 Subject: [PATCH 19/47] Complete RequestGetFails --- .../HttpResourceIntegrationTests.cpp | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index c9e6255b62f..ba1b163f785 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -22,12 +22,7 @@ using std::string; using std::vector; TEST_CLASS (HttpResourceIntegrationTest) { - TEST_METHOD(MakeIsNotNull) { - auto rc = IHttpResource::Make(); - Assert::IsFalse(nullptr == rc); - } - // This test always fails because the requested resource does not exist. TEST_METHOD(RequestGetSucceeds) { promise getPromise; int statusCode = 0; @@ -68,12 +63,30 @@ TEST_CLASS (HttpResourceIntegrationTest) { } TEST_METHOD(RequestGetFails) { - auto rc = IHttpResource::Make(); string error; - rc->SetOnError([&error](int64_t, string &&message) { error = message; }); - - rc->SendRequest("GET", "http://nonexistinghost", {}, {}, "text", false, 1000, false, [](int64_t) {}); - - Assert::AreEqual(string("No such host is known"), error); + promise promise; + + auto resource = IHttpResource::Make(); + resource->SetOnError([&error, &promise](int64_t, string &&message) { + error = message; + promise.set_value(); + }); + + resource->SendRequest( + "GET", + "http://nonexistinghost", + {}, + {}, + "text", + false, + 1000, + false, + [](int64_t) {} + ); + + promise.get_future().wait(); + + Logger::WriteMessage(error.c_str()); + Assert::AreNotEqual(string{}, error); } }; From 523b490e09571e0583593d670596fcb74b9c168f Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sun, 6 Feb 2022 01:20:34 -0800 Subject: [PATCH 20/47] Add HttpModule to default modules --- .../RNTesterIntegrationTests.cpp | 1 + vnext/Shared/CreateModules.h | 2 ++ vnext/Shared/OInstance.cpp | 22 +++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp index f75e72c8730..0c5f2e0e674 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp @@ -25,6 +25,7 @@ std::wstring ToString(const TestStatus &status) { TEST_MODULE_INITIALIZE(InitModule) { Microsoft::React::SetRuntimeOptionBool("WebSocket.AcceptSelfSigned", true); Microsoft::React::SetRuntimeOptionBool("UseBeastWebSocket", false); + Microsoft::React::SetRuntimeOptionBool("Http.UseResourceModule", true); // WebSocketJSExecutor can't register native log hooks. Microsoft::React::SetRuntimeOptionBool("RNTester.UseWebDebugger", false); diff --git a/vnext/Shared/CreateModules.h b/vnext/Shared/CreateModules.h index 2594d906cc4..50b8d1e1dfa 100644 --- a/vnext/Shared/CreateModules.h +++ b/vnext/Shared/CreateModules.h @@ -29,6 +29,8 @@ extern std::unique_ptr CreateTimingModule( namespace Microsoft::React { +extern std::unique_ptr CreateHttpModule() noexcept; + extern std::unique_ptr CreateWebSocketModule() noexcept; } // namespace Microsoft::React diff --git a/vnext/Shared/OInstance.cpp b/vnext/Shared/OInstance.cpp index 0f67f0c4676..7dabfbca591 100644 --- a/vnext/Shared/OInstance.cpp +++ b/vnext/Shared/OInstance.cpp @@ -26,6 +26,8 @@ #include #include +#include +#include #include #include #include @@ -66,6 +68,18 @@ using namespace Microsoft::JSI; using std::make_shared; +namespace Microsoft::React { + +/*extern*/ std::unique_ptr CreateHttpModule() noexcept { + if (GetRuntimeOptionBool("Http.UseResourceModule")) { + return std::make_unique(); + } else { + return std::make_unique(); + } +} + +}// namespace + namespace facebook { namespace react { @@ -523,6 +537,14 @@ std::vector> InstanceImpl::GetDefaultNativeModules std::shared_ptr nativeQueue) { std::vector> modules; + modules.push_back(std::make_unique( + m_innerInstance, + "Networking", + [nativeQueue]() -> std::unique_ptr { + return Microsoft::React::CreateHttpModule(); + }, + nativeQueue)); + modules.push_back(std::make_unique( m_innerInstance, "WebSocketModule", From 40c56a46debd8d327d458000b052d23e6ec4a0d0 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sun, 6 Feb 2022 01:37:25 -0800 Subject: [PATCH 21/47] Move resume_in_queue back to WebSocket resource --- vnext/Shared/Utils/WinRTConversions.h | 30 ------------------- vnext/Shared/WinRTWebSocketResource.cpp | 39 +++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/vnext/Shared/Utils/WinRTConversions.h b/vnext/Shared/Utils/WinRTConversions.h index 7120203a1ec..ec733c49386 100644 --- a/vnext/Shared/Utils/WinRTConversions.h +++ b/vnext/Shared/Utils/WinRTConversions.h @@ -3,8 +3,6 @@ #pragma once -#include - // Windows API #include @@ -13,34 +11,6 @@ namespace Microsoft::React::Utilities { -/// -/// Implements an awaiter for Mso::DispatchQueue -/// -auto resume_in_queue(const Mso::DispatchQueue &queue) noexcept { - struct awaitable { - awaitable(const Mso::DispatchQueue &queue) noexcept : m_queue{queue} {} - - bool await_ready() const noexcept { - return false; - } - - void await_resume() const noexcept {} - - void await_suspend(std::experimental::coroutine_handle<> resume) noexcept { - m_callback = [context = resume.address()]() noexcept { - std::experimental::coroutine_handle<>::from_address(context)(); - }; - m_queue.Post(std::move(m_callback)); - } - - private: - Mso::DispatchQueue m_queue; - Mso::VoidFunctor m_callback; - }; - - return awaitable{queue}; -} // resume_in_queue - std::string HResultToString(winrt::hresult_error const &e); std::string HResultToString(winrt::hresult &&result); diff --git a/vnext/Shared/WinRTWebSocketResource.cpp b/vnext/Shared/WinRTWebSocketResource.cpp index 44f58772330..2ab79baa1b9 100644 --- a/vnext/Shared/WinRTWebSocketResource.cpp +++ b/vnext/Shared/WinRTWebSocketResource.cpp @@ -3,9 +3,12 @@ #include "WinRTWebSocketResource.h" -#include #include #include +#include + +// MSO +#include // Windows API #include @@ -44,6 +47,38 @@ using winrt::Windows::Storage::Streams::IDataReader; using winrt::Windows::Storage::Streams::IDataWriter; using winrt::Windows::Storage::Streams::UnicodeEncoding; +namespace { + +/// +/// Implements an awaiter for Mso::DispatchQueue +/// +auto resume_in_queue(const Mso::DispatchQueue &queue) noexcept { + struct awaitable { + awaitable(const Mso::DispatchQueue &queue) noexcept : m_queue{queue} {} + + bool await_ready() const noexcept { + return false; + } + + void await_resume() const noexcept {} + + void await_suspend(std::experimental::coroutine_handle<> resume) noexcept { + m_callback = [context = resume.address()]() noexcept { + std::experimental::coroutine_handle<>::from_address(context)(); + }; + m_queue.Post(std::move(m_callback)); + } + + private: + Mso::DispatchQueue m_queue; + Mso::VoidFunctor m_callback; + }; + + return awaitable{queue}; +} // resume_in_queue + +}// namespace + namespace Microsoft::React { // private @@ -160,7 +195,7 @@ fire_and_forget WinRTWebSocketResource::PerformWrite(string &&message, bool isBi co_await resume_on_signal(self->m_connectPerformed.get()); // Ensure connection attempt has finished - co_await Utilities::resume_in_queue(self->m_dispatchQueue); // Ensure writes happen sequentially + co_await resume_in_queue(self->m_dispatchQueue); // Ensure writes happen sequentially if (self->m_readyState != ReadyState::Open) { self = nullptr; From a7810f176a78f9b33b25954b910c2021da864947 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sun, 6 Feb 2022 01:38:22 -0800 Subject: [PATCH 22/47] Simplify WinRTConversions includes --- vnext/Shared/Utils/WinRTConversions.cpp | 3 +++ vnext/Shared/Utils/WinRTConversions.h | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vnext/Shared/Utils/WinRTConversions.cpp b/vnext/Shared/Utils/WinRTConversions.cpp index 403c923e331..25e8c3aa50a 100644 --- a/vnext/Shared/Utils/WinRTConversions.cpp +++ b/vnext/Shared/Utils/WinRTConversions.cpp @@ -3,6 +3,9 @@ #include "WinRTConversions.h" +// Standard Library +#include + namespace Microsoft::React::Utilities { std::string HResultToString(winrt::hresult_error const &e) { diff --git a/vnext/Shared/Utils/WinRTConversions.h b/vnext/Shared/Utils/WinRTConversions.h index ec733c49386..e20a554de8c 100644 --- a/vnext/Shared/Utils/WinRTConversions.h +++ b/vnext/Shared/Utils/WinRTConversions.h @@ -6,9 +6,6 @@ // Windows API #include -// Standard Library -#include - namespace Microsoft::React::Utilities { std::string HResultToString(winrt::hresult_error const &e); From ef7c9c1e13f9b96876cb4bd2ebd9601dd6ca2406 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sun, 6 Feb 2022 02:00:28 -0800 Subject: [PATCH 23/47] Rename runtime option to Http.UseResourcedModule --- vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp | 2 +- vnext/Shared/OInstance.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp index 0c5f2e0e674..511642041ac 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp @@ -25,7 +25,7 @@ std::wstring ToString(const TestStatus &status) { TEST_MODULE_INITIALIZE(InitModule) { Microsoft::React::SetRuntimeOptionBool("WebSocket.AcceptSelfSigned", true); Microsoft::React::SetRuntimeOptionBool("UseBeastWebSocket", false); - Microsoft::React::SetRuntimeOptionBool("Http.UseResourceModule", true); + Microsoft::React::SetRuntimeOptionBool("Http.UseResourcedModule", true); // WebSocketJSExecutor can't register native log hooks. Microsoft::React::SetRuntimeOptionBool("RNTester.UseWebDebugger", false); diff --git a/vnext/Shared/OInstance.cpp b/vnext/Shared/OInstance.cpp index 7dabfbca591..bb13a9fdad2 100644 --- a/vnext/Shared/OInstance.cpp +++ b/vnext/Shared/OInstance.cpp @@ -71,7 +71,7 @@ using std::make_shared; namespace Microsoft::React { /*extern*/ std::unique_ptr CreateHttpModule() noexcept { - if (GetRuntimeOptionBool("Http.UseResourceModule")) { + if (GetRuntimeOptionBool("Http.UseResourcedModule")) { return std::make_unique(); } else { return std::make_unique(); From 4af0d7a7bc4e2b63fc8e4efb175808b0802d3b9f Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sun, 6 Feb 2022 02:05:15 -0800 Subject: [PATCH 24/47] clang format --- .../HttpResourceIntegrationTests.cpp | 19 ++---- vnext/Shared/IHttpResource.h | 2 +- vnext/Shared/Modules/HttpModule.cpp | 24 +++---- vnext/Shared/Modules/HttpModule.h | 2 +- vnext/Shared/OInstance.cpp | 6 +- vnext/Shared/Utils/WinRTConversions.cpp | 2 +- vnext/Shared/WinRTHttpResource.cpp | 65 ++++++++++--------- vnext/Shared/WinRTHttpResource.h | 19 +++--- vnext/Shared/WinRTWebSocketResource.cpp | 4 +- 9 files changed, 62 insertions(+), 81 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index ba1b163f785..3e7f7e82aaa 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//C4996: 'gethostbyaddr': Use getnameinfo() or GetNameInfoW() instead +// C4996: 'gethostbyaddr': Use getnameinfo() or GetNameInfoW() instead #define _WINSOCK_DEPRECATED_NO_WARNINGS -#include -#include #include #include +#include +#include // Standard Library #include @@ -22,7 +22,6 @@ using std::string; using std::vector; TEST_CLASS (HttpResourceIntegrationTest) { - TEST_METHOD(RequestGetSucceeds) { promise getPromise; int statusCode = 0; @@ -72,17 +71,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { promise.set_value(); }); - resource->SendRequest( - "GET", - "http://nonexistinghost", - {}, - {}, - "text", - false, - 1000, - false, - [](int64_t) {} - ); + resource->SendRequest("GET", "http://nonexistinghost", {}, {}, "text", false, 1000, false, [](int64_t) {}); promise.get_future().wait(); diff --git a/vnext/Shared/IHttpResource.h b/vnext/Shared/IHttpResource.h index 8e8f6dc5f98..bfe86145f0c 100644 --- a/vnext/Shared/IHttpResource.h +++ b/vnext/Shared/IHttpResource.h @@ -43,7 +43,7 @@ struct IHttpResource { virtual void ClearCookies() noexcept = 0; - virtual void SetOnRequest(std::function &&handler) noexcept = 0;//TODO: Keep??? + virtual void SetOnRequest(std::function &&handler) noexcept = 0; // TODO: Keep??? virtual void SetOnResponse(std::function &&handler) noexcept = 0; virtual void SetOnData(std::function &&handler) noexcept = 0; virtual void SetOnError( diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 16768db3e4a..137c076dffd 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -21,7 +21,7 @@ using Microsoft::React::IHttpResource; constexpr char moduleName[] = "Networking"; -//TODO: Add to shared header? (See WebSocketModule) +// TODO: Add to shared header? (See WebSocketModule) static void SendEvent(weak_ptr weakInstance, string &&eventName, dynamic &&args) { if (auto instance = weakInstance.lock()) { instance->callJSFunction("RCTDeviceEventEmitter", "emit", dynamic::array(std::move(eventName), std::move(args))); @@ -38,29 +38,23 @@ static shared_ptr CreateHttpResource(weak_ptr weakInsta } // TODO: Test response content. - dynamic args = dynamic::array( - requestId, - response.StatusCode, - headers, - response.Url - ); + dynamic args = dynamic::array(requestId, response.StatusCode, headers, response.Url); SendEvent(weakInstance, "didReceiveNetworkResponse", std::move(args)); }); - resource->SetOnData( - [weakInstance](int64_t requestId, std::string &&responseData) { - dynamic args = dynamic::array(requestId, std::move(responseData)); + resource->SetOnData([weakInstance](int64_t requestId, std::string &&responseData) { + dynamic args = dynamic::array(requestId, std::move(responseData)); - SendEvent(weakInstance, "didReceiveNetworkData", std::move(args)); + SendEvent(weakInstance, "didReceiveNetworkData", std::move(args)); - //TODO: Move into separate method IF not executed right after onData() - SendEvent(weakInstance, "didCompleteNetworkResponse", dynamic::array(requestId)); + // TODO: Move into separate method IF not executed right after onData() + SendEvent(weakInstance, "didCompleteNetworkResponse", dynamic::array(requestId)); }); resource->SetOnError([weakInstance](int64_t requestId, string &&message) { dynamic args = dynamic::array(requestId, std::move(message)); - //TODO: isTimeout errorArgs.push_back(true); + // TODO: isTimeout errorArgs.push_back(true); SendEvent(weakInstance, "didCompleteNetworkResponse", std::move(args)); }); @@ -68,7 +62,7 @@ static shared_ptr CreateHttpResource(weak_ptr weakInsta return resource; } -} // namespace +} // namespace namespace Microsoft::React { diff --git a/vnext/Shared/Modules/HttpModule.h b/vnext/Shared/Modules/HttpModule.h index 7fc5f41fc3f..1ed59cfab2b 100644 --- a/vnext/Shared/Modules/HttpModule.h +++ b/vnext/Shared/Modules/HttpModule.h @@ -50,7 +50,7 @@ class HttpModule : public facebook::xplat::module::CxxModule { std::shared_ptr m_resource; std::shared_ptr m_holder; - //TODO: Decide whether to keep + // TODO: Decide whether to keep /// /// Notifies an event to the current React Instance. /// diff --git a/vnext/Shared/OInstance.cpp b/vnext/Shared/OInstance.cpp index bb13a9fdad2..302a52f8fcb 100644 --- a/vnext/Shared/OInstance.cpp +++ b/vnext/Shared/OInstance.cpp @@ -78,7 +78,7 @@ namespace Microsoft::React { } } -}// namespace +} // namespace Microsoft::React namespace facebook { namespace react { @@ -540,9 +540,7 @@ std::vector> InstanceImpl::GetDefaultNativeModules modules.push_back(std::make_unique( m_innerInstance, "Networking", - [nativeQueue]() -> std::unique_ptr { - return Microsoft::React::CreateHttpModule(); - }, + [nativeQueue]() -> std::unique_ptr { return Microsoft::React::CreateHttpModule(); }, nativeQueue)); modules.push_back(std::make_unique( diff --git a/vnext/Shared/Utils/WinRTConversions.cpp b/vnext/Shared/Utils/WinRTConversions.cpp index 25e8c3aa50a..6a98e1c750f 100644 --- a/vnext/Shared/Utils/WinRTConversions.cpp +++ b/vnext/Shared/Utils/WinRTConversions.cpp @@ -19,4 +19,4 @@ std::string HResultToString(winrt::hresult &&result) { return HResultToString(winrt::hresult_error(std::move(result), winrt::hresult_error::from_abi)); } -}// namespace +} // namespace Microsoft::React::Utilities diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 9661093c6b2..812c388d42c 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -17,12 +17,15 @@ using std::scoped_lock; using std::shared_ptr; using std::string; +using winrt::fire_and_forget; +using winrt::hresult_error; +using winrt::to_hstring; +using winrt::to_string; using winrt::Windows::Foundation::Uri; using winrt::Windows::Security::Cryptography::CryptographicBuffer; +using winrt::Windows::Storage::StorageFile; using winrt::Windows::Storage::Streams::DataReader; using winrt::Windows::Storage::Streams::UnicodeEncoding; -using winrt::Windows::Storage::StorageFile; -using winrt::Windows::Web::Http::Headers::HttpMediaTypeHeaderValue; using winrt::Windows::Web::Http::HttpBufferContent; using winrt::Windows::Web::Http::HttpMethod; using winrt::Windows::Web::Http::HttpRequestMessage; @@ -30,20 +33,15 @@ using winrt::Windows::Web::Http::HttpStreamContent; using winrt::Windows::Web::Http::HttpStringContent; using winrt::Windows::Web::Http::IHttpClient; using winrt::Windows::Web::Http::IHttpContent; -using winrt::fire_and_forget; -using winrt::hresult_error; -using winrt::to_hstring; -using winrt::to_string; - -namespace { +using winrt::Windows::Web::Http::Headers::HttpMediaTypeHeaderValue; -} // namespace +namespace {} // namespace namespace Microsoft::React { #pragma region WinRTHttpResource -//TODO: Multi-thread issues? +// TODO: Multi-thread issues? /*static*/ int64_t WinRTHttpResource::s_lastRequestId = 0; WinRTHttpResource::WinRTHttpResource(IHttpClient client) noexcept : m_client{client} {} @@ -62,7 +60,6 @@ void WinRTHttpResource::SendRequest( int64_t timeout, bool withCredentials, std::function &&callback) noexcept /*override*/ { - auto requestId = ++s_lastRequestId; // Enforce supported args @@ -117,7 +114,7 @@ void WinRTHttpResource::SendRequest( // TODO: Add support } else { // BodyData::Type::Empty - //TODO: Error 'cause unsupported?? + // TODO: Error 'cause unsupported?? } if (content != nullptr) { @@ -134,7 +131,7 @@ void WinRTHttpResource::SendRequest( } } if (!contentLength.empty()) { - const auto contentLengthHeader = _atoi64(contentLength.c_str());//TODO: Check error? + const auto contentLengthHeader = _atoi64(contentLength.c_str()); // TODO: Check error? content.Headers().ContentLength(contentLengthHeader); } @@ -162,28 +159,31 @@ void WinRTHttpResource::AbortRequest(int64_t requestId) noexcept /*override*/ { try { request.Cancel(); } catch (hresult_error const &) { - //TODO: Propagate error? + // TODO: Propagate error? } } void WinRTHttpResource::ClearCookies() noexcept /*override*/ { assert(false); - //NOT IMPLEMENTED + // NOT IMPLEMENTED } void WinRTHttpResource::SetOnRequest(function &&handler) noexcept /*override*/ { m_onRequest = std::move(handler); } -void WinRTHttpResource::SetOnResponse(function &&handler) noexcept /*override*/ { +void WinRTHttpResource::SetOnResponse(function &&handler) noexcept +/*override*/ { m_onResponse = std::move(handler); } -void WinRTHttpResource::SetOnData(function &&handler) noexcept /*override*/ { +void WinRTHttpResource::SetOnData(function &&handler) noexcept +/*override*/ { m_onData = std::move(handler); } -void WinRTHttpResource::SetOnError(function &&handler) noexcept /*override*/ { +void WinRTHttpResource::SetOnError(function &&handler) noexcept +/*override*/ { m_onError = std::move(handler); } @@ -199,9 +199,10 @@ void WinRTHttpResource::RemoveRequest(int64_t requestId) noexcept { m_requests.erase(requestId); } -fire_and_forget WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage request, bool textResponse) noexcept { +fire_and_forget +WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage request, bool textResponse) noexcept { auto self = shared_from_this(); - //TODO: Set timeout? + // TODO: Set timeout? // Ensure background thread co_await winrt::resume_background(); @@ -221,19 +222,20 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpReq } auto response = sendRequestOp.GetResults(); - if (response) {//TODO: check nullptr? + if (response) { // TODO: check nullptr? if (self->m_onResponse) { Headers headers; for (auto header : response.Headers()) { headers.emplace(to_string(header.Key()), to_string(header.Value())); } string url = to_string(response.RequestMessage().RequestUri().AbsoluteUri()); - self->m_onResponse(0 /*requestId*/, {static_cast(response.StatusCode()), std::move(headers), std::move(url)}); + self->m_onResponse( + 0 /*requestId*/, {static_cast(response.StatusCode()), std::move(headers), std::move(url)}); } } - //TODO: Incremental updates? - if (response && response.Content()) {//TODO: check nullptr? + // TODO: Incremental updates? + if (response && response.Content()) { // TODO: check nullptr? auto inputStream = co_await response.Content().ReadAsInputStreamAsync(); auto reader = DataReader{inputStream}; @@ -242,15 +244,14 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpReq } // Only support response sizes up to 10MB. - //TODO: WHY???? + // TODO: WHY???? co_await reader.LoadAsync(10 * 1024 * 1024); auto length = reader.UnconsumedBufferLength(); if (textResponse) { std::vector data(length); reader.ReadBytes(data); - string responseData = - string(Common::Utilities::CheckedReinterpretCast(data.data()), data.size()); + string responseData = string(Common::Utilities::CheckedReinterpretCast(data.data()), data.size()); if (self->m_onData) { self->m_onData(0 /*requestId*/, std::move(responseData)); @@ -258,28 +259,28 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpReq } else { auto buffer = reader.ReadBuffer(length); auto data = CryptographicBuffer::EncodeToBase64String(buffer); - auto responseData = to_string(data);//TODO: string view??? + auto responseData = to_string(data); // TODO: string view??? if (self->m_onData) { self->m_onData(0 /*requestId*/, std::move(responseData)); } } - //TODO: self->OnRequestSuccess OR, keep in self->OnData. + // TODO: self->OnRequestSuccess OR, keep in self->OnData. } else { if (self->m_onError) { self->m_onError(requestId, response == nullptr ? "request failed" : "No response content"); } } } catch (...) { - //TODO: Lose generic catch + // TODO: Lose generic catch if (self->m_onError) { self->m_onError(requestId, "Unhandled exception during request"); } } self->RemoveRequest(requestId); - co_return;//TODO: keep? + co_return; // TODO: keep? } #pragma endregion WinRTHttpResource @@ -292,4 +293,4 @@ fire_and_forget WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpReq #pragma endregion IHttpResource -}// namespace +} // namespace Microsoft::React diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index 6aed8e90acc..d266bdcf3aa 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -14,7 +14,6 @@ namespace Microsoft::React { class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_this { - typedef winrt::Windows::Foundation::IAsyncOperationWithProgress< winrt::Windows::Web::Http::HttpResponseMessage, winrt::Windows::Web::Http::HttpProgress> @@ -35,14 +34,14 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t void RemoveRequest(int64_t requestId) noexcept; - //TODO: Make non-trivial args r-value?? + // TODO: Make non-trivial args r-value?? winrt::fire_and_forget PerformSendRequest(/*TODO: shared self?,*/ - int64_t requestId, - winrt::Windows::Web::Http::HttpRequestMessage request, - bool textResponse - /*, requestId?*/) noexcept; + int64_t requestId, + winrt::Windows::Web::Http::HttpRequestMessage request, + bool textResponse + /*, requestId?*/) noexcept; -public: + public: WinRTHttpResource() noexcept; WinRTHttpResource(winrt::Windows::Web::Http::IHttpClient client) noexcept; @@ -65,10 +64,10 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t #pragma endregion IHttpResource void SetOnRequest(std::function &&handler) noexcept override; - void SetOnResponse(std::function &&handler) noexcept override; - void SetOnData(std::function &&handler) noexcept override; + void SetOnResponse(std::function &&handler) noexcept override; + void SetOnData(std::function &&handler) noexcept override; void SetOnError( std::function &&handler) noexcept override; }; -} // namespace +} // namespace Microsoft::React diff --git a/vnext/Shared/WinRTWebSocketResource.cpp b/vnext/Shared/WinRTWebSocketResource.cpp index 2ab79baa1b9..7368d505a68 100644 --- a/vnext/Shared/WinRTWebSocketResource.cpp +++ b/vnext/Shared/WinRTWebSocketResource.cpp @@ -3,9 +3,9 @@ #include "WinRTWebSocketResource.h" +#include #include #include -#include // MSO #include @@ -77,7 +77,7 @@ auto resume_in_queue(const Mso::DispatchQueue &queue) noexcept { return awaitable{queue}; } // resume_in_queue -}// namespace +} // namespace namespace Microsoft::React { From b225b8eac68c139d949d6cc2c4dc9e9e03183789 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sun, 6 Feb 2022 02:05:40 -0800 Subject: [PATCH 25/47] Change files --- ...ative-windows-7bb307e0-b40f-48f0-a356-a43c860eeefb.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-7bb307e0-b40f-48f0-a356-a43c860eeefb.json diff --git a/change/react-native-windows-7bb307e0-b40f-48f0-a356-a43c860eeefb.json b/change/react-native-windows-7bb307e0-b40f-48f0-a356-a43c860eeefb.json new file mode 100644 index 00000000000..35d0932dbbf --- /dev/null +++ b/change/react-native-windows-7bb307e0-b40f-48f0-a356-a43c860eeefb.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Implement IHttpResource", + "packageName": "react-native-windows", + "email": "julio.rocha@microsoft.com", + "dependentChangeType": "patch" +} From a7f9aeed262a904ae826fac376b449ba29cae1e8 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sun, 6 Feb 2022 21:07:52 -0800 Subject: [PATCH 26/47] Update x86 DLL boundary --- vnext/Desktop.DLL/react-native-win32.x86.def | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnext/Desktop.DLL/react-native-win32.x86.def b/vnext/Desktop.DLL/react-native-win32.x86.def index e8588618d35..41d828bfb25 100644 --- a/vnext/Desktop.DLL/react-native-win32.x86.def +++ b/vnext/Desktop.DLL/react-native-win32.x86.def @@ -60,7 +60,7 @@ EXPORTS ?makeChakraRuntime@JSI@Microsoft@@YG?AV?$unique_ptr@VRuntime@jsi@facebook@@U?$default_delete@VRuntime@jsi@facebook@@@std@@@std@@$$QAUChakraRuntimeArgs@12@@Z ?CreateTimingModule@react@facebook@@YG?AV?$unique_ptr@VCxxModule@module@xplat@facebook@@U?$default_delete@VCxxModule@module@xplat@facebook@@@std@@@std@@ABV?$shared_ptr@VMessageQueueThread@react@facebook@@@4@@Z ??0WebSocketModule@React@Microsoft@@QAE@XZ -?Make@IHttpResource@React@Microsoft@@SG?AV?$unique_ptr@UIHttpResource@React@Microsoft@@U?$default_delete@UIHttpResource@React@Microsoft@@@std@@@std@@XZ +?Make@IHttpResource@React@Microsoft@@SG?AV?$shared_ptr@UIHttpResource@React@Microsoft@@@std@@XZ ??0NetworkingModule@React@Microsoft@@QAE@XZ ?MakeJSQueueThread@ReactNative@Microsoft@@YG?AV?$shared_ptr@VMessageQueueThread@react@facebook@@@std@@XZ ?Hash128@SpookyHashV2@hash@folly@@SGXPBXIPA_K1@Z From 61a2f06c0d48109540733a55e6928fa3d0ca0155 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 7 Feb 2022 00:25:44 -0800 Subject: [PATCH 27/47] Using SetRtOpt in InitModule --- .../RNTesterIntegrationTests.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp index 511642041ac..a7c290513d9 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp @@ -23,12 +23,14 @@ std::wstring ToString(const TestStatus &status) { } // namespace Microsoft::VisualStudio::CppUnitTestFramework TEST_MODULE_INITIALIZE(InitModule) { - Microsoft::React::SetRuntimeOptionBool("WebSocket.AcceptSelfSigned", true); - Microsoft::React::SetRuntimeOptionBool("UseBeastWebSocket", false); - Microsoft::React::SetRuntimeOptionBool("Http.UseResourcedModule", true); + using Microsoft::React::SetRuntimeOptionBool; + + SetRuntimeOptionBool("WebSocket.AcceptSelfSigned", true); + SetRuntimeOptionBool("UseBeastWebSocket", false); + SetRuntimeOptionBool("Http.UseResourcedModule", true); // WebSocketJSExecutor can't register native log hooks. - Microsoft::React::SetRuntimeOptionBool("RNTester.UseWebDebugger", false); + SetRuntimeOptionBool("RNTester.UseWebDebugger", false); } TEST_CLASS (RNTesterIntegrationTests) { From 8d24165eaa164793c5eb3074ea8385b2d93f722a Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Mon, 7 Feb 2022 01:06:55 -0800 Subject: [PATCH 28/47] Add test RequestGetHeadersSucceeds --- .../HttpResourceIntegrationTests.cpp | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index 3e7f7e82aaa..d4d7ad62f56 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -61,6 +61,60 @@ TEST_CLASS (HttpResourceIntegrationTest) { Assert::AreEqual(200, statusCode); } + // TODO: Hangs! Likely headers make server not send response. + TEST_METHOD(RequestGetHeadersSucceeds) { + promise getPromise; + int statusCode = 0; + + // HTTP call scope + { + auto server = std::make_shared("127.0.0.1", static_cast(5555)); + server->SetOnGet([](const http::request &request) -> http::response { + http::response response; + response.result(http::status::ok); + response.set(http::field::content_type, "text/html"); + response.set(http::field::content_encoding, "utf-8"); + response.set(http::field::content_length, "0"); + + return response; + }); + server->Start(); + + auto resource = IHttpResource::Make(); + resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { + statusCode = static_cast(response.StatusCode); + for (auto& header : response.Headers) { + auto &k = header.first; + auto &v = header.second; + + continue; + } + + getPromise.set_value(); + }); + //clang-format off + resource->SendRequest( + "GET", + "http://localhost:5555", + { + { "Content-Type", "application/json" }, + { "Content-Encoding", "ASCII" } + }, + {} /*bodyData*/, + "text", + false, + 1000 /*timeout*/, + false /*withCredentials*/, + [](int64_t) {}); + //clang-format on + server->Stop(); + } + // Synchronize response. + getPromise.get_future().wait(); + + Assert::AreEqual(200, statusCode); + } + TEST_METHOD(RequestGetFails) { string error; promise promise; From de84a7922028563ff195b2339d240c09042734b6 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 8 Feb 2022 22:14:00 -0800 Subject: [PATCH 29/47] Allow aborting server --- .../HttpResourceIntegrationTests.cpp | 21 +++++++++++++----- vnext/Shared/WinRTHttpResource.cpp | 22 ++++++++++++------- vnext/Test/HttpServer.cpp | 8 ++++++- vnext/Test/HttpServer.h | 2 +- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index d4d7ad62f56..55e1b4d2ead 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -63,7 +63,8 @@ TEST_CLASS (HttpResourceIntegrationTest) { // TODO: Hangs! Likely headers make server not send response. TEST_METHOD(RequestGetHeadersSucceeds) { - promise getPromise; + promise promise; + string error; int statusCode = 0; // HTTP call scope @@ -81,7 +82,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { server->Start(); auto resource = IHttpResource::Make(); - resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { + resource->SetOnResponse([&promise, &statusCode](int64_t, IHttpResource::Response response) { statusCode = static_cast(response.StatusCode); for (auto& header : response.Headers) { auto &k = header.first; @@ -90,7 +91,13 @@ TEST_CLASS (HttpResourceIntegrationTest) { continue; } - getPromise.set_value(); + promise.set_value(); + }); + resource->SetOnError([&promise, &error, &server](int64_t, string &&message) { + error = std::move(message); + promise.set_value(); + + server->Stop(true /*abort*/); }); //clang-format off resource->SendRequest( @@ -98,7 +105,8 @@ TEST_CLASS (HttpResourceIntegrationTest) { "http://localhost:5555", { { "Content-Type", "application/json" }, - { "Content-Encoding", "ASCII" } + { "Content-Encoding", "ASCII" }, + { "A", "V" }, }, {} /*bodyData*/, "text", @@ -109,9 +117,10 @@ TEST_CLASS (HttpResourceIntegrationTest) { //clang-format on server->Stop(); } - // Synchronize response. - getPromise.get_future().wait(); + promise.get_future().wait(); + + Assert::AreEqual({}, error); Assert::AreEqual(200, statusCode); } diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 812c388d42c..d2a21424345 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -79,11 +79,10 @@ void WinRTHttpResource::SendRequest( // TODO: Check casing for (auto &header : headers) { if (header.first == "content-type") { - bool success = HttpMediaTypeHeaderValue::TryParse(to_hstring(header.first), contentType); - if (m_onError) { - m_onError(requestId, "Failed to parse Content-Type"); + bool success = HttpMediaTypeHeaderValue::TryParse(to_hstring(header.second), contentType); + if (!success && m_onError) { + return m_onError(requestId, "Failed to parse Content-Type"); } - return; } else if (header.first == "content-encoding") { contentEncoding = header.second; } else if (header.first == "content-length") { @@ -91,10 +90,9 @@ void WinRTHttpResource::SendRequest( } else if (header.first == "authorization") { bool success = request.Headers().TryAppendWithoutValidation(to_hstring(header.first), to_hstring(header.second)); - if (m_onError) { - m_onError(requestId, "Failed to append Authorization"); + if (!success && m_onError) { + return m_onError(requestId, "Failed to append Authorization"); } - return; } else { request.Headers().Append(to_hstring(header.first), to_hstring(header.second)); } @@ -139,8 +137,16 @@ void WinRTHttpResource::SendRequest( } PerformSendRequest(requestId, request, responseType == "text"); + } catch (std::exception const& e) { + if (m_onError) { + m_onError(requestId, e.what()); + } + } catch(hresult_error const& e) { + if (m_onError) { + m_onError(requestId, Utilities::HResultToString(e)); + } } catch (...) { // TODO: Delcare specific exception types - // TODO: OnRequestError + m_onError(requestId, "Unidentified error sending HTTP request"); } } diff --git a/vnext/Test/HttpServer.cpp b/vnext/Test/HttpServer.cpp index 2938b2d3dce..0ae58e1e828 100644 --- a/vnext/Test/HttpServer.cpp +++ b/vnext/Test/HttpServer.cpp @@ -252,8 +252,14 @@ void HttpServer::Start() }); } -void HttpServer::Stop() +void HttpServer::Stop(bool abort) { + if (m_context.stopped()) + return; + + if (abort) + m_context.stop(); + m_contextThread.join(); if (m_acceptor.is_open()) diff --git a/vnext/Test/HttpServer.h b/vnext/Test/HttpServer.h index bda5da21b5b..63dde171b9a 100644 --- a/vnext/Test/HttpServer.h +++ b/vnext/Test/HttpServer.h @@ -79,7 +79,7 @@ class HttpServer : public std::enable_shared_from_this void Accept(); void Start(); - void Stop(); + void Stop(bool abort=false); /// // Callback to invoke after a successful response is sent. From e6b9c108bb52154d5c66d82feb30216e68c1a7a9 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Tue, 8 Feb 2022 23:08:15 -0800 Subject: [PATCH 30/47] Use case-insensitive comparison for headers --- .../HttpResourceIntegrationTests.cpp | 4 +++- vnext/Shared/WinRTHttpResource.cpp | 11 ++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index 55e1b4d2ead..a3a7368ef1a 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -106,7 +106,8 @@ TEST_CLASS (HttpResourceIntegrationTest) { { { "Content-Type", "application/json" }, { "Content-Encoding", "ASCII" }, - { "A", "V" }, + { "name3", "value3" }, + { "name4", "value4" }, }, {} /*bodyData*/, "text", @@ -122,6 +123,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { Assert::AreEqual({}, error); Assert::AreEqual(200, statusCode); + //TODO: Validate response headers } TEST_METHOD(RequestGetFails) { diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index d2a21424345..b351ada0a1d 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -76,18 +76,19 @@ void WinRTHttpResource::SendRequest( string contentEncoding; string contentLength; - // TODO: Check casing + // Headers are generally case-insensitive + // https://www.ietf.org/rfc/rfc2616.txt section 4.2 for (auto &header : headers) { - if (header.first == "content-type") { + if (_stricmp(header.first.c_str(), "content-type") == 0) { bool success = HttpMediaTypeHeaderValue::TryParse(to_hstring(header.second), contentType); if (!success && m_onError) { return m_onError(requestId, "Failed to parse Content-Type"); } - } else if (header.first == "content-encoding") { + } else if (_stricmp(header.first.c_str(), "content-encoding") == 0) { contentEncoding = header.second; - } else if (header.first == "content-length") { + } else if (_stricmp(header.first.c_str(), "content-length") == 0) { contentLength = header.second; - } else if (header.first == "authorization") { + } else if (_stricmp(header.first.c_str(), "authorization") == 0) { bool success = request.Headers().TryAppendWithoutValidation(to_hstring(header.first), to_hstring(header.second)); if (!success && m_onError) { From 1d1b9e99b792f2d96ac868895eaec4123039f819 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 9 Feb 2022 00:39:06 -0800 Subject: [PATCH 31/47] Collect both response and content headers --- .../HttpResourceIntegrationTests.cpp | 48 +++++++++++-------- vnext/Shared/IHttpResource.h | 2 +- vnext/Shared/WinRTHttpResource.cpp | 12 +++-- vnext/Shared/WinRTHttpResource.h | 6 +-- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index a3a7368ef1a..296b8be8cf8 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -8,6 +8,7 @@ #include #include #include +#include // Standard Library #include @@ -65,7 +66,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { TEST_METHOD(RequestGetHeadersSucceeds) { promise promise; string error; - int statusCode = 0; + IHttpResource::Response response; // HTTP call scope { @@ -73,24 +74,21 @@ TEST_CLASS (HttpResourceIntegrationTest) { server->SetOnGet([](const http::request &request) -> http::response { http::response response; response.result(http::status::ok); - response.set(http::field::content_type, "text/html"); - response.set(http::field::content_encoding, "utf-8"); + + // Response header + response.set(http::field::server, "Microsoft::React::Test::HttpServer"); + // Response content header response.set(http::field::content_length, "0"); + // Response arbitrary header + response.set("ResponseHeaderName1", "ResponseHeaderValue1"); return response; }); server->Start(); auto resource = IHttpResource::Make(); - resource->SetOnResponse([&promise, &statusCode](int64_t, IHttpResource::Response response) { - statusCode = static_cast(response.StatusCode); - for (auto& header : response.Headers) { - auto &k = header.first; - auto &v = header.second; - - continue; - } - + resource->SetOnResponse([&promise, &response](int64_t, IHttpResource::Response callbackResponse) { + response = callbackResponse; promise.set_value(); }); resource->SetOnError([&promise, &error, &server](int64_t, string &&message) { @@ -99,15 +97,16 @@ TEST_CLASS (HttpResourceIntegrationTest) { server->Stop(true /*abort*/); }); + //clang-format off resource->SendRequest( "GET", "http://localhost:5555", { - { "Content-Type", "application/json" }, - { "Content-Encoding", "ASCII" }, - { "name3", "value3" }, - { "name4", "value4" }, + {"Content-Type", "application/json"}, + {"Content-Encoding", "ASCII"}, + {"name3", "value3"}, + {"name4", "value4"}, }, {} /*bodyData*/, "text", @@ -116,14 +115,25 @@ TEST_CLASS (HttpResourceIntegrationTest) { false /*withCredentials*/, [](int64_t) {}); //clang-format on + server->Stop(); } promise.get_future().wait(); - Assert::AreEqual({}, error); - Assert::AreEqual(200, statusCode); - //TODO: Validate response headers + Assert::AreEqual({}, error, L"Error encountered"); + for (auto header : response.Headers) { + if (header.first == "Server") { + Assert::AreEqual({"Microsoft::React::Test::HttpServer"}, header.second, L"Wrong header"); + } else if (header.first == "Content-Length") { + Assert::AreEqual({"0"}, header.second, L"Wrong header"); + } else if (header.first == "ResponseHeaderName1") { + Assert::AreEqual({"ResponseHeaderValue1"}, header.second, L"Wrong header"); + } else { + string message = "Unexpected header: [" + header.first + "]=[" + header.second + "]"; + Assert::Fail(Microsoft::Common::Unicode::Utf8ToUtf16(message).c_str()); + } + } } TEST_METHOD(RequestGetFails) { diff --git a/vnext/Shared/IHttpResource.h b/vnext/Shared/IHttpResource.h index bfe86145f0c..7f97a304310 100644 --- a/vnext/Shared/IHttpResource.h +++ b/vnext/Shared/IHttpResource.h @@ -47,7 +47,7 @@ struct IHttpResource { virtual void SetOnResponse(std::function &&handler) noexcept = 0; virtual void SetOnData(std::function &&handler) noexcept = 0; virtual void SetOnError( - std::function &&handler) noexcept = 0; + std::function &&handler) noexcept = 0; }; } // namespace Microsoft::React diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index b351ada0a1d..eead5a545ac 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -138,11 +138,11 @@ void WinRTHttpResource::SendRequest( } PerformSendRequest(requestId, request, responseType == "text"); - } catch (std::exception const& e) { + } catch (std::exception const &e) { if (m_onError) { m_onError(requestId, e.what()); } - } catch(hresult_error const& e) { + } catch (hresult_error const &e) { if (m_onError) { m_onError(requestId, Utilities::HResultToString(e)); } @@ -189,7 +189,7 @@ void WinRTHttpResource::SetOnData(function &&handler) noexcept +void WinRTHttpResource::SetOnError(function &&handler) noexcept /*override*/ { m_onError = std::move(handler); } @@ -232,9 +232,15 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage requ if (response) { // TODO: check nullptr? if (self->m_onResponse) { Headers headers; + + // Gather headers for both the response content and the response itself + // See Invoke-WebRequest PowerShell cmdlet or Chromium response handling for (auto header : response.Headers()) { headers.emplace(to_string(header.Key()), to_string(header.Value())); } + for (auto header : response.Content().Headers()) { + headers.emplace(to_string(header.Key()), to_string(header.Value())); + } string url = to_string(response.RequestMessage().RequestUri().AbsoluteUri()); self->m_onResponse( 0 /*requestId*/, {static_cast(response.StatusCode()), std::move(headers), std::move(url)}); diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index d266bdcf3aa..dbe830e5c98 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -28,7 +28,7 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t std::function m_onRequest; std::function m_onResponse; std::function m_onData; - std::function m_onError; + std::function m_onError; void AddRequest(int64_t requestId, ResponseType response) noexcept; @@ -66,8 +66,8 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t void SetOnRequest(std::function &&handler) noexcept override; void SetOnResponse(std::function &&handler) noexcept override; void SetOnData(std::function &&handler) noexcept override; - void SetOnError( - std::function &&handler) noexcept override; + void SetOnError(std::function + &&handler) noexcept override; }; } // namespace Microsoft::React From 8b2b2eb6e24118b04e29da9c96c715115fd4ad6b Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 9 Feb 2022 00:54:28 -0800 Subject: [PATCH 32/47] Address feedback --- vnext/Shared/IHttpResource.h | 2 +- vnext/Shared/Modules/HttpModule.cpp | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/vnext/Shared/IHttpResource.h b/vnext/Shared/IHttpResource.h index 7f97a304310..7da47f0d2b7 100644 --- a/vnext/Shared/IHttpResource.h +++ b/vnext/Shared/IHttpResource.h @@ -3,7 +3,7 @@ #pragma once -// Standard Libryary +// Standard Library #include #include #include diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 137c076dffd..45a595f8695 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -22,16 +22,16 @@ using Microsoft::React::IHttpResource; constexpr char moduleName[] = "Networking"; // TODO: Add to shared header? (See WebSocketModule) -static void SendEvent(weak_ptr weakInstance, string &&eventName, dynamic &&args) { - if (auto instance = weakInstance.lock()) { +static void SendEvent(weak_ptr weakReactInstance, string &&eventName, dynamic &&args) { + if (auto instance = weakReactInstance.lock()) { instance->callJSFunction("RCTDeviceEventEmitter", "emit", dynamic::array(std::move(eventName), std::move(args))); } } -static shared_ptr CreateHttpResource(weak_ptr weakInstance) { +static shared_ptr CreateHttpResource(weak_ptr weakReactInstance) { auto resource = IHttpResource::Make(); - resource->SetOnResponse([weakInstance](int64_t requestId, IHttpResource::Response &&response) { + resource->SetOnResponse([weakReactInstance](int64_t requestId, IHttpResource::Response &&response) { dynamic headers = dynamic::object(); for (auto &header : response.Headers) { headers[header.first] = header.second; @@ -40,23 +40,23 @@ static shared_ptr CreateHttpResource(weak_ptr weakInsta // TODO: Test response content. dynamic args = dynamic::array(requestId, response.StatusCode, headers, response.Url); - SendEvent(weakInstance, "didReceiveNetworkResponse", std::move(args)); + SendEvent(weakReactInstance, "didReceiveNetworkResponse", std::move(args)); }); - resource->SetOnData([weakInstance](int64_t requestId, std::string &&responseData) { + resource->SetOnData([weakReactInstance](int64_t requestId, std::string &&responseData) { dynamic args = dynamic::array(requestId, std::move(responseData)); - SendEvent(weakInstance, "didReceiveNetworkData", std::move(args)); + SendEvent(weakReactInstance, "didReceiveNetworkData", std::move(args)); // TODO: Move into separate method IF not executed right after onData() - SendEvent(weakInstance, "didCompleteNetworkResponse", dynamic::array(requestId)); + SendEvent(weakReactInstance, "didCompleteNetworkResponse", dynamic::array(requestId)); }); - resource->SetOnError([weakInstance](int64_t requestId, string &&message) { + resource->SetOnError([weakReactInstance](int64_t requestId, string &&message) { dynamic args = dynamic::array(requestId, std::move(message)); // TODO: isTimeout errorArgs.push_back(true); - SendEvent(weakInstance, "didCompleteNetworkResponse", std::move(args)); + SendEvent(weakReactInstance, "didCompleteNetworkResponse", std::move(args)); }); return resource; @@ -89,7 +89,7 @@ std::vector HttpModule::getMethods() auto weakHolder = weak_ptr(m_holder); auto holder = weakHolder.lock(); - auto weakInstance = weak_ptr(holder->Module->getInstance()); + auto weakReactInstance = weak_ptr(holder->Module->getInstance()); return { From 773867e3c130467a3b932ec08f494f1021d0a478 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Wed, 9 Feb 2022 00:59:49 -0800 Subject: [PATCH 33/47] Remove member function HttpModule::SendEvent --- vnext/Shared/Modules/HttpModule.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vnext/Shared/Modules/HttpModule.h b/vnext/Shared/Modules/HttpModule.h index 1ed59cfab2b..7d7297719b1 100644 --- a/vnext/Shared/Modules/HttpModule.h +++ b/vnext/Shared/Modules/HttpModule.h @@ -49,11 +49,5 @@ class HttpModule : public facebook::xplat::module::CxxModule { std::shared_ptr m_resource; std::shared_ptr m_holder; - - // TODO: Decide whether to keep - /// - /// Notifies an event to the current React Instance. - /// - void SendEvent0(std::string &&eventName, folly::dynamic &¶meters); }; } // namespace Microsoft::React From ec85a95d9a5bffe94b922505f6c593183e20fa8d Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Thu, 10 Feb 2022 17:41:35 -0800 Subject: [PATCH 34/47] Remove SetOnRequest --- .../HttpResourceIntegrationTests.cpp | 23 +++++++----- vnext/Test/HttpServer.cpp | 35 ++++++++----------- vnext/Test/HttpServer.h | 5 ++- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp index 296b8be8cf8..8f0092597d1 100644 --- a/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/HttpResourceIntegrationTests.cpp @@ -24,12 +24,13 @@ using std::vector; TEST_CLASS (HttpResourceIntegrationTest) { TEST_METHOD(RequestGetSucceeds) { - promise getPromise; + promise promise; + string error; int statusCode = 0; // HTTP call scope { - auto server = std::make_shared("127.0.0.1", static_cast(5555)); + auto server = std::make_shared("127.0.0.1", static_cast(5556)); server->SetOnGet([](const http::request &request) -> http::response { http::response response; response.result(http::status::ok); @@ -39,13 +40,19 @@ TEST_CLASS (HttpResourceIntegrationTest) { server->Start(); auto resource = IHttpResource::Make(); - resource->SetOnResponse([&getPromise, &statusCode](int64_t, IHttpResource::Response response) { + resource->SetOnResponse([&promise, &statusCode](int64_t, IHttpResource::Response response) { statusCode = static_cast(response.StatusCode); - getPromise.set_value(); + promise.set_value(); + }); + resource->SetOnError([&promise, &error, &server](int64_t, string &&message) { + error = std::move(message); + promise.set_value(); + + server->Abort(); }); resource->SendRequest( "GET", - "http://localhost:5555", + "http://localhost:5556", {} /*header*/, {} /*bodyData*/, "text", @@ -57,12 +64,12 @@ TEST_CLASS (HttpResourceIntegrationTest) { server->Stop(); } // Synchronize response. - getPromise.get_future().wait(); + promise.get_future().wait(); + Assert::AreEqual({}, error); Assert::AreEqual(200, statusCode); } - // TODO: Hangs! Likely headers make server not send response. TEST_METHOD(RequestGetHeadersSucceeds) { promise promise; string error; @@ -95,7 +102,7 @@ TEST_CLASS (HttpResourceIntegrationTest) { error = std::move(message); promise.set_value(); - server->Stop(true /*abort*/); + server->Abort(); }); //clang-format off diff --git a/vnext/Test/HttpServer.cpp b/vnext/Test/HttpServer.cpp index 0ae58e1e828..cfaee9df886 100644 --- a/vnext/Test/HttpServer.cpp +++ b/vnext/Test/HttpServer.cpp @@ -72,10 +72,6 @@ void HttpSession::OnRead(error_code ec, size_t /*transferred*/) Respond(); // ISS:2735328 - Handle request. } -// disable __WARNING_IMPLICIT_CTOR -#pragma warning(push) -#pragma warning(disable : 25001) - void HttpSession::Respond() { switch (m_request.method()) @@ -164,8 +160,6 @@ void HttpSession::Start() Read(); } -#pragma warning(pop) - #pragma endregion // HttpSession #pragma region HttpServer @@ -233,9 +227,9 @@ void HttpServer::OnAccept(error_code ec, tcp::socket socket) session->Start(); } + // ISS:2735328: Uncomment after implementing multiple context threading. // Accept next connection. - // Accept(); //ISS:2735328: Uncomment after implementing multiple context - // threading. + // Accept(); } void HttpServer::Start() @@ -245,27 +239,31 @@ void HttpServer::Start() m_contextThread = std::thread([self = shared_from_this()]() { // See - // https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/io_context/run/overload1.html + // https://www.boost.org/doc/libs/1_76_0/doc/html/boost_asio/reference/io_context/run/overload1.html // The run() function blocks until all work has finished and there are no // more handlers to be dispatched, or until the io_context has been stopped. self->m_context.run(); }); } -void HttpServer::Stop(bool abort) +void HttpServer::Stop() { - if (m_context.stopped()) - return; - - if (abort) - m_context.stop(); - m_contextThread.join(); if (m_acceptor.is_open()) m_acceptor.close(); } +void HttpServer::Abort() +{ + if (m_context.stopped()) + return; + + m_context.stop(); + + Stop(); +} + void HttpServer::SetOnResponseSent(function &&handler) noexcept { m_callbacks.OnResponseSent = std::move(handler); @@ -277,11 +275,6 @@ void HttpServer::SetOnGet( m_callbacks.OnGet = std::move(handler); } -void HttpServer::SetOnRequest(function&& handler) noexcept -{ - m_callbacks.OnRequest = std::move(handler); -} - #pragma endregion HttpServer } // namespace Microsoft::React::Test diff --git a/vnext/Test/HttpServer.h b/vnext/Test/HttpServer.h index 63dde171b9a..0df63995c1f 100644 --- a/vnext/Test/HttpServer.h +++ b/vnext/Test/HttpServer.h @@ -79,7 +79,8 @@ class HttpServer : public std::enable_shared_from_this void Accept(); void Start(); - void Stop(bool abort=false); + void Stop(); + void Abort(); /// // Callback to invoke after a successful response is sent. @@ -92,8 +93,6 @@ class HttpServer : public std::enable_shared_from_this /// void SetOnGet(std::function( const boost::beast::http::request &)> &&onGet) noexcept; - - void SetOnRequest(std::function&& handler) noexcept; }; } // namespace Microsoft::React::Test From d6cf31bcc2fc0486ed6f05e9f72015bf87c20db3 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Thu, 10 Feb 2022 17:43:39 -0800 Subject: [PATCH 35/47] Delete Beast HTTP resource --- vnext/Desktop/HttpResource.cpp | 164 ------------------ vnext/Desktop/HttpResource.h | 53 ------ vnext/Desktop/React.Windows.Desktop.vcxproj | 6 +- .../React.Windows.Desktop.vcxproj.filters | 6 - 4 files changed, 1 insertion(+), 228 deletions(-) delete mode 100644 vnext/Desktop/HttpResource.cpp delete mode 100644 vnext/Desktop/HttpResource.h diff --git a/vnext/Desktop/HttpResource.cpp b/vnext/Desktop/HttpResource.cpp deleted file mode 100644 index 03c604b8a05..00000000000 --- a/vnext/Desktop/HttpResource.cpp +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" - -#include "HttpResource.h" - -#include -#include -#include - -using namespace boost::asio::ip; -using namespace boost::beast::http; - -using std::shared_ptr; -using std::string; -using std::unique_ptr; - -using boostecr = boost::system::error_code const &; - -namespace Microsoft::React { -#pragma region HttpResource members - -HttpResource::HttpResource() noexcept : m_resolver{m_context}, m_socket{m_context} {} - -void HttpResource::SendRequest( - const string &method, - const string &urlString, - const Headers &headers, - BodyData &&bodyData, - const string &responseType, - bool useIncrementalUpdates, - int64_t timeout, - std::function &&callback) noexcept { - // Enforce supported args - assert(responseType == "text" || responseType == "base64"); - assert(!useIncrementalUpdates); - - // ISS:2306365 - Callback with the requestId - - // Validate verb. - unique_ptr url; - try { - url = std::make_unique(string{urlString}); - } catch (...) { - m_errorHandler("Malformed URL"); - return; - } - request req; - req.version(11 /*HTTP 1.1*/); - req.method(string_to_verb(method)); - req.target(url->Target()); - req.set(field::host, url->host); // ISS:2306365 - Determine/append port. - req.set(field::user_agent, BOOST_BEAST_VERSION_STRING); - - for (const auto &header : headers) { - // ISS:2306365 - Deal with content-type, content-encoding? - req.set(header.first, header.second); - } - - // if (!bodyData.empty()) { - // if (!bodyData["string"].empty()) { - // } else if (!bodyData["base64"].empty()) { - // } else if (!bodyData["uri"].empty()) { - // assert(false); // Not implemented. - // } else { - // // Empty request - // } - //} - - if (bodyData.Type == BodyData::Type::String) { - } else if (bodyData.Type == BodyData::Type::Base64) { - } else if (bodyData.Type == BodyData::Type::Uri) { - } else { - } - - m_context.restart(); - - // Send/Receive request. - m_resolver.async_resolve(url->host, url->port, [this, &req](boostecr ec, tcp::resolver::results_type results) { - if (ec) { - if (m_errorHandler) - m_errorHandler(ec.message()); - } else { - boost::asio::async_connect( - m_socket, results.begin(), results.end(), [this, &req](boostecr ec, const basic_resolver_iterator &) { - if (ec) { - if (m_errorHandler) - m_errorHandler(ec.message()); - } else { - async_write(m_socket, move(req), [this](boostecr ec, size_t size) { - if (ec) { - if (m_errorHandler) - m_errorHandler(ec.message()); - } else { - if (m_requestHandler) - m_requestHandler(); - - async_read(m_socket, m_buffer, m_response, [this](boostecr ec, size_t size) { - if (ec) { - if (m_errorHandler) - m_errorHandler(ec.message()); - } else { - if (m_responseHandler) - m_responseHandler(boost::beast::buffers_to_string(m_buffer.data())); - - boost::system::error_code bec; - m_socket.shutdown(tcp::socket::shutdown_both, bec); - if (bec && boost::system::errc::not_connected != bec) // not_connected may happen. Not - // an actual error. - { - if (m_errorHandler) - m_errorHandler(bec.message()); - - // ISS:2306365 - Callback? - } - } - }); // async_read - } - }); // async_write - } - }); // async_connect - } - }); // async_resolve - - m_context.run(); -} - -void HttpResource::AbortRequest() noexcept { - m_context.stop(); - - if (m_socket.is_open()) { - boost::system::error_code bec; - m_socket.shutdown(tcp::socket::shutdown_both, bec); - if (bec && boost::system::errc::not_connected != bec) { - if (m_errorHandler) - m_errorHandler(bec.message()); - } - } -} - -void HttpResource::ClearCookies() noexcept { - assert(false); // Not yet implemented. -} - -#pragma region Handler setters - -void HttpResource::SetOnRequest(std::function &&handler) noexcept { - m_requestHandler = move(handler); -} - -void HttpResource::SetOnResponse(std::function &&handler) noexcept { - m_responseHandler = move(handler); -} - -void HttpResource::SetOnError(std::function &&handler) noexcept { - m_errorHandler = move(handler); -} - -#pragma endregion Handler setters - -#pragma endregion HttpResource members - -} // namespace Microsoft::React diff --git a/vnext/Desktop/HttpResource.h b/vnext/Desktop/HttpResource.h deleted file mode 100644 index 9df259c7f87..00000000000 --- a/vnext/Desktop/HttpResource.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include - -#include -#include -#include -#include - -namespace Microsoft::React { - -class HttpResource : public IHttpResource, public std::enable_shared_from_this { - boost::asio::io_context m_context; - boost::asio::ip::tcp::resolver m_resolver; - boost::asio::ip::tcp::socket m_socket; - - // TODO: Remove? - boost::beast::flat_buffer m_buffer; - boost::beast::http::request m_request; - boost::beast::http::response m_response; - - std::function m_requestHandler; - std::function m_responseHandler; - std::function m_errorHandler; - - public: - HttpResource() noexcept; - -#pragma region IHttpResource members - - void SendRequest( - const std::string &method, - const std::string &url, - const Headers &headers, - BodyData &&bodyData, - const std::string &responseType, - bool useIncrementalUpdates, - std::int64_t timeout, - std::function &&callback) noexcept override; - void AbortRequest() noexcept override; - void ClearCookies() noexcept override; - - void SetOnRequest(std::function &&handler) noexcept override; - void SetOnResponse(std::function &&handler) noexcept override; - void SetOnError(std::function &&handler) noexcept override; - -#pragma endregion -}; - -} // namespace Microsoft::React diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj b/vnext/Desktop/React.Windows.Desktop.vcxproj index e432a2c682a..713e4e908c8 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj @@ -235,9 +235,6 @@ Create - - true - true @@ -301,7 +298,6 @@ - @@ -355,4 +351,4 @@ <_CppWinRTOriginalRootNamespace /> - + \ No newline at end of file diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters index f59900e15ec..8eea3100c0a 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters @@ -122,9 +122,6 @@ Source Files\CxxReactWin32 - - Source Files - Source Files @@ -187,9 +184,6 @@ Header Files - - Header Files - Header Files From dbd368557cfd9246833408132fb55b1b18dfd7bc Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Thu, 10 Feb 2022 18:50:07 -0800 Subject: [PATCH 36/47] Catch specific exceptions where applicable --- vnext/Shared/WinRTHttpResource.cpp | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index eead5a545ac..ae1a66fc6ca 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -65,9 +65,8 @@ void WinRTHttpResource::SendRequest( // Enforce supported args assert(responseType == "text" || responseType == "base64"); - // TODO:Callback? + // TODO:Keep callback argument? - // TODO: Use exception->hresult_error conversion try { HttpMethod httpMethod{to_hstring(method)}; Uri uri{to_hstring(url)}; @@ -106,7 +105,7 @@ void WinRTHttpResource::SendRequest( auto buffer = CryptographicBuffer::DecodeFromBase64String(to_hstring(bodyData.Data)); content = HttpBufferContent{buffer}; } else if (BodyData::Type::Uri == bodyData.Type) { - auto file = StorageFile::GetFileFromApplicationUriAsync(Uri{to_hstring(bodyData.Data)}).get(); // TODO: async?? + auto file = StorageFile::GetFileFromApplicationUriAsync(Uri{to_hstring(bodyData.Data)}).get(); auto stream = file.OpenReadAsync().get(); content = HttpStreamContent{stream}; } else if (BodyData::Type::Form == bodyData.Type) { @@ -130,7 +129,7 @@ void WinRTHttpResource::SendRequest( } } if (!contentLength.empty()) { - const auto contentLengthHeader = _atoi64(contentLength.c_str()); // TODO: Check error? + const auto contentLengthHeader = _atoi64(contentLength.c_str()); // TODO: Alternatives to _atoi64? content.Headers().ContentLength(contentLengthHeader); } @@ -146,7 +145,7 @@ void WinRTHttpResource::SendRequest( if (m_onError) { m_onError(requestId, Utilities::HResultToString(e)); } - } catch (...) { // TODO: Delcare specific exception types + } catch (...) { m_onError(requestId, "Unidentified error sending HTTP request"); } } @@ -165,8 +164,8 @@ void WinRTHttpResource::AbortRequest(int64_t requestId) noexcept /*override*/ { try { request.Cancel(); - } catch (hresult_error const &) { - // TODO: Propagate error? + } catch (hresult_error const &e) { + m_onError(requestId, Utilities::HResultToString(e)); } } @@ -209,13 +208,13 @@ void WinRTHttpResource::RemoveRequest(int64_t requestId) noexcept { fire_and_forget WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage request, bool textResponse) noexcept { auto self = shared_from_this(); - // TODO: Set timeout? // Ensure background thread co_await winrt::resume_background(); try { auto sendRequestOp = self->m_client.SendRequestAsync(request); + self->AddRequest(requestId, sendRequestOp); co_await lessthrow_await_adapter{sendRequestOp}; @@ -229,7 +228,7 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage requ } auto response = sendRequestOp.GetResults(); - if (response) { // TODO: check nullptr? + if (response) { if (self->m_onResponse) { Headers headers; @@ -248,7 +247,7 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage requ } // TODO: Incremental updates? - if (response && response.Content()) { // TODO: check nullptr? + if (response && response.Content()) { auto inputStream = co_await response.Content().ReadAsInputStreamAsync(); auto reader = DataReader{inputStream}; @@ -278,15 +277,20 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage requ self->m_onData(0 /*requestId*/, std::move(responseData)); } } - - // TODO: self->OnRequestSuccess OR, keep in self->OnData. } else { if (self->m_onError) { self->m_onError(requestId, response == nullptr ? "request failed" : "No response content"); } } + } catch (std::exception const &e) { + if (self->m_onError) { + self->m_onError(requestId, e.what()); + } + } catch (hresult_error const &e) { + if (self->m_onError) { + self->m_onError(requestId, Utilities::HResultToString(e)); + } } catch (...) { - // TODO: Lose generic catch if (self->m_onError) { self->m_onError(requestId, "Unhandled exception during request"); } From 664593032864d5585de80c3b2f73f034dbd85a97 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 11 Feb 2022 15:07:19 -0800 Subject: [PATCH 37/47] Rename Add/Remove Request to Track/Untrack Response --- vnext/Shared/WinRTHttpResource.cpp | 18 +++++++++--------- vnext/Shared/WinRTHttpResource.h | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index ae1a66fc6ca..458f52e2bcb 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -155,8 +155,8 @@ void WinRTHttpResource::AbortRequest(int64_t requestId) noexcept /*override*/ { { scoped_lock lock{m_mutex}; - auto iter = m_requests.find(requestId); - if (iter == std::end(m_requests)) { + auto iter = m_responses.find(requestId); + if (iter == std::end(m_responses)) { return; } request = iter->second; @@ -195,14 +195,14 @@ void WinRTHttpResource::SetOnError(functionm_client.SendRequestAsync(request); - self->AddRequest(requestId, sendRequestOp); + self->TrackResponse(requestId, sendRequestOp); co_await lessthrow_await_adapter{sendRequestOp}; auto result = sendRequestOp.ErrorCode(); @@ -223,7 +223,7 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage requ if (self->m_onError) { self->m_onError(requestId, Utilities::HResultToString(std::move(result))); } - self->RemoveRequest(requestId); + self->UntrackResponse(requestId); co_return; } @@ -296,7 +296,7 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage requ } } - self->RemoveRequest(requestId); + self->UntrackResponse(requestId); co_return; // TODO: keep? } diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index dbe830e5c98..03331afa36c 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -23,16 +23,16 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t winrt::Windows::Web::Http::IHttpClient m_client; std::mutex m_mutex; - std::unordered_map m_requests; + std::unordered_map m_responses; std::function m_onRequest; std::function m_onResponse; std::function m_onData; std::function m_onError; - void AddRequest(int64_t requestId, ResponseType response) noexcept; + void TrackResponse(int64_t requestId, ResponseType response) noexcept; - void RemoveRequest(int64_t requestId) noexcept; + void UntrackResponse(int64_t requestId) noexcept; // TODO: Make non-trivial args r-value?? winrt::fire_and_forget PerformSendRequest(/*TODO: shared self?,*/ From 585c4148b2f772b2bdaf1f5cfb7e039bf1791f7d Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 11 Feb 2022 15:32:44 -0800 Subject: [PATCH 38/47] Use r-value references where applicable --- vnext/Shared/IHttpResource.h | 8 ++++---- vnext/Shared/WinRTHttpResource.cpp | 24 ++++++++++++------------ vnext/Shared/WinRTHttpResource.h | 19 ++++++++----------- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/vnext/Shared/IHttpResource.h b/vnext/Shared/IHttpResource.h index 7da47f0d2b7..f5a92c2bf3b 100644 --- a/vnext/Shared/IHttpResource.h +++ b/vnext/Shared/IHttpResource.h @@ -30,11 +30,11 @@ struct IHttpResource { virtual ~IHttpResource() noexcept {} virtual void SendRequest( - const std::string &method, - const std::string &url, - const Headers &&headers, + std::string &&method, + std::string &&url, + Headers &&headers, BodyData &&bodyData, - const std::string &responseType, + std::string &&responseType, bool useIncrementalUpdates, int64_t timeout, bool withCredentials, diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 458f52e2bcb..3bbe9d81422 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -35,8 +35,6 @@ using winrt::Windows::Web::Http::IHttpClient; using winrt::Windows::Web::Http::IHttpContent; using winrt::Windows::Web::Http::Headers::HttpMediaTypeHeaderValue; -namespace {} // namespace - namespace Microsoft::React { #pragma region WinRTHttpResource @@ -44,18 +42,18 @@ namespace Microsoft::React { // TODO: Multi-thread issues? /*static*/ int64_t WinRTHttpResource::s_lastRequestId = 0; -WinRTHttpResource::WinRTHttpResource(IHttpClient client) noexcept : m_client{client} {} +WinRTHttpResource::WinRTHttpResource(IHttpClient&& client) noexcept : m_client{std::move(client)} {} WinRTHttpResource::WinRTHttpResource() noexcept : WinRTHttpResource(winrt::Windows::Web::Http::HttpClient()) {} #pragma region IHttpResource void WinRTHttpResource::SendRequest( - const string &method, - const string &url, - const Headers &&headers, + string &&method, + string &&url, + Headers &&headers, BodyData &&bodyData, - const string &responseType, + string &&responseType, bool useIncrementalUpdates, int64_t timeout, bool withCredentials, @@ -68,8 +66,8 @@ void WinRTHttpResource::SendRequest( // TODO:Keep callback argument? try { - HttpMethod httpMethod{to_hstring(method)}; - Uri uri{to_hstring(url)}; + HttpMethod httpMethod{to_hstring(std::move(method))}; + Uri uri{to_hstring(std::move(url))}; HttpRequestMessage request{httpMethod, uri}; HttpMediaTypeHeaderValue contentType{nullptr}; string contentEncoding; @@ -136,7 +134,7 @@ void WinRTHttpResource::SendRequest( request.Content(content); } - PerformSendRequest(requestId, request, responseType == "text"); + PerformSendRequest(requestId, std::move(request), responseType == "text"); } catch (std::exception const &e) { if (m_onError) { m_onError(requestId, e.what()); @@ -206,14 +204,16 @@ void WinRTHttpResource::UntrackResponse(int64_t requestId) noexcept { } fire_and_forget -WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage request, bool textResponse) noexcept { +WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage&& request, bool textResponse) noexcept { + // Keep references after coroutine suspension. auto self = shared_from_this(); + auto coRequest = std::move(request); // Ensure background thread co_await winrt::resume_background(); try { - auto sendRequestOp = self->m_client.SendRequestAsync(request); + auto sendRequestOp = self->m_client.SendRequestAsync(coRequest); self->TrackResponse(requestId, sendRequestOp); diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index 03331afa36c..24329c5d66f 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -34,26 +34,23 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t void UntrackResponse(int64_t requestId) noexcept; - // TODO: Make non-trivial args r-value?? - winrt::fire_and_forget PerformSendRequest(/*TODO: shared self?,*/ - int64_t requestId, - winrt::Windows::Web::Http::HttpRequestMessage request, - bool textResponse - /*, requestId?*/) noexcept; + winrt::fire_and_forget PerformSendRequest(int64_t requestId, + winrt::Windows::Web::Http::HttpRequestMessage&& request, + bool textResponse) noexcept; public: WinRTHttpResource() noexcept; - WinRTHttpResource(winrt::Windows::Web::Http::IHttpClient client) noexcept; + WinRTHttpResource(winrt::Windows::Web::Http::IHttpClient&& client) noexcept; #pragma region IHttpResource void SendRequest( - const std::string &method, - const std::string &url, - const Headers &&headers, + std::string &&method, + std::string &&url, + Headers &&headers, BodyData &&bodyData, - const std::string &responseType, + std::string &&responseType, bool useIncrementalUpdates, int64_t timeout, bool withCredentials, From 614e568c0c44bf449bc69d7098327a839df51965 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 11 Feb 2022 19:05:57 -0800 Subject: [PATCH 39/47] Set runtime option Http.UseMonolithicModule --- .../RNTesterIntegrationTests.cpp | 2 +- .../Microsoft.ReactNative/Base/CoreNativeModules.cpp | 5 +---- vnext/Shared/Modules/HttpModule.cpp | 4 ++-- vnext/Shared/OInstance.cpp | 6 +++--- vnext/Shared/WinRTHttpResource.cpp | 12 +++++++----- vnext/Shared/WinRTHttpResource.h | 9 +++++---- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp index a7c290513d9..53d21d2bff2 100644 --- a/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp +++ b/vnext/Desktop.IntegrationTests/RNTesterIntegrationTests.cpp @@ -27,7 +27,7 @@ TEST_MODULE_INITIALIZE(InitModule) { SetRuntimeOptionBool("WebSocket.AcceptSelfSigned", true); SetRuntimeOptionBool("UseBeastWebSocket", false); - SetRuntimeOptionBool("Http.UseResourcedModule", true); + SetRuntimeOptionBool("Http.UseMonolithicModule", false); // WebSocketJSExecutor can't register native log hooks. SetRuntimeOptionBool("RNTester.UseWebDebugger", false); diff --git a/vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp b/vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp index 5f12657dc5a..4c1b23037f9 100644 --- a/vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp +++ b/vnext/Microsoft.ReactNative/Base/CoreNativeModules.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -47,9 +46,7 @@ std::vector GetCoreModules( std::vector modules; modules.emplace_back( - Microsoft::React::NetworkingModule::Name, - []() { return std::make_unique(); }, - jsMessageQueue); + "Networking", []() { return Microsoft::React::CreateHttpModule(); }, jsMessageQueue); modules.emplace_back( "Timing", diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 45a595f8695..77ae516c94e 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -95,7 +95,7 @@ std::vector HttpModule::getMethods() { { "sendRequest", - [weakHolder = weak_ptr(m_holder)](dynamic args, Callback cb)//TODO: Check whether 'cb' is needed + [weakHolder = weak_ptr(m_holder)](dynamic args, Callback callback) { auto holder = weakHolder.lock(); if (!holder) { @@ -145,7 +145,7 @@ std::vector HttpModule::getMethods() params["incrementalUpdates"].asBool(), static_cast(params["timeout"].asDouble()), false,//withCredentials, - {}// callback + {} //callback ); } // If resource available } diff --git a/vnext/Shared/OInstance.cpp b/vnext/Shared/OInstance.cpp index 302a52f8fcb..604cd150e58 100644 --- a/vnext/Shared/OInstance.cpp +++ b/vnext/Shared/OInstance.cpp @@ -71,10 +71,10 @@ using std::make_shared; namespace Microsoft::React { /*extern*/ std::unique_ptr CreateHttpModule() noexcept { - if (GetRuntimeOptionBool("Http.UseResourcedModule")) { - return std::make_unique(); - } else { + if (GetRuntimeOptionBool("Http.UseMonolithicModule")) { return std::make_unique(); + } else { + return std::make_unique(); } } diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 3bbe9d81422..c67285be75f 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -39,10 +39,10 @@ namespace Microsoft::React { #pragma region WinRTHttpResource -// TODO: Multi-thread issues? +// TODO: Check for multi-thread issues if there are multiple instances. /*static*/ int64_t WinRTHttpResource::s_lastRequestId = 0; -WinRTHttpResource::WinRTHttpResource(IHttpClient&& client) noexcept : m_client{std::move(client)} {} +WinRTHttpResource::WinRTHttpResource(IHttpClient &&client) noexcept : m_client{std::move(client)} {} WinRTHttpResource::WinRTHttpResource() noexcept : WinRTHttpResource(winrt::Windows::Web::Http::HttpClient()) {} @@ -63,7 +63,9 @@ void WinRTHttpResource::SendRequest( // Enforce supported args assert(responseType == "text" || responseType == "base64"); - // TODO:Keep callback argument? + if (callback) { + callback({requestId}); + } try { HttpMethod httpMethod{to_hstring(std::move(method))}; @@ -204,7 +206,7 @@ void WinRTHttpResource::UntrackResponse(int64_t requestId) noexcept { } fire_and_forget -WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage&& request, bool textResponse) noexcept { +WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage &&request, bool textResponse) noexcept { // Keep references after coroutine suspension. auto self = shared_from_this(); auto coRequest = std::move(request); @@ -271,7 +273,7 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage&& re } else { auto buffer = reader.ReadBuffer(length); auto data = CryptographicBuffer::EncodeToBase64String(buffer); - auto responseData = to_string(data); // TODO: string view??? + auto responseData = to_string(std::wstring_view(data)); if (self->m_onData) { self->m_onData(0 /*requestId*/, std::move(responseData)); diff --git a/vnext/Shared/WinRTHttpResource.h b/vnext/Shared/WinRTHttpResource.h index 24329c5d66f..0d515606921 100644 --- a/vnext/Shared/WinRTHttpResource.h +++ b/vnext/Shared/WinRTHttpResource.h @@ -34,14 +34,15 @@ class WinRTHttpResource : public IHttpResource, public std::enable_shared_from_t void UntrackResponse(int64_t requestId) noexcept; - winrt::fire_and_forget PerformSendRequest(int64_t requestId, - winrt::Windows::Web::Http::HttpRequestMessage&& request, - bool textResponse) noexcept; + winrt::fire_and_forget PerformSendRequest( + int64_t requestId, + winrt::Windows::Web::Http::HttpRequestMessage &&request, + bool textResponse) noexcept; public: WinRTHttpResource() noexcept; - WinRTHttpResource(winrt::Windows::Web::Http::IHttpClient&& client) noexcept; + WinRTHttpResource(winrt::Windows::Web::Http::IHttpClient &&client) noexcept; #pragma region IHttpResource From ebad3a98ccd4ac6d5a3061612125552aa477c5ca Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 11 Feb 2022 19:59:22 -0800 Subject: [PATCH 40/47] Define GetHttpModuleName and GetWebSocketModuleName --- vnext/Shared/CreateModules.h | 2 ++ vnext/Shared/Modules/HttpModule.cpp | 5 +++++ vnext/Shared/Modules/WebSocketModule.cpp | 4 ++++ vnext/Shared/OInstance.cpp | 4 ++-- vnext/Shared/WinRTHttpResource.cpp | 4 +++- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/vnext/Shared/CreateModules.h b/vnext/Shared/CreateModules.h index 50b8d1e1dfa..1cfe1c7d594 100644 --- a/vnext/Shared/CreateModules.h +++ b/vnext/Shared/CreateModules.h @@ -29,8 +29,10 @@ extern std::unique_ptr CreateTimingModule( namespace Microsoft::React { +extern const char *GetHttpModuleName() noexcept; extern std::unique_ptr CreateHttpModule() noexcept; +extern const char *GetWebSocketModuleName() noexcept; extern std::unique_ptr CreateWebSocketModule() noexcept; } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 77ae516c94e..163065f6dbf 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -189,4 +189,9 @@ std::vector HttpModule::getMethods() // clang-format on #pragma endregion CxxModule + +/*extern*/ const char *GetHttpModuleName() noexcept { + return moduleName; +} + } // namespace Microsoft::React diff --git a/vnext/Shared/Modules/WebSocketModule.cpp b/vnext/Shared/Modules/WebSocketModule.cpp index 3e70ebe8c67..827d2e14047 100644 --- a/vnext/Shared/Modules/WebSocketModule.cpp +++ b/vnext/Shared/Modules/WebSocketModule.cpp @@ -246,6 +246,10 @@ std::vector WebSocketModule::getMeth } // getMethods // clang-format on +/*extern*/ const char* GetWebSocketModuleName() noexcept { + return moduleName; +} + /*extern*/ std::unique_ptr CreateWebSocketModule() noexcept { return std::make_unique(); } diff --git a/vnext/Shared/OInstance.cpp b/vnext/Shared/OInstance.cpp index 604cd150e58..0aee1c4949d 100644 --- a/vnext/Shared/OInstance.cpp +++ b/vnext/Shared/OInstance.cpp @@ -539,13 +539,13 @@ std::vector> InstanceImpl::GetDefaultNativeModules modules.push_back(std::make_unique( m_innerInstance, - "Networking", + Microsoft::React::GetHttpModuleName(), [nativeQueue]() -> std::unique_ptr { return Microsoft::React::CreateHttpModule(); }, nativeQueue)); modules.push_back(std::make_unique( m_innerInstance, - "WebSocketModule", + Microsoft::React::GetWebSocketModuleName(), [nativeQueue]() -> std::unique_ptr { return Microsoft::React::CreateWebSocketModule(); }, diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index c67285be75f..96d08826f0f 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -299,7 +299,9 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage &&re } self->UntrackResponse(requestId); - co_return; // TODO: keep? + + //TODO: keep? See https://devblogs.microsoft.com/oldnewthing/?p=106160 + co_return; } #pragma endregion WinRTHttpResource From bb8833f2d206a7df429f248b3ab70b5a930410e2 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Fri, 11 Feb 2022 22:39:30 -0800 Subject: [PATCH 41/47] Remove WS, HTTP and Timing from Desktop test runner --- vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp b/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp index b9de42423bb..28671f4a749 100644 --- a/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp +++ b/vnext/Desktop.IntegrationTests/DesktopTestRunner.cpp @@ -50,14 +50,6 @@ shared_ptr TestRunner::GetInstance( }, nativeQueue}, - {"WebSocketModule", []() -> unique_ptr { return std::make_unique(); }, nativeQueue}, - - {"Networking", - []() -> unique_ptr { return std::make_unique(); }, - nativeQueue}, - - {"Timing", [nativeQueue]() -> unique_ptr { return CreateTimingModule(nativeQueue); }, nativeQueue}, - // Apparently mandatory for /IntegrationTests {TestAppStateModule::name, []() -> unique_ptr { return std::make_unique(); }, From 6ffec132c5800a7549061dd88e05f10c93975069 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 12 Feb 2022 01:52:06 -0800 Subject: [PATCH 42/47] Update XHRTest --- vnext/src/IntegrationTests/XHRTest.js | 213 ++++++++++++++++---------- 1 file changed, 132 insertions(+), 81 deletions(-) diff --git a/vnext/src/IntegrationTests/XHRTest.js b/vnext/src/IntegrationTests/XHRTest.js index 0e74d59762c..3ad4e8e14e2 100644 --- a/vnext/src/IntegrationTests/XHRTest.js +++ b/vnext/src/IntegrationTests/XHRTest.js @@ -13,96 +13,147 @@ const {AppRegistry, View} = ReactNative; const {TestModule} = ReactNative.NativeModules; -class XHRTest extends React.Component<{}, Object> { - state: Object = { - downloading: false, - // set by onreadystatechange - contentLength: 1, - responseLength: 0, - // set by onprogress - progressTotal: 1, - progressLoaded: 0, - - readystateHandler: false, - progressHandler: true, - arraybuffer: false, +type State = { + done: ?number, + xhr: ?XMLHttpRequest, +}; + +class XHRTest extends React.Component<{...}, State> { + //state: Object = { + // downloading: false, + // // set by onreadystatechange + // contentLength: 1, + // responseLength: 0, + // // set by onprogress + // progressTotal: 1, + // progressLoaded: 0, + + // readystateHandler: false, + // progressHandler: true, + // arraybuffer: false, + //}; + + state: State = { + done: 0, + xhr: null, }; - xhr: ?XMLHttpRequest = null; + //xhr: ?XMLHttpRequest = null; - _download = () => { - return new Promise((resolve, reject) => { - let xhr; - if (this.xhr) { - xhr = this.xhr; - xhr.abort(); - } else { - xhr = this.xhr = new XMLHttpRequest(); - } + //_download = () => { + // return new Promise((resolve, reject) => { + // let xhr; + // if (this.xhr) { + // xhr = this.xhr; + // xhr.abort(); + // } else { + // xhr = this.xhr = new XMLHttpRequest(); + // } - const onreadystatechange = () => { - if (xhr.readyState === xhr.HEADERS_RECEIVED) { - const contentLength = parseInt( - xhr.getResponseHeader('Content-Length'), - 10, - ); - this.setState({ - contentLength, - responseLength: 0, - }); - } else if (xhr.readyState === xhr.LOADING && xhr.response) { - this.setState({ - responseLength: xhr.response.length, - }); - } - }; - - if (this.state.readystateHandler) { - xhr.onreadystatechange = onreadystatechange; + // const onreadystatechange = () => { + // if (xhr.readyState === xhr.HEADERS_RECEIVED) { + // const contentLength = parseInt( + // xhr.getResponseHeader('Content-Length'), + // 10, + // ); + // this.setState({ + // contentLength, + // responseLength: 0, + // }); + // } else if (xhr.readyState === xhr.LOADING && xhr.response) { + // this.setState({ + // responseLength: xhr.response.length, + // }); + // } + // }; + + // if (this.state.readystateHandler) { + // xhr.onreadystatechange = onreadystatechange; + // } + // //ISS:2306365 - Uncomment when native module is complete. + // //if (this.state.progressHandler) { + // // xhr.onprogress = onprogress; + // //} + // if (this.state.arraybuffer) { + // xhr.responseType = 'arraybuffer'; + // } + // xhr.onload = () => { + // this.setState({downloading: false}); + // if (xhr.status === 200) { + // let responseType = `Response is a string, ${xhr.response.length} characters long.`; + // if (xhr.response instanceof ArrayBuffer) { + // responseType = `Response is an ArrayBuffer, ${xhr.response.byteLength} bytes long.`; + // } + // console.log('Download complete!', responseType); + // } else if (xhr.status !== 0) { + // console.error( + // 'Error', + // `Server returned HTTP status of ${xhr.status}: ${xhr.responseText}`, + // ); + // } else { + // console.error('Error', xhr.responseText); + // } + // }; + // xhr.open('GET', 'https://en.wikipedia.org/favicon.ico'); + // // Avoid gzip so we can actually show progress + // xhr.setRequestHeader('Accept-Encoding', ''); + // xhr.send(); + + // this.setState({downloading: true}); + // }); + //}; + + _do = () => { + this.xhr.onload = () => { + console.log('onload'); + + this.setState({ + done: 1 + }); + }; + this.xhr.open('GET', 'https://raw.githubusercontent.com/microsoft/react-native-windows/react-native-windows_v0.67.1/NuGet.Config'); + this.xhr.setRequestHeader('Accept-Encoding', ''); + this.xhr.send(); + }; + + _fetch = () => { + fetch('https://raw.githubusercontent.com/microsoft/react-native-windows/react-native-windows_v0.67.1/NuGet.Config').then(res => { + console.log('fetched'); + return res.text(); + }).then(text => { + console.log('textd'); + }); + }; + + _done = () => { + console.log(`_done ${this.state.done}`); + return this.state.done === 1; + }; + + _waitFor = (condition: any, timeout: any, callback: any) => { + let remaining = timeout; + const timeoutFunction = function () { + if (condition()) { + callback(true); + return; } - //ISS:2306365 - Uncomment when native module is complete. - //if (this.state.progressHandler) { - // xhr.onprogress = onprogress; - //} - if (this.state.arraybuffer) { - xhr.responseType = 'arraybuffer'; + remaining--; + if (remaining === 0) { + callback(false); + } else { + setTimeout(timeoutFunction, 1000); } - xhr.onload = () => { - this.setState({downloading: false}); - if (xhr.status === 200) { - let responseType = `Response is a string, ${xhr.response.length} characters long.`; - if (xhr.response instanceof ArrayBuffer) { - responseType = `Response is an ArrayBuffer, ${xhr.response.byteLength} bytes long.`; - } - console.log('Download complete!', responseType); - } else if (xhr.status !== 0) { - console.error( - 'Error', - `Server returned HTTP status of ${xhr.status}: ${xhr.responseText}`, - ); - } else { - console.error('Error', xhr.responseText); - } - }; - xhr.open('GET', 'https://en.wikipedia.org/favicon.ico'); - // Avoid gzip so we can actually show progress - xhr.setRequestHeader('Accept-Encoding', ''); - xhr.send(); - - this.setState({downloading: true}); - }); + }; + setTimeout(timeoutFunction, 1000); }; componentDidMount() { - this._download().then( - function (data) {}, - function (e) { - console.log(e); - TestModule.markTestPassed(false); - }, - ); - - TestModule.markTestPassed(true); + this.xhr = new XMLHttpRequest(); + this._do(); + //this._fetch(); + this._waitFor(this._done, 5, doneSucceeded => { + TestModule.markTestPassed(doneSucceeded); + }); } render(): React.Node { From 3e2a266591cc7ab9d612408b4d1b1aea04f4077f Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 12 Feb 2022 01:57:48 -0800 Subject: [PATCH 43/47] Implement cxxCallback --- vnext/Shared/Modules/HttpModule.cpp | 7 ++++--- vnext/Shared/WinRTHttpResource.cpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/vnext/Shared/Modules/HttpModule.cpp b/vnext/Shared/Modules/HttpModule.cpp index 163065f6dbf..c7eba8fac76 100644 --- a/vnext/Shared/Modules/HttpModule.cpp +++ b/vnext/Shared/Modules/HttpModule.cpp @@ -21,7 +21,6 @@ using Microsoft::React::IHttpResource; constexpr char moduleName[] = "Networking"; -// TODO: Add to shared header? (See WebSocketModule) static void SendEvent(weak_ptr weakReactInstance, string &&eventName, dynamic &&args) { if (auto instance = weakReactInstance.lock()) { instance->callJSFunction("RCTDeviceEventEmitter", "emit", dynamic::array(std::move(eventName), std::move(args))); @@ -95,7 +94,7 @@ std::vector HttpModule::getMethods() { { "sendRequest", - [weakHolder = weak_ptr(m_holder)](dynamic args, Callback callback) + [weakHolder = weak_ptr(m_holder)](dynamic args, Callback cxxCallback) { auto holder = weakHolder.lock(); if (!holder) { @@ -145,7 +144,9 @@ std::vector HttpModule::getMethods() params["incrementalUpdates"].asBool(), static_cast(params["timeout"].asDouble()), false,//withCredentials, - {} //callback + [cxxCallback = std::move(cxxCallback)](int64_t requestId) { + cxxCallback({requestId}); + } ); } // If resource available } diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 96d08826f0f..71801070838 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -64,7 +64,7 @@ void WinRTHttpResource::SendRequest( assert(responseType == "text" || responseType == "base64"); if (callback) { - callback({requestId}); + callback(requestId); } try { From c4750dd44246c789cec6c12b0cb087780e7b828a Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 12 Feb 2022 03:15:37 -0800 Subject: [PATCH 44/47] Pass request ID to callbacks --- vnext/Shared/WinRTHttpResource.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 71801070838..14d0babee4d 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -244,7 +244,7 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage &&re } string url = to_string(response.RequestMessage().RequestUri().AbsoluteUri()); self->m_onResponse( - 0 /*requestId*/, {static_cast(response.StatusCode()), std::move(headers), std::move(url)}); + requestId, {static_cast(response.StatusCode()), std::move(headers), std::move(url)}); } } @@ -268,7 +268,7 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage &&re string responseData = string(Common::Utilities::CheckedReinterpretCast(data.data()), data.size()); if (self->m_onData) { - self->m_onData(0 /*requestId*/, std::move(responseData)); + self->m_onData(requestId, std::move(responseData)); } } else { auto buffer = reader.ReadBuffer(length); @@ -276,7 +276,7 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage &&re auto responseData = to_string(std::wstring_view(data)); if (self->m_onData) { - self->m_onData(0 /*requestId*/, std::move(responseData)); + self->m_onData(requestId, std::move(responseData)); } } } else { From 41ed58ab31216f5ba105b3d648bc7625401e4adc Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 12 Feb 2022 04:33:01 -0800 Subject: [PATCH 45/47] Set default TestStatus for TestResult --- vnext/IntegrationTests/TestRunner.h | 2 +- vnext/Shared/Modules/WebSocketModule.cpp | 2 +- vnext/Shared/WinRTHttpResource.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vnext/IntegrationTests/TestRunner.h b/vnext/IntegrationTests/TestRunner.h index abaf3e4e77e..f41e9ddfc14 100644 --- a/vnext/IntegrationTests/TestRunner.h +++ b/vnext/IntegrationTests/TestRunner.h @@ -14,7 +14,7 @@ namespace Microsoft::React::Test { enum class TestStatus : unsigned int { Pending = 0, Passed, Failed }; struct TestResult { - TestStatus Status; + TestStatus Status{TestStatus::Pending}; std::wstring Message; }; diff --git a/vnext/Shared/Modules/WebSocketModule.cpp b/vnext/Shared/Modules/WebSocketModule.cpp index 827d2e14047..6aa1335caff 100644 --- a/vnext/Shared/Modules/WebSocketModule.cpp +++ b/vnext/Shared/Modules/WebSocketModule.cpp @@ -246,7 +246,7 @@ std::vector WebSocketModule::getMeth } // getMethods // clang-format on -/*extern*/ const char* GetWebSocketModuleName() noexcept { +/*extern*/ const char *GetWebSocketModuleName() noexcept { return moduleName; } diff --git a/vnext/Shared/WinRTHttpResource.cpp b/vnext/Shared/WinRTHttpResource.cpp index 14d0babee4d..571e4c3d13d 100644 --- a/vnext/Shared/WinRTHttpResource.cpp +++ b/vnext/Shared/WinRTHttpResource.cpp @@ -300,7 +300,7 @@ WinRTHttpResource::PerformSendRequest(int64_t requestId, HttpRequestMessage &&re self->UntrackResponse(requestId); - //TODO: keep? See https://devblogs.microsoft.com/oldnewthing/?p=106160 + // TODO: keep? See https://devblogs.microsoft.com/oldnewthing/?p=106160 co_return; } From 366bfcc1ad38968bcee3987116c19c07f3d9b9d3 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 12 Feb 2022 04:34:27 -0800 Subject: [PATCH 46/47] Update XHR test --- vnext/src/IntegrationTests/XHRTest.js | 122 +++++--------------------- 1 file changed, 21 insertions(+), 101 deletions(-) diff --git a/vnext/src/IntegrationTests/XHRTest.js b/vnext/src/IntegrationTests/XHRTest.js index 3ad4e8e14e2..f2e37b66aa9 100644 --- a/vnext/src/IntegrationTests/XHRTest.js +++ b/vnext/src/IntegrationTests/XHRTest.js @@ -14,120 +14,42 @@ const {AppRegistry, View} = ReactNative; const {TestModule} = ReactNative.NativeModules; type State = { - done: ?number, + statusCode: ?number, xhr: ?XMLHttpRequest, }; class XHRTest extends React.Component<{...}, State> { - //state: Object = { - // downloading: false, - // // set by onreadystatechange - // contentLength: 1, - // responseLength: 0, - // // set by onprogress - // progressTotal: 1, - // progressLoaded: 0, - - // readystateHandler: false, - // progressHandler: true, - // arraybuffer: false, - //}; state: State = { - done: 0, + statusCode: 0, xhr: null, }; - //xhr: ?XMLHttpRequest = null; - - //_download = () => { - // return new Promise((resolve, reject) => { - // let xhr; - // if (this.xhr) { - // xhr = this.xhr; - // xhr.abort(); - // } else { - // xhr = this.xhr = new XMLHttpRequest(); - // } - - // const onreadystatechange = () => { - // if (xhr.readyState === xhr.HEADERS_RECEIVED) { - // const contentLength = parseInt( - // xhr.getResponseHeader('Content-Length'), - // 10, - // ); - // this.setState({ - // contentLength, - // responseLength: 0, - // }); - // } else if (xhr.readyState === xhr.LOADING && xhr.response) { - // this.setState({ - // responseLength: xhr.response.length, - // }); - // } - // }; - - // if (this.state.readystateHandler) { - // xhr.onreadystatechange = onreadystatechange; - // } - // //ISS:2306365 - Uncomment when native module is complete. - // //if (this.state.progressHandler) { - // // xhr.onprogress = onprogress; - // //} - // if (this.state.arraybuffer) { - // xhr.responseType = 'arraybuffer'; - // } - // xhr.onload = () => { - // this.setState({downloading: false}); - // if (xhr.status === 200) { - // let responseType = `Response is a string, ${xhr.response.length} characters long.`; - // if (xhr.response instanceof ArrayBuffer) { - // responseType = `Response is an ArrayBuffer, ${xhr.response.byteLength} bytes long.`; - // } - // console.log('Download complete!', responseType); - // } else if (xhr.status !== 0) { - // console.error( - // 'Error', - // `Server returned HTTP status of ${xhr.status}: ${xhr.responseText}`, - // ); - // } else { - // console.error('Error', xhr.responseText); - // } - // }; - // xhr.open('GET', 'https://en.wikipedia.org/favicon.ico'); - // // Avoid gzip so we can actually show progress - // xhr.setRequestHeader('Accept-Encoding', ''); - // xhr.send(); - - // this.setState({downloading: true}); - // }); - //}; - - _do = () => { - this.xhr.onload = () => { - console.log('onload'); + //TODO: Move to a new JS test. + _fetch = () => { + fetch('SOME_URL').then(res => { + console.log('fetched'); + return res.text(); + }).then(text => { + console.log('texted'); + }); + }; + _get = () => { + this.xhr = new XMLHttpRequest(); + this.xhr.onloadend = () => { this.setState({ - done: 1 + statusCode: this.xhr.status, }); }; this.xhr.open('GET', 'https://raw.githubusercontent.com/microsoft/react-native-windows/react-native-windows_v0.67.1/NuGet.Config'); - this.xhr.setRequestHeader('Accept-Encoding', ''); + this.xhr.setRequestHeader('Accept-Encoding', 'utf-8'); this.xhr.send(); }; - _fetch = () => { - fetch('https://raw.githubusercontent.com/microsoft/react-native-windows/react-native-windows_v0.67.1/NuGet.Config').then(res => { - console.log('fetched'); - return res.text(); - }).then(text => { - console.log('textd'); - }); - }; - - _done = () => { - console.log(`_done ${this.state.done}`); - return this.state.done === 1; + _getSucceeded = () => { + console.log(`_getSucceeded [${this.state.statusCode}],[${this.xhr.responseText.length}]`); + return this.state.statusCode === 200 && this.xhr.responseText.length === 387; }; _waitFor = (condition: any, timeout: any, callback: any) => { @@ -148,10 +70,8 @@ class XHRTest extends React.Component<{...}, State> { }; componentDidMount() { - this.xhr = new XMLHttpRequest(); - this._do(); - //this._fetch(); - this._waitFor(this._done, 5, doneSucceeded => { + this._get(); + this._waitFor(this._getSucceeded, 5, (doneSucceeded) => { TestModule.markTestPassed(doneSucceeded); }); } From cebdcce15b0954f9b04f18a8754e0c73ac5cb813 Mon Sep 17 00:00:00 2001 From: "Julio C. Rocha" Date: Sat, 12 Feb 2022 05:11:42 -0800 Subject: [PATCH 47/47] Format XHR test --- vnext/src/IntegrationTests/XHRTest.js | 40 ++++++++++++--------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/vnext/src/IntegrationTests/XHRTest.js b/vnext/src/IntegrationTests/XHRTest.js index f2e37b66aa9..e7106fc176d 100644 --- a/vnext/src/IntegrationTests/XHRTest.js +++ b/vnext/src/IntegrationTests/XHRTest.js @@ -14,42 +14,38 @@ const {AppRegistry, View} = ReactNative; const {TestModule} = ReactNative.NativeModules; type State = { - statusCode: ?number, - xhr: ?XMLHttpRequest, + statusCode: number, + xhr: XMLHttpRequest, }; class XHRTest extends React.Component<{...}, State> { - state: State = { statusCode: 0, - xhr: null, - }; - - //TODO: Move to a new JS test. - _fetch = () => { - fetch('SOME_URL').then(res => { - console.log('fetched'); - return res.text(); - }).then(text => { - console.log('texted'); - }); + xhr: new XMLHttpRequest(), }; _get = () => { - this.xhr = new XMLHttpRequest(); - this.xhr.onloadend = () => { + this.state.xhr.onloadend = () => { this.setState({ - statusCode: this.xhr.status, + statusCode: this.state.xhr.status, }); }; - this.xhr.open('GET', 'https://raw.githubusercontent.com/microsoft/react-native-windows/react-native-windows_v0.67.1/NuGet.Config'); - this.xhr.setRequestHeader('Accept-Encoding', 'utf-8'); - this.xhr.send(); + this.state.xhr.open( + 'GET', + 'https://raw.githubusercontent.com/microsoft/react-native-windows/react-native-windows_v0.67.1/NuGet.Config', + ); + this.state.xhr.setRequestHeader('Accept-Encoding', 'utf-8'); + this.state.xhr.send(); }; _getSucceeded = () => { - console.log(`_getSucceeded [${this.state.statusCode}],[${this.xhr.responseText.length}]`); - return this.state.statusCode === 200 && this.xhr.responseText.length === 387; + console.log( + `_getSucceeded [${this.state.statusCode}],[${this.state.xhr.responseText.length}]`, + ); + return ( + this.state.statusCode === 200 && + this.state.xhr.responseText.length === 387 + ); }; _waitFor = (condition: any, timeout: any, callback: any) => {