如何使用 haskell 中的 http-client 包向 IPv6 地址发出请求?
How to make a request to an IPv6 address using the http-client package in haskell?
我一直在尝试使用 Network.HTTP.Client
(https://hackage.haskell.org/package/http-client-0.7.10/docs/Network-HTTP-Client.html) 包中的 parseRequest
函数向 IPv6 地址发出请求,如下所示:
request <- parseRequest "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"
不是将其解析为 address/addrInfo,而是将其解析为主机名并抛出错误:does not exist (Name or service not known)
。作为下一步,我尝试将域指向相同的 IPv6 地址,然后在 parseRequest 中使用域名,然后它成功地将其解析为 IPv6 地址并发出请求。有没有其他方法可以直接使用 IPv6 地址来使用 http-client 包发出请求?
PS:我也试过 IP 地址周围没有方括号,在这种情况下错误是 Invalid URL
:
request <- parseRequest "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334"
更多上下文:
对于 IPv4 地址,getAddrInfo
函数生成的地址为:
AddrInfo {addrFlags = [AI_NUMERICHOST], addrFamily = AF_INET, addrSocketType = Stream, addrProtocol = 6, addrAddress = 139.59.90.1:80, addrCanonName = Nothing}
而对于 IPv6 地址(在方括号格式内):
AddrInfo {addrFlags = [AI_ADDRCONFIG], addrFamily = AF_UNSPEC, addrSocketType = Stream, addrProtocol = 6, addrAddress = 0.0.0.0:0, addrCanonName = Nothing}
错误打印为:
(ConnectionFailure Network.Socket.getAddrInfo (called with preferred socket type/protocol: AddrInfo {addrFlags = [AI_ADDRCONFIG], addrFamily = AF_UNSPEC, addrSocketType = Stream, addrProtocol = 6, addrAddress = 0.0.0.0:0, addrCanonName = Nothing}, host name: Just "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", service name: Just "80"): does not exist (Name or service not known))
当在 URL 中使用文字 IPv6 地址时,它应该用方括号括起来(根据 RFC 2732),这样文字地址中的冒号就不会被误解为某种形式端口指定。
当使用 C 库函数 getaddrinfo
(或等效的 Haskell 函数 getAddrInfo
)解析文字 IPv6 地址时,这些函数不需要处理这些额外的方括号,至少在 Linux 他们没有。
因此,HTTP 客户端库有责任在使用 getaddrinfo
和 http-client
包不这样做,至少从版本 0.7.10 开始。所以,这是一个错误,我可以看到你已经适当地提交了 bug report.
遗憾的是,我没有找到解决此问题的简单方法。您可以在解析后操作 Request
以从 host
字段中删除方括号,如下所示:
{-# LANGUAGE OverloadedStrings #-}
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Network.HTTP.Client
import Network.HTTP.Types.Status (statusCode)
main :: IO ()
main = do
manager <- newManager defaultManagerSettings
request <- parseRequest "http://[::1]"
let request' = request { host = removeBrackets (host request) }
response <- httpLbs request' manager
print response
removeBrackets :: ByteString -> ByteString
removeBrackets bs =
case BS.stripPrefix "[" bs >>= BS.stripSuffix "]" of
Just bs' -> bs'
Nothing -> bs
问题在于它还从 Host
header 中的值中删除了方括号,因此 HTTP 请求将包含 header:
Host: ::1
而不是正确的
Host: [::1]
这可能会或可能不会导致问题,具体取决于另一端的网络服务器。
您可以尝试使用已打补丁的 http-client
软件包。以下针对版本 0.7.10 的补丁似乎有效,但我没有对其进行广泛测试:
diff --git a/Network/HTTP/Client/Connection.hs b/Network/HTTP/Client/Connection.hs
index 0e329cd..719822e 100644
--- a/Network/HTTP/Client/Connection.hs
+++ b/Network/HTTP/Client/Connection.hs
@@ -15,6 +15,7 @@ module Network.HTTP.Client.Connection
import Data.ByteString (ByteString, empty)
import Data.IORef
+import Data.List (stripPrefix, isSuffixOf)
import Control.Monad
import Network.HTTP.Client.Types
import Network.Socket (Socket, HostAddress)
@@ -158,8 +159,12 @@ withSocket :: (Socket -> IO ())
withSocket tweakSocket hostAddress' host' port' f = do
let hints = NS.defaultHints { NS.addrSocketType = NS.Stream }
addrs <- case hostAddress' of
- Nothing ->
- NS.getAddrInfo (Just hints) (Just host') (Just $ show port')
+ Nothing -> do
+ let port'' = Just $ show port'
+ case ip6Literal host' of
+ Just lit -> NS.getAddrInfo (Just hints { NS.addrFlags = [NS.AI_NUMERICHOST] })
+ (Just lit) port''
+ Nothing -> NS.getAddrInfo (Just hints) (Just host') port''
Just ha ->
return
[NS.AddrInfo
@@ -173,6 +178,11 @@ withSocket tweakSocket hostAddress' host' port' f = do
E.bracketOnError (firstSuccessful addrs $ openSocket tweakSocket) NS.close f
+ where
+ ip6Literal h = case stripPrefix "[" h of
+ Just rest | "]" `isSuffixOf` rest -> Just (init rest)
+ _ -> Nothing
+
openSocket tweakSocket addr =
E.bracketOnError
(NS.socket (NS.addrFamily addr) (NS.addrSocketType addr)
我一直在尝试使用 Network.HTTP.Client
(https://hackage.haskell.org/package/http-client-0.7.10/docs/Network-HTTP-Client.html) 包中的 parseRequest
函数向 IPv6 地址发出请求,如下所示:
request <- parseRequest "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"
不是将其解析为 address/addrInfo,而是将其解析为主机名并抛出错误:does not exist (Name or service not known)
。作为下一步,我尝试将域指向相同的 IPv6 地址,然后在 parseRequest 中使用域名,然后它成功地将其解析为 IPv6 地址并发出请求。有没有其他方法可以直接使用 IPv6 地址来使用 http-client 包发出请求?
PS:我也试过 IP 地址周围没有方括号,在这种情况下错误是 Invalid URL
:
request <- parseRequest "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334"
更多上下文:
对于 IPv4 地址,getAddrInfo
函数生成的地址为:
AddrInfo {addrFlags = [AI_NUMERICHOST], addrFamily = AF_INET, addrSocketType = Stream, addrProtocol = 6, addrAddress = 139.59.90.1:80, addrCanonName = Nothing}
而对于 IPv6 地址(在方括号格式内):
AddrInfo {addrFlags = [AI_ADDRCONFIG], addrFamily = AF_UNSPEC, addrSocketType = Stream, addrProtocol = 6, addrAddress = 0.0.0.0:0, addrCanonName = Nothing}
错误打印为:
(ConnectionFailure Network.Socket.getAddrInfo (called with preferred socket type/protocol: AddrInfo {addrFlags = [AI_ADDRCONFIG], addrFamily = AF_UNSPEC, addrSocketType = Stream, addrProtocol = 6, addrAddress = 0.0.0.0:0, addrCanonName = Nothing}, host name: Just "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", service name: Just "80"): does not exist (Name or service not known))
当在 URL 中使用文字 IPv6 地址时,它应该用方括号括起来(根据 RFC 2732),这样文字地址中的冒号就不会被误解为某种形式端口指定。
当使用 C 库函数 getaddrinfo
(或等效的 Haskell 函数 getAddrInfo
)解析文字 IPv6 地址时,这些函数不需要处理这些额外的方括号,至少在 Linux 他们没有。
因此,HTTP 客户端库有责任在使用 getaddrinfo
和 http-client
包不这样做,至少从版本 0.7.10 开始。所以,这是一个错误,我可以看到你已经适当地提交了 bug report.
遗憾的是,我没有找到解决此问题的简单方法。您可以在解析后操作 Request
以从 host
字段中删除方括号,如下所示:
{-# LANGUAGE OverloadedStrings #-}
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Network.HTTP.Client
import Network.HTTP.Types.Status (statusCode)
main :: IO ()
main = do
manager <- newManager defaultManagerSettings
request <- parseRequest "http://[::1]"
let request' = request { host = removeBrackets (host request) }
response <- httpLbs request' manager
print response
removeBrackets :: ByteString -> ByteString
removeBrackets bs =
case BS.stripPrefix "[" bs >>= BS.stripSuffix "]" of
Just bs' -> bs'
Nothing -> bs
问题在于它还从 Host
header 中的值中删除了方括号,因此 HTTP 请求将包含 header:
Host: ::1
而不是正确的
Host: [::1]
这可能会或可能不会导致问题,具体取决于另一端的网络服务器。
您可以尝试使用已打补丁的 http-client
软件包。以下针对版本 0.7.10 的补丁似乎有效,但我没有对其进行广泛测试:
diff --git a/Network/HTTP/Client/Connection.hs b/Network/HTTP/Client/Connection.hs
index 0e329cd..719822e 100644
--- a/Network/HTTP/Client/Connection.hs
+++ b/Network/HTTP/Client/Connection.hs
@@ -15,6 +15,7 @@ module Network.HTTP.Client.Connection
import Data.ByteString (ByteString, empty)
import Data.IORef
+import Data.List (stripPrefix, isSuffixOf)
import Control.Monad
import Network.HTTP.Client.Types
import Network.Socket (Socket, HostAddress)
@@ -158,8 +159,12 @@ withSocket :: (Socket -> IO ())
withSocket tweakSocket hostAddress' host' port' f = do
let hints = NS.defaultHints { NS.addrSocketType = NS.Stream }
addrs <- case hostAddress' of
- Nothing ->
- NS.getAddrInfo (Just hints) (Just host') (Just $ show port')
+ Nothing -> do
+ let port'' = Just $ show port'
+ case ip6Literal host' of
+ Just lit -> NS.getAddrInfo (Just hints { NS.addrFlags = [NS.AI_NUMERICHOST] })
+ (Just lit) port''
+ Nothing -> NS.getAddrInfo (Just hints) (Just host') port''
Just ha ->
return
[NS.AddrInfo
@@ -173,6 +178,11 @@ withSocket tweakSocket hostAddress' host' port' f = do
E.bracketOnError (firstSuccessful addrs $ openSocket tweakSocket) NS.close f
+ where
+ ip6Literal h = case stripPrefix "[" h of
+ Just rest | "]" `isSuffixOf` rest -> Just (init rest)
+ _ -> Nothing
+
openSocket tweakSocket addr =
E.bracketOnError
(NS.socket (NS.addrFamily addr) (NS.addrSocketType addr)