悲しい話
もくじ
情弱でもSnapでファイルアップロードしたい!
HaskellのWebフレームワークSnap、使ってますか!最近ちょっと触ってて、ファイルアップロードがしたいなぁと思ってこんなハンドラを書きました。Snapでは、URLに紐ついたハンドラ(結局モナド)を登録して、特定のURLにアクセスされると、そのハンドラが実行されます。
こんな感じ:
handleUpload :: Handler App App () handleUpload = method GET (redirect "/") <|> method POST handlePost where process part = continue (\a -> (yield (partFileName part) a)) handlePost = do ret <- handleMultipart defaultUploadPolicy (\p -> process p) writeText $ T.pack $ show ret
ふーむ。この状態でこんなフォームを書いて送信してみます。
<form method='post' action='/upload'> <table id='info'> <tr> <td>file: </td><td><input type='file' name='image' size='20' /></td> </tr> <tr> <td></td> <td><input type='submit' value='send' /></td> </tr> </table> </form>
よくみるとformでのenctype指定がないのですが、最初私は気づきませんでした。さてブラウザからアクセス。
うーん、acceptできるハンドラがない、ねぇ…。最初、ルーティングがうまくいってないのかな、と思ったのですが、GETで/uploadにアクセスするとちゃんとリダイレクトがかかるので、そこは間違ってないだろう、となると、このPOSTのハンドラが変みたいだ、と目星をつけました。
finally catch try ->どれも動かない
handleMultiPartを消してreturnにするとちゃんとレスポンスが表示されるので、どうやらここで例外?が発生してる模様。じゃあ`catch`だ!とやってみても…
handleUpload = method GET (redirect "/") <|> method POST handlePost where process part = continue (\a -> (yield (partFileName part) a)) handlePost = do ret <- (handleMultipart defaultUploadPolicy (\p -> process p)) `catch` (\(e :: SomeException) -> [show e]) writeText $ T.pack $ show ret
こうしても、さっきとまったく同じ、「No handler accepted.」。すべての例外をキャッチできるはずのSomeExceptionを使ってもだめ。例外がキャッチできてないからか、finallyを使っても何も起こりません。tryを使ってもEitherが帰ってこず、処理がスキップされちゃいます。
これ、C++で-fno-exceptionした時となんか似てます。例外処理が動いてないのでは?…もっとも、C++の場合はもっと不思議なことが起こったりですが…。
Multi-Partのチェックとその失敗処理は例外と直行している
中身を見るしかないので読みましょう。まぬあるにて、どうぞ。 HaskellのドキュメントシステムHackageでは、rdocみたいにソースが読めます。すばらしい。
長いのですが、今回エラー処理が起こされているのはここでした。
-- not well-formed multipart? bomb out. when (ct /= "multipart/form-data") $ do debug $ "handleMultipart called with content-type=" ++ S.unpack ct ++ ", passing" pass
passというので実際に処理を抜けているようですね。このpassのドキュメントはこれ。Exception関係のものがシグネチャにあらわれていないのを見ればわかるように、例外とは一切関係なく処理をスキップ致します。finallyを使っても何も起こせないのは、こういう事だったのですね。
debugを有効にしたほうがいいかもしれない
環境変数にDEBUG=1を指定するとデバッグモードが有効になります。
すると、こんな感じのエラーログがひたすら出力されます。
[ 8] handleMultipart called with content-type=application/x-www-form-urlencoded, passing [ 8] Server.httpSession: finished running user handler [ 8] Server.httpSession: handled, skipping request body [ 8] httpSession/skipToEof: BEGIN [ 8] httpSession/skipToEof: continue
これならコンテント・タイプがおかしいんだな~ってすぐ気づけますね…。開発中はずっとこれをつけといたほうがいいかもしれませんね!
ちなみにうまくいくと
Justでファイル名が帰ってくるだけの簡単なサンプルプログラムでした。ファイルは扱ってないです…。
ネタもと
ぼんやりと「Haskellのエラー処理とMonadCatchIOの落とし穴 – 純粋関数型雑記」を見てたら思い至った話です。
「例外、失敗」を処理する方法がたくさんあるのは柔軟で良いと思うのですが、ルールをちゃんとあわせておくか、あるいは別種の「失敗」があることをちゃんと把握しておかないと意図しない挙動になっちゃうのは、悲しいですね…。