[英]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.
要他们。
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的文件操作在输出中提供了这个,而不是以这种处理程序的方式。
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
。)无论如何,您将使用需要将从那里获得的文件路径传递给您的处理程序。
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.