简体   繁体   English

如何使用Haskell Snap框架获取上传的文件?

[英]How do I get at uploaded files using the Haskell Snap framework?

The Snap framework provides the Snap.Util.FileUploads module , but it's pretty unfriendly. Snap框架提供了Snap.Util.FileUploads模块 ,但它非常不友好。 I'm writing a simple HTTP server for which I don't really care about file size restrictions, upload rate limit restrictions, worrying about finding some OS-agnostic temporary directory to put uploaded files in, writing multipart data stream processors, et cetera. 我正在编写一个简单的HTTP服务器,我并不真正关心文件大小限制,上传速率限制限制,担心找到一些与操作系统无关的临时目录以放入上传的文件,编写多部分数据流处理器等等。

Sure, it's great that Snap gives me this level of detail if I need it, but I don't. 当然,如果我需要它,Snap给我这么高的细节是很棒的,但我没有。 Worse, there are zero examples of how I could write a file upload handler. 更糟糕的是,我没有编写文件上传处理程序的示例。 I would have expected a handleFileUploadsSimple :: MonadSnap snap => snap [File] . handleFileUploadsSimple :: MonadSnap snap => snap [File]期望一个handleFileUploadsSimple :: MonadSnap snap => snap [File] Something like Scotty's files action , which, you guessed it, gives me the uploaded files, bish-bash-bosh. 有点像Scotty的files动作 ,你猜对了,给了我上传的文件,bish-bash-bosh。

If this does exist, where is it? 如果确实存在,那么它在哪里? If it doesn't, how should I write it? 如果没有,我应该怎么写呢?

The policies are important safety features; 这些政策是重要的安全特征; want them. 要他们。

Policies 政策

First you need to make your default policy. 首先,您需要制定默认策略。 I'll stick with their defaults for rate limiting etc, but set a maximum filesize of 2Mb instead of their default 128Kb. 我会坚持使用默认值进行速率限制等,但设置的最大文件大小为2Mb而不是默认的128Kb。

maxMb = 2
megaByte = 2^(20::Int)

myDefaultPolicy :: UploadPolicy
myDefaultPolicy = setMaximumFormInputSize (maxMb * megaByte) defaultUploadPolicy 

Next we need a per-part policy. 接下来我们需要按部件制定政策。 You don't seem to want to check anything about your uploads, which I think is odd, but we'll keep with the 2Mb limit. 您似乎不想查看有关上传的任何内容,我认为这很奇怪,但我们将保持2Mb限制。 We'll use the allowWithMaximumSize :: Int64 -> PartUploadPolicy function: 我们将使用allowWithMaximumSize :: Int64 -> PartUploadPolicy函数:

myPerPartPolicy :: PartInfo -> PartUploadPolicy
myPerPartPolicy _ = allowWithMaximumSize (maxMb * megaByte)

bear in mind that this is where Snap was expecting you to have a look at the mime type etc, since 请记住,这是Snap期待你看看mime类型等的地方,因为

data PartInfo =
    PartInfo { partFieldName   :: !ByteString
             , partFileName    :: !(Maybe ByteString)
             , partContentType :: !ByteString
             }

so you could define a more intelligent myPerPartPolicy that checks what field it came in on, and that it's in your whitelist of mime types, instead of ignoring the information. 所以你可以定义一个更智能的myPerPartPolicy来检查它所myPerPartPolicy字段,并且它在你的mime类型的白名单中,而不是忽略这些信息。 Perhaps you were planning on doing that since Scotty's files action provides this in the output rather than in this handler way. 也许你正计划这样做,因为Scotty的文件操作在输出中提供了这个,而不是以这种处理程序的方式。

Upload Handler 上传处理程序

You need your actual file handler. 你需要你的实际文件处理程序。 This is the function that does the grunt work with the uploads. 这是使用grount上传的功能。 Asking you to write it as a handler instead of just giving you the data allows you to write your code in a more static, modular kind of way, so I see it as an advantage over just throwing data out. 要求您将其编写为处理程序而不是仅仅为您提供数据允许您以更静态,模块化的方式编写代码,因此我认为它比仅仅抛出数据更有优势。

myBusinessLogic :: MonadSnap m => PartInfo -> FilePath -> m ()
myBusinessLogic = undefined -- whatever you actually want to do with your uploads

Warning : 警告

After the user handler runs (but before the Response body Enumerator is streamed to the client), the files are deleted from disk; 用户处理程序运行后(但在响应主体枚举器流式传输到客户端之前),文件将从磁盘中删除; so if you want to retain or use the uploaded files in the generated response, you would need to move or otherwise process them. 因此,如果要在生成的响应中保留或使用上载的文件,则需要移动或以其他方式处理它们。

Let's wrap that into something that ignores bad uploads and implements your business logic on valid ones, one at a time. 让我们把它包装成忽略不良上传的东西,并在有效的上传中实现业务逻辑,一次一个。 Notice that we've restricted ourselves to returning nothing so we can just run through the whole lot one-by-one using mapM_ , but if you like, you could do something cleverer by filtering out the ones you didn't want from the list, and returning something more interesting than () . 请注意,我们仅限于返回任何内容,因此我们可以使用mapM_整个批次,但如果您愿意,可以通过从列表中过滤出您不想要的内容来做一些更聪明的事情,并返回比()更有趣的东西。 I've used a filter anyway to point in that direction: 无论如何我都使用过滤器指向那个方向:

myUploadHandler :: MonadSnap m => 
                [(PartInfo, Either PolicyViolationException FilePath)] -> m ()
myUploadHandler xs = mapM_ handleOne (filter wanted xs) where
 wanted (_,Left _) = False
 wanted (_,Right _) = True
 handleOne (partInfo,Right filepath) = myBusinessLogic partInfo filepath       

You don't need to worry about OS-agnostic temporary files folder, because if you import System.Directory , you can use getTemporaryDirectory :: IO FilePath to get a temporary directory. 您无需担心与操作系统无关的临时文件文件夹,因为如果import System.Directory ,则可以使用getTemporaryDirectory :: IO FilePath来获取临时目录。 (If you prefer, you could use an application-specific directory in the current userspace, using the createDirectoryIfMissing:: Bool -> FilePath -> IO () and getAppUserDataDirectory :: String -> IO FilePath .) In any case, you'll need to pass the filepath you get from there to your handler. (如果您愿意,可以使用当前用户空间中的特定于应用程序的目录,使用createDirectoryIfMissing:: Bool -> FilePath -> IO ()getAppUserDataDirectory :: String -> IO FilePath 。)无论如何,您将使用需要将从那里获得的文件路径传递给您的处理程序。

All together 全部一起

Now 现在

handleFileUploads :: MonadSnap m => 
     FilePath -> UploadPolicy -> (PartInfo -> PartUploadPolicy)
       -> ([(PartInfo, Either PolicyViolationException FilePath)] -> m a) 
       -> m a

so you can write 所以你可以写

myHandleFileUploads :: MonadSnap m => FilePath -> m ()
myHandleFileUploads tempDir = 
   handleFileUploads tempDir myDefaultPolicy myPerPartPolicy myUploadHandler

You'll still need to pass the tempDir from the System.Directory stuff like I mentioned earlier, but this myHandleFileUploads is ready to roll. 您仍然需要像我之前提到的那样从System.Directory传递tempDir ,但是这个myHandleFileUploads已准备就绪了。

I feel you. 我能感觉得出你。 I had some brain picking and head scratching to do before things started to make sense. 在事情开始变得有意义之前,我有一些大脑挑选和头脑搔痒。

So here it goes. 所以这就是它。

--------------------------------------------------------------------------------------
-- | Handle file uploads
uploadFiles :: AppHandler ()
uploadFiles = do
  files <- handleMultipart defaultUploadPolicy $ \part -> do
    content <-  liftM B.concat EL.consume
    return (part, content)
  if any (\(_,c) -> (B.length c) > 16000000) files
    then error "One of the uploaded files is bigger then 16Mb limit size!"
    else saveFiles files
  syncDB
  redirect "/files/all"

This is specific to mongoDB so I also check for 16MB file size. 这是mongoDB特有的,所以我也检查了16MB的文件大小。

The "saveFiles" function is file storage specific. “saveFiles”功能是特定于文件存储的。 In my case it is mongoDB. 就我而言,它是mongoDB。 I'll include it just in case. 我会把它包括在内以防万一。

--------------------------------------------------------------------------------------
-- | Save uploaded files to the database
saveFiles :: [(PartInfo, ByteString)] -> AppHandler ()
saveFiles fs = do
  let filedoc p c =
    [ "name"     =: maybe "Filename" T.decodeUtf8 (partFileName p)
    , "category" =: "Unassigned"
    , "desc"     =: "Unassigned"
    , "type"     =: T.decodeUtf8 (partContentType p)
    , "size"     =: B.length c
    , "blob"     =: Binary c
    ]
  r <- eitherWithDB $ insertMany "files" [filedoc p c | (p,c) <- fs]
  either (error . show) (const $ return () ) r

Well, this is it pretty much. 嗯,这就是它。 It is not hard really once it clicks. 一旦点击它就不难。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM