Browse Source

URL: Added InputStreamOptions and enable POST data to be sent with URL-encoded parameters.

- Added a new URL::createInputStream() overload that takes an InputStreamOptions helper class to simplify stream creation.
  - Modified the internals of URL and WebInputStream so that parameters are only added to the request body when ParameterHandling is set to inPostData. This allows POST data to be added via URL::withPOSTData() and sent with URL-encoded parameters.
tags/2021-05-28
ed 4 years ago
parent
commit
fa8c446d0c
9 changed files with 353 additions and 163 deletions
  1. +11
    -9
      modules/juce_core/native/juce_android_Network.cpp
  2. +14
    -11
      modules/juce_core/native/juce_curl_Network.cpp
  3. +20
    -14
      modules/juce_core/native/juce_linux_Network.cpp
  4. +9
    -7
      modules/juce_core/native/juce_mac_Network.mm
  5. +12
    -9
      modules/juce_core/native/juce_win32_Network.cpp
  6. +132
    -43
      modules/juce_core/network/juce_URL.cpp
  7. +129
    -56
      modules/juce_core/network/juce_URL.h
  8. +22
    -10
      modules/juce_core/network/juce_WebInputStream.cpp
  9. +4
    -4
      modules/juce_core/network/juce_WebInputStream.h

+ 11
- 9
modules/juce_core/native/juce_android_Network.cpp View File

@@ -324,12 +324,13 @@ class WebInputStream::Pimpl
public:
enum { contentStreamCacheSize = 1024 };
Pimpl (WebInputStream&, const URL& urlToCopy, bool shouldBePost)
Pimpl (WebInputStream&, const URL& urlToCopy, bool isPOSTLike)
: url (urlToCopy),
isContentURL (urlToCopy.getScheme() == "content"),
isPost (shouldBePost),
httpRequest (isPost ? "POST" : "GET")
{}
addParametersToRequestBody (isPOSTLike),
httpRequest (isPOSTLike || url.hasPOSTData() ? "POST" : "GET")
{
}
~Pimpl()
{
@@ -373,14 +374,14 @@ public:
}
else
{
String address = url.toString (! isPost);
String address = url.toString (! addParametersToRequestBody);
if (! address.contains ("://"))
address = "http://" + address;
MemoryBlock postData;
if (isPost)
WebInputStream::createHeadersAndPostData (url, headers, postData);
if (url.hasPOSTData())
WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody);
jbyteArray postDataArray = nullptr;
@@ -406,7 +407,7 @@ public:
stream = GlobalRef (LocalRef<jobject> (env->CallStaticObjectMethod (HTTPStream,
HTTPStream.createHTTPStream,
javaString (address).get(),
(jboolean) isPost,
(jboolean) addParametersToRequestBody,
postDataArray,
javaString (headers).get(),
(jint) timeOutMs,
@@ -535,7 +536,8 @@ public:
private:
const URL url;
bool isContentURL, isPost, eofStreamReached = false;
const bool isContentURL, addParametersToRequestBody;
bool eofStreamReached = false;
int numRedirectsToFollow = 5, timeOutMs = 0;
String httpRequest, headers;
StringPairArray responseHeaders;


+ 14
- 11
modules/juce_core/native/juce_curl_Network.cpp View File

@@ -110,9 +110,12 @@ private:
class WebInputStream::Pimpl
{
public:
Pimpl (WebInputStream& ownerStream, const URL& urlToCopy, bool shouldUsePost)
: owner (ownerStream), url (urlToCopy), isPost (shouldUsePost),
httpRequest (isPost ? "POST" : "GET")
Pimpl (WebInputStream& ownerStream, const URL& urlToCopy, bool isPOSTLike)
: owner (ownerStream),
url (urlToCopy),
addParametersToRequestBody (isPOSTLike),
hasPOSTData (url.hasPOSTData()),
httpRequest (isPOSTLike || url.hasPOSTData() ? "POST" : "GET")
{
jassert (symbols); // Unable to load libcurl!
@@ -220,7 +223,7 @@ public:
//==============================================================================
bool setOptions()
{
auto address = url.toString (! isPost);
auto address = url.toString (! addParametersToRequestBody);
curl_version_info_data* data = symbols->curl_version_info (CURLVERSION_NOW);
jassert (data != nullptr);
@@ -228,8 +231,8 @@ public:
if (! requestHeaders.endsWithChar ('\n'))
requestHeaders << "\r\n";
if (isPost)
WebInputStream::createHeadersAndPostData (url, requestHeaders, headersAndPostData);
if (hasPOSTData)
WebInputStream::createHeadersAndPostData (url, requestHeaders, headersAndPostData, addParametersToRequestBody);
if (! requestHeaders.endsWithChar ('\n'))
requestHeaders << "\r\n";
@@ -244,7 +247,7 @@ public:
&& symbols->curl_easy_setopt (curl, CURLOPT_USERAGENT, userAgent.toRawUTF8()) == CURLE_OK
&& symbols->curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, (maxRedirects > 0 ? 1 : 0)) == CURLE_OK)
{
if (isPost)
if (hasPOSTData)
{
if (symbols->curl_easy_setopt (curl, CURLOPT_READDATA, this) != CURLE_OK
|| symbols->curl_easy_setopt (curl, CURLOPT_READFUNCTION, StaticCurlRead) != CURLE_OK)
@@ -256,7 +259,7 @@ public:
}
// handle special http request commands
bool hasSpecialRequestCmd = isPost ? (httpRequest != "POST") : (httpRequest != "GET");
const auto hasSpecialRequestCmd = hasPOSTData ? (httpRequest != "POST") : (httpRequest != "GET");
if (hasSpecialRequestCmd)
if (symbols->curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, httpRequest.toRawUTF8()) != CURLE_OK)
@@ -323,7 +326,7 @@ public:
listener = webInputListener;
if (isPost)
if (hasPOSTData)
postBuffer = &headersAndPostData;
size_t lastPos = static_cast<size_t> (-1);
@@ -342,7 +345,7 @@ public:
singleStep();
// call callbacks if this is a post request
if (isPost && listener != nullptr && lastPos != postPosition)
if (hasPOSTData && listener != nullptr && lastPos != postPosition)
{
lastPos = postPosition;
@@ -613,7 +616,7 @@ public:
// Options
int timeOutMs = 0;
int maxRedirects = 5;
const bool isPost;
const bool addParametersToRequestBody, hasPOSTData;
String httpRequest;
//==============================================================================


+ 20
- 14
modules/juce_core/native/juce_linux_Network.cpp View File

@@ -70,10 +70,13 @@ bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& /* targetEma
class WebInputStream::Pimpl
{
public:
Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool shouldUsePost)
: owner (pimplOwner), url (urlToCopy),
isPost (shouldUsePost), httpRequestCmd (shouldUsePost ? "POST" : "GET")
{}
Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool isPOSTLike)
: owner (pimplOwner),
url (urlToCopy),
addParametersToRequestBody (isPOSTLike),
httpRequestCmd (isPOSTLike || url.hasPOSTData() ? "POST" : "GET")
{
}
~Pimpl()
{
@@ -127,7 +130,7 @@ public:
return false;
}
address = url.toString (! isPost);
address = url.toString (! addParametersToRequestBody);
statusCode = createConnection (listener, numRedirectsToFollow);
return statusCode != 0;
@@ -256,7 +259,7 @@ private:
MemoryBlock postData;
int64 contentLength = -1, position = 0;
bool finished = false;
const bool isPost;
const bool addParametersToRequestBody;
int timeOutMs = 0;
int numRedirectsToFollow = 5;
String httpRequestCmd;
@@ -285,8 +288,8 @@ private:
{
closeSocket (false);
if (isPost)
WebInputStream::createHeadersAndPostData (url, headers, postData);
if (url.hasPOSTData())
WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody);
auto timeOutTime = Time::getMillisecondCounter();
@@ -367,8 +370,8 @@ private:
freeaddrinfo (result);
{
const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, proxyName, proxyPort, hostPath,
address, headers, postData, isPost, httpRequestCmd));
const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, proxyName, proxyPort, hostPath, address,
headers, postData, httpRequestCmd));
if (! sendHeader (socketHandle, requestHeader, timeOutTime, owner, listener))
{
@@ -474,7 +477,7 @@ private:
const String& proxyName, int proxyPort,
const String& hostPath, const String& originalURL,
const String& userHeaders, const MemoryBlock& postData,
bool isPost, const String& httpRequestCmd)
const String& httpRequestCmd)
{
MemoryOutputStream header;
@@ -488,15 +491,18 @@ private:
"." JUCE_STRINGIFY(JUCE_BUILDNUMBER));
writeValueIfNotPresent (header, userHeaders, "Connection:", "close");
if (isPost)
writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postData.getSize()));
const auto postDataSize = postData.getSize();
const auto hasPostData = postDataSize > 0;
if (hasPostData)
writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postDataSize));
if (userHeaders.isNotEmpty())
header << "\r\n" << userHeaders;
header << "\r\n\r\n";
if (isPost)
if (hasPostData)
header << postData;
return header.getMemoryBlock();


+ 9
- 7
modules/juce_core/native/juce_mac_Network.mm View File

@@ -943,9 +943,11 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE
class WebInputStream::Pimpl
{
public:
Pimpl (WebInputStream& pimplOwner, const URL& urlToUse, bool shouldBePost)
: owner (pimplOwner), url (urlToUse), isPost (shouldBePost),
httpRequestCmd (shouldBePost ? "POST" : "GET")
Pimpl (WebInputStream& pimplOwner, const URL& urlToUse, bool isPOSTLike)
: owner (pimplOwner),
url (urlToUse),
addParametersToRequestBody (isPOSTLike),
httpRequestCmd (isPOSTLike || url.hasPOSTData() ? "POST" : "GET")
{
}
@@ -1089,7 +1091,7 @@ private:
MemoryBlock postData;
int64 position = 0;
bool finished = false;
const bool isPost;
const bool addParametersToRequestBody;
int timeOutMs = 0;
int numRedirectsToFollow = 5;
String httpRequestCmd;
@@ -1101,7 +1103,7 @@ private:
{
jassert (connection == nullptr);
if (NSURL* nsURL = [NSURL URLWithString: juceStringToNS (url.toString (! isPost))])
if (NSURL* nsURL = [NSURL URLWithString: juceStringToNS (url.toString (! addParametersToRequestBody))])
{
if (NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: nsURL
cachePolicy: NSURLRequestReloadIgnoringLocalCacheData
@@ -1111,9 +1113,9 @@ private:
{
[req setHTTPMethod: httpMethod];
if (isPost)
if (url.hasPOSTData())
{
WebInputStream::createHeadersAndPostData (url, headers, postData);
WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody);
if (postData.getSize() > 0)
[req setHTTPBody: [NSData dataWithBytes: postData.getData()


+ 12
- 9
modules/juce_core/native/juce_win32_Network.cpp View File

@@ -35,10 +35,13 @@ namespace juce
class WebInputStream::Pimpl
{
public:
Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool shouldBePost)
: statusCode (0), owner (pimplOwner), url (urlToCopy), isPost (shouldBePost),
httpRequestCmd (isPost ? "POST" : "GET")
{}
Pimpl (WebInputStream& pimplOwner, const URL& urlToCopy, bool isPOSTLike)
: owner (pimplOwner),
url (urlToCopy),
addParametersToRequestBody (isPOSTLike),
httpRequestCmd (isPOSTLike || url.hasPOSTData() ? "POST" : "GET")
{
}
~Pimpl()
{
@@ -75,7 +78,7 @@ public:
return false;
}
String address = url.toString (! isPost);
auto address = url.toString (! addParametersToRequestBody);
while (numRedirectsToFollow-- >= 0)
{
@@ -226,7 +229,7 @@ public:
return true;
}
int statusCode;
int statusCode = 0;
private:
//==============================================================================
@@ -237,7 +240,7 @@ private:
MemoryBlock postData;
int64 position = 0;
bool finished = false;
const bool isPost;
const bool addParametersToRequestBody;
int timeOutMs = 0;
String httpRequestCmd;
int numRedirectsToFollow = 5;
@@ -288,8 +291,8 @@ private:
uc.lpszPassword = password;
uc.dwPasswordLength = passwordNumChars;
if (isPost)
WebInputStream::createHeadersAndPostData (url, headers, postData);
if (url.hasPOSTData())
WebInputStream::createHeadersAndPostData (url, headers, postData, addParametersToRequestBody);
if (InternetCrackUrl (address.toWideCharPointer(), 0, 0, &uc))
openConnection (uc, sessionHandle, address, listener);


+ 132
- 43
modules/juce_core/network/juce_URL.cpp View File

@@ -377,6 +377,11 @@ String URL::getFileName() const
}
#endif
URL::ParameterHandling URL::toHandling (bool usePostData)
{
return usePostData ? ParameterHandling::inPostData : ParameterHandling::inAddress;
}
File URL::fileFromFileSchemeURL (const URL& fileURL)
{
if (! fileURL.isLocalFile())
@@ -447,7 +452,9 @@ URL URL::getChildURL (const String& subPath) const
return u;
}
void URL::createHeadersAndPostData (String& headers, MemoryBlock& postDataToWrite) const
void URL::createHeadersAndPostData (String& headers,
MemoryBlock& postDataToWrite,
bool addParametersToBody) const
{
MemoryOutputStream data (postDataToWrite, false);
@@ -491,8 +498,10 @@ void URL::createHeadersAndPostData (String& headers, MemoryBlock& postDataToWrit
}
else
{
data << URLHelpers::getMangledParameters (*this)
<< postData;
if (addParametersToBody)
data << URLHelpers::getMangledParameters (*this) << postData;
else
data << postData;
// if the user-supplied headers didn't contain a content-type, add one now..
if (! headers.containsIgnoreCase ("Content-Type"))
@@ -656,73 +665,127 @@ private:
}
};
#endif
//==============================================================================
template <typename Member, typename Item>
static URL::InputStreamOptions with (URL::InputStreamOptions options, Member&& member, Item&& item)
{
options.*member = std::forward<Item> (item);
return options;
}
URL::InputStreamOptions::InputStreamOptions (ParameterHandling handling) : parameterHandling (handling) {}
URL::InputStreamOptions URL::InputStreamOptions::withProgressCallback (std::function<bool (int, int)> cb) const
{
return with (*this, &InputStreamOptions::progressCallback, std::move (cb));
}
URL::InputStreamOptions URL::InputStreamOptions::withExtraHeaders (const String& headers) const
{
return with (*this, &InputStreamOptions::extraHeaders, headers);
}
URL::InputStreamOptions URL::InputStreamOptions::withConnectionTimeoutMs (int timeout) const
{
return with (*this, &InputStreamOptions::connectionTimeOutMs, timeout);
}
URL::InputStreamOptions URL::InputStreamOptions::withResponseHeaders (StringPairArray* headers) const
{
return with (*this, &InputStreamOptions::responseHeaders, headers);
}
URL::InputStreamOptions URL::InputStreamOptions::withStatusCode (int* status) const
{
return with (*this, &InputStreamOptions::statusCode, status);
}
URL::InputStreamOptions URL::InputStreamOptions::withNumRedirectsToFollow (int numRedirects) const
{
return with (*this, &InputStreamOptions::numRedirectsToFollow, numRedirects);
}
URL::InputStreamOptions URL::InputStreamOptions::withHttpRequestCmd (const String& cmd) const
{
return with (*this, &InputStreamOptions::httpRequestCmd, cmd);
}
//==============================================================================
std::unique_ptr<InputStream> URL::createInputStream (bool usePostCommand,
OpenStreamProgressCallback* progressCallback,
void* progressCallbackContext,
String headers,
int timeOutMs,
StringPairArray* responseHeaders,
int* statusCode,
int numRedirectsToFollow,
String httpRequestCmd) const
std::unique_ptr<InputStream> URL::createInputStream (const InputStreamOptions& options) const
{
if (isLocalFile())
{
#if JUCE_IOS
// We may need to refresh the embedded bookmark.
return std::make_unique<iOSFileStreamWrapper<FileInputStream>> (const_cast<URL&>(*this));
return std::make_unique<iOSFileStreamWrapper<FileInputStream>> (const_cast<URL&> (*this));
#else
return getLocalFile().createInputStream();
#endif
}
auto wi = std::make_unique<WebInputStream> (*this, usePostCommand);
auto webInputStream = [&]
{
const auto usePost = options.getParameterHandling() == ParameterHandling::inPostData;
auto stream = std::make_unique<WebInputStream> (*this, usePost);
auto extraHeaders = options.getExtraHeaders();
if (extraHeaders.isNotEmpty())
stream->withExtraHeaders (extraHeaders);
auto timeout = options.getConnectionTimeoutMs();
if (timeout != 0)
stream->withConnectionTimeout (timeout);
auto requestCmd = options.getHttpRequestCmd();
if (requestCmd.isNotEmpty())
stream->withCustomRequestCommand (requestCmd);
stream->withNumRedirectsToFollow (options.getNumRedirectsToFollow());
return stream;
}();
struct ProgressCallbackCaller : public WebInputStream::Listener
{
ProgressCallbackCaller (OpenStreamProgressCallback* progressCallbackToUse, void* progressCallbackContextToUse)
: callback (progressCallbackToUse), data (progressCallbackContextToUse)
{}
ProgressCallbackCaller (std::function<bool (int, int)> progressCallbackToUse)
: callback (std::move (progressCallbackToUse))
{
}
bool postDataSendProgress (WebInputStream&, int bytesSent, int totalBytes) override
{
return callback (data, bytesSent, totalBytes);
return callback (bytesSent, totalBytes);
}
OpenStreamProgressCallback* callback;
void* const data;
std::function<bool (int, int)> callback;
};
std::unique_ptr<ProgressCallbackCaller> callbackCaller
(progressCallback != nullptr ? new ProgressCallbackCaller (progressCallback, progressCallbackContext) : nullptr);
if (headers.isNotEmpty())
wi->withExtraHeaders (headers);
if (timeOutMs != 0)
wi->withConnectionTimeout (timeOutMs);
if (httpRequestCmd.isNotEmpty())
wi->withCustomRequestCommand (httpRequestCmd);
auto callbackCaller = [&options]() -> std::unique_ptr<ProgressCallbackCaller>
{
if (auto progressCallback = options.getProgressCallback())
return std::make_unique<ProgressCallbackCaller> (progressCallback);
wi->withNumRedirectsToFollow (numRedirectsToFollow);
return {};
}();
bool success = wi->connect (callbackCaller.get());
auto success = webInputStream->connect (callbackCaller.get());
if (statusCode != nullptr)
*statusCode = wi->getStatusCode();
if (auto* status = options.getStatusCode())
*status = webInputStream->getStatusCode();
if (responseHeaders != nullptr)
*responseHeaders = wi->getResponseHeaders();
if (auto* responseHeaders = options.getResponseHeaders())
*responseHeaders = webInputStream->getResponseHeaders();
if (! success || wi->isError())
if (! success || webInputStream->isError())
return nullptr;
// Older GCCs complain about binding unique_ptr<WebInputStream>&& to unique_ptr<InputStream>
// if we just `return wi` here.
return std::unique_ptr<InputStream> (std::move (wi));
// std::move() needed here for older compilers
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wredundant-move")
return std::move (webInputStream);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
#if JUCE_ANDROID
@@ -752,7 +815,7 @@ std::unique_ptr<OutputStream> URL::createOutputStream() const
bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) const
{
const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
: createInputStream (usePostCommand));
: createInputStream (InputStreamOptions (toHandling (usePostCommand))));
if (in != nullptr)
{
@@ -766,7 +829,7 @@ bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) co
String URL::readEntireTextStream (bool usePostCommand) const
{
const std::unique_ptr<InputStream> in (isLocalFile() ? getLocalFile().createInputStream()
: createInputStream (usePostCommand));
: createInputStream (InputStreamOptions (toHandling (usePostCommand))));
if (in != nullptr)
return in->readEntireStreamAsString();
@@ -911,4 +974,30 @@ bool URL::launchInDefaultBrowser() const
return Process::openDocument (u, {});
}
//==============================================================================
std::unique_ptr<InputStream> URL::createInputStream (bool usePostCommand,
OpenStreamProgressCallback* cb,
void* context,
String headers,
int timeOutMs,
StringPairArray* responseHeaders,
int* statusCode,
int numRedirectsToFollow,
String httpRequestCmd) const
{
std::function<bool (int, int)> callback;
if (cb != nullptr)
callback = [context, cb] (int sent, int total) { return cb (context, sent, total); };
return createInputStream (InputStreamOptions (toHandling (usePostCommand))
.withProgressCallback (std::move (callback))
.withExtraHeaders (headers)
.withConnectionTimeoutMs (timeOutMs)
.withResponseHeaders (responseHeaders)
.withStatusCode (statusCode)
.withNumRedirectsToFollow(numRedirectsToFollow)
.withHttpRequestCmd (httpRequestCmd));
}
} // namespace juce

+ 129
- 56
modules/juce_core/network/juce_URL.h View File

@@ -258,38 +258,33 @@ public:
/** Returns a copy of this URL, with a block of data to send as the POST data.
If you're setting the POST data, be careful not to have any parameters set
as well, otherwise it'll all get thrown in together, and might not have the
desired effect.
If the URL already contains some POST data, this will replace it, rather
than being appended to it.
This data will only be used if you specify a post operation when you call
createInputStream().
If no HTTP command is set when calling createInputStream() to read from
this URL and some data has been set, it will do a POST request.
*/
URL withPOSTData (const String& postData) const;
/** Returns a copy of this URL, with a block of data to send as the POST data.
If you're setting the POST data, be careful not to have any parameters set
as well, otherwise it'll all get thrown in together, and might not have the
desired effect.
If the URL already contains some POST data, this will replace it, rather
than being appended to it.
This data will only be used if you specify a post operation when you call
createInputStream().
If no HTTP command is set when calling createInputStream() to read from
this URL and some data has been set, it will do a POST request.
*/
URL withPOSTData (const MemoryBlock& postData) const;
/** Returns the data that was set using withPOSTData(). */
String getPostData() const { return postData.toString(); }
/** Returns the data that was set using withPOSTData() as MemoryBlock. */
/** Returns the data that was set using withPOSTData() as a MemoryBlock. */
const MemoryBlock& getPostDataAsMemoryBlock() const noexcept { return postData; }
/** Returns true if this URL has POST data set using withPOSTData(). */
bool hasPOSTData() const noexcept { return postData.getSize() > 0; }
//==============================================================================
/** Tries to launch the system's default browser to open the URL.
@@ -308,13 +303,103 @@ public:
*/
static bool isProbablyAnEmailAddress (const String& possibleEmailAddress);
//==============================================================================
/** This callback function can be used by the createInputStream() method.
enum class ParameterHandling
{
inAddress,
inPostData
};
It allows your app to receive progress updates during a lengthy POST operation. If you
want to continue the operation, this should return true, or false to abort.
//==============================================================================
/** Class used to create a set of options to pass to the createInputStream() method.
You can chain together a series of calls to this class's methods to create
a set of whatever options you want to specify, e.g.
@code
if (auto inputStream = URL ("http://www.xyz.com/foobar")
.createInputStream (URL::InputStreamOptions (false).withConnectionTimeoutMs (1000)
.withNumRedirectsToFollow (0)))
{
...
}
@endcode
*/
using OpenStreamProgressCallback = bool (void* context, int bytesSent, int totalBytes);
class JUCE_API InputStreamOptions
{
public:
/** Constructor.
If parameterHandling is ParameterHandling::inPostData and the URL contains some
POST data to send set via one of its withPOSTData() methods, the URL parameters
will be transferred via the request body data. Otherwise the parameters will
be added to the URL address.
*/
explicit InputStreamOptions (ParameterHandling parameterHandling);
//==============================================================================
/** A callback function to keep track of the operation's progress.
This can be useful for lengthy POST operations, so that you can provide user feedback.
*/
InputStreamOptions withProgressCallback (std::function<bool (int bytesSent, int totalBytes)> progressCallback) const;
/** A string that will be appended onto the headers that are used for the request.
It must be a valid set of HTML header directives, separated by newlines.
*/
InputStreamOptions withExtraHeaders (const String& extraHeaders) const;
/** Specifies a timeout for the request in milliseconds.
If 0, this will use whatever default setting the OS chooses. If a negative
number, it will be infinite.
*/
InputStreamOptions withConnectionTimeoutMs (int connectionTimeoutMs) const;
/** If this is non-null, all the (key, value) pairs received as headers
in the response will be stored in this array.
*/
InputStreamOptions withResponseHeaders (StringPairArray* responseHeaders) const;
/** If this is non-null, it will get set to the http status code, if one
is known, or 0 if a code isn't available.
*/
InputStreamOptions withStatusCode (int* statusCode) const;
/** Specifies the number of redirects that will be followed before returning a response.
N.B. This will be ignored on Android which follows up to 5 redirects.
*/
InputStreamOptions withNumRedirectsToFollow (int numRedirectsToFollow) const;
/** Specifies which HTTP request to use.
If this is not set, then this will be determined by the value of `doPostLikeRequest`
or the presence of POST data set via URL::withPOSTData().
*/
InputStreamOptions withHttpRequestCmd (const String& httpRequestCmd) const;
//==============================================================================
ParameterHandling getParameterHandling() const noexcept { return parameterHandling; }
std::function<bool (int, int)> getProgressCallback() const noexcept { return progressCallback; }
String getExtraHeaders() const noexcept { return extraHeaders; }
int getConnectionTimeoutMs() const noexcept { return connectionTimeOutMs; }
StringPairArray* getResponseHeaders() const noexcept { return responseHeaders; }
int* getStatusCode() const noexcept { return statusCode; }
int getNumRedirectsToFollow() const noexcept { return numRedirectsToFollow; }
String getHttpRequestCmd() const noexcept { return httpRequestCmd; }
private:
//==============================================================================
const ParameterHandling parameterHandling;
std::function<bool (int, int)> progressCallback = nullptr;
String extraHeaders;
int connectionTimeOutMs = 0;
StringPairArray* responseHeaders = nullptr;
int* statusCode = nullptr;
int numRedirectsToFollow = 5;
String httpRequestCmd;
};
/** Attempts to open a stream that can read from this URL.
@@ -330,43 +415,11 @@ public:
If the URL represents a local file, then this method simply returns a FileInputStream.
@param doPostLikeRequest if true, the parameters added to this class will be transferred
via the HTTP headers which is typical for POST requests. Otherwise
the parameters will be added to the URL address. Additionally,
if the parameter httpRequestCmd is not specified (or empty) then this
parameter will determine which HTTP request command will be used
(POST or GET).
@param progressCallback if this is not a nullptr, it lets you supply a callback function
to keep track of the operation's progress. This can be useful
for lengthy POST operations, so that you can provide user feedback.
@param progressCallbackContext if a callback is specified, this value will be passed to
the function
@param extraHeaders if not empty, this string is appended onto the headers that
are used for the request. It must therefore be a valid set of HTML
header directives, separated by newlines.
@param connectionTimeOutMs if 0, this will use whatever default setting the OS chooses. If
a negative number, it will be infinite. Otherwise it specifies a
time in milliseconds.
@param responseHeaders if this is non-null, all the (key, value) pairs received as headers
in the response will be stored in this array
@param statusCode if this is non-null, it will get set to the http status code, if one
is known, or 0 if a code isn't available
@param numRedirectsToFollow specifies the number of redirects that will be followed before
returning a response (ignored for Android which follows up to 5 redirects)
@param httpRequestCmd Specify which HTTP Request to use. If this is empty, then doPostRequest
will determine the HTTP request.
@returns a valid input stream, or nullptr if there was an error trying to open it.
*/
std::unique_ptr<InputStream> createInputStream (bool doPostLikeRequest,
OpenStreamProgressCallback* progressCallback = nullptr,
void* progressCallbackContext = nullptr,
String extraHeaders = {},
int connectionTimeOutMs = 0,
StringPairArray* responseHeaders = nullptr,
int* statusCode = nullptr,
int numRedirectsToFollow = 5,
String httpRequestCmd = {}) const;
@param options a set of options that will be used when opening the stream.
@returns a valid input stream, or nullptr if there was an error trying to open it.
*/
std::unique_ptr<InputStream> createInputStream (const InputStreamOptions& options) const;
/** Attempts to open an output stream to a URL for writing
@@ -558,6 +611,25 @@ public:
*/
static URL createWithoutParsing (const String& url);
//==============================================================================
using OpenStreamProgressCallback = bool (void* context, int bytesSent, int totalBytes);
/** This method has been deprecated.
New code should use the method which takes an InputStreamOptions argument instead.
@see InputStreamOptions
*/
std::unique_ptr<InputStream> createInputStream (bool doPostLikeRequest,
OpenStreamProgressCallback* progressCallback = nullptr,
void* progressCallbackContext = nullptr,
String extraHeaders = {},
int connectionTimeOutMs = 0,
StringPairArray* responseHeaders = nullptr,
int* statusCode = nullptr,
int numRedirectsToFollow = 5,
String httpRequestCmd = {}) const;
private:
//==============================================================================
#if JUCE_IOS
@@ -594,9 +666,10 @@ private:
URL (const String&, int);
void init();
void addParameter (const String&, const String&);
void createHeadersAndPostData (String&, MemoryBlock&) const;
void createHeadersAndPostData (String&, MemoryBlock&, bool) const;
URL withUpload (Upload*) const;
static ParameterHandling toHandling (bool);
static File fileFromFileSchemeURL (const URL&);
String getDomainInternal (bool) const;


+ 22
- 10
modules/juce_core/network/juce_WebInputStream.cpp View File

@@ -24,13 +24,12 @@ namespace juce
{
WebInputStream::WebInputStream (const URL& url, const bool usePost)
: pimpl (new Pimpl (*this, url, usePost)), hasCalledConnect (false)
: pimpl (std::make_unique<Pimpl> (*this, url, usePost))
{
}
WebInputStream::~WebInputStream()
{
delete pimpl;
}
WebInputStream& WebInputStream::withExtraHeaders (const String& extra) { pimpl->withExtraHeaders (extra); return *this; }
@@ -60,28 +59,41 @@ bool WebInputStream::connect (Listener* listener)
StringPairArray WebInputStream::parseHttpHeaders (const String& headerData)
{
StringPairArray headerPairs;
StringArray headerLines = StringArray::fromLines (headerData);
auto headerLines = StringArray::fromLines (headerData);
// ignore the first line as this is the status line
for (int i = 1; i < headerLines.size(); ++i)
{
const String& headersEntry = headerLines[i];
const auto& headersEntry = headerLines[i];
if (headersEntry.isNotEmpty())
{
const String key (headersEntry.upToFirstOccurrenceOf (": ", false, false));
const String value (headersEntry.fromFirstOccurrenceOf (": ", false, false));
const String previousValue (headerPairs [key]);
headerPairs.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value));
const auto key = headersEntry.upToFirstOccurrenceOf (": ", false, false);
auto value = [&headersEntry, &headerPairs, &key]
{
const auto currentValue = headersEntry.fromFirstOccurrenceOf (": ", false, false);
const auto previousValue = headerPairs [key];
if (previousValue.isNotEmpty())
return previousValue + "," + currentValue;
return currentValue;
}();
headerPairs.set (key, value);
}
}
return headerPairs;
}
void WebInputStream::createHeadersAndPostData (const URL& aURL, String& headers, MemoryBlock& data)
void WebInputStream::createHeadersAndPostData (const URL& aURL,
String& headers,
MemoryBlock& data,
bool addParametersToBody)
{
aURL.createHeadersAndPostData (headers, data);
aURL.createHeadersAndPostData (headers, data, addParametersToBody);
}
} // namespace juce

+ 4
- 4
modules/juce_core/network/juce_WebInputStream.h View File

@@ -220,14 +220,14 @@ class JUCE_API WebInputStream : public InputStream
bool setPosition (int64 wantedPos) override;
private:
static void createHeadersAndPostData (const URL&, String&, MemoryBlock&);
static StringPairArray parseHttpHeaders (const String& headerData);
static void createHeadersAndPostData (const URL&, String&, MemoryBlock&, bool);
static StringPairArray parseHttpHeaders (const String&);
class Pimpl;
friend class Pimpl;
Pimpl* const pimpl;
bool hasCalledConnect;
std::unique_ptr<Pimpl> pimpl;
bool hasCalledConnect = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream)
};


Loading…
Cancel
Save