Skip to content

Commit f763ecb

Browse files
Merge pull request #37 from bellroy/allow-ipv6-getaddrinfo
Allow ipv6 getaddrinfo
2 parents a780518 + 212ea35 commit f763ecb

File tree

3 files changed

+82
-66
lines changed

3 files changed

+82
-66
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Revision history for wai-handler-hal
22

3+
## 0.4.0.1 -- 2025-02-19
4+
5+
- Use a `NonEmpty` list when consuming the result of `getAddrInfo`.
6+
- When resolving source IPs, do not require `AF_INET` (IPv4)
7+
addresses. This allows IPv6 source addresses to be passed through to
8+
the underlying `wai` `Application`.
9+
310
## 0.4.0.0 -- 2024-01-17
411

512
- New function: `Wai.Handler.Hal.runWithOptions :: Options ->

src/Network/Wai/Handler/Hal.hs

Lines changed: 74 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,23 @@ import qualified AWS.Lambda.Events.ApiGateway.ProxyResponse as HalResponse
5252
import Control.Exception (IOException, tryJust)
5353
import Control.Monad.IO.Class (MonadIO (..))
5454
import Data.ByteString (ByteString)
55-
import qualified Data.ByteString as B
56-
import qualified Data.ByteString.Base64 as B64
55+
import qualified Data.ByteString as ByteString
56+
import qualified Data.ByteString.Base64 as Base64
5757
import qualified Data.ByteString.Builder as Builder
5858
import qualified Data.ByteString.Builder.Extra as Builder
59-
import qualified Data.ByteString.Lazy as BL
59+
import qualified Data.ByteString.Lazy as LByteString
6060
import qualified Data.CaseInsensitive as CI
6161
import Data.Function ((&))
62+
import Data.Functor ((<&>))
6263
import Data.HashMap.Lazy (HashMap)
63-
import qualified Data.HashMap.Lazy as H
64+
import qualified Data.HashMap.Lazy as HashMap
6465
import qualified Data.IORef as IORef
6566
import Data.List (foldl', sort)
67+
import Data.List.NonEmpty (NonEmpty (..))
6668
import Data.Maybe (fromMaybe)
6769
import Data.Text (Text)
68-
import qualified Data.Text as T
69-
import qualified Data.Text.Encoding as T
70+
import qualified Data.Text as Text
71+
import qualified Data.Text.Encoding as Text
7072
import Data.Vault.Lazy (Key, Vault)
7173
import qualified Data.Vault.Lazy as Vault
7274
import Network.HTTP.Media (MediaType, matches, parseAccept, renderHeader)
@@ -160,12 +162,12 @@ runWithOptions ::
160162
HalRequest.ProxyRequest HalRequest.NoAuthorizer ->
161163
m HalResponse.ProxyResponse
162164
runWithOptions opts app req = liftIO $ do
163-
waiReq <- toWaiRequest opts req
164-
responseRef <- IORef.newIORef Nothing
165-
Wai.ResponseReceived <- app waiReq $ \waiResp ->
166-
Wai.ResponseReceived <$ IORef.writeIORef responseRef (Just waiResp)
167-
Just waiResp <- IORef.readIORef responseRef
168-
fromWaiResponse opts waiResp
165+
waiReq <- toWaiRequest opts req
166+
responseRef <- IORef.newIORef Nothing
167+
Wai.ResponseReceived <- app waiReq $ \waiResp ->
168+
Wai.ResponseReceived <$ IORef.writeIORef responseRef (Just waiResp)
169+
Just waiResp <- IORef.readIORef responseRef
170+
fromWaiResponse opts waiResp
169171

170172
-- | Convert a WAI 'Wai.Application' into a function that can
171173
-- be run by hal's 'AWS.Lambda.Runtime.mRuntimeWithContext'. This
@@ -222,16 +224,17 @@ toWaiRequest ::
222224
IO Wai.Request
223225
toWaiRequest opts req = do
224226
let port = portNumber opts
225-
pathSegments = T.splitOn "/" . T.dropWhile (== '/') $ HalRequest.path req
226-
query = sort . constructQuery $ HalRequest.multiValueQueryStringParameters req
227+
pathSegments =
228+
Text.splitOn "/" . Text.dropWhile (== '/') $ HalRequest.path req
229+
query =
230+
sort . constructQuery $ HalRequest.multiValueQueryStringParameters req
227231
hints =
228232
NS.defaultHints
229233
{ NS.addrFlags = [NS.AI_NUMERICHOST],
230-
NS.addrFamily = NS.AF_INET,
231234
NS.addrSocketType = NS.Stream
232235
}
233236
sourceIp =
234-
T.unpack
237+
Text.unpack
235238
. HalRequest.sourceIp
236239
. HalRequest.identity
237240
$ HalRequest.requestContext req
@@ -243,7 +246,7 @@ toWaiRequest opts req = do
243246
(Just @IOException)
244247
(NS.getAddrInfo (Just hints) (Just sourceIp) (Just $ show port))
245248
>>= \case
246-
Right (s : _) -> pure $ NS.addrAddress s
249+
Right (s :| _) -> pure $ NS.addrAddress s
247250
_ -> do
248251
hPutStrLn stderr $
249252
mconcat
@@ -253,53 +256,57 @@ toWaiRequest opts req = do
253256
]
254257
pure . NS.SockAddrInet port $ NS.tupleToHostAddress (127, 0, 0, 1)
255258
body <- returnChunks $ HalRequest.body req
256-
let waiReq =
257-
Wai.Request
258-
{ Wai.requestMethod = T.encodeUtf8 $ HalRequest.httpMethod req,
259-
Wai.httpVersion = HttpVersion 1 1,
260-
Wai.rawPathInfo =
261-
BL.toStrict
262-
. Builder.toLazyByteString
263-
$ encodePath pathSegments [],
264-
Wai.rawQueryString = case query of
265-
[] -> ""
266-
_ -> renderQuery True query,
267-
Wai.requestHeaders =
268-
sort
269-
. foldMap
270-
( \(hName, hValues) ->
271-
(CI.map T.encodeUtf8 hName,) . T.encodeUtf8 <$> hValues
272-
)
273-
. H.toList
274-
$ HalRequest.multiValueHeaders req,
275-
Wai.isSecure = True,
276-
Wai.remoteHost = sourceHost,
277-
Wai.pathInfo = pathSegments,
278-
Wai.queryString = query,
279-
Wai.requestBody = body,
280-
Wai.vault = vault opts,
281-
Wai.requestBodyLength =
282-
Wai.KnownLength . fromIntegral . BL.length $ HalRequest.body req,
283-
Wai.requestHeaderHost = getHeader hHost req,
284-
Wai.requestHeaderRange = getHeader hRange req,
285-
Wai.requestHeaderReferer = getHeader hReferer req,
286-
Wai.requestHeaderUserAgent = getHeader hUserAgent req
287-
}
288-
pure waiReq
259+
pure
260+
Wai.Request
261+
{ Wai.requestMethod = Text.encodeUtf8 $ HalRequest.httpMethod req,
262+
Wai.httpVersion = HttpVersion 1 1,
263+
Wai.rawPathInfo =
264+
LByteString.toStrict
265+
. Builder.toLazyByteString
266+
$ encodePath pathSegments [],
267+
Wai.rawQueryString = case query of
268+
[] -> ""
269+
_ -> renderQuery True query,
270+
Wai.requestHeaders =
271+
sort
272+
. foldMap
273+
( \(hName, hValues) ->
274+
hValues <&> \hValue ->
275+
(CI.map Text.encodeUtf8 hName, Text.encodeUtf8 hValue)
276+
)
277+
. HashMap.toList
278+
$ HalRequest.multiValueHeaders req,
279+
Wai.isSecure = True,
280+
Wai.remoteHost = sourceHost,
281+
Wai.pathInfo = pathSegments,
282+
Wai.queryString = query,
283+
Wai.requestBody = body,
284+
Wai.vault = vault opts,
285+
Wai.requestBodyLength =
286+
Wai.KnownLength
287+
. fromIntegral
288+
. LByteString.length
289+
$ HalRequest.body req,
290+
Wai.requestHeaderHost = getHeader hHost req,
291+
Wai.requestHeaderRange = getHeader hRange req,
292+
Wai.requestHeaderReferer = getHeader hReferer req,
293+
Wai.requestHeaderUserAgent = getHeader hUserAgent req
294+
}
289295

290-
-- | Unpack a lazy 'BL.ByteString' into its chunks, and return an IO
291-
-- action which returns each chunk in sequence, and returns 'B.empty'
292-
-- forever after the bytestring is exhausted.
293-
returnChunks :: BL.ByteString -> IO (IO B.ByteString)
296+
-- | Unpack a lazy 'LByteString.ByteString' into its chunks, and
297+
-- return an IO action which returns each chunk in sequence, and
298+
-- returns 'ByteString.empty' forever after the bytestring is
299+
-- exhausted.
300+
returnChunks :: LByteString.ByteString -> IO (IO ByteString.ByteString)
294301
returnChunks bs = do
295-
chunkRef <- IORef.newIORef $ BL.toChunks bs
302+
chunkRef <- IORef.newIORef $ LByteString.toChunks bs
296303
pure . IORef.atomicModifyIORef' chunkRef $
297304
\case
298305
[] -> mempty
299306
(ch : chs) -> (chs, ch)
300307

301308
constructQuery :: HashMap Text [Text] -> Query
302-
constructQuery = foldMap expandParamList . H.toList
309+
constructQuery = foldMap expandParamList . HashMap.toList
303310
where
304311
expandParamList :: (Text, [Text]) -> [QueryItem]
305312
expandParamList (param, values) =
@@ -309,7 +316,9 @@ constructQuery = foldMap expandParamList . H.toList
309316

310317
getHeader :: HeaderName -> HalRequest.ProxyRequest a -> Maybe ByteString
311318
getHeader h =
312-
fmap T.encodeUtf8 . H.lookup (CI.map T.decodeUtf8 h) . HalRequest.headers
319+
fmap Text.encodeUtf8
320+
. HashMap.lookup (CI.map Text.decodeUtf8 h)
321+
. HalRequest.headers
313322

314323
-- | Convert a WAI 'Wai.Response' into a hal
315324
-- 'HalResponse.ProxyResponse'.
@@ -326,7 +335,7 @@ fromWaiResponse opts (Wai.ResponseBuilder status headers builder) =
326335
. addHeaders headers
327336
. HalResponse.response status
328337
. createProxyBody opts (getContentType headers)
329-
. BL.toStrict
338+
. LByteString.toStrict
330339
$ Builder.toLazyByteString builder
331340
fromWaiResponse opts (Wai.ResponseStream status headers stream) = do
332341
builderRef <- IORef.newIORef mempty
@@ -340,19 +349,19 @@ fromWaiResponse opts (Wai.ResponseRaw _ resp) = fromWaiResponse opts resp
340349
readFilePart :: FilePath -> Maybe Wai.FilePart -> IO ByteString
341350
readFilePart path mPart = withFile path ReadMode $ \h -> do
342351
case mPart of
343-
Nothing -> B.hGetContents h
352+
Nothing -> ByteString.hGetContents h
344353
Just (Wai.FilePart offset count _) -> do
345354
hSeek h AbsoluteSeek offset
346-
B.hGet h $ fromIntegral count
355+
ByteString.hGet h $ fromIntegral count
347356

348357
createProxyBody :: Options -> MediaType -> ByteString -> HalResponse.ProxyBody
349358
createProxyBody opts contentType body =
350359
HalResponse.ProxyBody
351-
{ HalResponse.contentType = T.decodeUtf8 $ renderHeader contentType,
360+
{ HalResponse.contentType = Text.decodeUtf8 $ renderHeader contentType,
352361
HalResponse.serialized =
353362
if isBase64Encoded
354-
then T.decodeUtf8 $ B64.encode body
355-
else T.decodeUtf8 body,
363+
then Text.decodeUtf8 $ Base64.encode body
364+
else Text.decodeUtf8 body,
356365
HalResponse.isBase64Encoded
357366
}
358367
where
@@ -364,8 +373,8 @@ addHeaders headers response = foldl' addHeader response headers
364373
where
365374
addHeader r (hName, hValue) =
366375
HalResponse.addHeader
367-
(T.decodeUtf8 $ CI.original hName)
368-
(T.decodeUtf8 hValue)
376+
(Text.decodeUtf8 $ CI.original hName)
377+
(Text.decodeUtf8 hValue)
369378
r
370379

371380
-- | Try to find the content-type of a response, given the response

wai-handler-hal.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ common deps
5656
, hal >=0.4.7 && <0.4.11 || >=1.0.0 && <1.2
5757
, http-media ^>=0.8.1.1
5858
, http-types ^>=0.12.3
59-
, network >=2.8.0.0 && <3.3
59+
, network >=3.2.3.0 && <3.3
6060
, text ^>=1.2.3 || ^>=2.0 || ^>=2.1
6161
, unordered-containers ^>=0.2.10.0
6262
, vault ^>=0.3.1.0

0 commit comments

Comments
 (0)