Custom connection manager for http-client

Tweet
Posted on January 23, 2017 by Kwang Yul Seo

http-client provides the low-level API for HTTP client. In this post, I will explain how to create custom connection managers. If you want to know the basics of the library, read Making HTTP requests first.

Every HTTP request is made via a Manager. It handles the details of creating connections to the server such as managing a connection pool. It also allows us to configure various settings and setup secure connections (HTTPS).

The easiest way to create one is to use newManager defaultManagerSettings as follows.

import Network.HTTP.Client
import Network.HTTP.Types.Status (statusCode)

main :: IO ()
main = do
  manager <- newManager defaultManagerSettings

  request <- parseRequest "http://httpbin.org/post"
  response <- httpLbs request manager

  putStrLn $ "The status code was: " ++ (show $ statusCode $ responseStatus response)
  print $ responseBody response

But the default connection manager is not enough for some cases. One such case is the docker remote API. Because Docker listens on the unix domain socket by default for security reasons, we can’t access to the API with the default connection manager which uses tcp.

But we are not out of luck. We can configure the connection manager to create a unix domain socket instead of a tcp socket by setting a custom managerRawConnection field.

managerRawConnection :: ManagerSettings -> IO (Maybe HostAddress -> String -> Int -> IO Connection)

It is used by Manager to create a new Connection from the host and port. So we can make the connection manager to create a unix socket by replacing the default implementation with openUnixSocket.

import qualified Network.Socket as S
import qualified Network.Socket.ByteString as SBS
import Network.HTTP.Client
import Network.HTTP.Client.Internal (makeConnection)
import Network.HTTP.Types.Status (statusCode)

newUnixDomainSocketManager :: FilePath -> IO Manager
newUnixDomainSocketManager path = do
  let mSettings = defaultManagerSettings { managerRawConnection = return $ openUnixSocket path }
  newManager mSettings
  where
    openUnixSocket filePath _ _ _ = do
      s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
      S.connect s (S.SockAddrUnix filePath)
      makeConnection (SBS.recv s 8096)
                     (SBS.sendAll s)
                     (S.close s)

By creating a connection manager with “/var/run/docker.sock”, we can make a request to the docker. The code below returns the version of the docker.

main :: IO ()
main = do
  manager <- newUnixDomainSocketManager
   "/var/run/docker.sock"
  request <- parseRequest "http://192.168.99.100:2376/v1.25/version"
  response <- httpLbs request manager

  putStrLn $ "The status code was: " ++ (show $ statusCode $ responseStatus response)
  print $ responseBody response