Skip to content

Commit f5613f7

Browse files
authored
Added more explicitly public cache-control to content pages. (#9027)
1 parent aa2ec87 commit f5613f7

File tree

12 files changed

+87
-23
lines changed

12 files changed

+87
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ AppEngine version, listed here to ease deployment and troubleshooting.
55
* Bump runtimeVersion to `2025.10.22`.
66
* Upgraded stable Flutter analysis SDK to `3.35.6`.
77
* Upgraded pana to `0.23.0`.
8+
* Added more explicitly public `cache-control` to content pages.
89

910
## `20251017t101000-all`
1011
* Bump runtimeVersion to `2025.10.17`.

app/lib/frontend/handlers/cache_control.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ final class CacheControl {
6262
public: true,
6363
);
6464

65+
/// `Cache-Control` headers for package content pages, returning content that
66+
/// is not updated frequently.
67+
static const packageContentPage = CacheControl(
68+
maxAge: Duration(minutes: 30),
69+
public: true,
70+
);
71+
72+
/// `Cache-Control` headers for package listing pages, returning content that
73+
/// is may be updated frequently.
74+
static const packageListingPage = CacheControl(
75+
maxAge: Duration(minutes: 5),
76+
public: true,
77+
);
78+
6579
/// `Cache-Control` headers for API end-points returning completion data for
6680
/// use in IDE integrations.
6781
static const completionData = CacheControl(

app/lib/frontend/handlers/documentation.dart

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ import '../../shared/urls.dart';
2323
///
2424
/// - `/documentation/<package>/<version>`
2525
Future<shelf.Response> documentationHandler(shelf.Request request) async {
26+
final requestMethod = request.method.toUpperCase();
27+
if (requestMethod != 'HEAD' && requestMethod != 'GET') {
28+
return methodNotAllowedHandler(request);
29+
}
30+
2631
final docFilePath = parseRequestUri(request.requestedUri);
2732
if (docFilePath == null) {
2833
return notFoundHandler(request);
@@ -58,12 +63,6 @@ Future<shelf.Response> documentationHandler(shelf.Request request) async {
5863
),
5964
);
6065
}
61-
final String requestMethod = request.method.toUpperCase();
62-
63-
if (requestMethod != 'HEAD' && requestMethod != 'GET') {
64-
// TODO: Should probably be "method not supported"!
65-
return notFoundHandler(request);
66-
}
6766

6867
final package = docFilePath.package;
6968
final version = docFilePath.version!;

app/lib/frontend/handlers/landing.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:async';
66

77
import 'package:_pub_shared/search/tags.dart';
8+
import 'package:pub_dev/frontend/handlers/cache_control.dart';
89
import 'package:pub_dev/search/top_packages.dart';
910
import 'package:shelf/shelf.dart' as shelf;
1011

@@ -49,7 +50,10 @@ Future<shelf.Response> indexLandingHandler(shelf.Request request) async {
4950
}
5051

5152
if (requestContext.uiCacheEnabled) {
52-
return htmlResponse((await cache.uiIndexPage().get(_render))!);
53+
return htmlResponse(
54+
(await cache.uiIndexPage().get(_render))!,
55+
headers: CacheControl.packageListingPage.headers,
56+
);
5357
}
5458
return htmlResponse(await _render());
5559
}

app/lib/frontend/handlers/listing.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import 'dart:async';
77
import 'package:_pub_shared/search/search_form.dart';
88
import 'package:_pub_shared/search/tags.dart';
99
import 'package:logging/logging.dart';
10+
import 'package:pub_dev/frontend/handlers/cache_control.dart';
11+
import 'package:pub_dev/frontend/request_context.dart';
1012
import 'package:shelf/shelf.dart' as shelf;
1113

1214
import '../../package/name_tracker.dart';
@@ -94,6 +96,9 @@ Future<shelf.Response> _packagesHandlerHtmlCore(shelf.Request request) async {
9496
openSections: openSections,
9597
),
9698
status: statusCode,
99+
headers: statusCode == 200 && requestContext.uiCacheEnabled
100+
? CacheControl.packageListingPage.headers
101+
: null,
97102
);
98103
_searchOverallLatencyTracker.add(sw.elapsed);
99104
return result;

app/lib/frontend/handlers/package.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ Future<shelf.Response> _handlePackagePage({
301301
Entry<String>? cacheEntry,
302302
}) async {
303303
checkPackageVersionParams(packageName, versionName);
304+
final cacheEnabled = requestContext.uiCacheEnabled && cacheEntry != null;
304305

305306
final canonicalUrl = canonicalUrlFn(
306307
await _canonicalPackageName(packageName),
@@ -314,7 +315,7 @@ Future<shelf.Response> _handlePackagePage({
314315
}
315316
final Stopwatch sw = Stopwatch()..start();
316317
String? cachedPage;
317-
if (requestContext.uiCacheEnabled && cacheEntry != null) {
318+
if (cacheEnabled) {
318319
cachedPage = await cacheEntry.get();
319320
}
320321

@@ -350,12 +351,15 @@ Future<shelf.Response> _handlePackagePage({
350351
} else {
351352
throw StateError('Unknown result type: ${renderedResult.runtimeType}');
352353
}
353-
if (requestContext.uiCacheEnabled && cacheEntry != null) {
354+
if (cacheEnabled) {
354355
await cacheEntry.set(cachedPage);
355356
}
356357
_packageDoneLatencyTracker.add(sw.elapsed);
357358
}
358-
return htmlResponse(cachedPage);
359+
return htmlResponse(
360+
cachedPage,
361+
headers: cacheEnabled ? CacheControl.packageContentPage.headers : null,
362+
);
359363
}
360364

361365
/// Returns the optionally lowercased version of [name], but only if there

app/lib/frontend/request_context.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Future<RequestContext> buildRequestContext({
110110
// don't cache if client session is active
111111
!clientSessionCookieStatus.isPresent &&
112112
// sanity check, this should be covered by client session cookie
113-
(csrfToken?.isNotEmpty ?? false);
113+
!(csrfToken?.isNotEmpty ?? false);
114114
return RequestContext(
115115
indentJson: indentJson,
116116
blockRobots: !enableRobots,

app/lib/shared/handlers.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ shelf.Response notFoundHandler(
101101
return htmlResponse(body, status: 404, headers: headers);
102102
}
103103

104+
shelf.Response methodNotAllowedHandler(
105+
shelf.Request request, {
106+
Map<String, Object>? headers,
107+
}) {
108+
return shelf.Response(405, body: 'Method Not Allowed', headers: headers);
109+
}
110+
104111
shelf.Response rejectRobotsHandler(shelf.Request request) =>
105112
shelf.Response.ok('User-agent: *\nDisallow: /\n');
106113

app/lib/task/handlers.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:path/path.dart' as p;
66

77
import 'package:pub_dev/dartdoc/dartdoc_page.dart';
88
import 'package:pub_dev/dartdoc/models.dart';
9+
import 'package:pub_dev/frontend/handlers/cache_control.dart';
910
import 'package:pub_dev/shared/exceptions.dart';
1011
import 'package:pub_dev/shared/handlers.dart';
1112
import 'package:pub_dev/shared/redis_cache.dart';
@@ -90,7 +91,10 @@ Future<shelf.Response> handleDartDoc(
9091
);
9192
final htmlBytes = await htmlBytesCacheEntry.get();
9293
if (htmlBytes != null) {
93-
return htmlBytesResponse(htmlBytes);
94+
return htmlBytesResponse(
95+
htmlBytes,
96+
headers: CacheControl.packageContentPage.headers,
97+
);
9498
}
9599

96100
// check cached status for redirect or missing pages
@@ -211,7 +215,10 @@ Future<shelf.Response> handleDartDoc(
211215
switch (status.code) {
212216
case DocPageStatusCode.ok:
213217
await htmlBytesCacheEntry.set(bytes!);
214-
return htmlBytesResponse(bytes);
218+
return htmlBytesResponse(
219+
bytes,
220+
headers: CacheControl.packageContentPage.headers,
221+
);
215222
case DocPageStatusCode.redirect:
216223
return redirectPathResponse(status.redirectPath!);
217224
case DocPageStatusCode.missing:
@@ -236,13 +243,14 @@ Future<shelf.Response> handleDartDoc(
236243
}
237244

238245
if (request.method.toUpperCase() == 'HEAD') {
239-
return htmlResponse('');
246+
return htmlResponse('', headers: CacheControl.packageContentPage.headers);
240247
}
241248

242249
final acceptsGzip = request.acceptsGzipEncoding();
243250
return shelf.Response.ok(
244251
acceptsGzip ? dataGz : gzip.decode(dataGz),
245252
headers: {
253+
...CacheControl.packageContentPage.headers,
246254
'Content-Type': mime,
247255
'Vary': 'Accept-Encoding', // body depends on accept-encoding!
248256
if (acceptsGzip) 'Content-Encoding': 'gzip',

pkg/pub_integration/lib/src/fake_test_context_provider.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,20 @@ class TestContextProvider {
5858
await _fakePubServerProcess.kill();
5959
}
6060

61-
Future<TestUser> createAnonymousTestUser() async {
61+
Future<TestUser> createAnonymousTestUser({
62+
bool expectAllResponsesToBeCacheControlPublic = true,
63+
}) async {
6264
final session = await _testBrowser.createSession();
6365
return TestUser(
6466
email: '',
6567
browserApi: PubApiClient(pubHostedUrl),
6668
serverApi: PubApiClient(pubHostedUrl),
6769
withBrowserPage: <T>(Future<T> Function(Page) fn) async {
68-
return await session.withPage<T>(fn: fn);
70+
return await session.withPage<T>(
71+
fn: fn,
72+
expectAllResponsesToBeCacheControlPublic:
73+
expectAllResponsesToBeCacheControlPublic,
74+
);
6975
},
7076
readLatestEmail: () async => throw UnimplementedError(),
7177
createCredentials: () async => throw UnimplementedError(),

0 commit comments

Comments
 (0)