Skip to content

Commit a43a972

Browse files
committed
Set cache headers on package collection requests.
1 parent fd64cbc commit a43a972

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

Sources/App/Controllers/API/API+PackageCollectionController.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,20 @@ extension API {
6464
extension SignedCollection: @retroactive RequestDecodable {}
6565
extension SignedCollection: @retroactive ResponseEncodable {}
6666
extension SignedCollection: @retroactive AsyncRequestDecodable {}
67-
extension SignedCollection: @retroactive AsyncResponseEncodable {}
6867
extension SignedCollection: @retroactive Content {}
6968

69+
extension SignedCollection: @retroactive AsyncResponseEncodable {
70+
public func encodeResponse(for request: Request) async throws -> Response {
71+
var res = Response(status: .ok)
72+
try res.content.encode(self)
73+
74+
// Set cache control headers to tell Cloudflare not to transform the data, and to cache it for 2 hours.
75+
res.headers.replaceOrAdd(name: .cacheControl, value: "public, max-age=7200, s-maxage=7200, no-transform")
76+
77+
return res
78+
}
79+
}
80+
7081

7182
extension API {
7283
struct PostPackageCollectionDTO: Codable {

Tests/AppTests/PackageCollectionControllerTests.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,68 @@ extension AllTests.PackageCollectionControllerTests {
170170
}
171171
}
172172

173+
@Test(
174+
.disabled(
175+
if: !isRunningInCI() && EnvironmentClient.liveValue.collectionSigningPrivateKey() == nil,
176+
"Skip test for local user due to unset COLLECTION_SIGNING_PRIVATE_KEY env variable"
177+
)
178+
)
179+
func cache_control_headers_for_cloudflare() async throws {
180+
try await withDependencies {
181+
$0.date.now = .t0
182+
$0.environment.collectionSigningCertificateChain = EnvironmentClient.liveValue.collectionSigningCertificateChain
183+
$0.environment.collectionSigningPrivateKey = EnvironmentClient.liveValue.collectionSigningPrivateKey
184+
} operation: {
185+
try await withSPIApp { app in
186+
let p = try await savePackage(on: app.db, "https://github.com/foo/1")
187+
let v = try Version(id: UUID(),
188+
package: p,
189+
latest: .release,
190+
packageName: "P1-tag",
191+
reference: .tag(1, 2, 3),
192+
toolsVersion: "5.1")
193+
try await v.save(on: app.db)
194+
try await Product(version: v, type: .library(.automatic), name: "P1Lib", targets: ["t1"])
195+
.save(on: app.db)
196+
try await Repository(package: p,
197+
defaultBranch: "main",
198+
license: .mit,
199+
licenseUrl: "https://foo/mit",
200+
owner: "foo",
201+
summary: "summary 1").create(on: app.db)
202+
203+
// MUT
204+
try await app.testing().test(
205+
.GET,
206+
"foo/collection.json",
207+
afterResponse: { @MainActor res async throws in
208+
#expect(res.status == .ok)
209+
210+
// Verify Cache-Control contains no-transform
211+
let cacheControl = res.headers[.cacheControl].first ?? ""
212+
#expect(cacheControl.contains("no-transform"),
213+
"Cache-Control should contain 'no-transform', got: '\(cacheControl)'")
214+
#expect(cacheControl.contains("public"),
215+
"Cache-Control should contain 'public', got: '\(cacheControl)'")
216+
217+
// Verify Content-Type
218+
let contentType = res.headers[.contentType].first ?? ""
219+
#expect(contentType == "application/json; charset=utf-8",
220+
"Content-Type should be 'application/json; charset=utf-8', got: '\(contentType)'")
221+
222+
// Verify Content-Length is present
223+
let contentLength = res.headers[.contentLength].first
224+
#expect(contentLength != nil,
225+
"Content-Length header should be present")
226+
if let length = contentLength {
227+
#expect(Int(length) ?? 0 > 0,
228+
"Content-Length should be > 0, got: '\(length)'")
229+
}
230+
})
231+
}
232+
}
233+
}
234+
173235
}
174236

175237

0 commit comments

Comments
 (0)