{-# LANGUAGE Rank2Types #-}-- | A collection of utility functions for dealing with 'Enumerator's.moduleNetwork.Wai.Enumerator(-- * UtilitiesmapE-- * Conversions,-- ** Lazy byte stringstoLBS,fromLBS,fromLBS'-- ** Source,toSource-- ** Handle,fromHandle-- ** FilePath,fromFile,fromEitherFile-- * Filters,buffer)whereimportNetwork.Wai(Enumerator(..),Source(..))importqualifiedNetwork.Wai.SourceasSourceimportqualifiedData.ByteString.LazyasLimportqualifiedData.ByteStringasBimportSystem.IO(withBinaryFile,IOMode(ReadMode),Handle,hIsEOF)importData.ByteString.Lazy.Internal(defaultChunkSize)importControl.Concurrent(forkIO)importControl.Concurrent.MVarimportControl.Monad((<=<))-- | Performs a specified conversion on each 'B.ByteString' output by an-- enumerator.mapE::(B.ByteString->B.ByteString)->Enumerator->EnumeratormapEf(Enumeratore)=Enumerator$\iter->e(iter'iter)whereiter'itera=itera.f-- | This uses 'unsafeInterleaveIO' to lazily read from an enumerator. All-- normal lazy I/O warnings apply. In addition, since it is based on-- 'toSource', please observe all precautions for that function.toLBS::Enumerator->IOL.ByteStringtoLBS=Source.toLBS<=<toSource-- | This function safely converts a lazy bytestring into an enumerator.fromLBS::L.ByteString->EnumeratorfromLBSlbs=Enumerator$\itera0->helperitera0$L.toChunkslbswherehelper_a[]=return$Rightahelperitera(x:xs)=doea<-iteraxcaseeaofLefta'->return$Lefta'Righta'->helperitera'xs-- | Same as 'fromLBS', but the lazy bytestring is in the IO monad. This allows-- you to lazily read a file into memory, perform some mapping on the data and-- convert it into an enumerator.fromLBS'::IOL.ByteString->EnumeratorfromLBS'lbs'=Enumerator$\itera0->lbs'>>=\lbs->runEnumerator(fromLBSlbs)itera0-- | This function uses another thread to convert an 'Enumerator' to a-- 'Source'. In essence, this allows you to write code which \"pulls\" instead-- of code which is pushed to. While this can be a useful technique, some-- caveats apply:---- * It will be more resource heavy than using the 'Enumerator' directly.---- * You *must* consume all input. If you do not, then the other thread will be-- deadlocked.toSource::Enumerator->IOSourcetoSource(Enumeratore)=dobuff<-newEmptyMVar_<-forkIO$e(helperbuff)()>>putMVarbuffNothingreturn$sourcebuffwherehelper::MVar(MaybeB.ByteString)->()->B.ByteString->IO(Either()())helperbuff_bs=doputMVarbuff$Justbsreturn$Right()source::MVar(MaybeB.ByteString)->Sourcesourcemmbs=Source$dombs<-takeMVarmmbscasembsofNothing->do-- By putting Nothing back in, the source can be called-- again without causing a deadlock.putMVarmmbsNothingreturnNothingJustbs->return$Just(bs,sourcemmbs)-- | Read a chunk of data from the given 'Handle' at a time. We use-- 'defaultChunkSize' from the bytestring package to determine the largest-- chunk to take.fromHandle::Handle->EnumeratorfromHandleh=Enumerator$\itera->doeof<-hIsEOFhifeofthenreturn$Rightaelsedobs<-B.hGethdefaultChunkSizeea'<-iterabscaseea'ofLefta'->return$Lefta'Righta'->runEnumerator(fromHandleh)itera'-- | A little wrapper around 'fromHandle' which first opens a file for reading.fromFile::FilePath->EnumeratorfromFilefp=Enumerator$\itera0->withBinaryFilefpReadMode$\h->runEnumerator(fromHandleh)itera0-- | Since the response body is defined as an 'Either' 'FilePath' 'Enumerator',-- this function simply reduces the whole operator to an enumerator. This can-- be convenient for server implementations not optimizing file sending.fromEitherFile::EitherFilePathEnumerator->EnumeratorfromEitherFile=eitherfromFileid-- | Buffer chunks until we have a chunk of 'defaultChunkSize', and then send-- it.buffer::Enumerator->Enumeratorbuffer(Enumeratore)=Enumeratorgowheregoiterseed=dores<-e(iter'iter)(B.empty,seed)caseresofLeft(_,seed')->return$Leftseed'Right(buff,seed')->iterseed'buffiter'iter(buff,seed)bs=doifB.lengthbuff+B.lengthbs<defaultChunkSizethenreturn$Right(buff`B.append`bs,seed)elsedolet(x,y)=B.splitAt(defaultChunkSize-B.lengthbuff)bsres<-iterseed$buff`B.append`xcaseresofLeftseed'->return$Left(y,seed')-- y is ignoredRightseed'->return$Right(y,seed')