├── .gitignore ├── CHANGELOG.md ├── Makefile ├── README.md ├── example ├── ClientSample.idr └── example.ipkg ├── http.ipkg └── src ├── HttpClient.idr ├── HttpClient ├── Base.idr ├── Foreign.idr ├── ForeignTypes.idr ├── Headers.idr ├── Json.idr ├── Methods.idr └── Requests.idr ├── Makefile ├── http.c ├── http.h ├── memstream.c └── memstream.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.ibc 2 | *~ 3 | *.o 4 | *.out 5 | httpclient_doc/ 6 | example/clientsample 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # (2016-03-08) 3 | 4 | 5 | * Add debug flag cd1a0d4 6 | * Add new modules to package config 3f5136b 7 | * Initial version 198f0b0 8 | * Refactor into a better layer system 6350cd3 9 | * Update README 1d0c6e7 10 | 11 | 12 | 13 | 14 | # (2016-03-07) 15 | 16 | 17 | * Initial version 198f0b0 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | idris --build http.ipkg --warnpartial 3 | 4 | install: 5 | idris --install http.ipkg --warnpartial 6 | 7 | doc: 8 | idris --mkdoc http.ipkg 9 | 10 | test: 11 | idris --testpkg http.ipkg 12 | 13 | clean: 14 | idris --clean http.ipkg 15 | rm -rf httpclient_doc 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## idris http client 3 | 4 | This is a very limited httpclient implementation based on libcurl for idris. 5 | It is still littered with debug output and currently just supports a minimal subset 6 | of HTTP features. It is probably not threadsafe, is leaking memory, the API is far from stable and not documented etc. 7 | 8 | ### Example 9 | 10 | Below are some examples of the usage for some request kinds: 11 | 12 | ```idris 13 | module Main 14 | 15 | import HttpClient 16 | 17 | Show Reply where 18 | show (MkReply statusCode header body) = concat $ intersperse "\n" 19 | ["\nstatusCode: " , 20 | show statusCode , 21 | "header:" , 22 | show header, 23 | "body:", 24 | body] 25 | 26 | example: String -> IO (Response Reply) -> IO () 27 | example intro resp= do 28 | putStrLn intro 29 | putStrLn $ show !resp 30 | putStrLn "\n\n\n" 31 | 32 | main: IO () 33 | main = do 34 | example "GET request" $ httpClient 35 | $ get 36 | $ url "http://httpbin.org/get" 37 | 38 | example "POST request" $ httpClient 39 | $ post "language=idris&http=libcurl" . 40 | withHeader (Link, "up") . 41 | withHeader (Accept, "*/*") 42 | $ url "http://httpbin.org/post" 43 | 44 | example "DELETE request" $ httpClient $ delete "" . 45 | withHeaders [ 46 | (User_Agent, "idris-httpclient"), 47 | (X_ "Some-Header", "my-data") 48 | ] 49 | $ url "http://httpbin.org/delete" 50 | 51 | example "Follow request" $ httpClient $ get 52 | $ follow 53 | $ url "http://httpbin.org/redirect/1" 54 | ``` 55 | 56 | Compile this with 57 | ```bash 58 | idris -p httpclient Main.idr -o main 59 | ``` 60 | 61 | 62 | ### Installation 63 | 64 | Installation is only tested on Mac OS X. You will need libcurl (and idris 0.10.2): 65 | 66 | ```bash 67 | brew install curl 68 | ``` 69 | 70 | You will also need the lightyear parser library available from 71 | [LightYear](https://github.com/ziman/lightyear). Follow the build instructions to put it into your Idris distribution. 72 | 73 | The Makefile in `/src` assumes a standard location. If that is not ok, adopt to your needs. 74 | If you have the prerequisites, just do: 75 | 76 | ```bash 77 | make install 78 | ``` 79 | 80 | You will see a warning about a missing main file - that is ok, as long everything typechecks. 81 | 82 | ### Issues 83 | 84 | This is pretty much a learning project for me. If you find this project useful, and would like to have features, I am happy to learn in areas which help you. If you have suggestions to make the code better - please let me know. 85 | 86 | 87 | ### memstream 88 | 89 | Memstream implementation taken from http://piumarta.com/software/memstream/ 90 | -------------------------------------------------------------------------------- /example/ClientSample.idr: -------------------------------------------------------------------------------- 1 | module Main 2 | 3 | import HttpClient 4 | import HttpClient.Json 5 | 6 | Show Reply where 7 | show (MkReply statusCode header body) = concat $ Prelude.List.intersperse "\n" 8 | ["\nstatusCode: " , 9 | show statusCode , 10 | "header:" , 11 | show header, 12 | "body:", 13 | body] 14 | 15 | example: String -> IO (Response Reply) -> IO () 16 | example intro resp= do 17 | putStrLn intro 18 | putStrLn $ show !resp 19 | putStrLn "\n\n\n" 20 | 21 | main: IO () 22 | main = do 23 | example "GET request" $ httpClient 24 | $ get 25 | $ url "http://httpbin.org/get" 26 | 27 | example "POST request" $ httpClient 28 | $ post "language=idris&http=libcurl" . 29 | withHeader (Link, "up") . 30 | withHeader (Accept, "*/*") 31 | $ url "http://httpbin.org/post" 32 | 33 | example "POST json" $ httpClient 34 | $ post (JsonObject ( (insert "foo" (JsonString "bar")) . (insert "bar" (JsonString "foo" ))) empty) . 35 | withHeader (Content_Type, "application/json") 36 | $ url "http://httpbin.org/post" 37 | 38 | example "PUT request" $ httpClient 39 | $ put "language=idris&http=libcurl" . 40 | withHeader (Link, "up") . 41 | withHeader (Accept, "*/*") 42 | $ url "http://httpbin.org/put" 43 | 44 | example "DELETE request" $ httpClient $ delete "" . 45 | withHeaders [ 46 | (User_Agent, "idris-httpclient"), 47 | (X_ "Some-Header", "my-data") 48 | ] 49 | $ url "http://httpbin.org/delete" 50 | 51 | example "Follow request" $ httpClient $ get 52 | $ follow 53 | $ url "http://httpbin.org/redirect/1" 54 | -------------------------------------------------------------------------------- /example/example.ipkg: -------------------------------------------------------------------------------- 1 | package clientsample 2 | 3 | pkgs = httpclient 4 | 5 | modules = ClientSample 6 | 7 | main = ClientSample 8 | 9 | executable = clientsample 10 | -------------------------------------------------------------------------------- /http.ipkg: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | sourcedir = src 4 | 5 | modules = HttpClient, 6 | HttpClient.Base, 7 | HttpClient.Methods, 8 | HttpClient.Requests, 9 | HttpClient.Headers, 10 | HttpClient.Foreign, 11 | HttpClient.ForeignTypes, 12 | HttpClient.Json 13 | 14 | opts = "-p effects -p lightyear -p contrib" 15 | 16 | makefile = Makefile 17 | 18 | objs = http.o, http.h, memstream.o, memstream.h 19 | libs = curl 20 | -------------------------------------------------------------------------------- /src/HttpClient.idr: -------------------------------------------------------------------------------- 1 | ||| HttpClient provides a module to issue Http requests 2 | module HttpClient 3 | 4 | import HttpClient.Base 5 | import HttpClient.ForeignTypes 6 | import public HttpClient.Methods 7 | import public HttpClient.Requests 8 | import public HttpClient.Headers 9 | 10 | import public Data.SortedMap 11 | 12 | %access export 13 | %default total 14 | 15 | 16 | ||| make a client request 17 | ||| @ request the request record 18 | httpClient: (request: Request) -> IO (Response Reply) 19 | httpClient (MkRequest method url headers options) = do 20 | -- initialize the curl system 21 | Right ptr <- http_init | 22 | Left error => pure (Left (MkError "error initialising curl")) 23 | -- set the request url 24 | Right _ <- http_setopt_url url ptr | 25 | Left error => pure (Left (MkError "error setting url option")) 26 | -- append all extra headers 27 | for headers (\h => http_header_append h ptr) 28 | -- and other options (e.g. follow) which are treated differently by curl 29 | for options (\o => http_setopt_option o ptr) 30 | -- set the request method and if required the body 31 | Right _ <- http_setopt_method method ptr | 32 | Left error => pure (Left (MkError "error setting request method")) 33 | -- perform the request 34 | Right reply <- http_perform_high ptr | 35 | Left error => pure (Left (MkError "error performing final request")) 36 | pure $ Right $ reply 37 | -------------------------------------------------------------------------------- /src/HttpClient/Base.idr: -------------------------------------------------------------------------------- 1 | module HttpClient.Base 2 | 3 | import HttpClient.Requests 4 | import HttpClient.Methods 5 | import HttpClient.Headers 6 | import HttpClient.Foreign 7 | import Data.Fin 8 | 9 | %access export 10 | %default total 11 | 12 | ||| initialzie the curl subsystem 13 | http_init: IO (Response CURLPTR) 14 | http_init = do_http_init >>= responsePtr 15 | 16 | ||| set the url for the request 17 | ||| @ url the url 18 | ||| @ curlHandle the curlHandle 19 | http_setopt_url: (url: String) -> (curlHandle: CURLPTR) -> IO (Response Ok) 20 | http_setopt_url url (MkHttp ptr) = 21 | responseTy <$> (do_http_setopt_url url ptr) 22 | 23 | ||| set the request method 24 | ||| @ method the request method 25 | ||| @ curlHandle the curlHandle 26 | http_setopt_method: (method: Method) -> (curlHandle: CURLPTR) -> IO (Response Ok) 27 | http_setopt_method (GET) (MkHttp ptr) = do_http_setopt_method 0 ptr 28 | http_setopt_method (POST d) (MkHttp ptr) = 29 | do 30 | r1 <- do_http_setopt_method 1 ptr 31 | r2 <- do_http_setopt_postfields (bodyToString d) ptr 32 | pure $ r2 33 | http_setopt_method (PUT d) (MkHttp ptr) = 34 | do 35 | do_http_setopt_method 2 ptr 36 | r <- do_http_setopt_postfields (bodyToString d) ptr 37 | pure $ r 38 | http_setopt_method (DELETE d) (MkHttp ptr) = do_http_setopt_method 3 ptr 39 | 40 | http_header_append: (header: Header) -> (curlHandle: CURLPTR) -> IO (CURLPTR) 41 | http_header_append header (MkHttp ptr) = 42 | MkHttp <$> (do_http_header_append (showHeader header) ptr) 43 | 44 | http_setopt_option: Option -> (curlHandle: CURLPTR) -> IO (Response Ok) 45 | http_setopt_option FOLLOW (MkHttp ptr) = do_http_setopt_follow ptr 46 | 47 | 48 | ||| higher level perform of the request, which 49 | ||| transforms the request into a reply 50 | ||| @ curlHandle the curlHandle 51 | http_perform_high: (curlHandle: CURLPTR) -> IO (Response Reply) 52 | http_perform_high curlPtr = do 53 | responsePtr <- do_http_perform curlPtr 54 | if !(nullPtr $ getResponsePtr responsePtr) 55 | then pure $ Left $ MkError ("Error in curl subsystem") 56 | else 57 | do 58 | body <- response_body responsePtr 59 | header <- parseHeaders <$> response_header responsePtr 60 | statusCode <- response_code responsePtr 61 | do_http_cleanup curlPtr 62 | pure $ Right $ MkReply statusCode header body 63 | -------------------------------------------------------------------------------- /src/HttpClient/Foreign.idr: -------------------------------------------------------------------------------- 1 | module HttpClient.Foreign 2 | 3 | import HttpClient.Requests 4 | import public HttpClient.ForeignTypes 5 | 6 | %include C "http.h" 7 | %include C "curl/curl.h" 8 | %link C "http.o" 9 | %link C "memstream.o" 10 | %lib C "curl" 11 | 12 | %access export 13 | %default total 14 | 15 | ||| extract the primitive Ptr from a RESPONSEPTR 16 | getResponsePtr: RESPONSEPTR -> Ptr 17 | getResponsePtr (MkResponse ptr) = ptr 18 | 19 | ||| map a C interface response to a Response 20 | responseTy: Int -> Response Ok 21 | responseTy resp = 22 | if (resp == 0) 23 | then Right MkOk 24 | else Left (MkNotOk resp) 25 | 26 | responsePtr: Ptr -> IO (Response CURLPTR) 27 | responsePtr ptr = if !(Strings.nullPtr ptr) 28 | then pure $ Left (MkError "ptr is null") 29 | else pure $(Right $ MkHttp ptr) 30 | 31 | -- PROJECTIONS 32 | 33 | ||| projection for the C Response struct 34 | response_body: RESPONSEPTR -> IO String 35 | response_body (MkResponse ptr) = 36 | foreign FFI_C "response_body" (Ptr -> IO String) ptr 37 | 38 | ||| projection for the C Response struct 39 | response_header: RESPONSEPTR -> IO String 40 | response_header (MkResponse ptr) = 41 | foreign FFI_C "response_header" (Ptr -> IO String) ptr 42 | 43 | ||| projection for the C Response struct 44 | response_code: RESPONSEPTR -> IO Int 45 | response_code (MkResponse ptr) = 46 | foreign FFI_C "response_code" (Ptr -> IO Int) ptr 47 | 48 | -- PRIMITIVE IO OPERATIONS 49 | 50 | -- Setter 51 | 52 | ||| set the method for the request 53 | do_http_setopt_method: Int -> Ptr -> IO (Response Ok) 54 | do_http_setopt_method m ptr = 55 | responseTy <$> foreign FFI_C "http_easy_setopt_method" (Ptr -> Int -> IO Int) ptr m 56 | 57 | ||| set POST data 58 | do_http_setopt_postfields: String -> Ptr -> IO (Response Ok) 59 | do_http_setopt_postfields d ptr = 60 | responseTy <$> foreign FFI_C "http_easy_setopt_postfields" (Ptr -> String -> IO Int) ptr d 61 | 62 | ||| set the url for the request 63 | ||| * url the url 64 | do_http_setopt_url: (url: String) -> Ptr -> IO Int 65 | do_http_setopt_url url ptr = 66 | foreign FFI_C "http_easy_setopt_url" (Ptr -> String -> IO Int) ptr url 67 | 68 | do_http_header_append: (header: String) -> Ptr -> IO Ptr 69 | do_http_header_append header ptr = 70 | foreign FFI_C "http_header_append" (Ptr -> String -> IO Ptr) ptr header 71 | 72 | do_http_setopt_follow: Ptr -> IO (Response Ok) 73 | do_http_setopt_follow ptr = 74 | responseTy <$> foreign FFI_C "http_easy_setopt_follow" (Ptr -> IO Int) ptr 75 | 76 | -- Lifecycle 77 | 78 | ||| initialze the curl subsystem 79 | do_http_init: IO (Ptr) 80 | do_http_init = 81 | foreign FFI_C "http_easy_init" (IO Ptr) 82 | 83 | ||| low level perform of the request 84 | do_http_perform: CURLPTR -> IO (RESPONSEPTR) 85 | do_http_perform (MkHttp ptr) = 86 | MkResponse <$> foreign FFI_C "http_easy_perform" (Ptr -> IO Ptr) ptr 87 | 88 | ||| cleanup the curl subsystem 89 | do_http_cleanup: CURLPTR -> IO () 90 | do_http_cleanup (MkHttp ptr) = 91 | foreign FFI_C "http_easy_cleanup" (Ptr -> IO ()) ptr 92 | -------------------------------------------------------------------------------- /src/HttpClient/ForeignTypes.idr: -------------------------------------------------------------------------------- 1 | module HttpClient.ForeignTypes 2 | 3 | %access export 4 | %default total 5 | 6 | -- Data Types 7 | 8 | ||| A handle for curl 9 | public export 10 | data CURLPTR = 11 | ||| constructor 12 | MkHttp Ptr 13 | 14 | ||| A handle for the curl response struct 15 | public export 16 | data RESPONSEPTR = 17 | ||| constructor 18 | MkResponse Ptr 19 | -------------------------------------------------------------------------------- /src/HttpClient/Headers.idr: -------------------------------------------------------------------------------- 1 | module HttpClient.Headers 2 | 3 | %default total 4 | %access export 5 | 6 | public export 7 | Header: Type 8 | Header = (String, String) 9 | 10 | %name Header header 11 | 12 | public export 13 | Headers: Type 14 | Headers = List Header 15 | 16 | %name Headers headers 17 | 18 | private 19 | parseHeader: String -> Header 20 | parseHeader h = let keyValue = break (\c => c == ':') h 21 | key = trim $ fst keyValue 22 | valueColon = snd keyValue 23 | value = trim $ substr 1 (length valueColon) valueColon 24 | in (key, value) 25 | 26 | ||| parse a string of response headers into a Headers 27 | parseHeaders: String -> Headers 28 | parseHeaders h = parseHeader <$> lines h 29 | 30 | ||| Taken from https://en.wikipedia.org/wiki/List_of_HTTP_header_fields 31 | public export 32 | data HeaderFields = 33 | Accept | 34 | Accept_CharSet | 35 | Accept_Encoding | 36 | Accept_Language | 37 | Accept_Datetime | 38 | Authorization | 39 | Cache_Control | 40 | Connection | 41 | Cookie | 42 | Content_Length | 43 | Content_MD5 | 44 | Content_Type | 45 | Date | 46 | Expect | 47 | Forwarded | 48 | From | 49 | If_Match | 50 | If_Modified_Since | 51 | If_None_Match | 52 | If_Range | 53 | Link | 54 | Max_Forwards | 55 | Origin | 56 | Pragma | 57 | Proxy_Authorization | 58 | Range | 59 | Referer | 60 | User_Agent | 61 | Upgrade | 62 | Via | 63 | Warning | 64 | ||| An arbitrary HTTP header in the form of 65 | ||| "X-Foo" 66 | X_ String 67 | 68 | %name HeaderFields hf, hf1, hf2 69 | 70 | Show HeaderFields where 71 | show Accept = "Accept" 72 | show Accept_CharSet = "Accept-CharSet" 73 | show Accept_Encoding = "Accept-Encoding" 74 | show Accept_Language = "Accept-Language" 75 | show Accept_Datetime = "Accept-Datetime" 76 | show Authorization = "Authorization" 77 | show Cache_Control = "Cache-Control" 78 | show Connection = "Connection" 79 | show Cookie = "Cookie" 80 | show Content_Length = "Content-Length" 81 | show Content_MD5 = "Content-MD5" 82 | show Content_Type = "Content-Type" 83 | show Date = "Date" 84 | show Expect = "Expect" 85 | show Forwarded = "Forwarded" 86 | show From = "From" 87 | show If_Match = "If-Match" 88 | show If_Modified_Since = "If-Modified-Since" 89 | show If_None_Match = "If-None-Match" 90 | show If_Range = "If-Range" 91 | show Link = "Link" 92 | show Max_Forwards = "Max-Forwards" 93 | show Origin = "Origin" 94 | show Pragma = "Pragma" 95 | show Proxy_Authorization = "Proxy-Authorization" 96 | show Range = "Range" 97 | show Referer = "Referer" 98 | show User_Agent = "User-Agent" 99 | show Upgrade = "Upgrade" 100 | show Via = "Via" 101 | show Warning = "Warning" 102 | show (X_ x) = "X-" ++ x 103 | 104 | implicit headerFieldsToString: HeaderFields -> String 105 | headerFieldsToString hf = show hf 106 | 107 | showHeader: Header -> String 108 | showHeader header = (fst header) ++ ": " ++ (snd header) 109 | -------------------------------------------------------------------------------- /src/HttpClient/Json.idr: -------------------------------------------------------------------------------- 1 | module HttpClient.Json 2 | 3 | -- This file is copied from the example directory of lightyear 4 | 5 | import public Lightyear 6 | import public Lightyear.Char 7 | import public Lightyear.Strings 8 | 9 | import public Data.SortedMap 10 | 11 | %access public export 12 | 13 | data JsonValue = JsonString String 14 | | JsonNumber Double 15 | | JsonBool Bool 16 | | JsonNull 17 | | JsonArray (List JsonValue) 18 | | JsonObject (SortedMap String JsonValue) 19 | 20 | Show JsonValue where 21 | show (JsonString s) = show s 22 | show (JsonNumber x) = show x 23 | show (JsonBool True ) = "true" 24 | show (JsonBool False) = "false" 25 | show JsonNull = "null" 26 | show (JsonArray xs) = show xs 27 | show (JsonObject xs) = 28 | "{" ++ intercalate ", " (map fmtItem $ SortedMap.toList xs) ++ "}" 29 | where 30 | intercalate : String -> List String -> String 31 | intercalate sep [] = "" 32 | intercalate sep [x] = x 33 | intercalate sep (x :: xs) = x ++ sep ++ intercalate sep xs 34 | 35 | fmtItem (k, v) = show k ++ ": " ++ show v 36 | 37 | hex : Parser Int 38 | hex = do 39 | c <- map (ord . toUpper) $ satisfy isHexDigit 40 | pure $ if c >= ord '0' && c <= ord '9' then c - ord '0' 41 | else 10 + c - ord 'A' 42 | 43 | hexQuad : Parser Int 44 | hexQuad = do 45 | a <- hex 46 | b <- hex 47 | c <- hex 48 | d <- hex 49 | pure $ a * 4096 + b * 256 + c * 16 + d 50 | 51 | specialChar : Parser Char 52 | specialChar = do 53 | c <- anyChar 54 | case c of 55 | '"' => pure '"' 56 | '\\' => pure '\\' 57 | '/' => pure '/' 58 | 'b' => pure '\b' 59 | 'f' => pure '\f' 60 | 'n' => pure '\n' 61 | 'r' => pure '\r' 62 | 't' => pure '\t' 63 | 'u' => map chr hexQuad 64 | _ => fail "expected special char" 65 | 66 | jsonString' : Parser (List Char) 67 | jsonString' = (char '"' *!> pure Prelude.List.Nil) <|> do 68 | c <- satisfy (/= '"') 69 | if (c == '\\') then map (::) specialChar <*> jsonString' 70 | else map (c ::) jsonString' 71 | 72 | jsonString : Parser String 73 | jsonString = char '"' *> map pack jsonString' "JSON string" 74 | 75 | -- inspired by Haskell's Data.Scientific module 76 | record Scientific where 77 | constructor MkScientific 78 | coefficient : Integer 79 | exponent : Integer 80 | 81 | scientificToDouble : Scientific -> Double 82 | scientificToDouble (MkScientific c e) = fromInteger c * exp 83 | where exp = if e < 0 then 1 / pow 10 (fromIntegerNat (- e)) 84 | else pow 10 (fromIntegerNat e) 85 | 86 | parseScientific : Parser Scientific 87 | parseScientific = do sign <- maybe 1 (const (-1)) `map` opt (char '-') 88 | digits <- some digit 89 | hasComma <- isJust `map` opt (char '.') 90 | decimals <- if hasComma then some digit else pure Prelude.List.Nil 91 | hasExponent <- isJust `map` opt (char 'e') 92 | exponent <- if hasExponent then integer else pure 0 93 | pure $ MkScientific (sign * fromDigits (digits ++ decimals)) 94 | (exponent - cast (length decimals)) 95 | where fromDigits : List (Fin 10) -> Integer 96 | fromDigits = foldl (\a, b => 10 * a + cast b) 0 97 | 98 | jsonNumber : Parser Double 99 | jsonNumber = map scientificToDouble parseScientific 100 | 101 | jsonBool : Parser Bool 102 | jsonBool = (char 't' >! string "rue" *> return True) 103 | <|> (char 'f' >! string "alse" *> return False) "JSON Bool" 104 | 105 | jsonNull : Parser () 106 | jsonNull = (char 'n' >! string "ull" >! return ()) "JSON Null" 107 | 108 | mutual 109 | jsonArray : Parser (List JsonValue) 110 | jsonArray = char '[' *!> (jsonValue `sepBy` (char ',')) <* char ']' 111 | 112 | keyValuePair : Parser (String, JsonValue) 113 | keyValuePair = do 114 | key <- spaces *> jsonString <* spaces 115 | char ':' 116 | value <- jsonValue 117 | pure (key, value) 118 | 119 | jsonObject : Parser (SortedMap String JsonValue) 120 | jsonObject = map fromList (char '{' >! (keyValuePair `sepBy` char ',') <* char '}') 121 | 122 | jsonValue' : Parser JsonValue 123 | jsonValue' = (map JsonString jsonString) 124 | <|> (map JsonNumber jsonNumber) 125 | <|> (map JsonBool jsonBool) 126 | <|> (pure JsonNull <* jsonNull) 127 | <|>| map JsonArray jsonArray 128 | <|>| map JsonObject jsonObject 129 | 130 | jsonValue : Parser JsonValue 131 | jsonValue = spaces *> jsonValue' <* spaces 132 | 133 | jsonToplevelValue : Parser JsonValue 134 | jsonToplevelValue = (map JsonArray jsonArray) <|> (map JsonObject jsonObject) 135 | -------------------------------------------------------------------------------- /src/HttpClient/Methods.idr: -------------------------------------------------------------------------------- 1 | module HttpClient.Methods 2 | 3 | import Data.Fin 4 | import HttpClient.Json 5 | 6 | %access export 7 | %default partial 8 | 9 | total 10 | Body: Type 11 | Body = String 12 | 13 | total 14 | bodyToString: Body -> String 15 | bodyToString b = b 16 | 17 | ||| Interface to transform arbitrary types 18 | ||| into a Body of a request 19 | public export 20 | interface Writeable a where 21 | writeBody: a -> Body 22 | 23 | public export 24 | Writeable String where 25 | writeBody = id 26 | 27 | public export 28 | Writeable JsonValue where 29 | writeBody jsValue = show jsValue 30 | 31 | ||| the Http Method 32 | public export 33 | data Method: Type where 34 | ||| GET request without a body 35 | GET: Method 36 | ||| POST request with a body 37 | POST: Body -> Method 38 | ||| PUT request with a body 39 | PUT: Body -> Method 40 | ||| DELETE rquest with a body 41 | DELETE: Body -> Method 42 | 43 | %name Method method 44 | -------------------------------------------------------------------------------- /src/HttpClient/Requests.idr: -------------------------------------------------------------------------------- 1 | module HttpClient.Requests 2 | 3 | import HttpClient.Methods 4 | import HttpClient.Headers 5 | 6 | %access export 7 | %default total 8 | 9 | public export 10 | data Option = 11 | FOLLOW 12 | 13 | %name Option option 14 | 15 | public export 16 | record Request where 17 | constructor MkRequest 18 | method: Method 19 | url: String 20 | headers: Headers 21 | options: List Option 22 | 23 | %name Request request 24 | 25 | public export 26 | record Reply where 27 | constructor MkReply 28 | statusCode: Int 29 | header: Headers 30 | body: String 31 | 32 | %name Reply reply 33 | 34 | ||| Type to signify that responses we get from the API are ok, 35 | ||| e.g. they represent CURLE_OK values 36 | public export 37 | data Ok = 38 | MkOk 39 | 40 | ||| Error type 41 | public export 42 | data NotOk = 43 | MkNotOk Int | 44 | MkError String 45 | 46 | ||| Shortcut for Either NotOk x 47 | public export 48 | Response: (x: Type) -> Type 49 | Response x = Either NotOk x 50 | 51 | -- Request Builder 52 | 53 | ||| constructs a standard request towards an URL 54 | ||| and GET method 55 | url: (url: String) -> Request 56 | url url = MkRequest GET url [] [] 57 | 58 | ||| adds a header to the request 59 | withHeader: Header -> Request -> Request 60 | withHeader header request = record { headers = header :: (headers request)} request 61 | 62 | ||| adds headers to the request 63 | withHeaders: Headers -> Request -> Request 64 | withHeaders hs request = record { headers = hs ++ (headers request)} request 65 | 66 | private 67 | withMethod: Method -> Request -> Request 68 | withMethod method request = record { method = method } request 69 | 70 | private 71 | withOption: Option -> Request -> Request 72 | withOption option request = record { options = option :: (options request)} request 73 | 74 | ||| sets the GET request method 75 | get: Request -> Request 76 | get request = withMethod (GET) request 77 | 78 | ||| sets the POST request method together with the appropriate body 79 | ||| by default this is interpreted as urlenconded form data. 80 | post: Writeable a => a -> Request -> Request 81 | post body request = withMethod (POST $ writeBody body) request 82 | 83 | put: Writeable a => a -> Request -> Request 84 | put body request= withMethod (PUT $ writeBody body) request 85 | 86 | delete: Writeable a => a -> Request -> Request 87 | delete body = withMethod (DELETE $ writeBody body) 88 | 89 | ||| option to follow redirects 90 | follow: Request -> Request 91 | follow = withOption FOLLOW 92 | 93 | -- Instances 94 | 95 | Show Ok where 96 | show s = "OK" 97 | 98 | Show NotOk where 99 | show (MkError s) = s 100 | show (MkNotOk resp) = concat ["Not Ok ", show resp] 101 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # the compiler: gcc for C program 2 | CC = gcc 3 | 4 | # debug flags 5 | #DEBUG = -D_HTTPCLIENT_DEBUG=1 6 | 7 | # compiler flags: 8 | CFLAGS = -Wall -I/usr/local/opt/curl/include $(DEBUG) 9 | 10 | # linker flags: 11 | LDFLAGS = -L/usr/local/opt/curl/lib 12 | 13 | # define any libraries to link: 14 | LIBS = -lcurl 15 | 16 | SRCS = http.c memstream.c 17 | 18 | all: $(SRCS) 19 | $(CC) $(CFLAGS) -c $(SRCS) $(LFLAGS) 20 | 21 | clean: 22 | $(RM) *.o *~ a.out 23 | -------------------------------------------------------------------------------- /src/http.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "memstream.h" 6 | 7 | 8 | struct Response { 9 | long response_code; 10 | char *header; 11 | char *body; 12 | size_t body_size; 13 | size_t header_size; 14 | }; 15 | 16 | static struct Response response; 17 | static struct curl_slist *headerList=NULL; 18 | 19 | long response_code(struct Response *r) { 20 | return ((struct Response *) r)->response_code; 21 | } 22 | 23 | char *response_body(struct Response *r) { 24 | return ((struct Response *) r)->body; 25 | } 26 | 27 | char *response_header(struct Response *r) { 28 | #ifdef _HTTPCLIENT_DEBUG 29 | fprintf(stderr, "header in response_header: %s\n", ((struct Response *)r)->header); 30 | #endif 31 | return ((struct Response *) r)->header; 32 | } 33 | 34 | size_t response_body_size(struct Response *r) { 35 | return ((struct Response *) r)->body_size; 36 | } 37 | 38 | size_t response_header_size(struct Response *r) { 39 | return ((struct Response *) r)->header_size; 40 | } 41 | 42 | void *http_header_append(void *curl, char *header ) { 43 | #ifdef _HTTPCLIENT_DEBUG 44 | fprintf(stderr, "adding header: %s\n", header); 45 | #endif 46 | headerList = curl_slist_append(headerList, header ); 47 | return (void *) curl; 48 | } 49 | 50 | void* http_easy_init(void) { 51 | CURL *curl = curl_easy_init(); 52 | return (void *) curl; 53 | } 54 | 55 | void http_easy_cleanup(void *curl) { 56 | if (headerList != NULL) { 57 | curl_slist_free_all(headerList); 58 | headerList = NULL; 59 | } 60 | return curl_easy_cleanup((CURL *) curl); 61 | } 62 | 63 | int http_easy_setopt_url(void *curl, char *url) { 64 | return (int) curl_easy_setopt((CURL *) curl, CURLOPT_URL, url); 65 | } 66 | 67 | int http_easy_setopt_follow(void *curl) { 68 | return (int) curl_easy_setopt((CURL *) curl, CURLOPT_FOLLOWLOCATION, 1L); 69 | } 70 | 71 | int http_easy_setopt_method(void *curl, int method) { 72 | CURL *c = (CURL *) curl; 73 | switch (method) { 74 | case 1: // POST ; 75 | #ifdef _HTTPCLIENT_DEBUG 76 | fprintf(stderr, "setup post request"); 77 | #endif 78 | return curl_easy_setopt(curl, CURLOPT_POST, 1L); 79 | break; 80 | case 2: // PUT ; 81 | #ifdef _HTTPCLIENT_DEBUG 82 | fprintf(stderr, "setup put request"); 83 | #endif 84 | return curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); 85 | break; 86 | case 3: // DELETE ; 87 | #ifdef _HTTPCLIENT_DEBUG 88 | fprintf(stderr, "setup delete request"); 89 | #endif 90 | return curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); 91 | break; 92 | default: //GET ; 93 | #ifdef _HTTPCLIENT_DEBUG 94 | fprintf(stderr, "setup get request"); 95 | #endif 96 | return curl_easy_setopt(c, CURLOPT_HTTPGET, 1L); 97 | break; 98 | } 99 | } 100 | 101 | int http_easy_setopt_postfields(void *curl, char* data) { 102 | CURL *c = (CURL *) curl; 103 | return curl_easy_setopt(c, CURLOPT_POSTFIELDS, data); 104 | } 105 | 106 | void *http_easy_perform(void *curl) { 107 | CURL *curlHandle = (CURL *) curl; 108 | long response_code; 109 | CURLcode res; 110 | char *bodyBuffer; 111 | char *headerBuffer; 112 | size_t bodySize; 113 | size_t headerSize; 114 | FILE *bodyFile; 115 | FILE *headerFile; 116 | 117 | bodyFile = open_memstream (&bodyBuffer, &bodySize); 118 | headerFile = open_memstream (&headerBuffer, &headerSize); 119 | 120 | curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, (void *)bodyFile); 121 | curl_easy_setopt(curlHandle, CURLOPT_HEADERDATA, (void *)headerFile); 122 | 123 | if (headerList != NULL) { 124 | curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, headerList); 125 | } 126 | 127 | res = curl_easy_perform(curlHandle); 128 | fclose(bodyFile); 129 | fclose(headerFile); 130 | 131 | if (res != CURLE_OK) { 132 | fprintf(stderr, "curl_easy_perform() failed: %s\n--- %s, line %d\n", 133 | curl_easy_strerror(res), 134 | __FILE__, 135 | __LINE__); 136 | return NULL; 137 | } 138 | else { 139 | res = curl_easy_getinfo((CURL *) curl, CURLINFO_RESPONSE_CODE, &response_code); 140 | response.response_code = response_code; 141 | response.header_size = headerSize; 142 | response.header = headerBuffer; 143 | response.body_size = bodySize; 144 | response.body = bodyBuffer; 145 | #ifdef _HTTPCLIENT_DEBUG 146 | fprintf(stderr, "header in http_easy_perform: %s\n", headerBuffer); 147 | fprintf(stderr, "body in http_easy_perform: %s\n", bodyBuffer); 148 | #endif 149 | 150 | return &response; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/http.h: -------------------------------------------------------------------------------- 1 | #ifndef __HTTP_H 2 | #define __HTTP_H 3 | 4 | // 5 | // CURL_EXTERN CURL *curl_easy_init(void); 6 | // CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); 7 | // CURL_EXTERN CURLcode curl_easy_perform(CURL *curl); 8 | // CURL_EXTERN void curl_easy_cleanup(CURL *curl); 9 | 10 | long response_code(void *r); 11 | 12 | char *response_body(void *r); 13 | char *response_header(void *r); 14 | 15 | size_t response_body_size(void *r); 16 | size_t response_header_size(void *r); 17 | 18 | 19 | int http_easy_setopt_url(void *curl, char *url); 20 | int http_easy_setopt_method(void *curl, int method); 21 | int http_easy_setopt_postfields(void *curl, char* data); 22 | int http_easy_setopt_follow(void *curl); 23 | 24 | void *http_header_append(void *curl, char *header ); 25 | 26 | void *http_easy_init(void); 27 | void *http_easy_perform(void *curl); 28 | void http_easy_cleanup(void *curl); 29 | 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/memstream.c: -------------------------------------------------------------------------------- 1 | /* Compile this file and link the object with your program. On a recent 2 | * GNU/Linux machine the object file will be empty. On anything derived from 3 | * 4.4BSD (Darwin, the Three BSDs, etc.) it will contain an implementation of 4 | * open_memstream() as described in the POSIX and Linux manual pages. On 5 | * anything else it will probably cause a compilation error. 6 | * 7 | * ---------------------------------------------------------------------------- 8 | * 9 | * OPEN_MEMSTREAM(3) BSD and Linux Library Functions OPEN_MEMSTREAM(3) 10 | * 11 | * SYNOPSIS 12 | * #include "memstream.h" 13 | * 14 | * FILE *open_memstream(char **bufp, size_t *sizep); 15 | * 16 | * DESCRIPTION 17 | * The open_memstream() function opens a stream for writing to a buffer. 18 | * The buffer is dynamically allocated (as with malloc(3)), and 19 | * automatically grows as required. After closing the stream, the caller 20 | * should free(3) this buffer. 21 | * 22 | * When the stream is closed (fclose(3)) or flushed (fflush(3)), the 23 | * locations pointed to by bufp and sizep are updated to contain, 24 | * respectively, a pointer to the buffer and the current size of the 25 | * buffer. These values remain valid only as long as the caller performs 26 | * no further output on the stream. If further output is performed, then 27 | * the stream must again be flushed before trying to access these 28 | * variables. 29 | * 30 | * A null byte is maintained at the end of the buffer. This byte is not 31 | * included in the size value stored at sizep. 32 | * 33 | * The stream's file position can be changed with fseek(3) or fseeko(3). 34 | * Moving the file position past the end of the data already written fills 35 | * the intervening space with zeros. 36 | * 37 | * RETURN VALUE 38 | * Upon successful completion open_memstream() returns a FILE pointer. 39 | * Otherwise, NULL is returned and errno is set to indicate the error. 40 | * 41 | * CONFORMING TO 42 | * POSIX.1-2008 43 | * 44 | * ---------------------------------------------------------------------------- 45 | */ 46 | 47 | #include "memstream.h" 48 | 49 | #if _POSIX_C_SOURCE < 200809L 50 | 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | #define min(X, Y) (((X) < (Y)) ? (X) : (Y)) 58 | 59 | struct memstream 60 | { 61 | int position; 62 | int size; 63 | int capacity; 64 | char *contents; 65 | char **ptr; 66 | size_t *sizeloc; 67 | }; 68 | 69 | #if MEMSTREAM_DEBUG 70 | static void memstream_print(struct memstream *ms) 71 | { 72 | printf("memstream %p {", ms); 73 | printf(" %i", ms->position); 74 | printf(" %i", ms->size); 75 | printf(" %i", ms->capacity); 76 | printf(" %p", ms->contents); 77 | printf(" }\n"); 78 | } 79 | # define memstream_info(ARGS) printf ARGS 80 | #else 81 | # define memstream_print(ms) 82 | # define memstream_info(ARGS) 83 | #endif 84 | 85 | #define memstream_check(MS) if (!(MS)->contents) { errno= ENOMEM; return -1; } 86 | 87 | static int memstream_grow(struct memstream *ms, int minsize) 88 | { 89 | int newcap= ms->capacity * 2; memstream_check(ms); 90 | while (newcap <= minsize) newcap *= 2; memstream_info(("grow %p to %i\n", ms, newcap)); 91 | ms->contents= realloc(ms->contents, newcap); 92 | if (!ms->contents) return -1; /* errno == ENOMEM */ 93 | memset(ms->contents + ms->capacity, 0, newcap - ms->capacity); 94 | ms->capacity= newcap; 95 | *ms->ptr= ms->contents; /* size has not changed */ 96 | return 0; 97 | } 98 | 99 | static int memstream_read(void *cookie, char *buf, int count) 100 | { 101 | struct memstream *ms= (struct memstream *)cookie; memstream_check(ms); 102 | int n= min(ms->size - ms->position, count); memstream_info(("memstream_read %p %i\n", ms, count)); 103 | if (n < 1) return 0; 104 | memcpy(buf, ms->contents, n); 105 | ms->position += n; memstream_print(ms); 106 | return n; 107 | } 108 | 109 | static int memstream_write(void *cookie, const char *buf, int count) 110 | { 111 | struct memstream *ms= (struct memstream *)cookie; memstream_check(ms); 112 | if (ms->capacity <= ms->position + count) 113 | if (memstream_grow(ms, ms->position + count) < 0) /* errno == ENOMEM */ 114 | return -1; 115 | memcpy(ms->contents + ms->position, buf, count); memstream_info(("memstream_write %p %i\n", ms, count)); 116 | ms->position += count; 117 | if (ms->size < ms->position) *ms->sizeloc= ms->size= ms->position; memstream_print(ms); 118 | assert(ms->size < ms->capacity); 119 | assert(ms->contents[ms->size] == 0); 120 | return count; 121 | } 122 | 123 | static fpos_t memstream_seek(void *cookie, fpos_t offset, int whence) 124 | { 125 | struct memstream *ms= (struct memstream *)cookie; 126 | fpos_t pos= 0; memstream_check(ms); 127 | memstream_info(("memstream_seek %p %i %i\n", ms, (int)offset, whence)); 128 | switch (whence) { 129 | case SEEK_SET: pos= offset; break; 130 | case SEEK_CUR: pos= ms->position + offset; break; 131 | case SEEK_END: pos= ms->size + offset; break; 132 | default: errno= EINVAL; return -1; 133 | } 134 | if (pos >= ms->capacity) memstream_grow(ms, pos); 135 | ms->position= pos; 136 | if (ms->size < ms->position) *ms->sizeloc= ms->size= ms->position; memstream_print(ms); memstream_info(("=> %i\n", (int)pos)); 137 | assert(ms->size < ms->capacity && ms->contents[ms->size] == 0); 138 | return pos; 139 | } 140 | 141 | static int memstream_close(void *cookie) 142 | { 143 | struct memstream *ms= (struct memstream *)cookie; if (!ms->contents) { free(ms); errno= ENOMEM; return -1; } 144 | ms->size= min(ms->size, ms->position); 145 | *ms->ptr= ms->contents; 146 | *ms->sizeloc= ms->size; assert(ms->size < ms->capacity); 147 | ms->contents[ms->size]= 0; 148 | free(ms); 149 | return 0; 150 | } 151 | 152 | FILE *open_memstream(char **ptr, size_t *sizeloc) 153 | { 154 | if (ptr && sizeloc) { 155 | struct memstream *ms= calloc(1, sizeof(struct memstream)); 156 | FILE *fp= 0; if (!ms) return 0; /* errno == ENOMEM */ 157 | ms->position= ms->size= 0; 158 | ms->capacity= 4096; 159 | ms->contents= calloc(ms->capacity, 1); if (!ms->contents) { free(ms); return 0; } /* errno == ENOMEM */ 160 | ms->ptr= ptr; 161 | ms->sizeloc= sizeloc; 162 | memstream_print(ms); 163 | fp= funopen(ms, memstream_read, memstream_write, memstream_seek, memstream_close); 164 | if (!fp) { 165 | free(ms->contents); 166 | free(ms); 167 | return 0; /* errno set by funopen */ 168 | } 169 | *ptr= ms->contents; 170 | *sizeloc= ms->size; 171 | return fp; 172 | } 173 | errno= EINVAL; 174 | return 0; 175 | } 176 | 177 | #endif /* _POSIX_C_SOURCE < 200809L */ 178 | -------------------------------------------------------------------------------- /src/memstream.h: -------------------------------------------------------------------------------- 1 | #if defined(__linux__) 2 | # include 3 | #endif 4 | 5 | #include 6 | 7 | #if _POSIX_C_SOURCE < 200809L 8 | 9 | FILE *open_memstream(char **ptr, size_t *sizeloc); 10 | 11 | #endif /* _POSIX_C_SOURCE < 200809L */ 12 | --------------------------------------------------------------------------------