on master help sign in
Merge pull request #658 from haskell-servant/file-serving

Revamp file serving module

commitcce0f59ec8ba335f38d0c9ae5ed7da9656f648e5 authorOleg Grenrus <oleg.grenrus@iki.fi> dateTue 17 Jan 2017, 22:09 (10 years ago) parentc50fdef57445ac
5 files changed+71−21
doc/tutorial/Javascript.lhs+1−1 doc/tutorial/Server.lhs+7−3 servant-server/CHANGELOG.md+6−1 servant-server/src/Servant/Utils/StaticFiles.hs+55−14 servant-server/test/Servant/Utils/StaticFilesSpec.hs+2−2
doc/tutorial/Javascript.lhs+1−1
@@ -136,7 +136,7 @@ server = randomPoint
136136
137137server' :: Server API'
138138server' = server
139− :<|> serveDirectory "static"
139+ :<|> serveDirectoryFileServer "static"
140140
141141app :: Application
142142app = serve api' server'
doc/tutorial/Server.lhs+7−3
@@ -787,10 +787,10 @@ directory serving WAI application, namely:
787787
788788``` haskell ignore
789789-- exported by Servant and Servant.Server
790−serveDirectory :: FilePath -> Server Raw
790+serveDirectoryWebApp :: FilePath -> Server Raw
791791```
792792
793−`serveDirectory`'s argument must be a path to a valid directory.
793+`serveDirectoryWebApp`'s argument must be a path to a valid directory.
794794
795795Here's an example API that will serve some static files:
796796
@@ -807,7 +807,7 @@ staticAPI = Proxy
807807
808808``` haskell
809809server7 :: Server StaticAPI
810−server7 = serveDirectory "static-files"
810+server7 = serveDirectoryWebApp "static-files"
811811
812812app3 :: Application
813813app3 = serve staticAPI server7
@@ -821,6 +821,10 @@ In other words: If a client requests `/static/foo.txt`, the server will look for
821821`./static-files/foo.txt`. If that file exists it'll succeed and serve the file.
822822If it doesn't exist, the handler will fail with a `404` status code.
823823
824+`serveDirectoryWebApp` uses some standard settings that fit the use case of
825+serving static files for most web apps. You can find out about the other
826+options in the documentation of the `Servant.Utils.StaticFiles` module.
827+
824828## Nested APIs
825829
826830Let's see how you can define APIs in a modular way, while avoiding repetition.
servant-server/CHANGELOG.md+6−1
@@ -4,9 +4,14 @@
44* Add `err422` Unprocessable Entity
55 ([#646](https://github.com/haskell-servant/servant/pull/646))
66
7−* `Handler` is not abstract datatype. Migration hint: change `throwE` to `throwError`.
7+* `Handler` is now an abstract datatype. Migration hint: change `throwE` to `throwError`.
88 ([#641](https://github.com/haskell-servant/servant/issues/641))
99
10+* Deprecate `serveDirectory` and introduce `serveDirectoryFileServer`,
11+ `serveDirectoryWebApp`, `serveDirectoryWebAppLookup`, `serveDirectoryEmbedded`
12+ and `serveDirectoryWith` which offer 4 default options and a more flexible
13+ one for serving static files.
14+
10150.7.1
1116------
1217
servant-server/src/Servant/Utils/StaticFiles.hs+55−14
@@ -1,20 +1,29 @@
11{-# LANGUAGE CPP #-}
2−-- | This module defines a sever-side handler that lets you serve static files.
2+-- | This module defines server-side handlers that lets you serve static files.
33--
4−-- - 'serveDirectory' lets you serve anything that lives under a particular
5−-- directory on your filesystem.
6−module Servant.Utils.StaticFiles (
7− serveDirectory,
8− ) where
9−
10−import Network.Wai.Application.Static (defaultFileServerSettings,
11− staticApp)
4+-- The most common needs for a web application are covered by
5+-- 'serveDirectoryWebApp`, but the other variants allow you to use
6+-- different `StaticSettings` and 'serveDirectoryWith' even allows you
7+-- to specify arbitrary 'StaticSettings' to be used for serving static files.
8+module Servant.Utils.StaticFiles
9+ ( serveDirectoryWebApp
10+ , serveDirectoryWebAppLookup
11+ , serveDirectoryFileServer
12+ , serveDirectoryEmbedded
13+ , serveDirectoryWith
14+ , -- * Deprecated
15+ serveDirectory
16+ ) where
17+
18+import Data.ByteString (ByteString)
19+import Network.Wai.Application.Static
1220import Servant.API.Raw (Raw)
1321import Servant.Server (Server)
1422import System.FilePath (addTrailingPathSeparator)
1523#if !MIN_VERSION_wai_app_static(3,1,0)
1624import Filesystem.Path.CurrentOS (decodeString)
1725#endif
26+import WaiAppStatic.Storage.Filesystem (ETagLookup)
1827
1928-- | Serve anything under the specified directory as a 'Raw' endpoint.
2029--
@@ -22,7 +31,7 @@ import Filesystem.Path.CurrentOS (decodeString)
2231-- type MyApi = "static" :> Raw
2332--
2433-- server :: Server MyApi
25−-- server = serveDirectory "\/var\/www"
34+-- server = serveDirectoryWebApp "\/var\/www"
2635-- @
2736--
2837-- would capture any request to @\/static\/\<something>@ and look for
@@ -33,13 +42,45 @@ import Filesystem.Path.CurrentOS (decodeString)
3342--
3443-- If your goal is to serve HTML, CSS and Javascript files that use the rest of the API
3544-- as a webapp backend, you will most likely not want the static files to be hidden
36−-- behind a /\/static\// prefix. In that case, remember to put the 'serveDirectory'
45+-- behind a /\/static\// prefix. In that case, remember to put the 'serveDirectoryWebApp'
3746-- handler in the last position, because /servant/ will try to match the handlers
3847-- in order.
48+--
49+-- Corresponds to the `defaultWebAppSettings` `StaticSettings` value.
50+serveDirectoryWebApp :: FilePath -> Server Raw
51+serveDirectoryWebApp = staticApp . defaultWebAppSettings . fixPath
52+
53+-- | Same as 'serveDirectoryWebApp', but uses `defaultFileServerSettings`.
54+serveDirectoryFileServer :: FilePath -> Server Raw
55+serveDirectoryFileServer = staticApp . defaultFileServerSettings . fixPath
56+
57+-- | Same as 'serveDirectoryWebApp', but uses 'webAppSettingsWithLookup'.
58+serveDirectoryWebAppLookup :: ETagLookup -> FilePath -> Server Raw
59+serveDirectoryWebAppLookup etag =
60+ staticApp . flip webAppSettingsWithLookup etag . fixPath
61+
62+-- | Uses 'embeddedSettings'.
63+serveDirectoryEmbedded :: [(FilePath, ByteString)] -> Server Raw
64+serveDirectoryEmbedded files = staticApp (embeddedSettings files)
65+
66+-- | Alias for 'staticApp'. Lets you serve a directory
67+-- with arbitrary 'StaticSettings'. Useful when you want
68+-- particular settings not covered by the four other
69+-- variants. This is the most flexible method.
70+serveDirectoryWith :: StaticSettings -> Server Raw
71+serveDirectoryWith = staticApp
72+
73+-- | Same as 'serveDirectoryFileServer'. It used to be the only
74+-- file serving function in servant pre-0.10 and will be kept
75+-- around for a few versions, but is deprecated.
3976serveDirectory :: FilePath -> Server Raw
40−serveDirectory =
77+serveDirectory = serveDirectoryFileServer
78+{-# DEPRECATED serveDirectory "Use serveDirectoryFileServer instead" #-}
79+
80+fixPath :: FilePath -> FilePath
81+fixPath =
4182#if MIN_VERSION_wai_app_static(3,1,0)
42− staticApp . defaultFileServerSettings . addTrailingPathSeparator
83+ addTrailingPathSeparator
4384#else
44− staticApp . defaultFileServerSettings . decodeString . addTrailingPathSeparator
85+ decodeString . addTrailingPathSeparator
4586#endif
servant-server/test/Servant/Utils/StaticFilesSpec.hs+2−2
@@ -18,7 +18,7 @@ import Test.Hspec.Wai (get, shouldRespondWith, with)
1818import Servant.API ((:<|>) ((:<|>)), Capture, Get, Raw, (:>), JSON)
1919import Servant.Server (Server, serve)
2020import Servant.ServerSpec (Person (Person))
21−import Servant.Utils.StaticFiles (serveDirectory)
21+import Servant.Utils.StaticFiles (serveDirectoryFileServer)
2222
2323type Api =
2424 "dummy_api" :> Capture "person_name" String :> Get '[JSON] Person
@@ -34,7 +34,7 @@ app = serve api server
3434server :: Server Api
3535server =
3636 (\ name_ -> return (Person name_ 42))
37− :<|> serveDirectory "static"
37+ :<|> serveDirectoryFileServer "static"
3838
3939withStaticFiles :: IO () -> IO ()
4040withStaticFiles action = withSystemTempDirectory "servant-test" $ \ tmpDir ->