import Foundation import Hummingbird import NIOCore /// Serves a custom error page for requests that match neither a route nor a static file. /// /// Placed ahead of `FileMiddleware` in the middleware chain, it catches the `.notFound` error /// that bubbles up when no file exists for the requested path and responds with the preloaded /// error page and a `404 Not Found` status. public struct NotFoundMiddleware { // MARK: Properties /// The body of the error page served on a not-found response. private let page: ByteBuffer // MARK: Initializers /// Creates a middleware that serves the error page (`404.html`) from the static files folder. /// /// The page is read once, at construction. A minimal fallback body is used when the file is /// missing. /// - Parameter staticFilesPath: the folder, relative to the working directory, the static files are served from. public init( _ staticFilesPath: String ) { let path = StaticFile.errorHTML.path(relativeTo: staticFilesPath) if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { self.init(page: .init(bytes: data)) } else { self.init(page: .init(string: "404 Not Found")) } } /// Creates a middleware that serves the given error page on a not-found response. /// - Parameter page: the body of the error page. init( page: ByteBuffer ) { self.page = page } } // MARK: - RouterMiddleware extension NotFoundMiddleware: RouterMiddleware { // MARK: Functions public func handle( _ request: Request, context: Context, next: (Request, Context) async throws -> Response ) async throws -> Response { do { return try await next(request, context) } catch let error { // Only intercept "not found"; let every other error propagate. guard let responseError = error as? any HTTPResponseError, responseError.status == .notFound else { throw error } var headers = HTTPFields() headers[.contentType] = StaticFile.errorHTML.contentType return Response( status: .notFound, headers: headers, body: .init(byteBuffer: page) ) } } }