我目前正在研究Scotty的网站开发,到目前为止看起来还不错。我担心,似乎没有办法丢弃文件上传(或者更好的任意POST主体)文件大小超过一定限度而不先接收整个文件。 https://github.com/scotty-web/scotty/blob/master/examples/upload.hs上的示例没有提及文件大小限制,我无法在文档中找到任何内容。
我当然可以在ByteString上做length
,但是我不知道在整个文件已经加载到内存之前它是如何工作的。
答案 0 :(得分:6)
您应该能够设置一些maxBytes
参数,懒洋洋地从每个文件内容中取出maxBytes
,将文件上传分区为失败和成功,然后处理它们中的每一个。这是一些未经测试的代码,用于说明我在您的应用程序环境中的含义:
post "/upload" $ do
fs <- files
let maxBytes = 9000 -- etc
fs' = [ (fieldName, BS.unpack (fileName fi), B.take (maxBytes + 1) (fileContent fi)) | (fieldName,fi) <- fs ]
(oks, fails) = partition ((<= maxBytes) . B.length) fs' -- separate out failures
liftIO $ sequence_ [ B.writeFile ("uploads" </> fn) fc | (_,fn,fc) <- oks ]
-- do something with 'fails'
-- and continue...
完全有可能只是过滤掉故障&#34;在飞行中&#34;但是这个解决方案更适合你想要对失败做些什么 - 这应该说明这个想法。这个解决方案应该照顾你的顾虑;由于您使用的是惰性ByteString
,B.take
不应该读取任何要标记为上传失败的文件的完整内容。
答案 1 :(得分:0)
来自https://github.com/scotty-web/scotty/issues/203
作为解决方法,我通过放置Content-Type标头来防止Scotty解析正文:
{-# LANGUAGE OverloadedStrings #-}
module Main
( main
) where
import Control.Exception (bracket)
import Control.Exception.Base (catch, throwIO)
import Control.Monad.Trans (liftIO)
import qualified Data.ByteString as BS
import Data.CaseInsensitive (CI)
import Network.HTTP.Types.Header (hContentType)
import Network.Wai (Middleware, Request, requestHeaders)
import Network.Wai.Parse
(BackEnd, FileInfo(..), getRequestBodyType, parseRequestBody)
import System.FilePath ((</>))
import System.IO (hClose)
import System.IO.Error (isDoesNotExistError)
import System.Posix.Files (removeLink)
import System.Posix.Temp (mkstemp)
import Web.Scotty
data UploadState = UploadState
{ size :: !Int
}
removeIfExists :: FilePath -> IO ()
removeIfExists path = removeLink path `catch` handleExists
where
handleExists e
| isDoesNotExistError e = return ()
| otherwise = throwIO e
fileBackend :: BackEnd UploadState
fileBackend _ (FileInfo _fname _cntType ()) reader = bracket start stop work
where
st0 = UploadState {size = 0}
start = mkstemp ("uploads" </> "tmp-")
stop (p, h) = do
hClose h
removeIfExists p
work (_p, h) = do
st <- loop h st0
return st
loop h st = do
bs <- reader
if BS.null bs
then return st
else do
BS.hPut h bs
loop h st {size = size st + BS.length bs}
scottyHack :: Middleware
scottyHack app req resp =
case getRequestBodyType req of
Nothing -> app req resp
Just _ -> app (fixRequest req) resp
xContentType :: CI BS.ByteString
xContentType = "X-Content-Type"
fixRequest :: Request -> Request
fixRequest req = req {requestHeaders = map putaway $ requestHeaders req}
where
putaway (h, v) =
if h == hContentType
then (xContentType, v)
else (h, v)
unFixRequest :: Request -> Request
unFixRequest req = req {requestHeaders = map putback $ requestHeaders req}
where
putback (h, v) =
if h == xContentType
then (hContentType, v)
else (h, v)
main :: IO ()
main =
scotty 3000 $ do
middleware scottyHack
post "/upload" $ do
req <- request
(_, docs) <- liftIO $ parseRequestBody fileBackend (unFixRequest req)
json $ map (size . fileContent . snd) docs