1

我目前正在研究 Scotty 进行 Web 开发,到目前为止它看起来还不错。不过我很担心,似乎没有办法在文件大小超过一定限制的情况下丢弃文件上传(或者更好的是任意 POST 正文)而不先接收整个文件。https://github.com/scotty-web/scotty/blob/master/examples/upload.hs上的示例没有提到文件大小限制,我在文档中找不到任何内容。

我当然可以length在 ByteString 上做一个,但是在整个文件已经加载到内存之前我看不到它是如何工作的。

4

2 回答 2

7

您应该能够设置一些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...

也完全有可能“即时”过滤掉故障,但该解决方案更具体地针对您想要对故障执行的操作——尽管这应该说明这个想法。该解决方案应该解决您的顾虑;由于您使用的是lazy ByteStrings,B.take因此不必读取任何要标记为上传失败的文件的全部内容。

于 2014-07-11T14:11:24.497 回答
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
于 2019-06-22T23:03:43.263 回答