Merge pull request #658 from haskell-servant/file-serving

Revamp file serving module
This commit is contained in:
Oleg Grenrus 2017-01-17 22:09:57 +02:00 committed by GitHub
commit cce0f59ec8
5 changed files with 70 additions and 20 deletions

View file

@ -136,7 +136,7 @@ server = randomPoint
server' :: Server API' server' :: Server API'
server' = server server' = server
:<|> serveDirectory "static" :<|> serveDirectoryFileServer "static"
app :: Application app :: Application
app = serve api' server' app = serve api' server'

View file

@ -787,10 +787,10 @@ directory serving WAI application, namely:
``` haskell ignore ``` haskell ignore
-- exported by Servant and Servant.Server -- exported by Servant and Servant.Server
serveDirectory :: FilePath -> Server Raw serveDirectoryWebApp :: FilePath -> Server Raw
``` ```
`serveDirectory`'s argument must be a path to a valid directory. `serveDirectoryWebApp`'s argument must be a path to a valid directory.
Here's an example API that will serve some static files: Here's an example API that will serve some static files:
@ -807,7 +807,7 @@ staticAPI = Proxy
``` haskell ``` haskell
server7 :: Server StaticAPI server7 :: Server StaticAPI
server7 = serveDirectory "static-files" server7 = serveDirectoryWebApp "static-files"
app3 :: Application app3 :: Application
app3 = serve staticAPI server7 app3 = serve staticAPI server7
@ -821,6 +821,10 @@ In other words: If a client requests `/static/foo.txt`, the server will look for
`./static-files/foo.txt`. If that file exists it'll succeed and serve the file. `./static-files/foo.txt`. If that file exists it'll succeed and serve the file.
If it doesn't exist, the handler will fail with a `404` status code. If it doesn't exist, the handler will fail with a `404` status code.
`serveDirectoryWebApp` uses some standard settings that fit the use case of
serving static files for most web apps. You can find out about the other
options in the documentation of the `Servant.Utils.StaticFiles` module.
## Nested APIs ## Nested APIs
Let's see how you can define APIs in a modular way, while avoiding repetition. Let's see how you can define APIs in a modular way, while avoiding repetition.

View file

@ -4,9 +4,14 @@
* Add `err422` Unprocessable Entity * Add `err422` Unprocessable Entity
([#646](https://github.com/haskell-servant/servant/pull/646)) ([#646](https://github.com/haskell-servant/servant/pull/646))
* `Handler` is not abstract datatype. Migration hint: change `throwE` to `throwError`. * `Handler` is now an abstract datatype. Migration hint: change `throwE` to `throwError`.
([#641](https://github.com/haskell-servant/servant/issues/641)) ([#641](https://github.com/haskell-servant/servant/issues/641))
* Deprecate `serveDirectory` and introduce `serveDirectoryFileServer`,
`serveDirectoryWebApp`, `serveDirectoryWebAppLookup`, `serveDirectoryEmbedded`
and `serveDirectoryWith` which offer 4 default options and a more flexible
one for serving static files.
0.7.1 0.7.1
------ ------

View file

@ -1,20 +1,29 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
-- | This module defines a sever-side handler that lets you serve static files. -- | This module defines server-side handlers that lets you serve static files.
-- --
-- - 'serveDirectory' lets you serve anything that lives under a particular -- The most common needs for a web application are covered by
-- directory on your filesystem. -- 'serveDirectoryWebApp`, but the other variants allow you to use
module Servant.Utils.StaticFiles ( -- different `StaticSettings` and 'serveDirectoryWith' even allows you
serveDirectory, -- to specify arbitrary 'StaticSettings' to be used for serving static files.
module Servant.Utils.StaticFiles
( serveDirectoryWebApp
, serveDirectoryWebAppLookup
, serveDirectoryFileServer
, serveDirectoryEmbedded
, serveDirectoryWith
, -- * Deprecated
serveDirectory
) where ) where
import Network.Wai.Application.Static (defaultFileServerSettings, import Data.ByteString (ByteString)
staticApp) import Network.Wai.Application.Static
import Servant.API.Raw (Raw) import Servant.API.Raw (Raw)
import Servant.Server (Server) import Servant.Server (Server)
import System.FilePath (addTrailingPathSeparator) import System.FilePath (addTrailingPathSeparator)
#if !MIN_VERSION_wai_app_static(3,1,0) #if !MIN_VERSION_wai_app_static(3,1,0)
import Filesystem.Path.CurrentOS (decodeString) import Filesystem.Path.CurrentOS (decodeString)
#endif #endif
import WaiAppStatic.Storage.Filesystem (ETagLookup)
-- | Serve anything under the specified directory as a 'Raw' endpoint. -- | Serve anything under the specified directory as a 'Raw' endpoint.
-- --
@ -22,7 +31,7 @@ import Filesystem.Path.CurrentOS (decodeString)
-- type MyApi = "static" :> Raw -- type MyApi = "static" :> Raw
-- --
-- server :: Server MyApi -- server :: Server MyApi
-- server = serveDirectory "\/var\/www" -- server = serveDirectoryWebApp "\/var\/www"
-- @ -- @
-- --
-- would capture any request to @\/static\/\<something>@ and look for -- would capture any request to @\/static\/\<something>@ and look for
@ -33,13 +42,45 @@ import Filesystem.Path.CurrentOS (decodeString)
-- --
-- If your goal is to serve HTML, CSS and Javascript files that use the rest of the API -- If your goal is to serve HTML, CSS and Javascript files that use the rest of the API
-- as a webapp backend, you will most likely not want the static files to be hidden -- as a webapp backend, you will most likely not want the static files to be hidden
-- behind a /\/static\// prefix. In that case, remember to put the 'serveDirectory' -- behind a /\/static\// prefix. In that case, remember to put the 'serveDirectoryWebApp'
-- handler in the last position, because /servant/ will try to match the handlers -- handler in the last position, because /servant/ will try to match the handlers
-- in order. -- in order.
--
-- Corresponds to the `defaultWebAppSettings` `StaticSettings` value.
serveDirectoryWebApp :: FilePath -> Server Raw
serveDirectoryWebApp = staticApp . defaultWebAppSettings . fixPath
-- | Same as 'serveDirectoryWebApp', but uses `defaultFileServerSettings`.
serveDirectoryFileServer :: FilePath -> Server Raw
serveDirectoryFileServer = staticApp . defaultFileServerSettings . fixPath
-- | Same as 'serveDirectoryWebApp', but uses 'webAppSettingsWithLookup'.
serveDirectoryWebAppLookup :: ETagLookup -> FilePath -> Server Raw
serveDirectoryWebAppLookup etag =
staticApp . flip webAppSettingsWithLookup etag . fixPath
-- | Uses 'embeddedSettings'.
serveDirectoryEmbedded :: [(FilePath, ByteString)] -> Server Raw
serveDirectoryEmbedded files = staticApp (embeddedSettings files)
-- | Alias for 'staticApp'. Lets you serve a directory
-- with arbitrary 'StaticSettings'. Useful when you want
-- particular settings not covered by the four other
-- variants. This is the most flexible method.
serveDirectoryWith :: StaticSettings -> Server Raw
serveDirectoryWith = staticApp
-- | Same as 'serveDirectoryFileServer'. It used to be the only
-- file serving function in servant pre-0.10 and will be kept
-- around for a few versions, but is deprecated.
serveDirectory :: FilePath -> Server Raw serveDirectory :: FilePath -> Server Raw
serveDirectory = serveDirectory = serveDirectoryFileServer
{-# DEPRECATED serveDirectory "Use serveDirectoryFileServer instead" #-}
fixPath :: FilePath -> FilePath
fixPath =
#if MIN_VERSION_wai_app_static(3,1,0) #if MIN_VERSION_wai_app_static(3,1,0)
staticApp . defaultFileServerSettings . addTrailingPathSeparator addTrailingPathSeparator
#else #else
staticApp . defaultFileServerSettings . decodeString . addTrailingPathSeparator decodeString . addTrailingPathSeparator
#endif #endif

View file

@ -18,7 +18,7 @@ import Test.Hspec.Wai (get, shouldRespondWith, with)
import Servant.API ((:<|>) ((:<|>)), Capture, Get, Raw, (:>), JSON) import Servant.API ((:<|>) ((:<|>)), Capture, Get, Raw, (:>), JSON)
import Servant.Server (Server, serve) import Servant.Server (Server, serve)
import Servant.ServerSpec (Person (Person)) import Servant.ServerSpec (Person (Person))
import Servant.Utils.StaticFiles (serveDirectory) import Servant.Utils.StaticFiles (serveDirectoryFileServer)
type Api = type Api =
"dummy_api" :> Capture "person_name" String :> Get '[JSON] Person "dummy_api" :> Capture "person_name" String :> Get '[JSON] Person
@ -34,7 +34,7 @@ app = serve api server
server :: Server Api server :: Server Api
server = server =
(\ name_ -> return (Person name_ 42)) (\ name_ -> return (Person name_ 42))
:<|> serveDirectory "static" :<|> serveDirectoryFileServer "static"
withStaticFiles :: IO () -> IO () withStaticFiles :: IO () -> IO ()
withStaticFiles action = withSystemTempDirectory "servant-test" $ \ tmpDir -> withStaticFiles action = withSystemTempDirectory "servant-test" $ \ tmpDir ->