Search code examples
swiftvapor

How to serve file for URL with a given prefix?


Context

I have been serving files via Vapor (v4) successfully with:

func configure(_ app: Application) throws {
   // ...
   app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
   // ...
}

Problem

  • This attempts to map any/every URL that would match a file within publicDirectory.
  • How would one limit the "reach" of the FileMiddleware to only requests whose URL have a specific prefix? (say /files/)

As in, if I have /Public/foobar.txt, one would request it via GET /files/foobar.txt while GET /foobar.txt would NOT match anything.

Considered approaches

  • My understanding is that Middleware is applied server-wide (affects every "route") and not a subset ... so this wouldn't work.
  • Moving /Public/foobar.txt to /Public/files/foobar.txt is NOT a solution as I'm trying to limit URLs that can potentially get mapped to the file system...
  • Redirecting GET /files/foobar.txt to GET /foobar.txt is also not acceptable. Again, I need to limit the potential URLs which are mapped to the file system.

Solution

  • It seems that FileMiddleware will only work globally and not if attached to groups of routes as is usually the case with instances of middleware.

    If you have a folder called Private in the project folder (i.e. the same folder holding your Public folder), then accessing files contained in it is straightforward:

    public func configure(_ app: Application) throws {
        app.get("files", "**") { req -> Response in
            let filepath = req.parameters.getCatchall()
            let workPath = DirectoryConfiguration.detect().workingDirectory
            var fileURL = URL(fileURLWithPath: workPath)
                .appendingPathComponent("Private", isDirectory: true)
                .appendingPathComponent(filepath.joined(separator: "/"), isDirectory: false)
            return req.fileio.streamFile(at: fileURL.path)
        }
    }
    

    Assuming you are running this minimal project on localhost:8080, it will serve files via the URL:

    http://localhost:8080/files/path/to/myFile.txt

    EDIT

    OP indicates flat files only. In the light of comment, made it work with arbitrary paths under Private/. I'll leave you to add action if file/path does not exist.