如何使用 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 客户端库有责任在使用 getaddrinfohttp-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)