Introduce HTTP::Dir and HTTP::FileSystem

HTTP::StaticFileHandler should use an abstraction instead of a string to serve files from.

In go we have http.Dir that is a wrapper around a string but it adds the Open(name string) (File, error) method, i think something similar would be great to have in crystal, here’s my attempt at translating the same abstraction we have in go.

# https://godoc.org/net/http#FileSystem
abstract class HTTP::FileSystem
  abstract def open(path : String) : IO
  end
end

# https://godoc.org/net/http#Dir
class HTTP::Dir < HTTP::FileSystem
  def open(path : String) : IO
    # ...
  end
end

Then HTTP::StaticFileHandler could work like this

class HTTP::StaticFileHandler
    def initialize(@fs : HTTP::FileSystem, fallthrough = true, directory_listing = true)
    @fallthrough = !!fallthrough
    @directory_listing = !!directory_listing
  end

  # use @fs.open to open files
end

This could be implemented in a backwards compatible way by overloading new(String) to create an HTTP::Dir and use it.

I don’t think it makes much sense to introduce two new types just for StaticFileHandler. To customize the way this handler accesses a file system, it would be much cleaner to just extract all filesystem-specific calls to private methods which can be overridden in a subclass.

As mentioned earlier, in the long term it might be great to have some kind of filesystem abstraction, but it should be universal, not just for StaticFileHandler.

I agree, having a global FIleSystem abstraction would be nicer in the long term, I’m going to refactor the filesystem calls in HTTP::StaticFileHandler into private methods and PR it, if thats alright with you?

EDIT: After implementing it its pretty hackish, i think implementing the filesystem abstraction should be opened as an issue on the crystal repo, I’ll open it if you agree.

Has this Issue been created already? I’m interested.

Coming from Golang, the global FileSystem abstraction is a huge enabler for a lot of cool stuff.

I’m on board on refactoring the StaticFileHandler to isolate syscalls and other logic into protected methods. That will allow to subclass and customize the behaviour in some scenarios.

Yet I would consider those as :nodoc: or not stable api. It won’t be worst as the current state were some frameworks monkey patch StaticFileHandler to extend the behavior to:

  • handle partial responses
  • remove some url components to mount the handler at other than root. (This should be handled in a more generic / composable RouteHandler at some point though)
  • customize the content to list in a directory
  • customize the directory listing template