mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 00:01:50 +00:00
Bug 1367551
- Cancel pushes when we already have the item in cache. r=mayhemer,mcmanus
MozReview-Commit-ID: 24N0Jm85wcC --HG-- extra : rebase_source : e3babc6e569ce723c1f35bd587bb5f0b59f79cfd
This commit is contained in:
parent
01440593f7
commit
17f1c3bcb9
@ -37,6 +37,11 @@
|
||||
#include "mozilla/Sprintf.h"
|
||||
#include "nsSocketTransportService2.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsICacheEntry.h"
|
||||
#include "nsICacheStorageService.h"
|
||||
#include "nsICacheStorage.h"
|
||||
#include "CacheControlParser.h"
|
||||
#include "LoadContextInfo.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
@ -1809,15 +1814,15 @@ Http2Session::RecvPushPromise(Http2Session *self)
|
||||
// does the pushed origin belong on this connection?
|
||||
LOG3(("Http2Session::RecvPushPromise %p origin check %s", self,
|
||||
pushedStream->Origin().get()));
|
||||
RefPtr<nsStandardURL> pushedURL;
|
||||
rv = Http2Stream::MakeOriginURL(pushedStream->Origin(), pushedURL);
|
||||
RefPtr<nsStandardURL> pushedOrigin;
|
||||
rv = Http2Stream::MakeOriginURL(pushedStream->Origin(), pushedOrigin);
|
||||
nsAutoCString pushedHostName;
|
||||
int32_t pushedPort = -1;
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = pushedURL->GetHost(pushedHostName);
|
||||
rv = pushedOrigin->GetHost(pushedHostName);
|
||||
}
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = pushedURL->GetPort(&pushedPort);
|
||||
rv = pushedOrigin->GetPort(&pushedPort);
|
||||
}
|
||||
if (NS_FAILED(rv) ||
|
||||
!self->TestJoinConnection(pushedHostName, pushedPort)) {
|
||||
@ -1840,6 +1845,35 @@ Http2Session::RecvPushPromise(Http2Session *self)
|
||||
self->ResetDownstreamState();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Kick off a lookup into the HTTP cache so we can cancel the push if it's
|
||||
// unneeded (we already have it in our local regular cache). See bug 1367551.
|
||||
nsCOMPtr<nsICacheStorageService> css =
|
||||
do_GetService("@mozilla.org/netwerk/cache-storage-service;1");
|
||||
mozilla::OriginAttributes oa;
|
||||
pushedStream->GetOriginAttributes(&oa);
|
||||
RefPtr<LoadContextInfo> lci = GetLoadContextInfo(false, oa);
|
||||
nsCOMPtr<nsICacheStorage> ds;
|
||||
css->DiskCacheStorage(lci, false, getter_AddRefs(ds));
|
||||
// Build up our full URL for the cache lookup
|
||||
nsAutoCString spec;
|
||||
spec.Assign(pushedStream->Origin());
|
||||
spec.Append(pushedStream->Path());
|
||||
RefPtr<nsStandardURL> pushedURL;
|
||||
// Nifty trick: this doesn't actually do anything origin-specific, it's just
|
||||
// named that way. So by passing it the full spec here, we get a URL with
|
||||
// the full path.
|
||||
// Another nifty trick! Even though this is using nsIURIs (which are not
|
||||
// generally ok off the main thread), since we're not using the protocol
|
||||
// handler to create any URIs, this will work just fine here. Don't try this
|
||||
// at home, though, kids. I'm a trained professional.
|
||||
if (NS_SUCCEEDED(Http2Stream::MakeOriginURL(spec, pushedURL))) {
|
||||
LOG3(("Http2Session::RecvPushPromise %p check disk cache for entry", self));
|
||||
RefPtr<CachePushCheckCallback> cpcc = new CachePushCheckCallback(self, promisedID, pushedStream->GetRequestString());
|
||||
if (NS_FAILED(ds->AsyncOpenURI(pushedURL, EmptyCString(), nsICacheStorage::OPEN_READONLY|nsICacheStorage::OPEN_SECRETLY, cpcc))) {
|
||||
LOG3(("Http2Session::RecvPushPromise %p failed to open cache entry for push check", self));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pushedStream->SetHTTPState(Http2Stream::RESERVED_BY_REMOTE);
|
||||
@ -1853,6 +1887,167 @@ Http2Session::RecvPushPromise(Http2Session *self)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(Http2Session::CachePushCheckCallback, nsICacheEntryOpenCallback);
|
||||
|
||||
Http2Session::CachePushCheckCallback::CachePushCheckCallback(Http2Session *session, uint32_t promisedID, const nsACString &requestString)
|
||||
:mPromisedID(promisedID)
|
||||
{
|
||||
mSession = session;
|
||||
mRequestHead.ParseHeaderSet(requestString.BeginReading());
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Http2Session::CachePushCheckCallback::OnCacheEntryCheck(nsICacheEntry *entry, nsIApplicationCache *appCache, uint32_t *result)
|
||||
{
|
||||
MOZ_ASSERT(OnSocketThread(), "Not on socket thread?!");
|
||||
|
||||
// We never care to fully open the entry, since we won't actually use it.
|
||||
// We just want to be able to do all our checks to see if a future channel can
|
||||
// use this entry, or if we need to accept the push.
|
||||
*result = nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
|
||||
|
||||
bool isForcedValid = false;
|
||||
entry->GetIsForcedValid(&isForcedValid);
|
||||
|
||||
nsHttpResponseHead cachedResponseHead;
|
||||
nsresult rv = nsHttp::GetHttpResponseHeadFromCacheEntry(entry, &cachedResponseHead);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Couldn't make sense of what's in the cache entry, go ahead and accept
|
||||
// the push.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if ((cachedResponseHead.Status() / 100) != 2) {
|
||||
// Assume the push is sending us a success, while we don't have one in the
|
||||
// cache, so we'll accept the push.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Get the method that was used to generate the cached response
|
||||
nsXPIDLCString buf;
|
||||
rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
|
||||
if (NS_FAILED(rv)) {
|
||||
// Can't check request method, accept the push
|
||||
return NS_OK;
|
||||
}
|
||||
nsAutoCString pushedMethod;
|
||||
mRequestHead.Method(pushedMethod);
|
||||
if (!buf.Equals(pushedMethod)) {
|
||||
// Methods don't match, accept the push
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
int64_t size, contentLength;
|
||||
rv = nsHttp::CheckPartial(entry, &size, &contentLength, &cachedResponseHead);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Couldn't figure out if this was partial or not, accept the push.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (size == int64_t(-1) || contentLength != size) {
|
||||
// This is partial content in the cache, accept the push.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAutoCString requestedETag;
|
||||
if (NS_FAILED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag))) {
|
||||
// Can't check etag
|
||||
return NS_OK;
|
||||
}
|
||||
if (!requestedETag.IsEmpty()) {
|
||||
nsAutoCString cachedETag;
|
||||
if (NS_FAILED(cachedResponseHead.GetHeader(nsHttp::ETag, cachedETag))) {
|
||||
// Can't check etag
|
||||
return NS_OK;
|
||||
}
|
||||
if (!requestedETag.Equals(cachedETag)) {
|
||||
// ETags don't match, accept the push.
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoCString imsString;
|
||||
Unused << mRequestHead.GetHeader(nsHttp::If_Modified_Since, imsString);
|
||||
if (!buf.IsEmpty()) {
|
||||
uint32_t ims = buf.ToInteger(&rv);
|
||||
uint32_t lm;
|
||||
rv = cachedResponseHead.GetLastModifiedValue(&lm);
|
||||
if (NS_SUCCEEDED(rv) && lm && lm < ims) {
|
||||
// The push appears to be newer than what's in our cache, accept it.
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoCString cacheControlRequestHeader;
|
||||
Unused << mRequestHead.GetHeader(nsHttp::Cache_Control, cacheControlRequestHeader);
|
||||
CacheControlParser cacheControlRequest(cacheControlRequestHeader);
|
||||
if (cacheControlRequest.NoStore()) {
|
||||
// Don't use a no-store cache entry, accept the push.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsXPIDLCString cachedAuth;
|
||||
rv = entry->GetMetaDataElement("auth", getter_Copies(cachedAuth));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
uint32_t lastModifiedTime;
|
||||
rv = entry->GetLastModified(&lastModifiedTime);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
if ((gHttpHandler->SessionStartTime() > lastModifiedTime) && !cachedAuth.IsEmpty()) {
|
||||
// Need to revalidate this, as the auth is old. Accept the push.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (cachedAuth.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization)) {
|
||||
// They're pushing us something with auth, but we didn't cache anything
|
||||
// with auth. Accept the push.
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool weaklyFramed, isImmutable;
|
||||
nsHttp::DetermineFramingAndImmutability(entry, &cachedResponseHead, true,
|
||||
&weaklyFramed, &isImmutable);
|
||||
|
||||
// We'll need this value in later computations...
|
||||
uint32_t lastModifiedTime;
|
||||
rv = entry->GetLastModified(&lastModifiedTime);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Ugh, this really sucks. OK, accept the push.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Determine if this is the first time that this cache entry
|
||||
// has been accessed during this session.
|
||||
bool fromPreviousSession =
|
||||
(gHttpHandler->SessionStartTime() > lastModifiedTime);
|
||||
|
||||
bool validationRequired = nsHttp::ValidationRequired(isForcedValid,
|
||||
&cachedResponseHead, 0/*NWGH: ??? - loadFlags*/, false, isImmutable, false, mRequestHead, entry,
|
||||
cacheControlRequest, fromPreviousSession);
|
||||
|
||||
if (validationRequired) {
|
||||
// A real channel would most likely hit the net at this point, so let's
|
||||
// accept the push.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// If we get here, then we would be able to use this cache entry. Cancel the
|
||||
// push so as not to waste any more bandwidth.
|
||||
mSession->CleanupStream(mPromisedID, NS_ERROR_FAILURE, Http2Session::REFUSED_STREAM_ERROR);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Http2Session::CachePushCheckCallback::OnCacheEntryAvailable(
|
||||
nsICacheEntry *entry, bool isNew, nsIApplicationCache *appCache,
|
||||
nsresult result)
|
||||
{
|
||||
// Nothing to do here, all the work is in OnCacheEntryCheck.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Http2Session::RecvPing(Http2Session *self)
|
||||
{
|
||||
|
@ -17,6 +17,8 @@
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsDeque.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsHttpRequestHead.h"
|
||||
#include "nsICacheEntryOpenCallback.h"
|
||||
|
||||
#include "Http2Compression.h"
|
||||
|
||||
@ -529,6 +531,22 @@ private:
|
||||
|
||||
nsDataHashtable<nsCStringHashKey, bool> mJoinConnectionCache;
|
||||
|
||||
class CachePushCheckCallback final : public nsICacheEntryOpenCallback
|
||||
{
|
||||
public:
|
||||
CachePushCheckCallback(Http2Session *session, uint32_t promisedID, const nsACString &requestString);
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSICACHEENTRYOPENCALLBACK
|
||||
|
||||
private:
|
||||
~CachePushCheckCallback() { }
|
||||
|
||||
RefPtr<Http2Session> mSession;
|
||||
uint32_t mPromisedID;
|
||||
nsHttpRequestHead mRequestHead;
|
||||
};
|
||||
|
||||
private:
|
||||
/// connect tunnels
|
||||
void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *);
|
||||
|
@ -1557,5 +1557,15 @@ Http2Stream::Finish0RTT(bool aRestart, bool aAlpnChanged)
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Http2Stream::GetOriginAttributes(mozilla::OriginAttributes *oa)
|
||||
{
|
||||
if (!mSocketTransport) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
return mSocketTransport->GetOriginAttributes(oa);
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
||||
|
@ -168,6 +168,8 @@ public:
|
||||
bool Do0RTT();
|
||||
nsresult Finish0RTT(bool aRestart, bool aAlpnIgnored);
|
||||
|
||||
nsresult GetOriginAttributes(mozilla::OriginAttributes *oa);
|
||||
|
||||
protected:
|
||||
static void CreatePushHashKey(const nsCString &scheme,
|
||||
const nsCString &hostHeader,
|
||||
|
@ -8,19 +8,26 @@
|
||||
#include "HttpLog.h"
|
||||
|
||||
#include "nsHttp.h"
|
||||
#include "CacheControlParser.h"
|
||||
#include "PLDHashTable.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/HashFunctions.h"
|
||||
#include "nsCRT.h"
|
||||
#include "nsHttpRequestHead.h"
|
||||
#include "nsHttpResponseHead.h"
|
||||
#include "nsICacheEntry.h"
|
||||
#include "nsIRequest.h"
|
||||
#include <errno.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
// define storage for all atoms
|
||||
#define HTTP_ATOM(_name, _value) nsHttpAtom nsHttp::_name = { _value };
|
||||
namespace nsHttp {
|
||||
#define HTTP_ATOM(_name, _value) nsHttpAtom _name = { _value };
|
||||
#include "nsHttpAtomList.h"
|
||||
#undef HTTP_ATOM
|
||||
}
|
||||
|
||||
// find out how many atoms we have
|
||||
#define HTTP_ATOM(_name, _value) Unused_ ## _name,
|
||||
@ -89,8 +96,9 @@ static const PLDHashTableOps ops = {
|
||||
};
|
||||
|
||||
// We put the atoms in a hash table for speedy lookup.. see ResolveAtom.
|
||||
namespace nsHttp {
|
||||
nsresult
|
||||
nsHttp::CreateAtomTable()
|
||||
CreateAtomTable()
|
||||
{
|
||||
MOZ_ASSERT(!sAtomTable, "atom table already initialized");
|
||||
|
||||
@ -106,7 +114,7 @@ nsHttp::CreateAtomTable()
|
||||
|
||||
// fill the table with our known atoms
|
||||
const char *const atoms[] = {
|
||||
#define HTTP_ATOM(_name, _value) nsHttp::_name._val,
|
||||
#define HTTP_ATOM(_name, _value) _name._val,
|
||||
#include "nsHttpAtomList.h"
|
||||
#undef HTTP_ATOM
|
||||
nullptr
|
||||
@ -126,7 +134,7 @@ nsHttp::CreateAtomTable()
|
||||
}
|
||||
|
||||
void
|
||||
nsHttp::DestroyAtomTable()
|
||||
DestroyAtomTable()
|
||||
{
|
||||
delete sAtomTable;
|
||||
sAtomTable = nullptr;
|
||||
@ -142,14 +150,14 @@ nsHttp::DestroyAtomTable()
|
||||
}
|
||||
|
||||
Mutex *
|
||||
nsHttp::GetLock()
|
||||
GetLock()
|
||||
{
|
||||
return sLock;
|
||||
}
|
||||
|
||||
// this function may be called from multiple threads
|
||||
nsHttpAtom
|
||||
nsHttp::ResolveAtom(const char *str)
|
||||
ResolveAtom(const char *str)
|
||||
{
|
||||
nsHttpAtom atom = { nullptr };
|
||||
|
||||
@ -213,7 +221,7 @@ static const char kValidTokenMap[128] = {
|
||||
1, 1, 1, 0, 1, 0, 1, 0 // 120
|
||||
};
|
||||
bool
|
||||
nsHttp::IsValidToken(const char *start, const char *end)
|
||||
IsValidToken(const char *start, const char *end)
|
||||
{
|
||||
if (start == end)
|
||||
return false;
|
||||
@ -228,7 +236,7 @@ nsHttp::IsValidToken(const char *start, const char *end)
|
||||
}
|
||||
|
||||
const char*
|
||||
nsHttp::GetProtocolVersion(uint32_t pv)
|
||||
GetProtocolVersion(uint32_t pv)
|
||||
{
|
||||
switch (pv) {
|
||||
case HTTP_VERSION_2:
|
||||
@ -247,7 +255,7 @@ nsHttp::GetProtocolVersion(uint32_t pv)
|
||||
|
||||
// static
|
||||
void
|
||||
nsHttp::TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest)
|
||||
TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest)
|
||||
{
|
||||
nsAutoCString str(aSource);
|
||||
|
||||
@ -259,7 +267,7 @@ nsHttp::TrimHTTPWhitespace(const nsACString& aSource, nsACString& aDest)
|
||||
|
||||
// static
|
||||
bool
|
||||
nsHttp::IsReasonableHeaderValue(const nsACString &s)
|
||||
IsReasonableHeaderValue(const nsACString &s)
|
||||
{
|
||||
// Header values MUST NOT contain line-breaks. RFC 2616 technically
|
||||
// permits CTL characters, including CR and LF, in header values provided
|
||||
@ -276,7 +284,7 @@ nsHttp::IsReasonableHeaderValue(const nsACString &s)
|
||||
}
|
||||
|
||||
const char *
|
||||
nsHttp::FindToken(const char *input, const char *token, const char *seps)
|
||||
FindToken(const char *input, const char *token, const char *seps)
|
||||
{
|
||||
if (!input)
|
||||
return nullptr;
|
||||
@ -303,7 +311,7 @@ nsHttp::FindToken(const char *input, const char *token, const char *seps)
|
||||
}
|
||||
|
||||
bool
|
||||
nsHttp::ParseInt64(const char *input, const char **next, int64_t *r)
|
||||
ParseInt64(const char *input, const char **next, int64_t *r)
|
||||
{
|
||||
MOZ_ASSERT(input);
|
||||
MOZ_ASSERT(r);
|
||||
@ -328,11 +336,212 @@ nsHttp::ParseInt64(const char *input, const char **next, int64_t *r)
|
||||
}
|
||||
|
||||
bool
|
||||
nsHttp::IsPermanentRedirect(uint32_t httpStatus)
|
||||
IsPermanentRedirect(uint32_t httpStatus)
|
||||
{
|
||||
return httpStatus == 301 || httpStatus == 308;
|
||||
}
|
||||
|
||||
bool
|
||||
ValidationRequired(bool isForcedValid, nsHttpResponseHead *cachedResponseHead,
|
||||
uint32_t loadFlags, bool allowStaleCacheContent,
|
||||
bool isImmutable, bool customConditionalRequest,
|
||||
nsHttpRequestHead &requestHead,
|
||||
nsICacheEntry *entry, CacheControlParser &cacheControlRequest,
|
||||
bool fromPreviousSession)
|
||||
{
|
||||
// Check isForcedValid to see if it is possible to skip validation.
|
||||
// Don't skip validation if we have serious reason to believe that this
|
||||
// content is invalid (it's expired).
|
||||
// See netwerk/cache2/nsICacheEntry.idl for details
|
||||
if (isForcedValid &&
|
||||
(!cachedResponseHead->ExpiresInPast() ||
|
||||
!cachedResponseHead->MustValidateIfExpired())) {
|
||||
LOG(("NOT validating based on isForcedValid being true.\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the LOAD_FROM_CACHE flag is set, any cached data can simply be used
|
||||
if (loadFlags & nsIRequest::LOAD_FROM_CACHE || allowStaleCacheContent) {
|
||||
LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
|
||||
// it's revalidated with the server.
|
||||
if ((loadFlags & nsIRequest::VALIDATE_ALWAYS) && !isImmutable) {
|
||||
LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Even if the VALIDATE_NEVER flag is set, there are still some cases in
|
||||
// which we must validate the cached response with the server.
|
||||
if (loadFlags & nsIRequest::VALIDATE_NEVER) {
|
||||
LOG(("VALIDATE_NEVER set\n"));
|
||||
// if no-store validate cached response (see bug 112564)
|
||||
if (cachedResponseHead->NoStore()) {
|
||||
LOG(("Validating based on no-store logic\n"));
|
||||
return true;
|
||||
} else {
|
||||
LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// check if validation is strictly required...
|
||||
if (cachedResponseHead->MustValidate()) {
|
||||
LOG(("Validating based on MustValidate() returning TRUE\n"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// possibly serve from cache for a custom If-Match/If-Unmodified-Since
|
||||
// conditional request
|
||||
if (customConditionalRequest &&
|
||||
!requestHead.HasHeader(nsHttp::If_Match) &&
|
||||
!requestHead.HasHeader(nsHttp::If_Unmodified_Since)) {
|
||||
LOG(("Validating based on a custom conditional request\n"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// previously we also checked for a query-url w/out expiration
|
||||
// and didn't do heuristic on it. but defacto that is allowed now.
|
||||
//
|
||||
// Check if the cache entry has expired...
|
||||
|
||||
bool doValidation = true;
|
||||
uint32_t now = NowInSeconds();
|
||||
|
||||
uint32_t age = 0;
|
||||
nsresult rv = cachedResponseHead->ComputeCurrentAge(now, now, &age);
|
||||
if (NS_FAILED(rv)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t freshness = 0;
|
||||
rv = cachedResponseHead->ComputeFreshnessLifetime(&freshness);
|
||||
if (NS_FAILED(rv)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t expiration = 0;
|
||||
rv = entry->GetExpirationTime(&expiration);
|
||||
if (NS_FAILED(rv)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t maxAgeRequest, maxStaleRequest, minFreshRequest;
|
||||
|
||||
LOG((" NowInSeconds()=%u, expiration time=%u, freshness lifetime=%u, age=%u",
|
||||
now, expiration, freshness, age));
|
||||
|
||||
if (cacheControlRequest.NoCache()) {
|
||||
LOG((" validating, no-cache request"));
|
||||
doValidation = true;
|
||||
} else if (cacheControlRequest.MaxStale(&maxStaleRequest)) {
|
||||
uint32_t staleTime = age > freshness ? age - freshness : 0;
|
||||
doValidation = staleTime > maxStaleRequest;
|
||||
LOG((" validating=%d, max-stale=%u requested", doValidation, maxStaleRequest));
|
||||
} else if (cacheControlRequest.MaxAge(&maxAgeRequest)) {
|
||||
doValidation = age > maxAgeRequest;
|
||||
LOG((" validating=%d, max-age=%u requested", doValidation, maxAgeRequest));
|
||||
} else if (cacheControlRequest.MinFresh(&minFreshRequest)) {
|
||||
uint32_t freshTime = freshness > age ? freshness - age : 0;
|
||||
doValidation = freshTime < minFreshRequest;
|
||||
LOG((" validating=%d, min-fresh=%u requested", doValidation, minFreshRequest));
|
||||
} else if (now <= expiration) {
|
||||
doValidation = false;
|
||||
LOG((" not validating, expire time not in the past"));
|
||||
} else if (cachedResponseHead->MustValidateIfExpired()) {
|
||||
doValidation = true;
|
||||
} else if (loadFlags & nsIRequest::VALIDATE_ONCE_PER_SESSION) {
|
||||
// If the cached response does not include expiration infor-
|
||||
// mation, then we must validate the response, despite whether
|
||||
// or not this is the first access this session. This behavior
|
||||
// is consistent with existing browsers and is generally expected
|
||||
// by web authors.
|
||||
if (freshness == 0)
|
||||
doValidation = true;
|
||||
else
|
||||
doValidation = fromPreviousSession;
|
||||
} else {
|
||||
doValidation = true;
|
||||
}
|
||||
|
||||
LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
|
||||
return doValidation;
|
||||
}
|
||||
|
||||
nsresult
|
||||
GetHttpResponseHeadFromCacheEntry(nsICacheEntry *entry, nsHttpResponseHead *cachedResponseHead)
|
||||
{
|
||||
nsXPIDLCString buf;
|
||||
// A "original-response-headers" metadata element holds network original headers,
|
||||
// i.e. the headers in the form as they arrieved from the network.
|
||||
// We need to get the network original headers first, because we need to keep them
|
||||
// in order.
|
||||
nsresult rv = entry->GetMetaDataElement("original-response-headers", getter_Copies(buf));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = cachedResponseHead->ParseCachedOriginalHeaders((char *) buf.get());
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG((" failed to parse original-response-headers\n"));
|
||||
}
|
||||
}
|
||||
|
||||
buf.Adopt(0);
|
||||
// A "response-head" metadata element holds response head, e.g. response status
|
||||
// line and headers in the form Firefox uses them internally (no dupicate
|
||||
// headers, etc.).
|
||||
rv = entry->GetMetaDataElement("response-head", getter_Copies(buf));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Parse string stored in a "response-head" metadata element.
|
||||
// These response headers will be merged with the orignal headers (i.e. the
|
||||
// headers stored in a "original-response-headers" metadata element).
|
||||
rv = cachedResponseHead->ParseCachedHead(buf.get());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
buf.Adopt(0);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
CheckPartial(nsICacheEntry* aEntry, int64_t *aSize, int64_t *aContentLength,
|
||||
nsHttpResponseHead *responseHead)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
rv = aEntry->GetDataSize(aSize);
|
||||
|
||||
if (NS_ERROR_IN_PROGRESS == rv) {
|
||||
*aSize = -1;
|
||||
rv = NS_OK;
|
||||
}
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!responseHead) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
*aContentLength = responseHead->ContentLength();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
DetermineFramingAndImmutability(nsICacheEntry *entry,
|
||||
nsHttpResponseHead *responseHead, bool isHttps,
|
||||
bool *weaklyFramed, bool *isImmutable)
|
||||
{
|
||||
nsXPIDLCString framedBuf;
|
||||
nsresult rv = entry->GetMetaDataElement("strongly-framed", getter_Copies(framedBuf));
|
||||
// describe this in terms of explicitly weakly framed so as to be backwards
|
||||
// compatible with old cache contents which dont have strongly-framed makers
|
||||
*weaklyFramed = NS_SUCCEEDED(rv) && framedBuf.EqualsLiteral("0");
|
||||
*isImmutable = !*weaklyFramed && isHttps && responseHead->Immutable();
|
||||
}
|
||||
|
||||
} // namespace nsHttp
|
||||
|
||||
|
||||
template<typename T> void
|
||||
localEnsureBuffer(UniquePtr<T[]> &buf, uint32_t newSize,
|
||||
|
@ -22,11 +22,17 @@
|
||||
#define NS_HTTP_VERSION_1_1 11
|
||||
#define NS_HTTP_VERSION_2_0 20
|
||||
|
||||
class nsICacheEntry;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class Mutex;
|
||||
|
||||
namespace net {
|
||||
class nsHttpResponseHead;
|
||||
class nsHttpRequestHead;
|
||||
class CacheControlParser;
|
||||
|
||||
enum {
|
||||
// SPDY_VERSION_2 = 2, REMOVED
|
||||
// SPDY_VERSION_3 = 3, REMOVED
|
||||
@ -125,46 +131,46 @@ struct nsHttpAtom
|
||||
const char *_val;
|
||||
};
|
||||
|
||||
struct nsHttp
|
||||
namespace nsHttp
|
||||
{
|
||||
static MOZ_MUST_USE nsresult CreateAtomTable();
|
||||
static void DestroyAtomTable();
|
||||
MOZ_MUST_USE nsresult CreateAtomTable();
|
||||
void DestroyAtomTable();
|
||||
|
||||
// The mutex is valid any time the Atom Table is valid
|
||||
// This mutex is used in the unusual case that the network thread and
|
||||
// main thread might access the same data
|
||||
static Mutex *GetLock();
|
||||
Mutex *GetLock();
|
||||
|
||||
// will dynamically add atoms to the table if they don't already exist
|
||||
static nsHttpAtom ResolveAtom(const char *);
|
||||
static nsHttpAtom ResolveAtom(const nsACString &s)
|
||||
nsHttpAtom ResolveAtom(const char *);
|
||||
inline nsHttpAtom ResolveAtom(const nsACString &s)
|
||||
{
|
||||
return ResolveAtom(PromiseFlatCString(s).get());
|
||||
}
|
||||
|
||||
// returns true if the specified token [start,end) is valid per RFC 2616
|
||||
// section 2.2
|
||||
static bool IsValidToken(const char *start, const char *end);
|
||||
bool IsValidToken(const char *start, const char *end);
|
||||
|
||||
static inline bool IsValidToken(const nsACString &s) {
|
||||
inline bool IsValidToken(const nsACString &s) {
|
||||
return IsValidToken(s.BeginReading(), s.EndReading());
|
||||
}
|
||||
|
||||
// Strip the leading or trailing HTTP whitespace per fetch spec section 2.2.
|
||||
static void TrimHTTPWhitespace(const nsACString& aSource,
|
||||
void TrimHTTPWhitespace(const nsACString& aSource,
|
||||
nsACString& aDest);
|
||||
|
||||
// Returns true if the specified value is reasonable given the defintion
|
||||
// in RFC 2616 section 4.2. Full strict validation is not performed
|
||||
// currently as it would require full parsing of the value.
|
||||
static bool IsReasonableHeaderValue(const nsACString &s);
|
||||
bool IsReasonableHeaderValue(const nsACString &s);
|
||||
|
||||
// find the first instance (case-insensitive comparison) of the given
|
||||
// |token| in the |input| string. the |token| is bounded by elements of
|
||||
// |separators| and may appear at the beginning or end of the |input|
|
||||
// string. null is returned if the |token| is not found. |input| may be
|
||||
// null, in which case null is returned.
|
||||
static const char *FindToken(const char *input, const char *token,
|
||||
const char *FindToken(const char *input, const char *token,
|
||||
const char *separators);
|
||||
|
||||
// This function parses a string containing a decimal-valued, non-negative
|
||||
@ -175,22 +181,40 @@ struct nsHttp
|
||||
//
|
||||
// TODO(darin): Replace this with something generic.
|
||||
//
|
||||
static MOZ_MUST_USE bool ParseInt64(const char *input, const char **next,
|
||||
MOZ_MUST_USE bool ParseInt64(const char *input, const char **next,
|
||||
int64_t *result);
|
||||
|
||||
// Variant on ParseInt64 that expects the input string to contain nothing
|
||||
// more than the value being parsed.
|
||||
static inline MOZ_MUST_USE bool ParseInt64(const char *input,
|
||||
inline MOZ_MUST_USE bool ParseInt64(const char *input,
|
||||
int64_t *result) {
|
||||
const char *next;
|
||||
return ParseInt64(input, &next, result) && *next == '\0';
|
||||
}
|
||||
|
||||
// Return whether the HTTP status code represents a permanent redirect
|
||||
static bool IsPermanentRedirect(uint32_t httpStatus);
|
||||
bool IsPermanentRedirect(uint32_t httpStatus);
|
||||
|
||||
// Returns the APLN token which represents the used protocol version.
|
||||
static const char* GetProtocolVersion(uint32_t pv);
|
||||
const char* GetProtocolVersion(uint32_t pv);
|
||||
|
||||
bool ValidationRequired(bool isForcedValid, nsHttpResponseHead *cachedResponseHead,
|
||||
uint32_t loadFlags, bool allowStaleCacheContent,
|
||||
bool isImmutable, bool customConditionalRequest,
|
||||
nsHttpRequestHead &requestHead,
|
||||
nsICacheEntry *entry, CacheControlParser &cacheControlRequest,
|
||||
bool fromPreviousSession);
|
||||
|
||||
nsresult GetHttpResponseHeadFromCacheEntry(nsICacheEntry *entry,
|
||||
nsHttpResponseHead *cachedResponseHead);
|
||||
|
||||
nsresult CheckPartial(nsICacheEntry* aEntry, int64_t *aSize,
|
||||
int64_t *aContentLength,
|
||||
nsHttpResponseHead *responseHead);
|
||||
|
||||
void DetermineFramingAndImmutability(nsICacheEntry *entry, nsHttpResponseHead *cachedResponseHead,
|
||||
bool isHttps, bool *weaklyFramed,
|
||||
bool *isImmutable);
|
||||
|
||||
// Declare all atoms
|
||||
//
|
||||
@ -198,10 +222,10 @@ struct nsHttp
|
||||
// to you by the magic of C preprocessing. Add new atoms to nsHttpAtomList
|
||||
// and all support logic will be auto-generated.
|
||||
//
|
||||
#define HTTP_ATOM(_name, _value) static nsHttpAtom _name;
|
||||
#define HTTP_ATOM(_name, _value) extern nsHttpAtom _name;
|
||||
#include "nsHttpAtomList.h"
|
||||
#undef HTTP_ATOM
|
||||
};
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// utilities...
|
||||
|
@ -3955,27 +3955,9 @@ bypassCacheEntryOpen:
|
||||
nsresult
|
||||
nsHttpChannel::CheckPartial(nsICacheEntry* aEntry, int64_t *aSize, int64_t *aContentLength)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
rv = aEntry->GetDataSize(aSize);
|
||||
|
||||
if (NS_ERROR_IN_PROGRESS == rv) {
|
||||
*aSize = -1;
|
||||
rv = NS_OK;
|
||||
}
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsHttpResponseHead* responseHead = mCachedResponseHead
|
||||
? mCachedResponseHead
|
||||
: mResponseHead;
|
||||
|
||||
if (!responseHead)
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
|
||||
*aContentLength = responseHead->ContentLength();
|
||||
|
||||
return NS_OK;
|
||||
return nsHttp::CheckPartial(aEntry, aSize, aContentLength,
|
||||
mCachedResponseHead ? mCachedResponseHead
|
||||
: mResponseHead);
|
||||
}
|
||||
|
||||
void
|
||||
@ -4057,32 +4039,9 @@ nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appC
|
||||
// Get the cached HTTP response headers
|
||||
mCachedResponseHead = new nsHttpResponseHead();
|
||||
|
||||
// A "original-response-headers" metadata element holds network original headers,
|
||||
// i.e. the headers in the form as they arrieved from the network.
|
||||
// We need to get the network original headers first, because we need to keep them
|
||||
// in order.
|
||||
rv = entry->GetMetaDataElement("original-response-headers", getter_Copies(buf));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = mCachedResponseHead->ParseCachedOriginalHeaders((char *) buf.get());
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG((" failed to parse original-response-headers\n"));
|
||||
}
|
||||
}
|
||||
|
||||
buf.Adopt(0);
|
||||
// A "response-head" metadata element holds response head, e.g. response status
|
||||
// line and headers in the form Firefox uses them internally (no dupicate
|
||||
// headers, etc.).
|
||||
rv = entry->GetMetaDataElement("response-head", getter_Copies(buf));
|
||||
rv = nsHttp::GetHttpResponseHeadFromCacheEntry(entry, mCachedResponseHead);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Parse string stored in a "response-head" metadata element.
|
||||
// These response headers will be merged with the orignal headers (i.e. the
|
||||
// headers stored in a "original-response-headers" metadata element).
|
||||
rv = mCachedResponseHead->ParseCachedHead(buf.get());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
buf.Adopt(0);
|
||||
|
||||
bool isCachedRedirect = WillRedirect(mCachedResponseHead);
|
||||
|
||||
// Do not return 304 responses from the cache, and also do not return
|
||||
@ -4188,126 +4147,20 @@ nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appC
|
||||
bool isForcedValid = false;
|
||||
entry->GetIsForcedValid(&isForcedValid);
|
||||
|
||||
nsXPIDLCString framedBuf;
|
||||
rv = entry->GetMetaDataElement("strongly-framed", getter_Copies(framedBuf));
|
||||
// describe this in terms of explicitly weakly framed so as to be backwards
|
||||
// compatible with old cache contents which dont have strongly-framed makers
|
||||
bool weaklyFramed = NS_SUCCEEDED(rv) && framedBuf.EqualsLiteral("0");
|
||||
bool isImmutable = !weaklyFramed && isHttps && mCachedResponseHead->Immutable();
|
||||
bool weaklyFramed, isImmutable;
|
||||
nsHttp::DetermineFramingAndImmutability(entry, mCachedResponseHead, isHttps,
|
||||
&weaklyFramed, &isImmutable);
|
||||
|
||||
// Cached entry is not the entity we request (see bug #633743)
|
||||
if (ResponseWouldVary(entry)) {
|
||||
LOG(("Validating based on Vary headers returning TRUE\n"));
|
||||
canAddImsHeader = false;
|
||||
doValidation = true;
|
||||
}
|
||||
// Check isForcedValid to see if it is possible to skip validation.
|
||||
// Don't skip validation if we have serious reason to believe that this
|
||||
// content is invalid (it's expired).
|
||||
// See netwerk/cache2/nsICacheEntry.idl for details
|
||||
else if (isForcedValid &&
|
||||
(!mCachedResponseHead->ExpiresInPast() ||
|
||||
!mCachedResponseHead->MustValidateIfExpired())) {
|
||||
LOG(("NOT validating based on isForcedValid being true.\n"));
|
||||
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES_USED> used;
|
||||
++used;
|
||||
doValidation = false;
|
||||
}
|
||||
// If the LOAD_FROM_CACHE flag is set, any cached data can simply be used
|
||||
else if (mLoadFlags & nsIRequest::LOAD_FROM_CACHE || mAllowStaleCacheContent) {
|
||||
LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
|
||||
doValidation = false;
|
||||
}
|
||||
// If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
|
||||
// it's revalidated with the server.
|
||||
else if ((mLoadFlags & nsIRequest::VALIDATE_ALWAYS) && !isImmutable) {
|
||||
LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
|
||||
doValidation = true;
|
||||
}
|
||||
// Even if the VALIDATE_NEVER flag is set, there are still some cases in
|
||||
// which we must validate the cached response with the server.
|
||||
else if (mLoadFlags & nsIRequest::VALIDATE_NEVER) {
|
||||
LOG(("VALIDATE_NEVER set\n"));
|
||||
// if no-store validate cached response (see bug 112564)
|
||||
if (mCachedResponseHead->NoStore()) {
|
||||
LOG(("Validating based on no-store logic\n"));
|
||||
doValidation = true;
|
||||
}
|
||||
else {
|
||||
LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
|
||||
doValidation = false;
|
||||
}
|
||||
}
|
||||
// check if validation is strictly required...
|
||||
else if (mCachedResponseHead->MustValidate()) {
|
||||
LOG(("Validating based on MustValidate() returning TRUE\n"));
|
||||
doValidation = true;
|
||||
// possibly serve from cache for a custom If-Match/If-Unmodified-Since
|
||||
// conditional request
|
||||
} else if (mCustomConditionalRequest &&
|
||||
!mRequestHead.HasHeader(nsHttp::If_Match) &&
|
||||
!mRequestHead.HasHeader(nsHttp::If_Unmodified_Since)) {
|
||||
LOG(("Validating based on a custom conditional request\n"));
|
||||
doValidation = true;
|
||||
} else {
|
||||
// previously we also checked for a query-url w/out expiration
|
||||
// and didn't do heuristic on it. but defacto that is allowed now.
|
||||
//
|
||||
// Check if the cache entry has expired...
|
||||
|
||||
uint32_t now = NowInSeconds();
|
||||
|
||||
uint32_t age = 0;
|
||||
rv = mCachedResponseHead->ComputeCurrentAge(now, now, &age);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
uint32_t freshness = 0;
|
||||
rv = mCachedResponseHead->ComputeFreshnessLifetime(&freshness);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
uint32_t expiration = 0;
|
||||
rv = entry->GetExpirationTime(&expiration);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
uint32_t maxAgeRequest, maxStaleRequest, minFreshRequest;
|
||||
|
||||
LOG((" NowInSeconds()=%u, expiration time=%u, freshness lifetime=%u, age=%u",
|
||||
now, expiration, freshness, age));
|
||||
|
||||
if (cacheControlRequest.NoCache()) {
|
||||
LOG((" validating, no-cache request"));
|
||||
doValidation = true;
|
||||
} else if (cacheControlRequest.MaxStale(&maxStaleRequest)) {
|
||||
uint32_t staleTime = age > freshness ? age - freshness : 0;
|
||||
doValidation = staleTime > maxStaleRequest;
|
||||
LOG((" validating=%d, max-stale=%u requested", doValidation, maxStaleRequest));
|
||||
} else if (cacheControlRequest.MaxAge(&maxAgeRequest)) {
|
||||
doValidation = age > maxAgeRequest;
|
||||
LOG((" validating=%d, max-age=%u requested", doValidation, maxAgeRequest));
|
||||
} else if (cacheControlRequest.MinFresh(&minFreshRequest)) {
|
||||
uint32_t freshTime = freshness > age ? freshness - age : 0;
|
||||
doValidation = freshTime < minFreshRequest;
|
||||
LOG((" validating=%d, min-fresh=%u requested", doValidation, minFreshRequest));
|
||||
} else if (now <= expiration) {
|
||||
doValidation = false;
|
||||
LOG((" not validating, expire time not in the past"));
|
||||
} else if (mCachedResponseHead->MustValidateIfExpired()) {
|
||||
doValidation = true;
|
||||
} else if (mLoadFlags & nsIRequest::VALIDATE_ONCE_PER_SESSION) {
|
||||
// If the cached response does not include expiration infor-
|
||||
// mation, then we must validate the response, despite whether
|
||||
// or not this is the first access this session. This behavior
|
||||
// is consistent with existing browsers and is generally expected
|
||||
// by web authors.
|
||||
if (freshness == 0)
|
||||
doValidation = true;
|
||||
else
|
||||
doValidation = fromPreviousSession;
|
||||
}
|
||||
else
|
||||
doValidation = true;
|
||||
|
||||
LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
|
||||
doValidation = nsHttp::ValidationRequired(
|
||||
isForcedValid, mCachedResponseHead, mLoadFlags,
|
||||
mAllowStaleCacheContent, isImmutable, mCustomConditionalRequest,
|
||||
mRequestHead, entry, cacheControlRequest, fromPreviousSession);
|
||||
}
|
||||
|
||||
|
||||
|
@ -958,6 +958,102 @@ function test_http2_status_phrase() {
|
||||
chan.asyncOpen2(listener);
|
||||
}
|
||||
|
||||
var PulledDiskCacheListener = function() {};
|
||||
PulledDiskCacheListener.prototype = new Http2CheckListener();
|
||||
PulledDiskCacheListener.prototype.EXPECTED_DATA = "this was pulled via h2";
|
||||
PulledDiskCacheListener.prototype.readData = "";
|
||||
PulledDiskCacheListener.prototype.onDataAvailable = function testOnDataAvailable(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.isHttp2Connection = checkIsHttp2(request);
|
||||
this.accum += cnt;
|
||||
this.readData += read_stream(stream, cnt);
|
||||
};
|
||||
PulledDiskCacheListener.prototype.onStopRequest = function testOnStopRequest(request, ctx, status) {
|
||||
do_check_eq(this.EXPECTED_DATA, this.readData);
|
||||
Http2CheckListener.prorotype.onStopRequest.call(this, request, ctx, status);
|
||||
};
|
||||
|
||||
const DISK_CACHE_DATA = "this is from disk cache";
|
||||
|
||||
var FromDiskCacheListener = function() {};
|
||||
FromDiskCacheListener.prototype = {
|
||||
onStartRequestFired: false,
|
||||
onDataAvailableFired: false,
|
||||
readData: "",
|
||||
|
||||
onStartRequest: function testOnStartRequest(request, ctx) {
|
||||
this.onStartRequestFired = true;
|
||||
if (!Components.isSuccessCode(request.status)) {
|
||||
do_throw("Channel should have a success code! (" + request.status + ")");
|
||||
}
|
||||
|
||||
do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
|
||||
do_check_true(request.requestSucceeded);
|
||||
do_check_eq(request.responseStatus, 200);
|
||||
},
|
||||
|
||||
onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
|
||||
this.onDataAvailableFired = true;
|
||||
this.readData += read_stream(stream, cnt);
|
||||
},
|
||||
|
||||
onStopRequest: function testOnStopRequest(request, ctx, status) {
|
||||
do_check_true(this.onStartRequestFired);
|
||||
do_check_true(Components.isSuccessCode(status));
|
||||
do_check_true(this.onDataAvailableFired);
|
||||
do_check_eq(this.readData, DISK_CACHE_DATA);
|
||||
|
||||
evict_cache_entries("disk");
|
||||
syncWithCacheIOThread(() => {
|
||||
// Now that we know the entry is out of the disk cache, check to make sure
|
||||
// we don't have this hiding in the push cache somewhere - if we do, it
|
||||
// didn't get cancelled, and we have a bug.
|
||||
var chan = makeChan("https://localhost:" + serverPort + "/diskcache");
|
||||
chan.listener = new PulledDiskCacheListener();
|
||||
chan.loadGroup = loadGroup;
|
||||
chan.asyncOpen2(listener);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var Http2DiskCachePushListener = function() {};
|
||||
|
||||
Http2DiskCachePushListener.prototype = new Http2CheckListener();
|
||||
|
||||
Http2DiskCachePushListener.onStopRequest = function(request, ctx, status) {
|
||||
do_check_true(this.onStartRequestFired);
|
||||
do_check_true(Components.isSuccessCode(status));
|
||||
do_check_true(this.onDataAvailableFired);
|
||||
do_check_true(this.isHttp2Connection == this.shouldBeHttp2);
|
||||
|
||||
// Now we need to open a channel to ensure we get data from the disk cache
|
||||
// for the pushed item, instead of from the push cache.
|
||||
var chan = makeChan("https://localhost:" + serverPort + "/diskcache");
|
||||
chan.listener = new FromDiskCacheListener();
|
||||
chan.loadGroup = loadGroup;
|
||||
chan.asyncOpen2(listener);
|
||||
};
|
||||
|
||||
function continue_test_http2_disk_cache_push(status, entry, appCache) {
|
||||
// TODO - store stuff in cache entry, then open an h2 channel that will push
|
||||
// this, once that completes, open a channel for the cache entry we made and
|
||||
// ensure it came from disk cache, not the push cache.
|
||||
var outputStream = entry.openOutputStream(0);
|
||||
outputStream.write(DISK_CACHE_DATA, DISK_CACHE_DATA.length);
|
||||
|
||||
// Now we open our URL that will push data for the URL above
|
||||
var chan = makeChan("https://localhost:" + serverPort + "/pushindisk");
|
||||
var listener = new Http2DiskCachePushListener();
|
||||
chan.loadGroup = loadGroup;
|
||||
chan.asyncOpen2(listener);
|
||||
}
|
||||
|
||||
function test_http2_disk_cache_push() {
|
||||
asyncOpenCacheEntry("https://localhost:" + serverPort + "/diskcache",
|
||||
"disk", Ci.nsICacheStorage.OPEN_NORMALLY, null,
|
||||
continue_test_http2_disk_cache_push, false);
|
||||
}
|
||||
|
||||
function test_complete() {
|
||||
resetPrefs();
|
||||
do_test_pending();
|
||||
@ -1044,6 +1140,7 @@ var tests = [ test_http2_post_big
|
||||
, test_http2_empty_data
|
||||
, test_http2_status_phrase
|
||||
, test_http2_doublepush
|
||||
, test_http2_disk_cache_push
|
||||
// Add new tests above here - best to add new tests before h1
|
||||
// streams get too involved
|
||||
// These next two must always come in this order
|
||||
|
@ -831,6 +831,22 @@ function handleRequest(req, res) {
|
||||
content = 'not pushed';
|
||||
}
|
||||
|
||||
else if (u.pathname === "/diskcache") {
|
||||
content = "this was pulled via h2";
|
||||
}
|
||||
|
||||
else if (u.pathname === "/pushindisk") {
|
||||
var pushedContent = "this was pushed via h2";
|
||||
push = res.push('/diskcache');
|
||||
push.writeHead(200, {
|
||||
'content-type': 'text/html',
|
||||
'pushed' : 'yes',
|
||||
'content-length' : pushedContent.length,
|
||||
'X-Connection-Http2': 'yes'
|
||||
});
|
||||
push.end(pushedContent);
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
if (req.httpVersionMajor != 2) {
|
||||
res.setHeader('Connection', 'close');
|
||||
|
Loading…
Reference in New Issue
Block a user