Happstack can be used to serve static files from disk, such as .html, .jpg, etc.
The file serving capabilities can be divided into two categories:
The most common way to serve files is by using serveDirectory
:
> data Browsing = EnableBrowsing | DisableBrowsing > > serveDirectory :: ( WebMonad Response m, ServerMonad m, FilterMonad Response m > , MonadIO m, MonadPlus m ) => > Browsing -- ^ enable/disable directory browsing > -> [FilePath] -- ^ index file names, used when path is a directory > -> FilePath -- ^ file/directory to serve > -> m Response
For example:
> serveDirectory EnableBrowsing ["index.html"] "path/to/directory/on/disk"
If the requested path does not map to a file or directory, then serveDirectory
returns mzero
.
If the requested path is a file then the file is served normally using serveFile
.
When a directory is requested, serveDirectory
will first try to find one of the index files (in the order they are listed). If that fails, it will show a directory listing if EnableBrowsing
, otherwise it will return forbidden "Directory index forbidden"
.
The formula for mapping the URL to a file on disk is just what you would expect:
So if the handler is:
And the request URL is:
Then we are going to look for:
The following demo will allow you to browse the directory that the server is running in. (So be careful where you run it).
> module Main where > > import Happstack.Server (Browsing(EnableBrowsing), nullConf, serveDirectory, simpleHTTP) > > main :: IO () > main = simpleHTTP nullConf $ serveDirectory EnableBrowsing [] "."
[Source code for the app is here.]
Simply run it and point your browser at http://localhost:8000/.
The request URL is sanitized so that users can not escape the top-level directory by adding extra .. or / characters to the URL.
The file serving code will follow symlinks. If you do not want that behavior then you will need to roll your own serving function. See the section on Advanced File Serving for more information.
Sometimes we want to serve files from disk whose name is not a direct mapping from the URL. For example, let's say that you have an image and you want to allow the client to request the images in different sizes by setting a query parameter. e.g.
Clearly, we can not just map the path info portion of the URL to a file disk, because all the different sizes have the same name -- only the query parameter is different. Instead, the application will use some custom algorithm to calculate where the image lives on the disk. It may even need to generate the resized image on-demand. Once the application knows where the file lives on disk it can use serveFile
to send that file as a Response
using sendFile
:
> serveFile :: (ServerMonad m, FilterMonad Response m, MonadIO m, MonadPlus m) => > (FilePath -> m String) -- ^ function for determining content-type of file. > -- Usually 'asContentType' or 'guessContentTypeM' > -> FilePath -- ^ path to the file to serve > -> m Response
The first argument is a function which calculates the mime-type for a FilePath
. The second argument is path to the file to send. So we might do something like:
> serveFile guessContentTypeM "/path/to/photos/photo_medium.jpg"
Note that even though the file is named photo_medium.jpg on the disk, that name is not exposed to the client. They will only see the name they requested, i.e., photo.jpg.
guessContentTypeM
will guess the content-type of the file by looking at the filename extension. But, if our photo app only supports JPEG files, there is no need to guess. Furthermore, the name of the file on the disk may not even have the proper extension. It could just be the md5sum of the file or something. So we can also hardcode the correct content-type:
> serveFile (asContentType "image/jpeg") "/path/to/photos/photo_medium.jpg"
The following, example attempts to serve its own source code for any incoming request.
> module Main where > > import Happstack.Server (asContentType, nullConf, serveFile, simpleHTTP) > > main :: IO () > main = > simpleHTTP nullConf $ serveFile (asContentType "text/x-haskell") "FileServingSingle.hs"
[Source code for the app is here.]
serveDirectory
and serveFile
should cover a majority of your file serving needs. But if you want something a little different, it is also possible to roll-your-own solution. The Happstack.Server.FileServe.BuildingBlocks
module contains all the pieces used to assemble the high-level serveDirectory
and serveFile
functions. You can reuse those pieces to build your own custom serving functions. For example, you might want to use a different method for calculating the mime-types, or perhaps you want to create a different look-and-feel for directory browsing, or maybe you want to use something other than sendFile
for sending the files. I recommend starting by copying the source for serveDirectory
or serveFile
and then modifying it to suit your needs.