与仆人一起处理常规表格帖子(application/x-www-form-urlencoded)
Handling regular form posts (application/x-www-form-urlencoded) with servant
如何使用 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
我怎样才能把事情串起来?
Servant 通过数据类型 FormUrlEncoded, and the class FromFormUrlEncoded 支持此功能(在 Servant 0.9 中重命名为 FromForm
)。
首先我们为表单数据定义一个数据类型,然后重写我们的处理程序来接受它。
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`"
我只是想为最近版本的 Servant 添加一个答案,因为我必须 google 各种事情才能 assemble 一个完整的、有效的表单处理版本。
以上答案适用于早期版本的 Servant,但在升级到 Servant 0.9 时我无法使用表单。
这是我的做法。
首先,他们从自定义表单实现切换到 http-api-data 中的实现,因此您需要在您的 cabal 文件中:
一些-project.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
之后,您可以在端点中使用来自 Servant 的常规 FormUrlEncoded
ContentType:
type ContactApi = "contact" :> ReqBody '[FormUrlEncoded] ContactForm
:> Post '[HTML] Html
差点忘了:如何渲染这个东西
您可能还需要一个显示表单的页面?那么,"name" 属性必须与您表单中的字段相匹配(我是这样做的,使用 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"
如何使用 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
我怎样才能把事情串起来?
Servant 通过数据类型 FormUrlEncoded, and the class FromFormUrlEncoded 支持此功能(在 Servant 0.9 中重命名为 FromForm
)。
首先我们为表单数据定义一个数据类型,然后重写我们的处理程序来接受它。
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`"
我只是想为最近版本的 Servant 添加一个答案,因为我必须 google 各种事情才能 assemble 一个完整的、有效的表单处理版本。
以上答案适用于早期版本的 Servant,但在升级到 Servant 0.9 时我无法使用表单。
这是我的做法。
首先,他们从自定义表单实现切换到 http-api-data 中的实现,因此您需要在您的 cabal 文件中:
一些-project.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
之后,您可以在端点中使用来自 Servant 的常规 FormUrlEncoded
ContentType:
type ContactApi = "contact" :> ReqBody '[FormUrlEncoded] ContactForm
:> Post '[HTML] Html
差点忘了:如何渲染这个东西
您可能还需要一个显示表单的页面?那么,"name" 属性必须与您表单中的字段相匹配(我是这样做的,使用 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"