跳转到内容

Yesod web 框架/控制器

100% developed
来自维基教科书,开放的书籍,面向开放的世界

服务器接口

[编辑 | 编辑源代码]

Yesod 使用 Web 应用程序接口 API,[1] 简称 WAI,将 servlet(即 web 应用程序)与服务器隔离开,并为服务器协议 CGI,[2] FastCGI,[3] SCGI,[4] Warp,[5] Launch(将本地 URL 打开到默认浏览器,在窗口关闭时关闭服务器)[6] 提供处理程序。

基础类型

[编辑 | 编辑源代码]

参见 ref.[7] Yesod 需要一个数据类型来实例化控制器类。这称为 基础 类型。在下面的示例中,它名为 "MyApp"。

REST 模型通过 Web 路径标识 Web 资源。这里,REST 资源使用带有 R 后缀的名称(例如 "HomeR")来命名,并在 parseRoutes 站点地图描述模板中列出。从这个列表中,派生路由名称和调度处理程序名称。

Yesod 利用模板 Haskell 元编程在编译时从模板生成代码,确保模板中的名称匹配并且所有内容都经过类型检查(例如,Web 资源名称和处理程序名称)。

通过插入 mkYesod 调用,这将调用模板 Haskell 原语来生成与路由类型成员相对应的代码[8],以及调度控制器类的实例,以便将 GET 调用调度到路由 HomeR 到一个名为将两者组合起来作为 "getHomeR" 的例程,期望一个与名称匹配的现有处理程序。

Hello World

[编辑 | 编辑源代码]

"Hello world" 示例基于 CGI 服务器接口(实际处理程序类型已经改变,但理念保持不变)

{- file wai-cgi-hello.hs -}
{-# LANGUAGE PackageImports, TypeFamilies, QuasiQuotes, MultiParamTypeClasses,
             TemplateHaskell, OverloadedStrings #-}
import "wai" Network.Wai
import "wai-extra" Network.Wai.Handler.CGI (run) -- interchangeable WAI handler

import "yesod" Yesod
import "yesod-core" Yesod.Handler (getRequest)
import "text" Data.Text (Text)
import "shakespeare" Text.Cassius (Color(..), colorBlack)

-- the Foundation type
data MyApp = MyApp

-- sitemap template, listing path, resource name and methods accepted
-- `mkYesod` takes the foundation type name as param. for name composition of dispatch functions
mkYesod "MyApp" [parseRoutes|
/ HomeR GET
|]

instance Yesod MyApp

-- indentation structured CSS template
myStyle :: [Text]  CssUrl url
myStyle paramStyle =
        [cassius|
.box
    border: 1px solid #{boxColor}
|]
        where
          boxColor = case paramStyle of
                        ["high-contrast"]  colorBlack
                        _  Color 0 0 255

-- indentation structured HTML template
myHtml :: [(Text, Text)]  HtmlUrl url
myHtml params = [hamlet|
<!-- indentation, or lack of it, under starting tags or commands ('$' prefix) 
     describe the content or sequence tree structure -->
<!-- '.' or '#' prefixes in tags introduce css styled "class" or "id" attribute values -->
<!-- interpolation of haskell expressions follow the "shakespeare templates" #{expr} syntax -->

<p>Hello World! There are <span .box>#{length params} parameters</span>:
$if null params
    <p>Nothing to list 
$else
    <ul>
         $forall param <- params
             <li>#{fst param}: #{snd param}
|]
getHomeR :: Handler RepHtml
getHomeR = do
        req <- getRequest
        let params = reqGetParams req
        paramStyle <- lookupGetParams "style"
        
        defaultLayout $ do
            -- adding widgets to the Widget monad (a ''Writer'' monad)
            setTitle "Yesod example"
            toWidgetHead $ myStyle paramStyle
            toWidgetBody $ myHtml params

-- there are ''run'' function variants for different WAI handlers

main = toWaiApp MyApp >>= run
# cgi test
export REMOTE_ADDR=127.0.0.1
export REQUEST_METHOD=GET
export PATH_INFO=/
export QUERY_STRING='p1=abc;p2=def;style=high-contrast'
./wai-cgi-hello

[7]

资源、路由和 HTTP 方法处理程序

[编辑 | 编辑源代码]

参见 ref.[9][10] Yesod 遵循访问 Web 文档的 REpresentational State Transfer 模型,使用路由构造函数识别文档和目录作为资源,并使用大写 R 后缀命名(例如,HomeR)。

路由表
parseRoutes 模板应列出指定路由片段、资源名称和要接受的调度方法的资源。

URL 段捕获作为参数是可能的,指定 '#' 前缀用于单段捕获,或 '*' 用于多段捕获,后跟参数类型。

-- given a MyApp foundation type

mkYesod "MyApp" [parseRoutes|
/                     HomeR      -- no http methods stated: all methods accepted
/blog                 BlogR      GET POST

-- the '#' prefix specify the path segment as a route handler parameter
/article/#ArticleId   ArticleR   GET PUT

-- the '*' prefix specify the parameter as a sequence of path pieces
/branch/*Texts        BranchR    GET

-- to simplify the grammar, compound types must use an alias, eg. type Texts for ''[Text]''
|]
  • 应用前面的模板生成以下路由构造函数
data Route MyApp = 
    HomeR                    -- referenced in templates as: @{HomeR}
    | BlogR                  -- in templates: @{BlogR}
    | ArticleR ArticleId     -- in templates: @{ArticleR myArticleId}
    | BranchR Texts          -- in templates: @{BranchR myBranchSegments}
  • 对于每种支持的 HTTP 方法,必须创建一个处理程序函数来匹配 mkYesodparseRoutes 模板生成的调度名称,方法是将方法名称(如果未指定方法,则使用前缀 "handler")附加到资源,如所描述的那样(实际版本处理程序类型已经改变,但理念保持不变)
-- for "/ HomeR"        -- no http methods stated ⇒ only one handler with prefix ''handler''
handlerHomeR :: HasReps t  Handler t

-- for "/blog BlogR GET POST"
getBlogR :: HasReps t  Handler t
postBlogR :: HasReps t  Handler t

-- for "/article/#ArticleId ArticleR GET PUT"
getArticleR :: HasReps t  ArticleId  Handler t
putArticleR :: HasReps t  ArticleId  Handler t

请求数据、参数、Cookie、语言和其他标头信息

[编辑 | 编辑源代码]

参见 ref.[9]

身份验证和授权

[编辑 | 编辑源代码]

参见 ref.[11] 身份验证插件:OpenId、BrowserId、电子邮件、Google 电子邮件、HashDB、RpxNow.[12]

身份验证后自动重定向有一个重要的设置。[13]

参见 ref.[14] 会话后端:ClientSession[15](它将会话存储在 Cookie 中)、ServerSession[16][17](它将大部分会话数据存储在服务器上)

>> 为了避免不必要的带宽开销,生产站点可以从一个单独的域名提供静态内容,以避免为每个请求传输会话 Cookie 的开销。

会话消息

[编辑 | 编辑源代码]

可以在会话中存储成功、失败或指示性消息(setMessage),如果存在,则通过 default_layout.hamlet 模板,由 default_layout 例程显示,并在咨询时清除。[18]

子站点

[编辑 | 编辑源代码]

用于工作流、文件服务或站点分区之类的通用 URL 前缀子站点。参见 ref.[19][20]

内置子站点:Static,[21][22] Auth[23]

表单处理和布局生成

[编辑 | 编辑源代码]

参见 ref.[24]

这里的 Form 类型是一个对象,它在 controller 中使用,用于解析和处理表单字段用户输入,并生成一个 (FormResult, Widget) 对,其中小部件包含表单的下一个渲染的布局,包括错误消息和标记。它还可用于生成带有空白或默认值的新表单。

表单类型采用一个要嵌入到视图中的 html 片段的函数的形式,该片段将包含出于安全目的的隐藏字段。

表单对象是从字段的 Applicative/Monadic 组合生成的,用于对字段输入进行组合/顺序解析。

有三种类型的表单

  • Applicative(带有表格布局),
  • Monadic(带有自由布局样式),两者都在 Yesod.Form.Functions 模块中,
  • Input(仅用于解析,不生成视图)位于 Yesod.Form.Input 模块中。

字段生成器,其名称由表单类型初始 (a|m|i) 后跟 (req|opt){- 必需或可选 -} 组成,具有一个 fieldParse 组件和一个 fieldView 组件。[25]

  • 函数 runForm{Post|Get} 会对表单字段输入运行字段解析器,并从视图中生成一个 (FormResult, Widget) 对,提供一个新的表单小部件,其中接收到的表单字段值作为默认值。函数后缀是表单提交中使用的 HTTP 方法。
  • generateForm{Post|Get} 会忽略来自客户端的输入,并生成一个空白或默认表单小部件。[26]

实际的函数参数和类型在 Yesod 版本中有所改变。请查看 Yesod 手册和库签名。

神奇之处在于 FormResult 数据类型 Applicative 实例,其中 (<*>) 收集了 FormFailure [textErrMsg] 结果值的错误消息。[27]

单子形式允许自由的表单布局和对 hiddenField 成员的更好处理。[24]

一个 Applicative[28] 表单的示例

-- a record for our form fields
data Person = Person {personName :: Text, personAge :: Int, personLikings :: Maybe Text}

-- the Form type has an extra parameter for an html snippet to be embedded, containing a CSRF token hidden field for security
type Form sub master x = Html  MForm sub master (FormResult x, Widget)

{-
-- for messages in validation functions:
  @param master: yesod instance to use in renderMessage (return from handler's getYesod)
  @param languages: page languages to use in renderMessage

-- optional defaults record:
  @param mbPersonDefaults: Just defaults_record, or Nothing for blank form
-}

personForm :: MyFoundationType  [Text]  Maybe Person  Form sub master Person
{- ''aopt'' (optional field AForm component) for "Maybe" fields,
   ''areq'' (required fld AForm comp.) will insert the "required" attribute
-}
personForm master languages mbPersonDefaults = renderTable $ 
  Person <$> areq textField            fldSettingsName    mbNameDefault 
         <*> areq customPersonAgeField fldSettingsAge     mbAgeDefault 
         <*> aopt textareaField        fldSettingsLikings mbLikingsDefault 
  where
    mbNameDefault    = fmap personName    mbPersonDefaults
    mbAgeDefault     = fmap personAge     mbPersonDefaults
    mbLikingsDefault = fmap personLikings mbPersonDefaults

    -- "fieldSettingsLabel" returns an initial fieldSettings record
    -- recently the "FieldSettings" record can be defined from a String label since it implements IsString
    fldSettingsName = (fieldSettingsLabel MsgName) {fsAttrs = [("maxlength","20")]}
    fldSettingsAge  = fieldSettingsLabel MsgAge
    fldSettingsLikings = (fieldSettingsLabel MsgLikings) {fsAttrs = [("cols","40"),("rows","10")]}

    customPersonAgeField = check validateAge intField

    validateAge y
        | y < 18    = Left $ renderMessage master languages MsgUnderAge
        | otherwise = Right y

参考资料

[编辑 | 编辑源代码]
  1. "The wai package". Hackage.haskell.org. Retrieved 2012-10-23.
  2. "The wai-extra package with CGI WAI handler". Hackage.haskell.org. Retrieved 2012-10-23.
  3. "The wai-handler-fastcgi package". Hackage.haskell.org. Retrieved 2012-10-23.
  4. "The wai-handler-scgi package". Hackage.haskell.org. Retrieved 2012-10-23.
  5. "The warp package". Hackage.haskell.org. Retrieved 2012-10-23.
  6. "The wai-handler-launch package". Hackage.haskell.org. Retrieved 2012-10-23.
  7. a b "book - Basics". Yesodweb.com. Retrieved 2012-10-23.
  8. The mkYesod code
  9. a b "book - Routing and Handlers". Yesodweb.com. Retrieved 2012-10-23.
  10. "Playing with Routes and Links". FPComplete.com. 2012-10-17. Retrieved 2012-10-28.
  11. "book - Authentication and Authorization". Yesodweb.com. Retrieved 2012-10-23.
  12. "The yesod-auth package". Hackage.haskell.org. Retrieved 2012-10-26.
  13. "book - Sessions - See section "Ultimate Destination"". Yesodweb.com. Retrieved 2012-11-17.
  14. "Sessions". Yesodweb.com. Retrieved 2012-10-23.
  15. "Web.ClientSession". Hackage.haskell.org. Retrieved 2012-10-25.
  16. "ServerSession: secure modular server-side sessions". Hackage.haskell.org. Retrieved 2018-10-29.
  17. "Web.ServerSession.Frontend.Yesod". Hackage.haskell.org. Retrieved 2018-10-29.
  18. "Session Messages". Yesodweb.com. Retrieved 2018-10-23.
  19. "创建子站点". Yesodweb.com. Retrieved 2012-10-25.
  20. "Yesod 和子站点:轻而易举". Monoid.se. 2012-08-22. Retrieved 2012-10-28.[]
  21. "Yesod 的魔力,第二部分 - 请参见“静态子站点”部分". Yesodweb.com. 2010-12-25. Retrieved 2012-10-25.
  22. "yesod-static 包 - 静态子站点". Hackage.haskell.org. Retrieved 2012-10-25.
  23. "yesod-auth 包 - 认证子站点". Hackage.haskell.org. Retrieved 2012-10-25.
  24. a b "书籍 - 表单". Yesodweb.com. Retrieved 2012-10-23.
  25. "Yesod.Form.Fields". Hackage.haskell.org. Retrieved 2012-10-23.
  26. "Yesod.Form.Functions runFormPost". Hackage.haskell.org. Retrieved 2012-10-25.
  27. "Yesod.Form.Types". Hackage.haskell.org. Retrieved 2012-10-23.
  28. "HaskellWiki - 可应用函子". haskell.org. Retrieved 2012-10-24.


华夏公益教科书