learningProgged learningProgged - 2 months ago 20x
HTTP Question

Basic way of sending HTTP/POST in Haskell using http-conduit

I'm trying to learn how to send simple string via HTTP/POST using Haskell and

(so that it works with https as well), reading the target url from a file, but it still seems all a bit overwhelming to me.

Basically the equivalent of what I learned to do with Racket here:
Sending HTTP POST in Racket

Could someone give me a small or most basic example of doing this?

I tried reading http://stackoverflow.com/a/13465653/5083453


Sure! The weird Haskell thing here to note is Haskell's record system. When you call parseUrl with a URL string, http-conduit is giving you back a Request record with some default values filled out, but the library expects you to fill out the rest.

For example, parseUrl always returns a Request with the HTTP method set to GET. It's up to us to overwrite that value by using the record update syntax – the thing where you append curly braces with new keys and values.

{-# LANGUAGE OverloadedStrings #-}

module Lib where

import Data.Aeson
import Network.HTTP.Client

buildRequest :: String -> RequestBody -> IO Request
buildRequest url body = do
  nakedRequest <- parseRequest url
  return (nakedRequest { method = "POST", requestBody = body })

send :: RequestBody -> IO ()
send s = do
  manager <- newManager defaultManagerSettings
  request <- buildRequest "http://httpbin.org/post" s
  response <- httpLbs request manager
  let Just obj = decode (responseBody response)
  print (obj :: Object)

If you run this in GHCi, you should be able to send POSTs to httpbin:

λ> :set -XOverloadedStrings
λ> send "hello there"
fromList [("origin",String "<snip>")
         ,("args",Object (fromList []))
         ,("data",String "hello there")
         ,("url",String "http://httpbin.org/post")
         ,("headers",Object (fromList [("Accept-Encoding",String "gzip")
         ,("Host",String "httpbin.org")
         ,("Content-Length",String "11")]))
         ,("files",Object (fromList []))
         ,("form",Object (fromList []))]

You'll also need the OverloadedStrings extension. Without it, two things will happen:

  • nakedRequest { method = "POST" } won't typecheck because the library expects a (strict) ByteString from the bytestring library. By default, "POST" and all string literals have type String a.k.a [Char]. While there is a function called pack that takes String and returns ByteString, it's much simpler to turn on overloaded strings. The compiler automatically calls pack on your behalf. There's more to it than that; see Oliver's blog post for the nitty gritty details.

  • Another expression that won't typecheck is send "hello there". send expects a RequestBody. And while again there's a function somewhere that would have type String -> RequestBody, it's much easier to turn on overloaded strings and have the compiler call it for you.