Как я могу ограничить размер загрузки файла в Скотти?

В настоящее время я изучаю Скотти для веб-разработки, и пока все выглядит довольно хорошо. Я обеспокоен тем, что, похоже, нет способа отменить загрузку файла (или, что еще лучше, произвольное тело POST), когда размер файла превышает определенный предел, без получения всего файла в первую очередь. В примере на https://github.com/scotty-web/scotty/blob/master/examples/upload.hs не упоминаются ограничения по размеру файла, и я не могу найти ничего в документации.

Я мог бы, конечно, сделать length на ByteString, но я не вижу, как это будет работать, пока весь файл уже загружен в память.

1 ответ

Решение

Вы должны быть в состоянии установить некоторые 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...

Также вполне возможно просто отфильтровывать сбои "на лету", но это решение более конкретно относится к тому, что вы хотите делать со сбоями - это должно проиллюстрировать эту идею. Это решение должно заботиться о ваших проблемах; так как вы используете ленивый ByteStrings, B.take не должен читать все содержимое файлов, которые будут помечены как неудачные загрузки.

С https://github.com/scotty-web/scotty/issues/203

В качестве обходного пути я не позволяю Скотти разбирать тело, убирая заголовок Content-Type:

{-# 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
Другие вопросы по тегам