-
Notifications
You must be signed in to change notification settings - Fork 189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Making getAddrInfo polymorphic #587
Changes from all commits
8c18f94
152aea0
c3fbccc
795cebb
80fadc1
30df86b
1411634
ff99e97
e32a05e
34bbbfe
ae72d08
b7ba6ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,8 @@ | |
|
||
module Network.Socket.Info where | ||
|
||
import Data.List.NonEmpty (NonEmpty(..)) | ||
import qualified Data.List.NonEmpty as NE | ||
import Foreign.Marshal.Alloc (alloca, allocaBytes) | ||
import Foreign.Marshal.Utils (maybeWith, with) | ||
import GHC.IO.Exception (IOErrorType(NoSuchThing)) | ||
|
@@ -200,53 +202,72 @@ defaultHints = AddrInfo { | |
, addrCanonName = Nothing | ||
} | ||
|
||
----------------------------------------------------------------------------- | ||
-- | Resolve a host or service name to one or more addresses. | ||
-- The 'AddrInfo' values that this function returns contain 'SockAddr' | ||
-- values that you can pass directly to 'connect' or | ||
-- 'bind'. | ||
-- | ||
-- This function is protocol independent. It can return both IPv4 and | ||
-- IPv6 address information. | ||
-- | ||
-- The 'AddrInfo' argument specifies the preferred query behaviour, | ||
-- socket options, or protocol. You can override these conveniently | ||
-- using Haskell's record update syntax on 'defaultHints', for example | ||
-- as follows: | ||
-- | ||
-- >>> let hints = defaultHints { addrFlags = [AI_NUMERICHOST], addrSocketType = Stream } | ||
-- | ||
-- You must provide a 'Just' value for at least one of the 'HostName' | ||
-- or 'ServiceName' arguments. 'HostName' can be either a numeric | ||
-- network address (dotted quad for IPv4, colon-separated hex for | ||
-- IPv6) or a hostname. In the latter case, its addresses will be | ||
-- looked up unless 'AI_NUMERICHOST' is specified as a hint. If you | ||
-- do not provide a 'HostName' value /and/ do not set 'AI_PASSIVE' as | ||
-- a hint, network addresses in the result will contain the address of | ||
-- the loopback interface. | ||
-- | ||
-- If the query fails, this function throws an IO exception instead of | ||
-- returning an empty list. Otherwise, it returns a non-empty list | ||
-- of 'AddrInfo' values. | ||
-- | ||
-- There are several reasons why a query might result in several | ||
-- values. For example, the queried-for host could be multihomed, or | ||
-- the service might be available via several protocols. | ||
-- | ||
-- Note: the order of arguments is slightly different to that defined | ||
-- for @getaddrinfo@ in RFC 2553. The 'AddrInfo' parameter comes first | ||
-- to make partial application easier. | ||
-- | ||
-- >>> addr:_ <- getAddrInfo (Just hints) (Just "127.0.0.1") (Just "http") | ||
-- >>> addrAddress addr | ||
-- 127.0.0.1:80 | ||
|
||
getAddrInfo | ||
class GetAddrInfo t where | ||
----------------------------------------------------------------------------- | ||
-- | Resolve a host or service name to one or more addresses. | ||
-- The 'AddrInfo' values that this function returns contain 'SockAddr' | ||
-- values that you can pass directly to 'connect' or | ||
-- 'bind'. | ||
-- | ||
-- This function calls @getaddrinfo(3)@, which never successfully returns | ||
-- with an empty list. If the query fails, 'getAddrInfo' throws an IO | ||
-- exception. | ||
-- | ||
-- For backwards-compatibility reasons, a hidden 'GetAddrInfo' class is used | ||
-- to make the result polymorphic. It only has instances for @[]@ (lists) | ||
-- and 'NonEmpty'. Use of 'NonEmpty' is recommended. | ||
-- | ||
-- This function is protocol independent. It can return both IPv4 and | ||
-- IPv6 address information. | ||
-- | ||
-- The 'AddrInfo' argument specifies the preferred query behaviour, | ||
-- socket options, or protocol. You can override these conveniently | ||
-- using Haskell's record update syntax on 'defaultHints', for example | ||
-- as follows: | ||
-- | ||
-- >>> let hints = defaultHints { addrFlags = [AI_NUMERICHOST], addrSocketType = Stream } | ||
-- | ||
-- You must provide a 'Just' value for at least one of the 'HostName' | ||
-- or 'ServiceName' arguments. 'HostName' can be either a numeric | ||
-- network address (dotted quad for IPv4, colon-separated hex for | ||
-- IPv6) or a hostname. In the latter case, its addresses will be | ||
-- looked up unless 'AI_NUMERICHOST' is specified as a hint. If you | ||
-- do not provide a 'HostName' value /and/ do not set 'AI_PASSIVE' as | ||
-- a hint, network addresses in the result will contain the address of | ||
-- the loopback interface. | ||
-- | ||
-- There are several reasons why a query might result in several | ||
-- values. For example, the queried-for host could be multihomed, or | ||
-- the service might be available via several protocols. | ||
-- | ||
-- Note: the order of arguments is slightly different to that defined | ||
-- for @getaddrinfo@ in RFC 2553. The 'AddrInfo' parameter comes first | ||
-- to make partial application easier. | ||
-- | ||
-- >>> import qualified Data.List.NonEmpty as NE | ||
-- >>> addr <- NE.head <$> getAddrInfo (Just hints) (Just "127.0.0.1") (Just "http") | ||
-- >>> addrAddress addr | ||
-- 127.0.0.1:80 | ||
-- | ||
-- Polymorphic version: @since 3.2.3.0 | ||
getAddrInfo | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
:: Maybe AddrInfo -- ^ preferred socket type or protocol | ||
-> Maybe HostName -- ^ host name to look up | ||
-> Maybe ServiceName -- ^ service name to look up | ||
-> IO (t AddrInfo) -- ^ resolved addresses, with "best" first | ||
|
||
instance GetAddrInfo [] where | ||
getAddrInfo = getAddrInfoList | ||
|
||
instance GetAddrInfo NE.NonEmpty where | ||
getAddrInfo = getAddrInfoNE | ||
|
||
getAddrInfoNE | ||
:: Maybe AddrInfo -- ^ preferred socket type or protocol | ||
-> Maybe HostName -- ^ host name to look up | ||
-> Maybe ServiceName -- ^ service name to look up | ||
-> IO [AddrInfo] -- ^ resolved addresses, with "best" first | ||
getAddrInfo hints node service = alloc getaddrinfo | ||
-> IO (NonEmpty AddrInfo) -- ^ resolved addresses, with "best" first | ||
getAddrInfoNE hints node service = alloc getaddrinfo | ||
where | ||
alloc body = withSocketsDo $ maybeWith withCString node $ \c_node -> | ||
maybeWith withCString service $ \c_service -> | ||
|
@@ -258,12 +279,7 @@ getAddrInfo hints node service = alloc getaddrinfo | |
if ret == 0 then do | ||
ptr_addrs <- peek ptr_ptr_addrs | ||
ais <- followAddrInfo ptr_addrs | ||
c_freeaddrinfo ptr_addrs | ||
-- POSIX requires that getaddrinfo(3) returns at least one addrinfo. | ||
-- See: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html | ||
case ais of | ||
[] -> ioError $ mkIOError NoSuchThing message Nothing Nothing | ||
_ -> return ais | ||
return ais | ||
else do | ||
err <- gai_strerror ret | ||
ioError $ ioeSetErrorString | ||
|
@@ -290,13 +306,33 @@ getAddrInfo hints node service = alloc getaddrinfo | |
filteredHints = hints | ||
#endif | ||
|
||
followAddrInfo :: Ptr AddrInfo -> IO [AddrInfo] | ||
getAddrInfoList | ||
:: Maybe AddrInfo | ||
-> Maybe HostName | ||
-> Maybe ServiceName | ||
-> IO [AddrInfo] | ||
getAddrInfoList hints node service = | ||
-- getAddrInfo never returns an empty list. | ||
NE.toList <$> getAddrInfoNE hints node service | ||
|
||
followAddrInfo :: Ptr AddrInfo -> IO (NonEmpty AddrInfo) | ||
followAddrInfo ptr_ai | ||
| ptr_ai == nullPtr = return [] | ||
-- POSIX requires that getaddrinfo(3) returns at least one addrinfo. | ||
-- See: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html | ||
| ptr_ai == nullPtr = ioError $ mkIOError NoSuchThing "getaddrinfo must return at least one addrinfo" Nothing Nothing | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be nice to capture There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error reporting of |
||
| otherwise = do | ||
a <- peek ptr_ai | ||
as <- (# peek struct addrinfo, ai_next) ptr_ai >>= followAddrInfo | ||
return (a : as) | ||
a <- peek ptr_ai | ||
ptr <- (# peek struct addrinfo, ai_next) ptr_ai | ||
(a :|) <$> go ptr | ||
where | ||
go :: Ptr AddrInfo -> IO [AddrInfo] | ||
go ptr | ||
| ptr == nullPtr = return [] | ||
| otherwise = do | ||
a' <- peek ptr | ||
ptr' <- (# peek struct addrinfo, ai_next) ptr | ||
as' <- go ptr' | ||
return (a':as') | ||
|
||
foreign import ccall safe "hsnet_getaddrinfo" | ||
c_getaddrinfo :: CString -> CString -> Ptr AddrInfo -> Ptr (Ptr AddrInfo) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rendered the haddocks, and it doesn't give the reader a way to see what instances of
GetAddrInfo
exist. I think we should document them here.Alternatively, we could use the
IsList
class? But I think that will be more confusing and should only be for-XOverloadedLists
support.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that
IsList
is confusing.1411634 improves the doc.