Starting to work on shelob-client too
Getty Ritter
7 years ago
1 | packages: shelob/shelob.cabal, shelob-client/shelob-client.cabal |
1 | {-# LANGUAGE FlexibleInstances #-} | |
2 | 1 | module Network.Shelob.Types where |
3 | 2 | |
4 | 3 | import qualified Control.Exception as Ex |
50 | 49 | instance Ex.Exception HTTPError where |
51 | 50 | toException = undefined |
52 | 51 | fromException = undefined |
53 | ||
54 | class HTTPResult t where | |
55 | fromResponse :: Either HTTPError Response -> t | |
56 | ||
57 | instance HTTPResult r => HTTPResult (Either HTTPError r) where | |
58 | fromResponse (Left err) = Left err | |
59 | fromResponse (Right v) = Right (fromResponse (Right v)) | |
60 | ||
61 | instance HTTPResult r => HTTPResult (Maybe r) where | |
62 | fromResponse (Left _) = Nothing | |
63 | fromResponse (Right v) = Just (fromResponse (Right v)) | |
64 | ||
65 | instance HTTPResult Response where | |
66 | fromResponse (Left err) = Ex.throw err | |
67 | fromResponse (Right v) = v |
2 | 2 | |
3 | 3 | module Network.Shelob where |
4 | 4 | |
5 | import Control.Monad.IO.Class (MonadIO(..)) | |
6 | 5 | import qualified Data.ByteString as BS |
7 | 6 | import qualified Data.ByteString.Char8 as BS8 |
8 | 7 | import qualified Network.Socket as Sock |
13 | 12 | import Network.Shelob.Response |
14 | 13 | import Network.Shelob.Types |
15 | 14 | |
16 | newtype ConnM a = ConnM { runConnM :: Connection -> IO a } | |
17 | ||
18 | instance Functor ConnM where | |
19 | fmap f (ConnM g) = ConnM (\ c -> fmap f (g c)) | |
20 | ||
21 | instance Applicative ConnM where | |
22 | pure x = ConnM (\ _ -> return x) | |
23 | ConnM f <*> ConnM x = ConnM $ \ c -> do | |
24 | f' <- f c | |
25 | x' <- x c | |
26 | return (f' x') | |
27 | ||
28 | instance Monad ConnM where | |
29 | ConnM x >>= f = ConnM $ \ c -> do | |
30 | x' <- x c | |
31 | runConnM (f x') c | |
32 | ||
33 | instance MonadIO ConnM where | |
34 | liftIO m = ConnM (\ _ -> m) | |
35 | ||
36 | connect :: Connection -> ConnM a -> IO a | |
37 | connect conn m = do | |
38 | rs <- runConnM m conn | |
39 | connClose conn | |
40 | return rs | |
41 | ||
42 | 15 | userAgent :: Header |
43 | 16 | userAgent = Header "User-Agent" "haskell/vriska" |
44 | 17 | |
48 | 21 | , userAgent |
49 | 22 | ] |
50 | 23 | |
51 | head :: BS.ByteString -> [Header] -> ConnM (Either HTTPError Response) | |
52 | head p hs = ConnM (\ c -> makeRequest c (Request MHead p (hs' ++ hs) "")) | |
24 | head :: BS.ByteString -> [Header] -> Request | |
25 | head p hs = Request MHead p (hs' ++ hs) "" | |
53 | 26 | where hs' = [ Header "Content-Length" "0" |
54 | 27 | , userAgent |
55 | 28 | ] |
56 | 29 | |
57 | get :: BS.ByteString -> [Header] -> ConnM (Either HTTPError Response) | |
58 | get p hs = ConnM (\ c -> makeRequest c (Request MGet p (hs' ++ hs) "")) | |
30 | get :: BS.ByteString -> [Header] -> Request | |
31 | get p hs = Request MGet p (hs' ++ hs) "" | |
59 | 32 | where hs' = [ Header "Content-Length" "0" |
60 | 33 | , userAgent |
61 | 34 | ] |
62 | 35 | |
63 | post :: BS.ByteString -> [Header] -> BS.ByteString -> ConnM (Either HTTPError Response) | |
64 | post p hs body = ConnM (\ c -> makeRequest c (Request MPost p (hs' ++ hs) body)) | |
36 | post :: BS.ByteString -> [Header] -> BS.ByteString -> Request | |
37 | post p hs body = Request MPost p (hs' ++ hs) body | |
65 | 38 | where hs' = [ Header "Content-Length" (BS8.pack (show (BS.length body))) |
66 | 39 | , userAgent |
67 | 40 | ] |
1 | name: shelob-client | |
2 | version: 0.1.0.0 | |
3 | -- synopsis: | |
4 | -- description: | |
5 | license: BSD3 | |
6 | license-file: LICENSE | |
7 | author: Getty Ritter <gettyritter@gmail.com> | |
8 | maintainer: Getty Ritter <gettyritter@gmail.com> | |
9 | copyright: ©2017 Getty Ritter | |
10 | -- category: | |
11 | build-type: Simple | |
12 | cabal-version: >= 1.14 | |
13 | ||
14 | library | |
15 | exposed-modules: Network.Shelob.Client | |
16 | hs-source-dirs: src | |
17 | ghc-options: -Wall | |
18 | build-depends: base >=4.7 && <4.10 | |
19 | , shelob | |
20 | , text | |
21 | , bytestring | |
22 | , aeson | |
23 | default-language: Haskell2010 | |
24 | default-extensions: OverloadedStrings, | |
25 | ScopedTypeVariables |
1 | {-# LANGUAGE FlexibleInstances #-} | |
2 | module Network.Shelob.Client where | |
3 | ||
4 | import Control.Monad.IO.Class (MonadIO(..)) | |
5 | import qualified Control.Exception as Ex | |
6 | import qualified Data.Aeson as Aeson | |
7 | import qualified Data.ByteString as BS | |
8 | import qualified Data.ByteString.Char8 as BS8 | |
9 | import qualified Data.Text as T | |
10 | import qualified Data.Text.Encoding as T | |
11 | ||
12 | import Network.Shelob | |
13 | import Network.Shelob.Types | |
14 | ||
15 | ||
16 | newtype ConnM a = ConnM { runConnM :: Connection -> IO a } | |
17 | ||
18 | instance Functor ConnM where | |
19 | fmap f (ConnM g) = ConnM (\ c -> fmap f (g c)) | |
20 | ||
21 | instance Applicative ConnM where | |
22 | pure x = ConnM (\ _ -> return x) | |
23 | ConnM f <*> ConnM x = ConnM $ \ c -> do | |
24 | f' <- f c | |
25 | x' <- x c | |
26 | return (f' x') | |
27 | ||
28 | instance Monad ConnM where | |
29 | ConnM x >>= f = ConnM $ \ c -> do | |
30 | x' <- x c | |
31 | runConnM (f x') c | |
32 | ||
33 | instance MonadIO ConnM where | |
34 | liftIO m = ConnM (\ _ -> m) | |
35 | ||
36 | -- * HTTPPayload | |
37 | -- | Types that implement | |
38 | class HTTPPayload t where | |
39 | toPayload :: t -> BS.ByteString | |
40 | ||
41 | instance HTTPPayload BS.ByteString where | |
42 | toPayload = id | |
43 | ||
44 | instance HTTPPayload T.Text where | |
45 | toPayload = T.encodeUtf8 | |
46 | ||
47 | newtype JSONPayload t = JSONPayload { fromJSONPayload :: t } | |
48 | ||
49 | instance Aeson.FromJSON t => Aeson.FromJSON (JSONPayload t) where | |
50 | parseJSON x = fmap JSONPayload (Aeson.parseJSON x) | |
51 | ||
52 | -- * HTTPResult | |
53 | class HTTPResult t where | |
54 | fromResponse :: Either HTTPError Response -> t | |
55 | ||
56 | instance HTTPResult r => HTTPResult (Either HTTPError r) where | |
57 | fromResponse (Left err) = Left err | |
58 | fromResponse (Right v) = Right (fromResponse (Right v)) | |
59 | ||
60 | instance HTTPResult r => HTTPResult (Maybe r) where | |
61 | fromResponse (Left _) = Nothing | |
62 | fromResponse (Right v) = Just (fromResponse (Right v)) | |
63 | ||
64 | onSuccess :: (Response -> a) -> Either HTTPError Response -> a | |
65 | onSuccess _ (Left err) = Ex.throw err | |
66 | onSuccess f (Right v) = f v | |
67 | ||
68 | instance HTTPResult Response where | |
69 | fromResponse = onSuccess id | |
70 | ||
71 | instance HTTPResult BS.ByteString where | |
72 | fromResponse = onSuccess (go . responseBody) | |
73 | where go Nothing = Ex.throw HTTPError | |
74 | go (Just x) = x | |
75 | ||
76 | instance Aeson.FromJSON t => HTTPResult (JSONPayload t) where | |
77 | fromResponse = onSuccess (go . responseBody) | |
78 | where go Nothing = Ex.throw HTTPError | |
79 | go (Just x) | |
80 | | Just rs <- Aeson.decode x = JSONPayload x | |
81 | | otherwise = Ex.throw HTTPError | |
82 | ||
83 | withConn :: Connection -> ConnM a -> IO a | |
84 | withConn conn m = do | |
85 | rs <- runConnM m conn | |
86 | connClose conn | |
87 | return rs | |
88 | ||
89 | doRequest :: HTTPResult r => Request -> ConnM r | |
90 | doRequest req = ConnM (\ c -> fromResponse `fmap` makeRequest c req) | |
91 | ||
92 | head :: HTTPResult r => BS.ByteString -> [Header] -> ConnM r | |
93 | head p hs = doRequest (Request MHead p (hs' ++ hs) "") | |
94 | where hs' = [ Header "Content-Length" "0" | |
95 | , userAgent | |
96 | ] | |
97 | ||
98 | get :: HTTPResult r => BS.ByteString -> [Header] -> ConnM r | |
99 | get p hs = doRequest (Request MGet p (hs' ++ hs) "") | |
100 | where hs' = [ Header "Content-Length" "0" | |
101 | , userAgent | |
102 | ] | |
103 | ||
104 | post :: HTTPResult r => BS.ByteString -> [Header] -> BS.ByteString -> ConnM r | |
105 | post p hs body = doRequest (Request MPost p (hs' ++ hs) body) | |
106 | where hs' = [ Header "Content-Length" (BS8.pack (show (BS.length body))) | |
107 | , userAgent | |
108 | ] |