跳轉到內容

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.


華夏公益教科書