-
Notifications
You must be signed in to change notification settings - Fork 0
/
KVS1Simple.hs
118 lines (102 loc) · 3.3 KB
/
KVS1Simple.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# LANGUAGE TemplateHaskell #-}
{-
# Example: Simple client-server key-value store
This is the first version of the 4-stage key-value store tutorial and implements a simple client-server key-value store. The client can `PUT` a key-value pair to the server and `GET` a value for a given key.
## Execution
```bash
# start server
cabal run kvs1 server
# on a different terminal for client
cabal run kvs1 client
GET hello
> Nothing
PUT hello world
> Just "world"
GET hello
> Just "world"
```
-}
module KVS1Simple where
import Choreography
import Choreography.Network.Http
import Data.IORef
import Data.Map (Map)
import Data.Map qualified as Map
import System.Environment
$(mkLoc "client")
$(mkLoc "server")
type Participants = ["client", "server"]
type State = Map String String
data Request = Put String String | Get String deriving (Show, Read)
type Response = Maybe String
-- | `readRequest` reads a request from the terminal.
readRequest :: IO Request
readRequest = do
putStrLn "Command?"
line <- getLine
case parseRequest line of
Just t -> return t
Nothing -> putStrLn "Invalid command" >> readRequest
where
parseRequest :: String -> Maybe Request
parseRequest s =
let l = words s
in case l of
["GET", k] -> Just (Get k)
["PUT", k, v] -> Just (Put k v)
_ -> Nothing
-- | `handleRequest` handle a request and returns the new the state.
handleRequest :: Request -> IORef State -> IO Response
handleRequest request stateRef = case request of
Put key value -> do
modifyIORef stateRef (Map.insert key value)
return (Just value)
Get key -> do
state <- readIORef stateRef
return (Map.lookup key state)
-- | `kvs` is a choreography that processes a single request located at the client and returns the response.
kvs ::
Located '["client"] Request ->
Located '["server"] (IORef State) ->
Choreo Participants IO (Located '["client"] Response)
kvs request stateRef = do
-- send the request to the server
request' <- (client, request) ~> server @@ nobody
-- the server handles the response and creates a response
response <-
server `locally` \un ->
handleRequest (un server request') (un server stateRef)
-- send the response back to the client
(server, response) ~> client @@ nobody
-- | `mainChoreo` is a choreography that serves as the entry point of the program.
-- It initializes the state and loops forever.
-- HIII :> (*>_*)
mainChoreo :: Choreo Participants IO ()
mainChoreo = do
stateRef <- server `_locally` newIORef (Map.empty :: State)
loop stateRef
where
loop :: Located '["server"] (IORef State) -> Choreo Participants IO ()
loop stateRef = do
request <- client `_locally` readRequest
response <- kvs request stateRef
client `locally_` \un -> do putStrLn ("> " ++ show (un client response))
loop stateRef
main :: IO ()
main = do
[loc] <- getArgs
case loc of
"client" -> runChoreography config mainChoreo "client"
"server" -> runChoreography config mainChoreo "server"
_ -> error "unknown party"
return ()
where
config =
mkHttpConfig
[ ("client", ("localhost", 3000)),
("server", ("localhost", 4000))
]