Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 27 additions & 25 deletions FlyingFox/Sources/Handlers/DirectoryHTTPHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,39 +55,41 @@ public struct DirectoryHTTPHandler: HTTPHandler {
}

public func handleRequest(_ request: HTTPRequest) async throws -> HTTPResponse {
guard
let filePath = makeFileURL(for: request.path),
let data = try? Data(contentsOf: filePath) else {
guard let filePath = makeFileURL(for: request.path) else {
return HTTPResponse(statusCode: .notFound)
}

var headers: HTTPHeaders = [
.contentType: FileHTTPHandler.makeContentType(for: filePath.absoluteString),
.cacheControl: cacheControl.getSerializedValue(),
.date: HTTPCacheControl.getDateHeaderValue()
]
do {
var headers: HTTPHeaders = [
.contentType: FileHTTPHandler.makeContentType(for: filePath.absoluteString),
.cacheControl: cacheControl.getSerializedValue(),
.date: HTTPCacheControl.getDateHeaderValue()
]

if let expiresValue = HTTPCacheControl.getExpiresValue(for: filePath) {
headers[.lastModified] = expiresValue
if let ifModifiedSince = request.headers[.ifModifiedSince], expiresValue == ifModifiedSince {
return HTTPResponse(statusCode: .notModified,
headers: headers)
if let expiresValue = HTTPCacheControl.getExpiresValue(for: filePath) {
headers[.lastModified] = expiresValue
if let ifModifiedSince = request.headers[.ifModifiedSince], expiresValue == ifModifiedSince {
return HTTPResponse(statusCode: .notModified,
headers: headers)
}
}
}

if let eTagValue = HTTPCacheControl.getETagValue(for: filePath) {
headers[.eTag] = eTagValue
if let ifNoneMatch = request.headers[.ifNoneMatch], eTagValue == ifNoneMatch {
return HTTPResponse(statusCode: .notModified,
headers: headers)
if let eTagValue = HTTPCacheControl.getETagValue(for: filePath) {
headers[.eTag] = eTagValue
if let ifNoneMatch = request.headers[.ifNoneMatch], eTagValue == ifNoneMatch {
return HTTPResponse(statusCode: .notModified,
headers: headers)
}
}
}

return HTTPResponse(
statusCode: .ok,
headers: headers,
body: data
)
return try HTTPResponse(
statusCode: .ok,
headers: headers,
body: HTTPBodySequence(file: filePath)
)
} catch {
return HTTPResponse(statusCode: .notFound)
}
}

func makeFileURL(for requestPath: String) -> URL? {
Expand Down
76 changes: 76 additions & 0 deletions FlyingFox/Tests/Handlers/DirectoryHTTPHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,82 @@ struct DirectoryHTTPHandlerTests {
)
}

@Test
func directoryHandler_streamsBody_fromFile() async throws {
let handler = DirectoryHTTPHandler(bundle: .module, subPath: "Stubs", serverPath: "server/path")

let response = try await handler.handleRequest(.make(path: "server/path/fish.json"))
guard case .httpBody(let body) = response.payload else {
Issue.record("expected .httpBody payload")
return
}
#expect(body.storage.sequence is AsyncBufferedFileSequence)
}

@Test
func directoryHandler_setsCacheHeaders_on200() async throws {
let handler = DirectoryHTTPHandler(bundle: .module, subPath: "Stubs", serverPath: "server/path")

let response = try await handler.handleRequest(.make(path: "server/path/fish.json"))
#expect(response.statusCode == .ok)
#expect(response.headers[.cacheControl]?.isEmpty == false)
#expect(response.headers[.date]?.isEmpty == false)
#expect(response.headers[.lastModified]?.isEmpty == false)
#expect(response.headers[.eTag]?.isEmpty == false)
}

@Test
func directoryHandler_returns304_whenIfModifiedSinceMatches() async throws {
let handler = DirectoryHTTPHandler(bundle: .module, subPath: "Stubs", serverPath: "server/path")

let initial = try await handler.handleRequest(.make(path: "server/path/fish.json"))
let lastModified = try #require(initial.headers[.lastModified])

let response = try await handler.handleRequest(.make(
path: "server/path/fish.json",
headers: [.ifModifiedSince: lastModified]
))
#expect(response.statusCode == .notModified)
#expect(response.headers[.lastModified] == lastModified)
}

@Test
func directoryHandler_returns200_whenIfModifiedSinceDoesNotMatch() async throws {
let handler = DirectoryHTTPHandler(bundle: .module, subPath: "Stubs", serverPath: "server/path")

let response = try await handler.handleRequest(.make(
path: "server/path/fish.json",
headers: [.ifModifiedSince: "Mon, 01 Jan 1990 00:00:00 GMT"]
))
#expect(response.statusCode == .ok)
}

@Test
func directoryHandler_returns304_whenIfNoneMatchMatches() async throws {
let handler = DirectoryHTTPHandler(bundle: .module, subPath: "Stubs", serverPath: "server/path")

let initial = try await handler.handleRequest(.make(path: "server/path/fish.json"))
let etag = try #require(initial.headers[.eTag])

let response = try await handler.handleRequest(.make(
path: "server/path/fish.json",
headers: [.ifNoneMatch: etag]
))
#expect(response.statusCode == .notModified)
#expect(response.headers[.eTag] == etag)
}

@Test
func directoryHandler_returns200_whenIfNoneMatchDoesNotMatch() async throws {
let handler = DirectoryHTTPHandler(bundle: .module, subPath: "Stubs", serverPath: "server/path")

let response = try await handler.handleRequest(.make(
path: "server/path/fish.json",
headers: [.ifNoneMatch: "\"deadbeef-0\""]
))
#expect(response.statusCode == .ok)
}

@Test
func directoryHandler_Returns404WhenFileDoesNotExist() async throws {
let handler = DirectoryHTTPHandler.directory(for: .module, subPath: "Stubs", serverPath: "server/path")
Expand Down
Loading