Let us begin with a simple TCP (transmission control protocol) client and server example. The network package provides a high-level interface for communication. You can install the same in Fedora, for example, using the following command:
$ sudo yum install ghc-network
Consider the following simple TCP client code:
-- tcp-client.hs import Network import System.IO main :: IO () main = withSocketsDo $ do handle <- connectTo localhost (PortNumber 3001) hPutStr handle Hello, world! hClose handle
After importing the required libraries, the main function connects to a localhost server running on Port 3001, sends a string Hello, world! and closes the connection.
The connectTo function defined in the Network module accepts a hostname, port number and returns a handle that can be used to transfer or receive data.
The type signatures of the withSocketsdo and connectTo functions are as follows:
ghci> :t withSocketsDo withSocketsDo :: IO a -> IO a ghci> :t connectTo connectTo :: HostName -> PortID -> IO GHC.IO.Handle.Types.Handle
The simple TCP server code is illustrated below:
-- tcp-server.hs import Network import System.IO main :: IO () main = withSocketsDo $ do sock <- listenOn $ PortNumber 3001 putStrLn Starting server ... handleConnections sock handleConnections :: Socket -> IO () handleConnections sock = do (handle, host, port) <- accept sock output <- hGetLine handle putStrLn output handleConnections sock
The main function starts a server on port 3001 and transfers the socket handler to a handleConnections function. It accepts any connection requests, reads the data, prints it to the server log, and waits for more clients.
First, you need to compile the tcp-server.hs and tcp-client.hs files using GHC:
$ ghc --make tcp-server.hs [1 of 1] Compiling Main ( tcp-server.hs, tcp-server.o ) Linking tcp-server ... $ ghc --make tcp-client.hs [1 of 1] Compiling Main ( tcp-client.hs, tcp-client.o ) Linking tcp-client ...
You can now start the TCP server in a terminal:
$ ./tcp-server Starting server ...
Then run the TCP client in another terminal:
./udp-client
You will now observe the Hello, world message printed in the terminal where the server is running:
$ ./tcp-server Starting server ... Hello, world!
The Network.Socket package exposes more low-level socket functionality for Haskell and can be used if you need finer access and control. For example, consider the following UDP (user datagram protocol) client code:
-- udp-client.hs import Network.Socket main :: IO () main = withSocketsDo $ do (server:_) <- getAddrInfo Nothing (Just localhost) (Just 3000) s <- socket (addrFamily server) Datagram defaultProtocol connect s (addrAddress server) send s Hello, world! sClose s
The getAddrInfo function resolves a host or service name to a network address. A UDP client connection is then requested for the server address, a message is sent, and the connection is closed. The type signatures of getAddrInfo, addrFamily and addrAddress are given below:
ghci> :t getAddrInfo getAddrInfo :: Maybe AddrInfo -> Maybe HostName -> Maybe ServiceName -> IO [AddrInfo] ghci> :t addrFamily addrFamily :: AddrInfo -> Family ghci> :t addrAddress addrAddress :: AddrInfo -> SockAddr
The corresponding UDP server code is as follows:
-- udp-server.hs import Network.Socket main :: IO () main = withSocketsDo $ do (server:_) <- getAddrInfo Nothing (Just localhost) (Just 3000) s <- socket (addrFamily server) Datagram defaultProtocol bindSocket s (addrAddress server) >> return s putStrLn Server started ... handleConnections s handleConnections :: Socket -> IO () handleConnections conn = do (text, _, _) <- recvFrom conn 1024 putStrLn text handleConnections conn
The UDP server binds to localhost and starts to listen on Port 3000. When a client connects, it reads a maximum of 1024 bytes of data, prints it to stdout, and waits to accept more connections. You can compile the udp-server.hs and udp-client.hs files using the following commands:
$ ghc --make udp-server.hs [1 of 1] Compiling Main ( udp-server.hs, udp-server.o ) Linking udp-server ... $ ghc --make udp-client.hs [1 of 1] Compiling Main ( udp-client.hs, udp-client.o ) Linking udp-client ...
You can start the UDP server in one terminal:
$ ./udp-server Server started ...
You can then run the UDP client in another terminal:
$ ./tcp-client
You will now see the Hello, world! message is printed in the terminal where the server is running:
$ ./udp-server Server started ... Hello, world!
The network-uri module has many useful URI (uniform resource identifier) parsing and test functions. You can install them on Fedora using the following command:
$ cabal install network-uri
The parseURI function takes a string and attempts to convert it into a URI. It returns Nothing if the input is not a valid URI, and returns the URI, otherwise. For example:
ghci> :m + Network.URI ghci> parseURI http://www.shakthimaan.com Just http://www.shakthimaan.com ghci> parseURI shakthimaan.com Nothing
The type signature of the parseURI function is given below:
ghci> :t parseURI parseURI :: String -> Maybe URI
A number of functions are available for testing the input URI as illustrated in the following examples:
ghci> isURI shakthimaan.com False ghci> isURI http://www.shakthimaan.com True ghci> isRelativeReference http://shakthimaan.com False ghci> isRelativeReference ../about.html True ghci> isAbsoluteURI http://www.shakthimaan.com True ghci> isAbsoluteURI shakthimaan.com False ghci> isIPv4address 192.168.100.2 True ghci> isIPv6address 2001:0db8:0a0b:12f0:0000:0000:0000:0001 True ghci> isIPv6address 192.168.100.2 False ghci> isIPv4address 2001:0db8:0a0b:12f0:0000:0000:0000:0001 False
The type signatures of the above functions are as follows:
ghci> :t isURI isURI :: String -> Bool ghci> :t isRelativeReference isRelativeReference :: String -> Bool ghci> :t isAbsoluteURI isAbsoluteURI :: String -> Bool ghci> :t isIPv4address isIPv4address :: String -> Bool ghci> :t isIPv6address isIPv6address :: String -> Bool
You can make a GET request for a URL and retrieve its contents. For example:
import Network import System.IO main = withSocketsDo $ do h <- connectTo www.shakthimaan.com (PortNumber 80) hSetBuffering h LineBuffering hPutStr h GET / HTTP/1.1\nhost: www.shakthimaan.com\n\n contents <- hGetContents h putStrLn contents hClose h
You can now compile and execute the above code, and it returns the index.html contents as shown below:
$ ghc --make get-network-uri.hs [1 of 1] Compiling Main (get-network-uri.hs, get-network-uri.o ) Linking get-network-uri ... $ ./get-network-uri HTTP/1.1 200 OK Date: Sun, 05 Apr 2015 01:37:19 GMT Server: Apache Last-Modified: Tue, 08 Jul 2014 04:01:16 GMT Accept-Ranges: bytes Content-Length: 4604 Content-Type: text/html ...
You can refer to the network-URI package documentation at https://hackage.haskell.org/package/network-uri-2.6.0.1/docs/Network-URI.html for more detailed information.
The whois Haskell package allows you to query for information about hosting servers and domain names. You can install the package on Ubuntu, for example, using the following command:
$ cabal install whois
The serverFor function returns a whois server that can be queried for more information regarding an IP or domain name. For example:
ghci> :m + Network.Whois ghci> serverFor shakthimaan.com Loading package array-0.4.0.1 ... linking ... done. Loading package deepseq-1.3.0.1 ... linking ... done. Loading package bytestring-0.10.0.2 ... linking ... done. Loading package old-locale-1.0.0.5 ... linking ... done. Loading package time-1.4.0.1 ... linking ... done. Loading package unix-2.6.0.1 ... linking ... done. Loading package network-2.6.0.2 ... linking ... done. Loading package transformers-0.4.3.0 ... linking ... done. Loading package mtl-2.2.1 ... linking ... done. Loading package text-1.2.0.4 ... linking ... done. Loading package parsec-3.1.9 ... linking ... done. Loading package network-uri-2.6.0.1 ... linking ... done. Loading package split-0.2.2 ... linking ... done. Loading package whois-1.2.2 ... linking ... done. Just (WhoisServer {hostname = com.whois-servers.net, port = 43, query = domain })
You can use the above specific information with the whois1 function to make a DNS (domain name system) query:
ghci> whois1 shakthimaan.com WhoisServer {hostname = com.whois-servers.net, port = 43, query = domain } Just \nWhois Server Version 2.0\n\nDomain names in the .com and .net domains can now be registered\n ...
You can also use the whois function to return information on the server as shown below:
ghci> whois shakthimaan.com Just \nWhois Server Version 2.0\n\nDomain names in the .com and .net domains can now be registered\n ...
The type signatures of the serverFor, whois1 and whois functions are as follows:
ghc> :t serverFor serverFor :: String -> Maybe WhoisServer ghci> :t whois1 whois1 :: String -> WhoisServer -> IO (Maybe String) ghci> :t whois whois :: String -> IO (Maybe String, Maybe String)
The dns package provides a number of useful functions to make DNS queries and handle the responses. You can install the same on Ubuntu, for example, using the following commands:
$ sudo apt-get install zlib1g-dev $ cabal install dns
A simple example of finding the IP addresses for the haskell.org domain is shown below:
ghci> import Network.DNS.Lookup ghci> import Network.DNS.Resolver ghci> let hostname = Data.ByteString.Char8.pack www.haskell.org ghci> rs <- makeResolvSeed defaultResolvConf ghci> withResolver rs $ \resolver -> lookupA resolver hostname Right [108.162.203.60,108.162.204.60]
The defaultResolvConf is of type ResolvConf and consists of the following default values:
-- * resolvInfo is RCFilePath \\/etc\/resolv.conf\. -- -- * resolvTimeout is 3,000,000 micro seconds. -- -- * resolvRetry is 3.
The makeResolvSeed and withResolver functions assist in making the actual DNS resolution. The lookupA function obtains all the A records for the DNS entry. Their type signatures are shown below:
ghci> :t makeResolvSeed makeResolvSeed :: ResolvConf -> IO ResolvSeed ghci> :t withResolver withResolver :: ResolvSeed -> (Resolver -> IO a) -> IO a ghci> :t lookupA lookupA :: Resolver -> dns-1.4.5:Network.DNS.Internal.Domain -> IO (Either dns-1.4.5:Network.DNS.Internal.DNSError [iproute-1.4.0:Data.IP.Addr.IPv4])
The lookupAAAA function returns all the IPv6 AAAA records for the domain. For example:
ghci> withResolver rs $ \resolver -> lookupAAAA resolver hostname Right [2400:cb00:2048:1::6ca2:cc3c,2400:cb00:2048:1::6ca2:cb3c]
Its type signature is shown below:
lookupAAAA :: Resolver -> dns-1.4.5:Network.DNS.Internal.Domain -> IO (Either dns-1.4.5:Network.DNS.Internal.DNSError [iproute-1.4.0:Data.IP.Addr.IPv6])
The MX records for the hostname can be returned using the lookupMX function. An example for the shakthimaan.com website is as follows:
ghci> import Network.DNS.Lookup ghci> import Network.DNS.Resolver ghci> let hostname = Data.ByteString.Char8.pack www.shakthimaan.com ghci> rs <- makeResolvSeed defaultResolvConf ghci> withResolver rs $ \resolver -> lookupMX resolver hostname Right [(shakthimaan.com.,0)]
The type signature of the lookupMX function is as follows:
ghci> :t lookupMX lookupMX :: Resolver -> dns-1.4.5:Network.DNS.Internal.Domain -> IO (Either dns-1.4.5:Network.DNS.Internal.DNSError [(dns-1.4.5:Network.DNS.Internal.Domain, Int)])
The name servers for the domain can be returned using the lookupNS function. For example:
ghci> withResolver rs $ \resolver -> lookupNS resolver hostname Right [ns22.webhostfreaks.com.,ns21.webhostfreaks.com.]
The type signature of the lookupNS function is shown below:
ghci> :t lookupNS lookupNS :: Resolver -> dns-1.4.5:Network.DNS.Internal.Domain -> IO (Either dns-1.4.5:Network.DNS.Internal.DNSError [dns-1.4.5:Network.DNS.Internal.Domain])
You can also return the entire DNS response using the lookupRaw function as illustrated below:
ghci> :m + Network.DNS.Types ghci> let hostname = Data.ByteString.Char8.pack www.ubuntu.com ghci> rs <- makeResolvSeed defaultResolvConf ghci> withResolver rs $ \resolver -> lookupRaw resolver hostname A Right (DNSFormat {header = DNSHeader {identifier = 29504, flags = DNSFlags {qOrR = QR_Response, opcode = OP_STD, authAnswer = False, trunCation = False, recDesired = True, recAvailable = True, rcode = NoErr}, qdCount = 1, anCount = 1, nsCount = 3, arCount = 3}, question = [ Question {qname = www.ubuntu.com., qtype = A}], answer = [ ResourceRecord {rrname = www.ubuntu.com., rrtype = A, rrttl = 61, rdlen = 4, rdata = 91.189.89.103}], authority = [ ResourceRecord {rrname = ubuntu.com., rrtype = NS, rrttl = 141593, rdlen = 16, rdata = ns2.canonical.com.}, ResourceRecord {rrname = ubuntu.com., rrtype = NS, rrttl = 141593, rdlen = 6, rdata = ns1.canonical.com.}, ResourceRecord {rrname = ubuntu.com., rrtype = NS, rrttl = 141593, rdlen = 6, rdata = ns3.canonical.com.}], additional = [ ResourceRecord {rrname = ns2.canonical.com., rrtype = A, rrttl = 88683, rdlen = 4, rdata = 91.189.95.3}, ResourceRecord {rrname = ns3.canonical.com., rrtype = A, rrttl = 88683, rdlen = 4, rdata = 91.189.91.139}, ResourceRecord {rrname = ns1.canonical.com., rrtype = A, rrttl = 88683, rdlen = 4, rdata = 91.189.94.173}]})
Please refer to the Network.DNS hackage Web page at https://hackage.haskell.org/package/dns for more information.