Bug 1203680 P3 Add a method to ensure an http upload stream is directly cloneable. r=mcmanus

This commit is contained in:
Ben Kelly 2015-09-14 12:04:56 -07:00
Родитель 2577b751f1
Коммит 35d27caaa0
3 изменённых файлов: 112 добавлений и 24 удалений

Просмотреть файл

@ -6,8 +6,9 @@
#include "nsISupports.idl"
interface nsIInputStream;
interface nsIRunnable;
[scriptable, uuid(62e6529a-5cf6-491a-82ef-b3a8273cdd19)]
[scriptable, uuid(2f712b52-19c5-4e0c-9e8f-b5c7c3b67049)]
interface nsIUploadChannel2 : nsISupports
{
/**
@ -47,7 +48,19 @@ interface nsIUploadChannel2 : nsISupports
readonly attribute boolean uploadStreamHasHeaders;
/**
* Clones the upload stream and returns an equivalent stream.
* Ensure the upload stream, if any, is cloneable. This may involve
* async copying, so a callback runnable must be provided. It will
* invoked on the current thread when the upload stream is ready
* for cloning. If the stream is already cloneable, then the callback
* will be invoked synchronously.
*/
[noscript]
void ensureUploadStreamIsCloneable(in nsIRunnable aCallback);
/**
* Clones the upload stream. May return failure if the upload stream
* is not cloneable. If this is not acceptable, use the
* ensureUploadStreamIsCloneable() method first.
*/
[noscript]
nsIInputStream cloneUploadStream();

Просмотреть файл

@ -19,6 +19,7 @@
#include "nsIPrincipal.h"
#include "nsIScriptError.h"
#include "nsISeekableStream.h"
#include "nsIStorageStream.h"
#include "nsITimedChannel.h"
#include "nsIEncodedChannel.h"
#include "nsIApplicationCacheChannel.h"
@ -574,17 +575,98 @@ HttpBaseChannel::SetUploadStream(nsIInputStream *stream,
return NS_OK;
}
static void
EnsureStreamBuffered(nsCOMPtr<nsIInputStream>& aStream)
namespace {
void
CopyComplete(void* aClosure, nsresult aStatus) {
// Called on the STS thread by NS_AsyncCopy
auto channel = static_cast<HttpBaseChannel*>(aClosure);
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableMethodWithArg<nsresult>(
channel, &HttpBaseChannel::EnsureUploadStreamIsCloneableComplete, aStatus);
NS_DispatchToMainThread(runnable.forget());
}
} // anonymous namespace
NS_IMETHODIMP
HttpBaseChannel::EnsureUploadStreamIsCloneable(nsIRunnable* aCallback)
{
if (!NS_InputStreamIsBuffered(aStream)) {
nsCOMPtr<nsIInputStream> bufferedStream;
nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
aStream,
4096);
NS_ENSURE_SUCCESS_VOID(rv);
aStream.swap(bufferedStream);
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
NS_ENSURE_ARG_POINTER(aCallback);
// We could in theory allow multiple callers to use this method,
// but the complexity does not seem worth it yet. Just fail if
// this is called more than once simultaneously.
NS_ENSURE_FALSE(mUploadCloneableCallback, NS_ERROR_UNEXPECTED);
// If the CloneUploadStream() will succeed, then synchronously invoke
// the callback to indicate we're already cloneable.
if (!mUploadStream || NS_InputStreamIsCloneable(mUploadStream)) {
aCallback->Run();
return NS_OK;
}
nsCOMPtr<nsIStorageStream> storageStream;
nsresult rv = NS_NewStorageStream(4096, UINT32_MAX,
getter_AddRefs(storageStream));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> newUploadStream;
rv = storageStream->NewInputStream(0, getter_AddRefs(newUploadStream));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIOutputStream> sink;
rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> source;
if (NS_InputStreamIsBuffered(mUploadStream)) {
source = mUploadStream;
} else {
rv = NS_NewBufferedInputStream(getter_AddRefs(source), mUploadStream, 4096);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
mUploadCloneableCallback = aCallback;
rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
4096, // copy segment size
CopyComplete, this);
if (NS_WARN_IF(NS_FAILED(rv))) {
mUploadCloneableCallback = nullptr;
return rv;
}
// Since we're consuming the old stream, replace it with the new
// stream immediately.
mUploadStream = newUploadStream;
// Explicity hold the stream alive until copying is complete. This will
// be released in EnsureUploadStreamIsCloneableComplete().
AddRef();
return NS_OK;
}
void
HttpBaseChannel::EnsureUploadStreamIsCloneableComplete(nsresult aStatus)
{
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
MOZ_ASSERT(mUploadCloneableCallback);
if (NS_SUCCEEDED(mStatus)) {
mStatus = aStatus;
}
mUploadCloneableCallback->Run();
mUploadCloneableCallback = nullptr;
// Release the reference we grabbed in EnsureUploadStreamIsCloneable() now
// that the copying is complete.
Release();
}
NS_IMETHODIMP
@ -598,21 +680,9 @@ HttpBaseChannel::CloneUploadStream(nsIInputStream** aClonedStream)
}
nsCOMPtr<nsIInputStream> clonedStream;
nsCOMPtr<nsIInputStream> replacementStream;
nsresult rv = NS_CloneInputStream(mUploadStream, getter_AddRefs(clonedStream),
getter_AddRefs(replacementStream));
nsresult rv = NS_CloneInputStream(mUploadStream, getter_AddRefs(clonedStream));
NS_ENSURE_SUCCESS(rv, rv);
if (replacementStream) {
mUploadStream.swap(replacementStream);
// Ensure that the replacement stream is buffered.
EnsureStreamBuffered(mUploadStream);
}
// Ensure that the cloned stream is buffered.
EnsureStreamBuffered(clonedStream);
clonedStream.forget(aClonedStream);
return NS_OK;

Просмотреть файл

@ -269,6 +269,10 @@ public: /* Necko internal use only... */
nsresult DoApplyContentConversions(nsIStreamListener *aNextListener,
nsIStreamListener **aNewNextListener);
// Callback on main thread when NS_AsyncCopy() is finished populating
// the new mUploadStream.
void EnsureUploadStreamIsCloneableComplete(nsresult aStatus);
protected:
nsCOMArray<nsISecurityConsoleMessage> mSecurityConsoleMessages;
@ -329,6 +333,7 @@ protected:
nsHttpRequestHead mRequestHead;
nsCOMPtr<nsIInputStream> mUploadStream;
nsCOMPtr<nsIRunnable> mUploadCloneableCallback;
nsAutoPtr<nsHttpResponseHead> mResponseHead;
nsRefPtr<nsHttpConnectionInfo> mConnectionInfo;
nsCOMPtr<nsIProxyInfo> mProxyInfo;