Skip to content

Commit cce0f59

Browse files
authored
Merge pull request #658 from haskell-servant/file-serving
Revamp file serving module
2 parents c50fdef + 57445ac commit cce0f59

File tree

5 files changed

+71
-21
lines changed

5 files changed

+71
-21
lines changed

doc/tutorial/Javascript.lhs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ server = randomPoint
136136
137137
server' :: Server API'
138138
server' = server
139-
:<|> serveDirectory "static"
139+
:<|> serveDirectoryFileServer "static"
140140
141141
app :: Application
142142
app = serve api' server'

doc/tutorial/Server.lhs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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
795795
Here's an example API that will serve some static files:
796796
@@ -807,7 +807,7 @@ staticAPI = Proxy
807807
808808
``` haskell
809809
server7 :: Server StaticAPI
810-
server7 = serveDirectory "static-files"
810+
server7 = serveDirectoryWebApp "static-files"
811811
812812
app3 :: Application
813813
app3 = 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.
822822
If 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
826830
Let's see how you can define APIs in a modular way, while avoiding repetition.

servant-server/CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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+
1015
0.7.1
1116
------
1217

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,37 @@
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
1220
import Servant.API.Raw (Raw)
1321
import Servant.Server (Server)
1422
import System.FilePath (addTrailingPathSeparator)
1523
#if !MIN_VERSION_wai_app_static(3,1,0)
1624
import 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
--
2130
-- @
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.
3976
serveDirectory :: 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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import Test.Hspec.Wai (get, shouldRespondWith, with)
1818
import Servant.API ((:<|>) ((:<|>)), Capture, Get, Raw, (:>), JSON)
1919
import Servant.Server (Server, serve)
2020
import Servant.ServerSpec (Person (Person))
21-
import Servant.Utils.StaticFiles (serveDirectory)
21+
import Servant.Utils.StaticFiles (serveDirectoryFileServer)
2222

2323
type Api =
2424
"dummy_api" :> Capture "person_name" String :> Get '[JSON] Person
@@ -34,7 +34,7 @@ app = serve api server
3434
server :: Server Api
3535
server =
3636
(\ name_ -> return (Person name_ 42))
37-
:<|> serveDirectory "static"
37+
:<|> serveDirectoryFileServer "static"
3838

3939
withStaticFiles :: IO () -> IO ()
4040
withStaticFiles action = withSystemTempDirectory "servant-test" $ \ tmpDir ->

0 commit comments

Comments
 (0)