Skip to content

Instantly share code, notes, and snippets.

@vzsg
Created May 25, 2017 22:35
Show Gist options
  • Save vzsg/3e693e459b7de6fc73493a73ecbed297 to your computer and use it in GitHub Desktop.
Save vzsg/3e693e459b7de6fc73493a73ecbed297 to your computer and use it in GitHub Desktop.
HTML5 Routing Compatibility Middleware for Vapor 2
extension Config {
public func setup() throws {
// ...
addConfigurable(middleware: Html5RoutingMiddleware.init, name: "html5")
}
// ...
}
{
// ...
"middleware": [
"error",
"html5", // <-- make sure this is before "file"
"date",
"file"
],
// ...
}
import Vapor
import HTTP
import Foundation
public final class Html5RoutingMiddleware: Middleware {
private let loader = DataFile()
public let defaultPath: String
public init(defaultPath: String) {
self.defaultPath = defaultPath
}
public func respond(to request: Request, chainingTo next: Responder) throws -> Response {
do {
return try next.respond(to: request)
}
catch let error as AbortError where error.status == .notFound {
guard request.headers["X-Requested-With"] != "XMLHttpRequest" else {
throw error
}
guard
let attributes = try? Foundation.FileManager.default.attributesOfItem(atPath: defaultPath),
let modifiedAt = attributes[.modificationDate] as? Date,
let fileSize = attributes[.size] as? NSNumber
else {
throw Abort.notFound
}
var headers: [HeaderKey: String] = [:]
// Generate ETag value, "HEX value of last modified date" + "-" + "file size"
let fileETag = "\(modifiedAt.timeIntervalSince1970)-\(fileSize.intValue)"
headers["ETag"] = fileETag
// Check if file has been cached already and return NotModified response if the etags match
if fileETag == request.headers["If-None-Match"] {
return Response(status: .notModified, headers: headers, body: .data([]))
}
// Set Content-Type header based on the media type
// Only set Content-Type if file not modified and returned above.
if
let fileExtension = defaultPath.components(separatedBy: ".").last,
let type = Request.mediaTypes[fileExtension]
{
headers["Content-Type"] = type
}
// File exists and was not cached, returning content of file.
if let fileBody = try? loader.read(at: defaultPath) {
return Response(status: .ok, headers: headers, body: .data(fileBody))
} else {
print("unable to load path")
throw Abort.notFound
}
}
}
}
extension Html5RoutingMiddleware: ConfigInitializable {
public convenience init(config: Config) throws {
let path = config.publicDir + (config["html5", "index"]?.string ?? "index.html")
self.init(defaultPath: path)
}
}
final class Routes: RouteCollection {
func build(_ builder: RouteBuilder) throws {
// ...
// Make sure to remove any "*" and "/" route handlers!
// E.g. the api-template has a "*" route that prints the request details. Kill it.
// ...
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment