Update examples + fix HEAD reqs
Getty Ritter
8 years ago
| 7 | 7 | main :: IO () |
| 8 | 8 | main = do |
| 9 | 9 | conn <- HTTP.httpConnection "gdritter.com" |
| 10 | (r0,r1,r2) <- HTTP.connect conn $ do | |
| 11 | h <- HTTP.head "/index.html" [HTTP.Header "Host" "gdritter.com"] | |
| 12 | a <- HTTP.get "/index.html" [HTTP.Header "Host" "gdritter.com"] | |
| 13 | b <- HTTP.get "/matzo/matzo.html" [HTTP.Header "Host" "gdritter.com"] | |
| 14 | return (h, a, b) | |
| 10 | ||
| 11 | r0 <- HTTP.makeRequest conn $ | |
| 12 | HTTP.head "/index.html" [HTTP.Header "Host" "gdritter.com"] | |
| 15 | 13 | pPrint r0 |
| 14 | ||
| 15 | r1 <- HTTP.makeRequest conn $ | |
| 16 | HTTP.get "/index.html" [HTTP.Header "Host" "gdritter.com"] | |
| 16 | 17 | pPrint r1 |
| 18 | ||
| 19 | r2 <- HTTP.makeRequest conn $ | |
| 20 | HTTP.get "/matzo/matzo.html" [HTTP.Header "Host" "gdritter.com"] | |
| 17 | 21 | pPrint r2 |
| 22 | ||
| 23 | HTTP.connClose conn | |
| 18 | 24 | return () |
| 23 | 23 | build-depends: base >=4.7 && <4.10 |
| 24 | 24 | , bytestring |
| 25 | 25 | , network |
| 26 | , deepseq | |
| 26 | 27 | default-language: Haskell2010 |
| 27 | 28 | default-extensions: OverloadedStrings, |
| 28 | 29 | ScopedTypeVariables |
| 34 | 34 | MConnect -> "CONNECT" |
| 35 | 35 | MPatch -> "PATCH" |
| 36 | 36 | MOther bs -> bs |
| 37 | ||
| 38 | 37 | pHeader :: Header -> B.Builder |
| 38 | pHeader (HContentLength v) = | |
| 39 | B.byteString "Content-Length: " <> | |
| 40 | B.intDec v <> crlf | |
| 39 | 41 | pHeader (Header k v) = |
| 40 | 42 | B.byteString k <> B.char7 ':' <> B.char7 ' ' <> |
| 41 | 43 | B.byteString v <> crlf |
| 16 | 16 | ) |
| 17 | 17 | | otherwise = error "???" |
| 18 | 18 | |
| 19 | readResponse :: Connection -> IO (Either HTTPError Response) | |
| 20 | readResponse conn = do | |
| 19 | readResponse :: Connection -> Request -> IO (Either HTTPError Response) | |
| 20 | readResponse conn req = do | |
| 21 | 21 | ln <- connReadLine conn |
| 22 | 22 | case BS.split 32 ln of |
| 23 | 23 | (_version:code:_reason) -> do |
| 24 | 24 | let c = pCode code |
| 25 | 25 | headers <- readHeaders conn |
| 26 | body <- readBody conn headers | |
| 27 | return (Right (Response headers c (Just body))) | |
| 26 | case requestMethod req of | |
| 27 | MHead -> return (Right (Response headers c Nothing)) | |
| 28 | _ -> do | |
| 29 | body <- readBody conn headers | |
| 30 | return (Right (Response headers c (Just body))) | |
| 28 | 31 | _ -> return (Left HTTPError) |
| 29 | 32 | |
| 30 | 33 | readHeaders :: Connection -> IO [Header] |
| 37 | 40 | let (key:vals) = BS.split 58 ln' |
| 38 | 41 | let val = BS.intercalate ":" vals |
| 39 | 42 | rs <- readHeaders conn |
| 40 |
return ( |
|
| 43 | return (toHeader key (BS.tail val) : rs) | |
| 44 | ||
| 45 | toHeader :: BS.ByteString -> BS.ByteString -> Header | |
| 46 | toHeader "Content-Length" v = HContentLength (read (BS8.unpack v)) | |
| 47 | toHeader k v = Header k v | |
| 41 | 48 | |
| 42 | 49 | readBody :: Connection -> [Header] -> IO BS.ByteString |
| 43 | 50 | readBody conn hs |
| 44 | = case [ read (BS8.unpack value) | |
| 45 | | Header name value <- hs | |
| 46 | , name == "Content-Length" | |
| 47 | ] | |
| 51 | = case [ ln | HContentLength ln <- hs ] | |
| 48 | 52 | of |
| 49 |
[ len ] -> |
|
| 53 | [ len ] -> do | |
| 54 | print len | |
| 55 | connRead conn len | |
| 50 | 56 | _ -> return "" |
| 1 | {-# LANGUAGE BangPatterns #-} | |
| 2 | ||
| 1 | 3 | module Network.Shelob.Types where |
| 2 | 4 | |
| 3 | 5 | import qualified Control.Exception as Ex |
| 6 | import Control.DeepSeq (NFData(..)) | |
| 4 | 7 | import qualified Data.ByteString as BS |
| 5 | 8 | import qualified Data.ByteString.Lazy as LBS |
| 6 | 9 | import Data.Typeable (Typeable) |
| 15 | 18 | | MOptions |
| 16 | 19 | | MConnect |
| 17 | 20 | | MPatch |
| 18 |
| MOther |
|
| 21 | | MOther !BS.ByteString | |
| 19 | 22 | deriving (Eq, Show) |
| 20 | 23 | |
| 21 | data Header = Header BS.ByteString BS.ByteString deriving (Eq, Show) | |
| 24 | instance NFData Method where | |
| 25 | rnf _ = () | |
| 26 | ||
| 27 | data Header | |
| 28 | = HContentLength !Int | |
| 29 | | Header !BS.ByteString !BS.ByteString | |
| 30 | deriving (Eq, Show) | |
| 31 | ||
| 32 | instance NFData Header where | |
| 33 | rnf (HContentLength v) = rnf v | |
| 34 | rnf (Header k v) = rnf k `seq` rnf v | |
| 35 | ||
| 22 | 36 | |
| 23 | 37 | data Request = Request |
| 24 | { requestMethod :: Method | |
| 25 | , requestPath :: BS.ByteString | |
| 26 | , requestHeaders :: [Header] | |
| 27 | , requestBody :: BS.ByteString | |
| 38 | { requestMethod :: !Method | |
| 39 | , requestPath :: !BS.ByteString | |
| 40 | , requestHeaders :: ![Header] | |
| 41 | , requestBody :: !BS.ByteString | |
| 28 | 42 | } deriving (Eq, Show) |
| 43 | ||
| 44 | instance NFData Request where | |
| 45 | rnf (Request m p h b) = rnf m `seq` rnf p `seq` rnf h `seq` rnf b | |
| 46 | ||
| 29 | 47 | |
| 30 | 48 | type ResponseCode = (Int, Int, Int) |
| 31 | 49 | |
| 32 | 50 | data Response = Response |
| 33 | { responseHeaders :: [Header] | |
| 34 | , responseCode :: ResponseCode | |
| 35 |
|
|
| 51 | { responseHeaders :: ![Header] | |
| 52 | , responseCode :: !ResponseCode | |
| 53 | , responseBody :: !(Maybe BS.ByteString) | |
| 36 | 54 | } deriving (Eq, Show) |
| 55 | ||
| 56 | instance NFData Response where | |
| 57 | rnf (Response h c b) = rnf h `seq` rnf c `seq` rnf b | |
| 58 | ||
| 37 | 59 | |
| 38 | 60 | -- | This "Connection" abstraction allows us to build on top of |
| 39 | 61 | -- arbitrary other connection packages for our HTTP |
| 44 | 66 | , connClose :: IO () |
| 45 | 67 | } |
| 46 | 68 | |
| 47 | data HTTPError = HTTPError deriving (Typeable, Show) | |
| 69 | ||
| 70 | data HTTPError | |
| 71 | = HTTPError deriving (Typeable, Show) | |
| 48 | 72 | |
| 49 | 73 | instance Ex.Exception HTTPError where |
| 50 | 74 | toException = undefined |
| 55 | 55 | makeRequest :: Connection -> Request -> IO (Either HTTPError Response) |
| 56 | 56 | makeRequest conn req = do |
| 57 | 57 | let pReq = pRequest req |
| 58 | print pReq | |
| 58 | 59 | connWrite conn pReq |
| 59 |
readResponse conn |
|
| 60 | readResponse conn req |
| 23 | 23 | default-language: Haskell2010 |
| 24 | 24 | default-extensions: OverloadedStrings, |
| 25 | 25 | ScopedTypeVariables |
| 26 | ||
| 27 | executable example | |
| 28 | hs-source-dirs: example | |
| 29 | main-is: Main.hs | |
| 30 | ghc-options: -Wall | |
| 31 | build-depends: base, shelob, shelob-client, pretty-show | |
| 32 | default-language: Haskell2010 | |
| 33 | default-extensions: OverloadedStrings |
| 34 | 34 | liftIO m = ConnM (\ _ -> m) |
| 35 | 35 | |
| 36 | 36 | -- * HTTPPayload |
| 37 | -- | Types that implement | |
| 37 | ||
| 38 | -- | Types that implement 'HTTPPayload' can be transparently used as | |
| 39 | -- the body of an HTTP request. | |
| 38 | 40 | class HTTPPayload t where |
| 39 | 41 | toPayload :: t -> BS.ByteString |
| 40 | 42 | |
| 44 | 46 | instance HTTPPayload T.Text where |
| 45 | 47 | toPayload = T.encodeUtf8 |
| 46 | 48 | |
| 49 | -- | The 'JSONPayload' type is used so that we can interpret the | |
| 50 | -- payload of a response | |
| 47 | 51 | newtype JSONPayload t = JSONPayload { fromJSONPayload :: t } |
| 48 | 52 | |
| 49 | 53 | instance Aeson.FromJSON t => Aeson.FromJSON (JSONPayload t) where |
| 77 | 81 | fromResponse = onSuccess (go . responseBody) |
| 78 | 82 | where go Nothing = Ex.throw HTTPError |
| 79 | 83 | go (Just x) |
| 80 |
| Just rs <- Aeson.decode |
|
| 84 | | Just rs <- Aeson.decodeStrict x = JSONPayload rs | |
| 81 | 85 | | otherwise = Ex.throw HTTPError |
| 82 | 86 | |
| 83 | 87 | withConn :: Connection -> ConnM a -> IO a |