Skip to content

Commit 2dcd795

Browse files
committed
Move MultipartBackend class to server package
1 parent 9cc91ac commit 2dcd795

File tree

6 files changed

+71
-100
lines changed

6 files changed

+71
-100
lines changed

servant-multipart-api/servant-multipart-api.cabal

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ library
3333

3434
-- other dependencies
3535
build-depends:
36-
resourcet >=1.2.2 && <1.3
37-
, servant >=0.16 && <0.19
36+
servant >=0.16 && <0.19
3837

3938
source-repository head
4039
type: git

servant-multipart-api/src/Servant/Multipart/API.hs

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,17 @@ module Servant.Multipart.API
2424
, MultipartData(..)
2525
, ToMultipart(..)
2626
, FromMultipart(..)
27-
, MultipartBackend(..)
27+
, MultipartResult
2828
, Tmp
2929
, Mem
3030
, Input(..)
3131
, FileData(..)
3232
) where
3333

34-
import Control.Monad.Trans.Resource
3534
import Data.Text (Text)
3635
import Data.Typeable
3736
import Servant.API
3837

39-
import qualified Data.ByteString as SBS
4038
import qualified Data.ByteString.Lazy as LBS
4139

4240
-- | Combinator for specifying a @multipart/form-data@ request
@@ -224,28 +222,16 @@ class ToMultipart tag a where
224222
instance ToMultipart tag (MultipartData tag) where
225223
toMultipart = id
226224

227-
class MultipartBackend tag where
228-
type MultipartResult tag :: *
229-
type MultipartBackendOptions tag :: *
230-
231-
backend :: Proxy tag
232-
-> MultipartBackendOptions tag
233-
-> InternalState
234-
-> ignored1
235-
-> ignored2
236-
-> IO SBS.ByteString
237-
-> IO (MultipartResult tag)
238-
239-
loadFile :: Proxy tag -> MultipartResult tag -> SourceIO LBS.ByteString
240-
241-
defaultBackendOptions :: Proxy tag -> MultipartBackendOptions tag
242-
243225
-- | Tag for data stored as a temporary file
244226
data Tmp
245227

246228
-- | Tag for data stored in memory
247229
data Mem
248230

231+
type family MultipartResult tag :: *
232+
type instance MultipartResult Tmp = FilePath
233+
type instance MultipartResult Mem = LBS.ByteString
234+
249235
instance HasLink sub => HasLink (MultipartForm tag a :> sub) where
250236
#if MIN_VERSION_servant(0,14,0)
251237
type MkLink (MultipartForm tag a :> sub) r = MkLink sub r

servant-multipart-client/exe/Upload.hs

Lines changed: 17 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,14 @@
33
{-# LANGUAGE OverloadedStrings #-}
44
{-# LANGUAGE TypeFamilies #-}
55

6-
import Control.Concurrent
7-
import Control.Monad
8-
import Control.Monad.IO.Class
9-
import Network.Socket (withSocketsDo)
10-
import Network.HTTP.Client hiding (Proxy)
11-
import Network.Wai.Handler.Warp
12-
import Servant
13-
import Servant.Multipart
6+
import Data.Proxy
7+
import Network.HTTP.Client hiding (Proxy)
8+
import Network.Socket (withSocketsDo)
9+
import Servant.API
10+
import Servant.Client (client, mkClientEnv, runClientM)
11+
import Servant.Client.Core (BaseUrl (BaseUrl), Scheme (Http))
12+
import Servant.Multipart.API
1413
import Servant.Multipart.Client
15-
import System.Environment (getArgs)
16-
import Servant.Client (client, runClientM, mkClientEnv)
17-
import Servant.Client.Core (BaseUrl(BaseUrl), Scheme(Http))
18-
19-
import qualified Data.ByteString.Lazy as LBS
2014

2115
-- Our API, which consists in a single POST endpoint at /
2216
-- that takes a multipart/form-data request body and
@@ -31,56 +25,22 @@ type family MemToTmp api where
3125
MemToTmp (MultipartForm Mem (MultipartData Mem)) = MultipartForm Tmp (MultipartData Tmp)
3226
MemToTmp a = a
3327

34-
api :: Proxy API
35-
api = Proxy
36-
3728
clientApi :: Proxy (MemToTmp API)
3829
clientApi = Proxy
3930

40-
-- The handler for our single endpoint.
41-
-- Its concrete type is:
42-
-- MultipartData -> Handler Integer
43-
--
44-
-- MultipartData consists in textual inputs,
45-
-- accessible through its "inputs" field, as well
46-
-- as files, accessible through its "files" field.
47-
upload :: Server API
48-
upload multipartData = do
49-
liftIO $ do
50-
putStrLn "Inputs:"
51-
forM_ (inputs multipartData) $ \input ->
52-
putStrLn $ " " ++ show (iName input)
53-
++ " -> " ++ show (iValue input)
54-
55-
forM_ (files multipartData) $ \file -> do
56-
let content = fdPayload file
57-
putStrLn $ "Content of " ++ show (fdFileName file)
58-
LBS.putStr content
59-
return 0
60-
61-
startServer :: IO ()
62-
startServer = run 8080 (serve api upload)
63-
6431
main :: IO ()
65-
main = do
66-
args <- getArgs
67-
case args of
68-
("run":_) -> withSocketsDo $ do
69-
_ <- forkIO startServer
70-
-- we fork the server in a separate thread and send a test
71-
-- request to it from the main thread.
72-
manager <- newManager defaultManagerSettings
73-
boundary <- genBoundary
74-
let burl = BaseUrl Http "localhost" 8080 ""
75-
runC cli = runClientM cli (mkClientEnv manager burl)
76-
resp <- runC $ client clientApi (boundary, form)
77-
print resp
78-
_ -> putStrLn "Pass run to run"
32+
main = withSocketsDo $ do
33+
manager <- newManager defaultManagerSettings
34+
boundary <- genBoundary
35+
let burl = BaseUrl Http "localhost" 8080 ""
36+
runC cli = runClientM cli (mkClientEnv manager burl)
37+
resp <- runC $ client clientApi (boundary, form)
38+
print resp
7939

8040
where form = MultipartData [ Input "title" "World"
8141
, Input "text" "Hello"
8242
]
83-
[ FileData "file" "./servant-multipart.cabal"
84-
"text/plain" "./servant-multipart.cabal"
85-
, FileData "otherfile" "./Setup.hs" "text/plain" "./Setup.hs"
43+
[ FileData "file" "./servant-multipart/servant-multipart.cabal"
44+
"text/plain" "./servant-multipart/servant-multipart.cabal"
45+
, FileData "otherfile" "./servant-multipart/Setup.hs" "text/plain" "./servant-multipart/Setup.hs"
8646
]

servant-multipart-client/servant-multipart-client.cabal

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,22 @@ executable upload
4545
default-language: Haskell2010
4646
build-depends:
4747
base
48-
, bytestring
4948
, http-client
5049
, network >=2.8 && <3.2
5150
, servant
52-
, servant-multipart
51+
, servant-multipart-api
5352
, servant-multipart-client
5453
, servant-client
5554
, servant-client-core
55+
56+
executable server
57+
hs-source-dirs: exe
58+
main-is: Server.hs
59+
default-language: Haskell2010
60+
build-depends:
61+
base
62+
, bytestring
63+
, network >=2.8 && <3.2
64+
, servant-multipart
5665
, servant-server
5766
, warp

servant-multipart-client/src/Servant/Multipart/Client.hs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,23 @@ import Data.List (foldl')
3232
#if !MIN_VERSION_base(4,11,0)
3333
import Data.Monoid ((<>))
3434
#endif
35-
import Data.Text.Encoding (encodeUtf8)
35+
import Data.Text.Encoding (encodeUtf8)
3636
import Data.Typeable
3737
import Network.HTTP.Media.MediaType ((//), (/:))
3838
import Servant.API
39-
import Servant.Client.Core (HasClient(..), RequestBody(RequestBodySource), setRequestBody)
40-
import Servant.Types.SourceT (SourceT(..), StepT(..), source)
41-
import System.Random (getStdRandom, randomR)
39+
import Servant.Client.Core (HasClient (..), RequestBody (RequestBodySource),
40+
setRequestBody)
41+
import Servant.Types.SourceT (SourceT (..), StepT (..), fromActionStep, source)
42+
import System.IO (IOMode (ReadMode), withFile)
43+
import System.Random (getStdRandom, randomR)
4244

4345
import qualified Data.ByteString.Lazy as LBS
4446

4547
-- | Upon seeing @MultipartForm a :> ...@ in an API type,
4648
-- servant-client will take a parameter of type @(LBS.ByteString, a)@,
4749
-- where the bytestring is the boundary to use (see 'genBoundary'), and
4850
-- replace the request body with the contents of the form.
49-
instance (ToMultipart tag a, HasClient m api, MultipartBackend tag)
51+
instance (ToMultipart tag a, HasClient m api, MultipartClient tag)
5052
=> HasClient m (MultipartForm' mods tag a :> api) where
5153

5254
type Client m (MultipartForm' mods tag a :> api) =
@@ -61,6 +63,21 @@ instance (ToMultipart tag a, HasClient m api, MultipartBackend tag)
6163
hoistClientMonad pm _ f cl = \a ->
6264
hoistClientMonad pm (Proxy @api) f (cl a)
6365

66+
class MultipartClient tag where
67+
loadFile :: Proxy tag -> MultipartResult tag -> SourceIO LBS.ByteString
68+
69+
instance MultipartClient Tmp where
70+
-- streams the file from disk
71+
loadFile _ fp =
72+
SourceT $ \k ->
73+
withFile fp ReadMode $ \hdl ->
74+
k (readHandle hdl)
75+
where
76+
readHandle hdl = fromActionStep LBS.null (LBS.hGet hdl 4096)
77+
78+
instance MultipartClient Mem where
79+
loadFile _ = source . pure
80+
6481
-- | Generates a boundary to be used to separate parts of the multipart.
6582
-- Requires 'IO' because it is randomized.
6683
genBoundary :: IO LBS.ByteString
@@ -93,8 +110,8 @@ genBoundary = LBS.pack
93110

94111
-- | Given a bytestring for the boundary, turns a `MultipartData` into
95112
-- a 'RequestBody'
96-
multipartToBody :: forall tag.
97-
MultipartBackend tag
113+
multipartToBody :: forall tag
114+
. MultipartClient tag
98115
=> LBS.ByteString
99116
-> MultipartData tag
100117
-> RequestBody

servant-multipart/src/Servant/Multipart.hs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,9 @@ import Servant.API.Modifiers (FoldLenient)
5959
import Servant.Docs hiding (samples)
6060
import Servant.Foreign hiding (contentType)
6161
import Servant.Server.Internal
62-
import Servant.Types.SourceT (SourceT(..), source, fromActionStep)
6362
import System.Directory
64-
import System.IO (IOMode(ReadMode), withFile)
6563

6664
import qualified Data.ByteString as SBS
67-
import qualified Data.ByteString.Lazy as LBS
6865

6966
-- | Lookup a textual input with the given @name@ attribute.
7067
lookupInput :: Text -> MultipartData tag -> Either String Text
@@ -96,6 +93,19 @@ fromRaw (inputs, files) = MultipartData is fs
9693

9794
dec = decodeUtf8
9895

96+
class MultipartBackend tag where
97+
type MultipartBackendOptions tag :: *
98+
99+
backend :: Proxy tag
100+
-> MultipartBackendOptions tag
101+
-> InternalState
102+
-> ignored1
103+
-> ignored2
104+
-> IO SBS.ByteString
105+
-> IO (MultipartResult tag)
106+
107+
defaultBackendOptions :: Proxy tag -> MultipartBackendOptions tag
108+
99109
-- | Upon seeing @MultipartForm a :> ...@ in an API type,
100110
--- servant-server will hand a value of type @a@ to your handler
101111
-- assuming the request body's content type is
@@ -222,27 +232,17 @@ data MultipartOptions tag = MultipartOptions
222232
}
223233

224234
instance MultipartBackend Tmp where
225-
type MultipartResult Tmp = FilePath
226235
type MultipartBackendOptions Tmp = TmpBackendOptions
227236

228237
defaultBackendOptions _ = defaultTmpBackendOptions
229-
-- streams the file from disk
230-
loadFile _ fp =
231-
SourceT $ \k ->
232-
withFile fp ReadMode $ \hdl ->
233-
k (readHandle hdl)
234-
where
235-
readHandle hdl = fromActionStep LBS.null (LBS.hGet hdl 4096)
236238
backend _ opts = tmpBackend
237239
where
238240
tmpBackend = tempFileBackEndOpts (getTmpDir opts) (filenamePat opts)
239241

240242
instance MultipartBackend Mem where
241-
type MultipartResult Mem = LBS.ByteString
242243
type MultipartBackendOptions Mem = ()
243244

244245
defaultBackendOptions _ = ()
245-
loadFile _ = source . pure
246246
backend _ _ _ = lbsBackEnd
247247

248248
-- | Configuration for the temporary file based backend.

0 commit comments

Comments
 (0)