You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Let's see how we can write a handle that uses a resource managed by Servant. The resource is created automatically by Servant when the server recieves a request, and the resource is automatically destroyed when the server is finished handling a request.
4
+
5
+
As usual, we start with a little bit of throat clearing.
Here we define an API type that uses the `WithResource` combinator. The server handler for an endpoint with a `WithResource res` component will receive a value of that type as an argument.
24
+
25
+
``` haskell
26
+
type API = WithResource Handle :> ReqBody '[PlainText] String :> Post '[JSON] NoContent
27
+
28
+
api :: Proxy API
29
+
api = Proxy
30
+
```
31
+
32
+
But this resource value has to come from somewhere. Servant obtains the value using an Acquire provided in the context. The Acquire knows how to both create and destroy resources of a particular type.
33
+
34
+
``` haskell
35
+
appContext :: Context '[Acquire Handle]
36
+
appContext = acquireHandle :. EmptyContext
37
+
38
+
acquireHandle :: Acquire Handle
39
+
acquireHandle = mkAcquire newHandle closeHandle
40
+
41
+
newHandle :: IO Handle
42
+
newHandle = do
43
+
putStrLn "opening file"
44
+
h <- openFile "test.txt" AppendMode
45
+
putStrLn "opened file"
46
+
return h
47
+
48
+
closeHandle :: Handle -> IO ()
49
+
closeHandle h = do
50
+
putStrLn "closing file"
51
+
hClose h
52
+
putStrLn "closed file"
53
+
```
54
+
55
+
Now we create the handler which will use this resource. This handler will write the request message to the System.IO.Handle which was provided to us. In some situations the handler will succeed, but in some in will fail. In either case, Servant will clean up the resource for us.
56
+
57
+
``` haskell
58
+
server :: Server API
59
+
server = writeToFile
60
+
61
+
where writeToFile :: (ReleaseKey, Handle) -> String -> Handler NoContent
62
+
writeToFile (_, h) msg = case msg of
63
+
"illegal" -> error "wait, that's illegal!"
64
+
legalMsg -> liftIO $ do
65
+
putStrLn "writing file"
66
+
hPutStrLn h legalMsg
67
+
putStrLn "wrote file"
68
+
return NoContent
69
+
```
70
+
71
+
Finally we run the server in the background while we post messages to it.
72
+
73
+
``` haskell
74
+
runApp :: IO ()
75
+
runApp = run 8080 (serveWithContext api appContext $ server)
76
+
77
+
postMsg :: String -> ClientM NoContent
78
+
postMsg = client api
79
+
80
+
main :: IO ()
81
+
main = do
82
+
mgr <- newManager defaultManagerSettings
83
+
bracket (forkIO $ runApp) killThread $\_ -> do
84
+
ms <- flip runClientM (mkClientEnv mgr (BaseUrl Http "localhost" 8080 "")) $ do
85
+
liftIO $ putStrLn "sending hello message"
86
+
_ <- postMsg "hello"
87
+
liftIO $ putStrLn "sending illegal message"
88
+
_ <- postMsg "illegal"
89
+
liftIO $ putStrLn "done"
90
+
print ms
91
+
```
92
+
93
+
This program prints
94
+
95
+
```
96
+
sending hello message
97
+
opening file
98
+
opened file
99
+
writing file
100
+
wrote file
101
+
closing file
102
+
closed file
103
+
sending illegal message
104
+
opening file
105
+
opened file
106
+
closing file
107
+
closed file
108
+
wait, that's illegal!
109
+
CallStack (from HasCallStack):
110
+
error, called at ManagedResource.lhs:63:24 in main:Main
and appends to a file called `test.txt`. We can see from the output that when a legal message is sent, the file is opened, written to, and closed. We can also see that when an illegal message is sent, the file is opened but not written to. Crucially, it is still closed even though the handler threw an exception.
0 commit comments