Bug 1064258 - Allow caching channels only store metadata, r=jduell

This commit is contained in:
Honza Bambas 2014-09-30 15:32:47 +02:00
parent e3d70ed01d
commit 193df74284
5 changed files with 214 additions and 7 deletions

View File

@ -17,7 +17,7 @@ interface nsIFile;
* 3) Support for uniquely identifying cached data in cases when the URL
* is insufficient (e.g., HTTP form submission).
*/
[scriptable, uuid(a77b664e-e707-4017-9c03-47bcedcb5b05)]
[scriptable, uuid(3d46b469-7405-416e-ba42-84899963b403)]
interface nsICachingChannel : nsICacheInfoChannel
{
/**
@ -64,6 +64,14 @@ interface nsICachingChannel : nsICacheInfoChannel
*/
attribute nsISupports cacheKey;
/**
* Instructs the channel to only store the metadata of the entry, and not
* the content. When reading an existing entry, this automatically sets
* LOAD_ONLY_IF_MODIFIED flag.
* Must be called before asyncOpen().
*/
attribute boolean cacheOnlyMetadata;
/**************************************************************************
* Caching channel specific load flags:
*/

View File

@ -208,6 +208,7 @@ nsHttpChannel::nsHttpChannel()
, mOfflineCacheLastModifiedTime(0)
, mCachedContentIsValid(false)
, mCachedContentIsPartial(false)
, mCacheOnlyMetadata(false)
, mTransactionReplaced(false)
, mAuthRetryPending(false)
, mProxyAuthPending(false)
@ -2826,8 +2827,16 @@ nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appC
if (mCachedContentIsPartial) {
rv = OpenCacheInputStream(entry, false, !!appCache);
*aResult = ENTRY_NEEDS_REVALIDATION;
return rv;
} else if (size == 0 && mCacheOnlyMetadata) {
// Don't break cache entry load when the entry's data size
// is 0 and mCacheOnlyMetadata flag is set. In that case we
// want to proceed since the LOAD_ONLY_IF_MODIFIED flag is
// also set.
MOZ_ASSERT(mLoadFlags & LOAD_ONLY_IF_MODIFIED);
} else {
return rv;
}
return rv;
}
}
@ -4003,14 +4012,24 @@ nsHttpChannel::InstallCacheListener(int64_t offset)
nsCOMPtr<nsIOutputStream> out;
rv = mCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
if (rv == NS_ERROR_NOT_AVAILABLE) {
LOG((" entry doomed, not writing it [channel=%p]", this));
// Entry is already doomed.
// This may happen when expiration time is set to past and the entry
// has been removed by the background eviction logic.
return NS_OK;
LOG((" entry doomed, not writing it [channel=%p]", this));
// Entry is already doomed.
// This may happen when expiration time is set to past and the entry
// has been removed by the background eviction logic.
return NS_OK;
}
if (NS_FAILED(rv)) return rv;
if (mCacheOnlyMetadata) {
LOG(("Not storing content, cacheOnlyMetadata set"));
// We must open and then close the output stream of the cache entry.
// This way we indicate the content has been written (despite with zero
// length) and the entry is now in the ready state with "having data".
out->Close();
return NS_OK;
}
// XXX disk cache does not support overlapped i/o yet
#if 0
// Mark entry valid inorder to allow simultaneous reading...
@ -5716,6 +5735,30 @@ nsHttpChannel::SetCacheKey(nsISupports *key)
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetCacheOnlyMetadata(bool *aOnlyMetadata)
{
NS_ENSURE_ARG(aOnlyMetadata);
*aOnlyMetadata = mCacheOnlyMetadata;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetCacheOnlyMetadata(bool aOnlyMetadata)
{
LOG(("nsHttpChannel::SetCacheOnlyMetadata [this=%p only-metadata=%d]\n",
this, aOnlyMetadata));
ENSURE_CALLED_BEFORE_ASYNC_OPEN();
mCacheOnlyMetadata = aOnlyMetadata;
if (aOnlyMetadata) {
mLoadFlags |= LOAD_ONLY_IF_MODIFIED;
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIResumableChannel
//-----------------------------------------------------------------------------

View File

@ -385,6 +385,7 @@ private:
// state flags
uint32_t mCachedContentIsValid : 1;
uint32_t mCachedContentIsPartial : 1;
uint32_t mCacheOnlyMetadata : 1;
uint32_t mTransactionReplaced : 1;
uint32_t mAuthRetryPending : 1;
uint32_t mProxyAuthPending : 1;

View File

@ -0,0 +1,154 @@
/**
* Check how nsICachingChannel.cacheOnlyMetadata works.
* - all channels involved in this test are set cacheOnlyMetadata = true
* - do a previously uncached request for a long living content
* - check we have downloaded the content from the server (channel provides it)
* - check the entry has metadata, but zero-length content
* - load the same URL again, now cached
* - check the channel is giving no content (no call to OnDataAvailable) but succeeds
* - repeat again, but for a different URL that is not cached (immediately expires)
* - only difference is that we get a newer version of the content from the server during the second request
*/
Cu.import("resource://testing-common/httpd.js");
XPCOMUtils.defineLazyGetter(this, "URL", function() {
return "http://localhost:" + httpServer.identity.primaryPort;
});
var httpServer = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
const responseBody1 = "response body 1";
const responseBody2a = "response body 2a";
const responseBody2b = "response body 2b";
function contentHandler1(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.setHeader("Cache-control", "max-age=999999");
response.bodyOutputStream.write(responseBody1, responseBody1.length);
}
var content2passCount = 0;
function contentHandler2(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.setHeader("Cache-control", "no-cache");
switch (content2passCount++) {
case 0:
response.setHeader("ETag", "testetag");
response.bodyOutputStream.write(responseBody2a, responseBody2a.length);
break;
case 1:
do_check_true(metadata.hasHeader("If-None-Match"));
do_check_eq(metadata.getHeader("If-None-Match"), "testetag");
response.bodyOutputStream.write(responseBody2b, responseBody2b.length);
break;
default:
throw "Unexpected request in the test";
}
}
function run_test()
{
httpServer = new HttpServer();
httpServer.registerPathHandler("/content1", contentHandler1);
httpServer.registerPathHandler("/content2", contentHandler2);
httpServer.start(-1);
run_test_content1a();
do_test_pending();
}
function run_test_content1a()
{
var chan = make_channel(URL + "/content1");
caching = chan.QueryInterface(Ci.nsICachingChannel);
caching.cacheOnlyMetadata = true;
chan.asyncOpen(new ChannelListener(contentListener1a, null), null);
}
function contentListener1a(request, buffer)
{
do_check_eq(buffer, responseBody1);
asyncOpenCacheEntry(URL + "/content1", "disk", 0, null, cacheCheck1)
}
function cacheCheck1(status, entry)
{
do_check_eq(status, 0);
do_check_eq(entry.dataSize, 0);
try {
do_check_neq(entry.getMetaDataElement("response-head"), null);
}
catch (ex) {
do_throw("Missing response head");
}
var chan = make_channel(URL + "/content1");
caching = chan.QueryInterface(Ci.nsICachingChannel);
caching.cacheOnlyMetadata = true;
chan.asyncOpen(new ChannelListener(contentListener1b, null, CL_IGNORE_CL), null);
}
function contentListener1b(request, buffer)
{
request.QueryInterface(Ci.nsIHttpChannel);
do_check_eq(request.requestMethod, "GET");
do_check_eq(request.responseStatus, 200);
do_check_eq(request.getResponseHeader("Cache-control"), "max-age=999999");
do_check_eq(buffer, "");
run_test_content2a();
}
// Now same set of steps but this time for an immediately expiring content.
function run_test_content2a()
{
var chan = make_channel(URL + "/content2");
caching = chan.QueryInterface(Ci.nsICachingChannel);
caching.cacheOnlyMetadata = true;
chan.asyncOpen(new ChannelListener(contentListener2a, null), null);
}
function contentListener2a(request, buffer)
{
do_check_eq(buffer, responseBody2a);
asyncOpenCacheEntry(URL + "/content2", "disk", 0, null, cacheCheck2)
}
function cacheCheck2(status, entry)
{
do_check_eq(status, 0);
do_check_eq(entry.dataSize, 0);
try {
do_check_neq(entry.getMetaDataElement("response-head"), null);
do_check_true(entry.getMetaDataElement("response-head").match('Etag: testetag'));
}
catch (ex) {
do_throw("Missing response head");
}
var chan = make_channel(URL + "/content2");
caching = chan.QueryInterface(Ci.nsICachingChannel);
caching.cacheOnlyMetadata = true;
chan.asyncOpen(new ChannelListener(contentListener2b, null), null);
}
function contentListener2b(request, buffer)
{
do_check_eq(buffer, responseBody2b);
httpServer.stop(do_test_finished);
}

View File

@ -158,6 +158,7 @@ skip-if = os == "android"
# Allocating 4GB might actually succeed on 64 bit machines
skip-if = bits != 32
[test_bug935499.js]
[test_bug1064258.js]
[test_udpsocket.js]
[test_doomentry.js]
[test_cacheflags.js]