8

如何使用 Servant 处理常规形式的 POST?特别是,给定一个 HTML 表单,如

<form action="/check" method="post">
  Solution:
  <input name="code" type="text">
  <input type="submit">
</form>

data CheckResult = Correct | Wrong

instance ToHtml CheckResult
    ...

checkCode :: Text -> Handler CheckResult
checkCode code = if code == "secret" then Correct else Wrong

我怎么把东西串起来?

4

2 回答 2

10

我只是想为最新版本的 Servant 添加一个答案,因为我必须在谷歌上搜索各种东西才能组装一个完整的、有效的表单处理版本。

上面的答案适用于早期版本的 Servant,但是在升级到 Servant 0.9 时,我在使用表单时遇到了困难。

这就是我的做法。

首先,他们从自定义 Form 实现切换到http-api-data中的实现,因此您需要在 cabal 文件中使用它:

一些项目.cabal

  build-depends:       base >= 4.7 && < 5
                     , aeson
                     , blaze-html
                     , http-api-data

接下来,您可以像上面一样声明一个表单,但您可以使用它GHC.Generics来自动派生一个FromForm实例:

{-# LANGUAGE DeriveGeneric     #-}

module Html.Contact where

import           GHC.Generics
import           Servant
import           Web.FormUrlEncoded          (FromForm)

data ContactForm = ContactForm
 { cname    :: !T.Text
 , cemail   :: !T.Text
 , cmessage :: !T.Text
 } deriving (Eq, Show, Generic)

instance FromForm ContactForm

之后,您可以FormUrlEncoded在端点中使用来自 Servant 的常规 ContentType:

type ContactApi = "contact" :> ReqBody '[FormUrlEncoded] ContactForm
                                   :> Post '[HTML] Html

差点忘了:如何渲染这个东西

您可能还需要一个页面,在哪里显示您的表单?好吧,“名称”属性必须与表单中的字段匹配(这是我的做法,使用Blaze):

contactForm :: H.Html
contactForm = H.section ! A.id "contact" ! A.class_ "container contact-us u-full-width u-max-full-width" $
  H.div ! A.class_ "row" $ do
    H.div ! A.class_ "eight columns contact-us-form" $
      H.form ! A.method "post" ! A.action "/contact" $ do
        H.div ! A.class_ "row" $ do
          H.div ! A.class_ "six columns" $
            H.input ! A.class_ "u-full-width" ! A.type_ "text" ! A.name "cname" ! A.placeholder "Name" ! A.id "nameInput"
          H.div ! A.class_ "six columns" $
            H.input ! A.class_ "u-full-width" ! A.type_ "text" !  A.name "cemail" ! A.placeholder "Email" ! A.id "emailInput"
        H.textarea ! A.class_ "u-full-width" ! A.name "cmessage" ! A.placeholder "Message" ! A.id "messageInput" $ ""
        H.input ! A.class_ "button u-pull-right" ! A.type_ "submit" !  A.value "Send"
于 2017-06-13T14:24:05.350 回答
3

Servant 通过数据类型FormUrlEncodedFromFormUrlEncoded类(FromForm在 Servant 0.9 中重命名为)支持这一点。

首先,我们为表单数据定义一个数据类型,并重写我们的处理程序以接受它。

data CheckRequest = CheckRequest { code :: Text }

checkCode :: CheckRequest -> Handler CheckResult
checkCode (CheckRequest code) = if code == "secret" then Correct else Wrong

然后我们指定一个 POST 类型的主体application/x-www-form-urlencoded

type API = "check"
         :> ReqBody '[FormUrlEncoded] CheckRequest
         :> Post '[HTML] CheckResult

现在所需要做CheckRequest的就是创建一个FromFormUrlEncoded.

instance FromFormUrlEncoded CheckRequest where
  --fromFormUrlEncoded :: [(Text, Text)] -> Either String CheckRequest
  fromFormUrlEncoded [("code", c)] = Right (CheckRequest c)
  fromFormUrlEncoded _             = Left "expected a single field `code`"
于 2016-10-02T17:34:47.187 回答