Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
80e950a
Declare remaining HTTP module event names
JunielKatarn Nov 15, 2022
380c766
Set Progress handler
JunielKatarn Nov 15, 2022
9cffa5b
More debug data
JunielKatarn Nov 16, 2022
18958d7
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn Nov 16, 2022
135fb49
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn Nov 16, 2022
b3724e8
Update AutolinkedNativeModules.g
JunielKatarn Nov 16, 2022
4993c4d
Define IHttpResource::SetOnIncrementalData
JunielKatarn Nov 16, 2022
2b4e144
Set up and invoke OnIncrementalData
JunielKatarn Nov 16, 2022
b417599
Clean up Progress functor
JunielKatarn Nov 17, 2022
33a94f0
Handle didReceiveNetworkDataProgress event
JunielKatarn Nov 17, 2022
2a9462c
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn Nov 21, 2022
1cfae6f
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn Nov 22, 2022
9e47aa3
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn Nov 23, 2022
108329b
Default segment size to 8MB
JunielKatarn Nov 23, 2022
80fad79
Define IHttpResource::SetOnResponseComplete
JunielKatarn Nov 23, 2022
cc2dad2
Revert playground-win32 generated files
JunielKatarn Nov 23, 2022
95321e6
Change files
JunielKatarn Nov 23, 2022
83245a9
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn Nov 30, 2022
8e5b939
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn Dec 2, 2022
beb54d8
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn Dec 6, 2022
9c5624e
Address feedback
JunielKatarn Dec 7, 2022
4f734df
Added docs for IHttpResource methods
JunielKatarn Dec 7, 2022
866fe11
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn Dec 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Implement HTTP incremental updates",
"packageName": "react-native-windows",
"email": "julio.rocha@microsoft.com",
"dependentChangeType": "patch"
}
23 changes: 19 additions & 4 deletions vnext/Shared/Modules/HttpModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ constexpr char moduleName[] = "Networking";
// React event names
constexpr char completedResponse[] = "didCompleteNetworkResponse";
constexpr char receivedResponse[] = "didReceiveNetworkResponse";
constexpr char receivedData[] = "didReceiveNetworkData";
constexpr char sentData[] = "didSendNetworkData";
constexpr char receivedIncrementalData[] = "didReceiveNetworkIncrementalData";
constexpr char receivedDataProgress[] = "didReceiveNetworkDataProgress";
constexpr char receivedData[] = "didReceiveNetworkData";

static void SetUpHttpResource(
shared_ptr<IHttpResource> resource,
Expand All @@ -60,9 +62,6 @@ static void SetUpHttpResource(

resource->SetOnData([weakReactInstance](int64_t requestId, string &&responseData) {
SendEvent(weakReactInstance, receivedData, dynamic::array(requestId, std::move(responseData)));

// TODO: Move into separate method IF not executed right after onData()
SendEvent(weakReactInstance, completedResponse, dynamic::array(requestId));
});

// Explicitly declaring function type to avoid type inference ambiguity.
Expand All @@ -72,6 +71,22 @@ static void SetUpHttpResource(
};
resource->SetOnData(std::move(onDataDynamic));

resource->SetOnIncrementalData(
[weakReactInstance](int64_t requestId, string &&responseData, int64_t progress, int64_t total) {
SendEvent(
weakReactInstance,
receivedIncrementalData,
dynamic::array(requestId, std::move(responseData), progress, total));
});

resource->SetOnDataProgress([weakReactInstance](int64_t requestId, int64_t progress, int64_t total) {
SendEvent(weakReactInstance, receivedDataProgress, dynamic::array(requestId, progress, total));
});

resource->SetOnResponseComplete([weakReactInstance](int64_t requestId) {
SendEvent(weakReactInstance, completedResponse, dynamic::array(requestId));
});

resource->SetOnError([weakReactInstance](int64_t requestId, string &&message, bool isTimeout) {
dynamic args = dynamic::array(requestId, std::move(message));
if (isTimeout) {
Expand Down
126 changes: 126 additions & 0 deletions vnext/Shared/Networking/IHttpResource.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,136 @@ struct IHttpResource {

virtual void ClearCookies() noexcept = 0;

/// <summary>
/// Sets a function to be invoked when a request has been successfully responded.
/// </summary>
/// <param name="handler">
///
/// Parameters:
/// <param name="requestId">
/// Unique number identifying the HTTP request
/// </param>
/// </param>
virtual void SetOnRequestSuccess(std::function<void(int64_t requestId)> &&handler) noexcept = 0;

/// <summary>
/// Sets a function to be invoked when a response arrives and its headers are received.
/// </summary>
/// <param name="handler">
///
/// Parameters:
/// <param name="requestId">
/// Unique number identifying the HTTP request
/// </param>
/// <param name="response">
/// Object containing basic response data
/// </param>
/// </param>
virtual void SetOnResponse(std::function<void(int64_t requestId, Response &&response)> &&handler) noexcept = 0;

/// <summary>
/// Sets a function to be invoked when response content data has been received.
/// </summary>
/// <param name="handler">
///
/// Parameters:
/// <param name="requestId">
/// Unique number identifying the HTTP request
/// </param>
/// <param name="responseData">
/// Response content payload (plain text or Base64-encoded)
/// </param>
/// </param>
virtual void SetOnData(std::function<void(int64_t requestId, std::string &&responseData)> &&handler) noexcept = 0;

/// <summary>
/// Sets a function to be invoked when response content data has been received.
/// </summary>
/// <param name="handler">
///
/// Parameters:
/// <param name="requestId">
/// Unique number identifying the HTTP request
/// </param>
/// <param name="responseData">
/// Structured response content payload (i.e. Blob data)
/// </param>
/// </param>
virtual void SetOnData(std::function<void(int64_t requestId, folly::dynamic &&responseData)> &&handler) noexcept = 0;

/// <summary>
/// Sets a function to be invoked when a response content increment has been received.
/// </summary>
/// <remarks>
/// The handler set by this method will only be called if the request sets the incremental updates flag.
/// The handler is also mutually exclusive with those set by `SetOnData`, which are used for one pass, non-incremental
/// updates.
/// </remarks>
/// <param name="handler">
///
/// Parameters:
/// <param name="requestId">
/// Unique number identifying the HTTP request
/// </param>
/// <param name="responseData">
/// Partial response content data increment (non-accumulative)
/// </param>
/// <param name="progress">
/// Number of bytes received so far
/// </param>
/// <param name="total">
/// Number of total bytes to receive
/// </param>
/// </param>
virtual void SetOnIncrementalData(
std::function<void(int64_t requestId, std::string &&responseData, int64_t progress, int64_t total)>
&&handler) noexcept = 0;

/// <summary>
/// Sets a function to be invoked when response content download progress is reported.
/// </summary>
/// <param name="handler">
///
/// Parameters:
/// <param name="requestId">
/// Unique number identifying the HTTP request
/// </param>
/// <param name="progress">
/// Number of bytes received so far
/// </param>
/// <param name="total">
/// Number of total bytes to receive
/// </param>
/// </param>
virtual void SetOnDataProgress(
std::function<void(int64_t requestId, int64_t progress, int64_t total)> &&handler) noexcept = 0;

/// <summary>
/// Sets a function to be invoked when a response has been fully handled (either succeeded or failed).
/// </summary>
/// <param name="handler">
///
/// Parameters:
/// <param name="requestId">
/// Unique number identifying the HTTP request
/// </param>
/// </param>
virtual void SetOnResponseComplete(std::function<void(int64_t requestId)> &&handler) noexcept = 0;

/// <summary>
/// Sets a function to be invoked when an error condition is found.
/// </summary>
/// <remarks>
/// The handler's purpose is not to report any given HTTP error status (i.e. 403, 501).
/// It is meant to report application errors when executing HTTP requests.
/// </remarks>
/// <param name="handler">
///
/// Parameters:
/// <param name="requestId">
/// Unique number identifying the HTTP request
/// </param>
/// </param>
virtual void SetOnError(
std::function<void(int64_t requestId, std::string &&errorMessage, bool isTimeout)> &&handler) noexcept = 0;
};
Expand Down
80 changes: 72 additions & 8 deletions vnext/Shared/Networking/WinRTHttpResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ using winrt::Windows::Web::Http::IHttpClient;
using winrt::Windows::Web::Http::IHttpContent;
using winrt::Windows::Web::Http::Headers::HttpMediaTypeHeaderValue;

namespace {

constexpr uint32_t operator""_KiB(unsigned long long int x) {
return static_cast<uint32_t>(1024 * x);
}

constexpr uint32_t operator""_MiB(unsigned long long int x) {
return static_cast<uint32_t>(1024_KiB * x);
}

constexpr char responseTypeText[] = "text";
constexpr char responseTypeBase64[] = "base64";
constexpr char responseTypeBlob[] = "blob";

} // namespace
namespace Microsoft::React::Networking {

// May throw winrt::hresult_error
Expand Down Expand Up @@ -258,7 +273,7 @@ void WinRTHttpResource::SendRequest(
bool withCredentials,
std::function<void(int64_t)> &&callback) noexcept /*override*/ {
// Enforce supported args
assert(responseType == "text" || responseType == "base64" || responseType == "blob");
assert(responseType == responseTypeText || responseType == responseTypeBase64 || responseType == responseTypeBlob);

if (callback) {
callback(requestId);
Expand Down Expand Up @@ -336,6 +351,22 @@ void WinRTHttpResource::SetOnData(function<void(int64_t requestId, dynamic &&res
m_onDataDynamic = std::move(handler);
}

void WinRTHttpResource::SetOnIncrementalData(
function<void(int64_t requestId, string &&responseData, int64_t progress, int64_t total)> &&handler) noexcept
/*override*/ {
m_onIncrementalData = std::move(handler);
}

void WinRTHttpResource::SetOnDataProgress(
function<void(int64_t requestId, int64_t progress, int64_t total)> &&handler) noexcept
/*override*/ {
m_onDataProgress = std::move(handler);
}

void WinRTHttpResource::SetOnResponseComplete(function<void(int64_t requestId)> &&handler) noexcept /*override*/ {
m_onComplete = std::move(handler);
}

void WinRTHttpResource::SetOnError(
function<void(int64_t requestId, string &&errorMessage, bool isTimeout)> &&handler) noexcept
/*override*/ {
Expand Down Expand Up @@ -392,6 +423,10 @@ WinRTHttpResource::PerformSendRequest(HttpMethod &&method, Uri &&rtUri, IInspect
self->m_onRequestSuccess(reqArgs->RequestId);
}

if (self->m_onComplete) {
self->m_onComplete(reqArgs->RequestId);
}

co_return;
}
} catch (const hresult_error &e) {
Expand All @@ -405,6 +440,9 @@ WinRTHttpResource::PerformSendRequest(HttpMethod &&method, Uri &&rtUri, IInspect

try {
auto sendRequestOp = self->m_client.SendRequestAsync(coRequest);

auto isText = reqArgs->ResponseType == responseTypeText;

self->TrackResponse(reqArgs->RequestId, sendRequestOp);

if (reqArgs->Timeout > 0) {
Expand Down Expand Up @@ -471,13 +509,14 @@ WinRTHttpResource::PerformSendRequest(HttpMethod &&method, Uri &&rtUri, IInspect
auto inputStream = co_await response.Content().ReadAsInputStreamAsync();
auto reader = DataReader{inputStream};

// #9510 - We currently accumulate all incoming request data in 10MB chunks
const uint32_t segmentSize = 10 * 1024 * 1024;
// Accumulate all incoming request data in 8MB chunks
// Note, the minimum apparent valid chunk size is 128 KB
// Apple's implementation appears to grab 5-8 KB chunks
const uint32_t segmentSize = reqArgs->IncrementalUpdates ? 128_KiB : 8_MiB;

// Let response handler take over, if set
if (auto responseHandler = self->m_responseHandler.lock()) {
if (responseHandler->Supports(reqArgs->ResponseType)) {
// #9510
vector<uint8_t> responseData{};
while (auto loaded = co_await reader.LoadAsync(segmentSize)) {
auto length = reader.UnconsumedBufferLength();
Expand All @@ -494,37 +533,62 @@ WinRTHttpResource::PerformSendRequest(HttpMethod &&method, Uri &&rtUri, IInspect
self->m_onRequestSuccess(reqArgs->RequestId);
}

if (self->m_onComplete) {
self->m_onComplete(reqArgs->RequestId);
}
co_return;
}
}

auto isText = reqArgs->ResponseType == "text";
if (isText) {
reader.UnicodeEncoding(UnicodeEncoding::Utf8);
}

// #9510
int64_t receivedBytes = 0;
string responseData;
winrt::Windows::Storage::Streams::IBuffer buffer;
while (auto loaded = co_await reader.LoadAsync(segmentSize)) {
auto length = reader.UnconsumedBufferLength();
receivedBytes += length;

if (isText) {
auto data = vector<uint8_t>(length);
reader.ReadBytes(data);

responseData += string(Common::Utilities::CheckedReinterpretCast<char *>(data.data()), data.size());
auto incrementData = string(Common::Utilities::CheckedReinterpretCast<char *>(data.data()), data.size());
// #9534 - Send incremental updates.
// See https://github.com/facebook/react-native/blob/v0.70.6/Libraries/Network/RCTNetworking.mm#L561
if (reqArgs->IncrementalUpdates) {
responseData = std::move(incrementData);

if (self->m_onIncrementalData) {
// For total, see #10849
self->m_onIncrementalData(reqArgs->RequestId, std::move(responseData), receivedBytes, 0 /*total*/);
}
} else {
responseData += std::move(incrementData);
}
} else {
buffer = reader.ReadBuffer(length);
auto data = CryptographicBuffer::EncodeToBase64String(buffer);

responseData += winrt::to_string(std::wstring_view(data));

if (self->m_onDataProgress) {
// For total, see #10849
self->m_onDataProgress(reqArgs->RequestId, receivedBytes, 0 /*total*/);
}
}
}

if (self->m_onData) {
// If dealing with text-incremental response data, use m_onIncrementalData instead
if (self->m_onData && !(reqArgs->IncrementalUpdates && isText)) {
self->m_onData(reqArgs->RequestId, std::move(responseData));
}

if (self->m_onComplete) {
self->m_onComplete(reqArgs->RequestId);
}
} else {
if (self->m_onError) {
self->m_onError(reqArgs->RequestId, response == nullptr ? "request failed" : "No response content", false);
Expand Down
10 changes: 10 additions & 0 deletions vnext/Shared/Networking/WinRTHttpResource.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class WinRTHttpResource : public IHttpResource,
std::function<void(int64_t requestId, std::string &&responseData)> m_onData;
std::function<void(int64_t requestId, folly::dynamic &&responseData)> m_onDataDynamic;
std::function<void(int64_t requestId, std::string &&errorMessage, bool isTimeout)> m_onError;
std::function<void(int64_t requestId, std::string &&responseData, int64_t progress, int64_t total)>
m_onIncrementalData;
std::function<void(int64_t requestId, int64_t progress, int64_t total)> m_onDataProgress;
std::function<void(int64_t requestId)> m_onComplete;

// Used for IHttpModuleProxy
std::weak_ptr<IUriHandler> m_uriHandler;
Expand Down Expand Up @@ -80,6 +84,12 @@ class WinRTHttpResource : public IHttpResource,
void SetOnResponse(std::function<void(int64_t requestId, Response &&response)> &&handler) noexcept override;
void SetOnData(std::function<void(int64_t requestId, std::string &&responseData)> &&handler) noexcept override;
void SetOnData(std::function<void(int64_t requestId, folly::dynamic &&responseData)> &&handler) noexcept override;
void SetOnIncrementalData(
std::function<void(int64_t requestId, std::string &&responseData, int64_t progress, int64_t total)>
&&handler) noexcept override;
void SetOnDataProgress(
std::function<void(int64_t requestId, int64_t progress, int64_t total)> &&handler) noexcept override;
void SetOnResponseComplete(std::function<void(int64_t requestId)> &&handler) noexcept override;
void SetOnError(
std::function<void(int64_t requestId, std::string &&errorMessage, bool isTimeout)> &&handler) noexcept override;

Expand Down