diff --git a/CMakeLists.txt b/CMakeLists.txt index e50fa9d..dbefd95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ if(ANDROID AND NOT TARGET AndroidExtensions) FetchContent_Declare( AndroidExtensions GIT_REPOSITORY https://github.com/BabylonJS/AndroidExtensions.git - GIT_TAG efaa68b2882d882470e7d1374b0d52d80db3ca35) + GIT_TAG 4a54e636bde25d4b9f1b02e2414ee642005ff244) message(STATUS "Fetching AndroidExtensions") FetchContent_MakeAvailable(AndroidExtensions) diff --git a/Include/UrlLib/UrlLib.h b/Include/UrlLib/UrlLib.h index c10adb8..b87a3e1 100644 --- a/Include/UrlLib/UrlLib.h +++ b/Include/UrlLib/UrlLib.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace UrlLib { @@ -17,6 +18,7 @@ namespace UrlLib enum class UrlMethod { Get, + Post }; enum class UrlResponseType @@ -49,6 +51,14 @@ namespace UrlLib arcana::task SendAsync(); + void SetRequestBody(std::string requestBody); + + void SetRequestHeader(std::string name, std::string value); + + std::optional GetResponseHeader(const std::string& headerName) const; + + const std::unordered_map& GetAllResponseHeaders() const; + UrlStatusCode StatusCode() const; std::string_view ResponseUrl() const; @@ -57,8 +67,6 @@ namespace UrlLib gsl::span ResponseBuffer() const; - std::optional GetResponseHeader(const std::string& headerName) const; - private: class Impl; class ImplBase; diff --git a/Source/UrlRequest_Android.cpp b/Source/UrlRequest_Android.cpp index bc0f7a5..cabf3c1 100644 --- a/Source/UrlRequest_Android.cpp +++ b/Source/UrlRequest_Android.cpp @@ -91,8 +91,33 @@ namespace UrlLib URL url{m_appPathOrUrl.data()}; URLConnection connection{url.OpenConnection()}; - connection.Connect(); + // set request headers + for (auto request : m_requestHeaders) + { + const std::string& key = request.first; + const std::string& value = request.second; + connection.SetRequestProperty(key, value); + } + m_requestHeaders.clear(); + + // if this a POST request + if (m_method == UrlMethod::Post) + { + ((HttpURLConnection)connection).SetRequestMethod("POST"); + connection.SetDoOutput(true); + + // need to manually set the content length of the request body + size_t numBytes = m_requestBody.size(); + connection.SetRequestProperty("Content-Length", std::to_string(numBytes)); + + OutputStream outputStream{connection.GetOutputStream()}; + OutputStreamWriter writer{outputStream}; + writer.Write(m_requestBody); + writer.Close(); + } + + connection.Connect(); if (connection.GetClass().IsAssignableFrom(HttpURLConnection::Class())) { m_statusCode = static_cast(((HttpURLConnection)connection).GetResponseCode()); diff --git a/Source/UrlRequest_Apple.mm b/Source/UrlRequest_Apple.mm index d351b48..22c8912 100644 --- a/Source/UrlRequest_Apple.mm +++ b/Source/UrlRequest_Apple.mm @@ -52,6 +52,25 @@ void Open(UrlMethod method, const std::string& url) NSURLSession* session{[NSURLSession sharedSession]}; NSURLRequest* request{[NSURLRequest requestWithURL:m_url]}; + NSMutableURLRequest* mutableRequest{[request mutableCopy]}; + + // set header requests + for (auto request: m_requestHeaders) + { + [mutableRequest setValue:@(request.second.data()) forHTTPHeaderField:@(request.first.data())]; + } + + if (m_method == UrlMethod::Post) + { + mutableRequest.HTTPMethod = @"POST"; + // set the body + NSString* stringBody = [NSString stringWithUTF8String:m_requestBody.data()]; + NSData* requestBodyData = [stringBody dataUsingEncoding:NSUTF8StringEncoding]; + mutableRequest.HTTPBody = requestBodyData; + } + + request = [mutableRequest copy]; + __block arcana::task_completion_source taskCompletionSource{}; id completionHandler{^(NSData* data, NSURLResponse* response, NSError* error) diff --git a/Source/UrlRequest_Base.h b/Source/UrlRequest_Base.h index 703b705..c8a32a0 100644 --- a/Source/UrlRequest_Base.h +++ b/Source/UrlRequest_Base.h @@ -21,6 +21,31 @@ namespace UrlLib m_cancellationSource.cancel(); } + void SetRequestBody(std::string requestBody) { + m_requestBody = requestBody; + } + + void SetRequestHeader(std::string name, std::string value) + { + m_requestHeaders[name] = value; + } + + const std::unordered_map& GetAllResponseHeaders() const + { + return m_headers; + } + + std::optional GetResponseHeader(const std::string& headerName) const + { + const auto it = m_headers.find(ToLower(headerName.data())); + if (it == m_headers.end()) + { + return {}; + } + + return it->second; + } + UrlResponseType ResponseType() const { return m_responseType; @@ -46,17 +71,6 @@ namespace UrlLib return m_responseString; } - std::optional GetResponseHeader(const std::string& headerName) const - { - const auto it = m_headers.find(ToLower(headerName.data())); - if (it == m_headers.end()) - { - return {}; - } - - return it->second; - } - protected: static std::string ToLower(const char* str) { @@ -77,5 +91,7 @@ namespace UrlLib std::string m_responseUrl{}; std::string m_responseString{}; std::unordered_map m_headers; + std::string m_requestBody{}; + std::unordered_map m_requestHeaders; }; } diff --git a/Source/UrlRequest_Shared.h b/Source/UrlRequest_Shared.h index d3e9652..0b8ff53 100644 --- a/Source/UrlRequest_Shared.h +++ b/Source/UrlRequest_Shared.h @@ -37,6 +37,26 @@ namespace UrlLib m_impl->ResponseType(value); } + void UrlRequest::SetRequestBody(std::string requestBody) + { + m_impl->SetRequestBody(requestBody); + } + + void UrlRequest::SetRequestHeader(std::string key, std::string value) + { + m_impl->SetRequestHeader(key, value); + } + + const std::unordered_map& UrlRequest::GetAllResponseHeaders() const + { + return m_impl->GetAllResponseHeaders(); + } + + std::optional UrlRequest::GetResponseHeader(const std::string& headerName) const + { + return m_impl->GetResponseHeader(headerName); + } + arcana::task UrlRequest::SendAsync() { return m_impl->SendAsync(); @@ -61,9 +81,4 @@ namespace UrlLib { return m_impl->ResponseBuffer(); } - - std::optional UrlRequest::GetResponseHeader(const std::string& headerName) const - { - return m_impl->GetResponseHeader(headerName); - } } diff --git a/Source/UrlRequest_Windows.cpp b/Source/UrlRequest_Windows.cpp index 0800294..3f185d7 100644 --- a/Source/UrlRequest_Windows.cpp +++ b/Source/UrlRequest_Windows.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace UrlLib { @@ -21,6 +22,8 @@ namespace UrlLib { case UrlMethod::Get: return Web::Http::HttpMethod::Get(); + case UrlMethod::Post: + return Web::Http::HttpMethod::Post(); default: throw std::runtime_error("Unsupported method"); } @@ -83,6 +86,34 @@ namespace UrlLib requestMessage.RequestUri(m_uri); requestMessage.Method(ConvertHttpMethod(m_method)); + std::string contentType; + + for (auto request : m_requestHeaders) + { + // content type needs to be set separately + if (request.first == "Content-Type") + { + contentType = request.second; + } + else + { + requestMessage.Headers().Append(winrt::to_hstring(request.first), winrt::to_hstring(request.second)); + } + } + + m_requestHeaders.clear(); + + // check the method + if (m_method == UrlMethod::Post) + { + // if post, set the content type + requestMessage.Content(Web::Http::HttpStringContent( + winrt::to_hstring(m_requestBody), + winrt::Windows::Storage::Streams::UnicodeEncoding::Utf8, + winrt::to_hstring(contentType)) + ); + } + Web::Http::HttpClient client; return arcana::create_task(client.SendRequestAsync(requestMessage)) .then(arcana::inline_scheduler, m_cancellationSource, [this](Web::Http::HttpResponseMessage responseMessage) @@ -99,6 +130,10 @@ namespace UrlLib { m_headers.insert(std::make_pair(winrt::to_string(iter.Key()), winrt::to_string(iter.Value()))); } + // process the content type response header + std::string contentTypeValue = winrt::to_string(responseMessage.Content().Headers().ContentType().ToString()); + std::string contentTypeKey = "content-type"; + m_headers.insert(std::make_pair(contentTypeKey, contentTypeValue)); switch (m_responseType) {