[英]Streaming bytes to network websocket
我有一個代碼使用文件句柄來模擬源( AWS S3
) Bytestring
式Bytestring
器。 如果我們想用Network.Websocket
作為水槽,就足夠了交換LBS.writeFile
在下面的代碼sendBinaryData
(帶手柄連接)?
{-# LANGUAGE OverloadedStrings,ScopedTypeVariables #-}
import qualified Aws
import qualified Aws.S3 as S3
import Data.Conduit (($$+-))
import qualified Data.Conduit.List as CL (mapM_)
import qualified Data.ByteString.Streaming.HTTP as SP
import qualified Data.ByteString.Lazy as LBS
import Streaming as S
import Streaming.Prelude as S hiding (show,print)
import Control.Concurrent.Async (async,waitCatch)
import Data.Text as T (Text)
data AwsConfig a = AwsConfig { _aws_cfg :: Aws.Configuration, _aws_s3cfg :: S3.S3Configuration a, _aws_httpmgr :: SP.Manager }
getObject :: AwsConfig Aws.NormalQuery -> T.Text -> T.Text -> IO Int
getObject cfg bucket key = do
req <- waitCatch =<< async (runResourceT $ do
{- Create a request object with S3.getObject and run the request with pureAws. -}
S3.GetObjectResponse { S3.gorResponse = rsp, S3.gorMetadata = mdata } <-
Aws.pureAws (_aws_cfg cfg) (_aws_s3cfg cfg) (_aws_httpmgr cfg) $
S3.getObject bucket key
{- Stream the response to a lazy bytestring -}
liftIO $ LBS.writeFile "testaws" LBS.empty -- this will be replaced by content-length of the bytes
let obj = (($$+- CL.mapM_ S.yield) . hoist lift ) (SP.responseBody rsp)
S.mapM_ (liftIO . (LBS.appendFile "testaws") . LBS.fromStrict) obj
return $ lookup "content-length" (S3.omUserMetadata mdata))
case req of
Left _ -> return 2 -- perhaps, we could use this to send an error message over websocket
Right _ -> return 0
對我來說,困惑的根源是如何確定流的終止? 對於文件,可通過writeFile
API來解決。 那么sendBinaryData
呢? 它是否以與writeFile
類似的方式處理終止? 還是由客戶端的數據解析器確定?
更新資料
這個問題是關於如何像上例中的文件句柄一樣將數據流式傳輸到websocket句柄(讓我們假定已提供了句柄),而不是真正地關於如何在resourceT
管理句柄。 conduit
似乎確實采用mapM_
方法接收數據。 因此,似乎確實是要走的路。
終止問題是因為我的這種思路:如果我們有一個函數在Websocket句柄的另一側監聽數據,那么確定消息的結尾似乎在流上下文中很重要。 給定如下功能:
f :: LBS.ByteString -> a
如果我們執行S.mapM_
將數據流傳輸到websocket句柄,是否需要添加某種end of stream
標記end of stream
以便另一側的f
偵聽可以停止處理惰性字節串。 否則, f
將不知道消息何時完成。
您認為句柄將需要其他技巧是正確的。 但是,由於您已經在使用ResourceT
monad轉換器,因此使用allocate
非常簡單 。 allocate
允許您在資源monad中創建一個句柄並注冊清除操作(在您的情況下,這只是關閉連接)。
ok <- runResourceT $ do
(releaseKey, handle) <-
allocate (WebSockets.acceptRequest request)
(`WebSockets.sendClose` closeMessage)
WebSockets.sendBinaryData handle data
return ok
where
request = ...
closeMessage = ...
data = ...
ok = ...
通過使用allocate
,可以確保在runResourceT
返回ok
時關閉句柄。
但是,我不能完全確定這就是您想要的。 在我看來, getObject
不應該知道如何接受和關閉WS連接; 也許應該將WS連接句柄作為參數,然后寫入。 如果將其返回類型升級為ResourceT
則可以向調用方收取getObject
費用,該調用方負責調用runResourceT
和分配WS句柄等。 但希望上面的示例足以使您繼續前進。
(注意-代碼未經測試。)
您的代碼將重新打開輸出文件,並在每次輸入數據包時都將其追加。顯然,更好的解決方案是使用LBS.hPutStr
使用已打開的文件句柄寫入文件。
也就是說,代替:
S.mapM_ (liftIO . (LBS.appendFile "testaws") . LBS.fromStrict) obj
您要使用:
S.mapM_ (liftIO . (LBS.hPutStr h) . LBS.fromStrict) obj
當然,這引用了句柄h
,它是從哪里來的?
一種解決方案是在調用getObject
的主體之前將其傳遞到getObject
或以其他方式創建它,例如:
getObject cfg bucket key = withFile "output" $ \h -> do
req <- ...
...
S.mapM_ (liftIO . (LBS.hPutStr h) . LBS.fromStrict) obj
...
或者,也許您必須在runResourceT內部創建...我不確定。
更新 -有關如何讓ResourceT為您管理文件句柄的信息,請參見@haoformayor的答案。
這里有些零碎的內容可能會使事情更容易理解。 首先,對於第一個小演示,修改您的getObject
,無論如何,我都使用ResourceT
Streaming.ByteString.writeFile
,通過延遲字節串來繞開彎路。
{-# LANGUAGE OverloadedStrings,ScopedTypeVariables #-}
import qualified Aws
import qualified Aws.S3 as S3
import Data.Conduit
import qualified Data.Conduit.List as CL (mapM_)
import qualified Data.ByteString.Streaming.HTTP as HTTP
import qualified Data.ByteString.Streaming as SB
import qualified Data.ByteString.Streaming.Internal as SB
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import Streaming as S
import Streaming.Prelude as S hiding (show,print)
import Control.Concurrent.Async (async,waitCatch)
import Data.Text as T (Text)
import qualified Network.WebSockets as WebSockets
import Control.Monad.Trans.Resource
data AwsConfig a = AwsConfig { _aws_cfg :: Aws.Configuration
, _aws_s3cfg :: S3.S3Configuration a
, _aws_httpmgr :: HTTP.Manager }
getObject :: AwsConfig Aws.NormalQuery -> FilePath -> T.Text -> T.Text -> IO Int
getObject cfg file bucket key = do
req <- waitCatch =<< async (runResourceT $ do
S3.GetObjectResponse { S3.gorResponse = rsp, S3.gorMetadata = mdata } <-
Aws.pureAws (_aws_cfg cfg) (_aws_s3cfg cfg) (_aws_httpmgr cfg) $
S3.getObject bucket key
let bytestream = do
-- lookup "content-length" (S3.omUserMetadata mdata))
SB.chunk B.empty -- this will be replaced by content-length
hoist lift (HTTP.responseBody rsp) $$+- CL.mapM_ SB.chunk
SB.writeFile file bytestream ) -- this is in ResourceT
case req of
Left _ -> return 2
Right _ -> return 0
我們可以從中大致抽象出您使用SB.writeFile
所做的SB.writeFile
:
getObjectAbstracted
:: (SB.ByteString (ResourceT IO) () -> ResourceT IO b)
-> AwsConfig Aws.NormalQuery -> S3.Bucket -> Text -> ResourceT IO b
getObjectAbstracted action cfg bucket key = do
S3.GetObjectResponse { S3.gorResponse = rsp, S3.gorMetadata = mdata } <-
Aws.pureAws (_aws_cfg cfg)
(_aws_s3cfg cfg)
(_aws_httpmgr cfg)
(S3.getObject bucket key)
action (hoist lift (HTTP.responseBody rsp) $$+- CL.mapM_ SB.chunk)
現在,我們需要一個不包含在流字節字符串庫中的小助手
mapMChunks_ :: Monad m => (B.ByteString -> m ()) -> SB.ByteString m r -> m r
mapMChunks_ act bytestream = do
(a S.:> r) <- SB.foldlChunksM (\_ bs -> act bs) (return ()) bytestream
return r
並可以使用流字節串按@haoformayor的計划進行或多或少的處理
writeConnection :: MonadIO m => WebSockets.Connection -> SB.ByteString m r -> m r
writeConnection connection =
mapMChunks_ (liftIO . WebSockets.sendBinaryData connection)
-- following `haoformayor`
connectWrite
:: (MonadResource m, WebSockets.WebSocketsData a)
=> WebSockets.PendingConnection
-> a -- closing message
-> SB.ByteString m r -- stream from aws
-> m r
connectWrite request closeMessage bytestream = do
(releaseKey, connection) <- allocate (WebSockets.acceptRequest request)
(`WebSockets.sendClose` closeMessage)
writeConnection connection bytestream
getObjectWS :: WebSockets.WebSocketsData a =>
WebSockets.PendingConnection
-> a
-> AwsConfig Aws.NormalQuery
-> S3.Bucket
-> Text
-> ResourceT IO ()
getObjectWS request closeMessage = getObjectAbstracted (connectWrite request closeMessage)
當然,到目前為止,這些都沒有利用conduit
和streaming
/ streaming-bytestring
之間的差異。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.