Yesod - 类型类 (5)

Yesod - 类型类 (5)为什么 80 的码农都做不了架构师 yesodmimetyp

为什么80%的码农都做不了架构师?>>>  hot3.png

Yesod 类型类

每个Yesod应用都需要实现Yesod类型类,目前为止我们只是在使用这个类型类的默认方法实现。在这章,我们将展开说明Yesod类型类的各个方法。
Yesod类型类给我们提供一个地方来定义一些设置。每一个都有默认的定义,并且他们通常都是正确的。但是如果我们需要通过自定义一些设置来实现更强大的应用,可以通过覆盖一些定义来实现。

一个常见的问题是为什么使用类型类而不是record呢?类型类有两个主要优点:

  • Yesod类型类的函数可以调用其他函数。但是record麻烦很多。
  • 简单的语法。我们可以提供默认的实现,用户可以只覆盖他想自定义的就行了。record实现起来就不是很方便了。

解析URLs

我们之前已经提到过Yesod怎么自动渲染类型安全的Url了。假如我们的路由定义如下:

mkYesod "MyApp" [parseRoutes| /some/path SomePathR GET ] 

如果我们我们在SomePathR里面定义一些Hamlet模板,Yesod是怎么渲染它的呢。Yesod总是优先构建绝对URLs,这对于构建站点XML的site map和atom订阅源,或者发送邮件尤其重要。但是为了构建绝对URL我们需要知道应用的域名。
你可能会想,我们可以从用户的请求里获取这些信息,但是我们是得不到端口的信息的。就算我们能从用户请求中得到端口信息,我们也无法得知是HTTP还是HTTPS。即使都知道这些,但是这样意味着,依赖于用户的输入会生成不同的URL。例如"example.com"和"www.example.com"。 这是不利于搜索引擎优化的。
最终,Yesod不会对你的网站地址做任何的假设。例如我有一个静态节点http://static.example.com,但是我让Yesod响应http://static.example.com/wiki,应用是没有方法猜测出用什么子路径的。所以Yesod需要明确的Root URL而不是让其自己猜测。对wiki这个例子来说你需要这么写。

instance Yesod MyWiki where approot = ApprootStatic "http://static.example.com/wiki" 

注意最后是没有斜线的。接下来当Yesod想要构建SomePathR的URL时。他确定SomePathR的相对路径是/some/path并将他和approot上的组成http://static.example.com/wiki/some/path
approot的默认值是ApprootRelative这实际上就是代表着不加任何前缀。在刚才的情况下生成的就是/some/path。这适用于你的应用托管在域名的顶级目录上的时候。当你有使用绝对URL情况的时候最好使用ApprootStatic
除了上面说的ApprootStatic之外你还可以使用ApprootMaster或者ApprootRequest。前一个允许你使用foundation值表示approot,例如它允许你从配置文件中加载设置。后一个允许你使用请求参数确定approot。使用此功能,您可以提供不同的域名,使用哪个取决于用户首先请求站点的地址。
模板项目默认使用ApprootMaster方式。启动时从"APPROOT"环境变量或者配置文件中取出你的approot。另外他还为测试和生产环境提供了不同的配置。所以你可以很轻松的用localhost测试在不同的域名中发布。你当然也可以修改这些配置文件的值。

joinPath

在转换类型安全的URL的时候Yesod使用了两个辅助函数。第一个是RenderRoute类型类的renderRoute。所有类型安全的URL都是此类型类的实例。renderRoute将值转换成一个路径片段列表例如SomePathR转化成["some","path"]

事实上renderRoute同时也应该生成查询参数的列表。但是默认的renderRoute实现总是会给一个空的查询参数列表。但是你可以重写覆盖它。

另一个函数是Yesod类型类的joinPath这个函数有四个参数。

  • foundation value
  • application root
  • 路径段列表
  • 查询参数列表 他返回一个URL。默认的实现是,通过"/"组合路径段添加到approot后面,然后再附加上查询参数。
    如果你对默认的URL感到满意那么你没有必要修改它。除非你想自定义一些规则。

cleanPath

joinPath对应的是cleanPath,让我们看看它做了些什么:

  1. 把请求的路径信息分割成小段。
  2. 把分割的路径段传递给cleanPath
  3. 如果cleanPath返回重定向(Left response),将会给客户端发送301。这用于强制规范URL。
  4. 不然的话我们尝试通过cleanPath调度相应(Right ),如果成功就返回,不然就返回404。 这种组合允许子网站完全控制其网址的显示方式,但允许主网站拥有修改后的网址。举个简单的例子,让我们看看我们怎么在URL始终显示最后的斜杠。
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} import Blaze.ByteString.Builder.Char.Utf8 (fromText) import Control.Arrow ((*)) import Data.Monoid (mappend) import qualified Data.Text as T import qualified Data.Text.Encoding as TE import Network.HTTP.Types (encodePath) import Yesod data Slash = Slash mkYesod "Slash" [parseRoutes| / RootR GET /foo FooR GET |] instance Yesod Slash where joinPath _ ar pieces' qs' = fromText ar `mappend` encodePath pieces qs where qs = map (TE.encodeUtf8 * go) qs' go "" = Nothing go x = Just $ TE.encodeUtf8 x pieces = pieces' ++ [""] -- We want to keep canonical URLs. Therefore, if the URL is missing a -- trailing slash, redirect. But the empty set of pieces always stays the -- same. cleanPath _ [] = Right [] cleanPath _ s | dropWhile (not . T.null) s == [""] = -- the only empty string is the last one Right $ init s -- Since joinPath will append the missing trailing slash, we simply -- remove empty pieces. | otherwise = Left $ filter (not . T.null) s getRootR :: Handler Html getRootR = defaultLayout [whamlet| <p> <a href=@{RootR}>RootR <p> <a href=@{FooR}>FooR |] getFooR :: Handler Html getFooR = getRootR main :: IO () main = warp 3000 Slash 

首先我们看joinPath的实现。这几乎是从默认的Yesod实现中复制的,只有一点不同,我们在最后加了一个空字符串。处理路径片段时空字符串将转义为一个斜杠。

defaultLayout

大多数网站都喜欢把一个通用模板应用到他们的所有页面。defaultLayout就是做这个的。虽然您可以轻松的定义自己的函数并调用它。但是当你重写defaultLayout的时候所用Yesod生成的页面(错误页面,认证页面)会自动应用这个样式。 覆盖是非常简单的:我们使用widgetToPageContent将Widget转换为标题,head标签和body标签,然后使用withUrlRenderer将Hamlet模板转换为Html值。我们甚至可以在defaultLayout中添加额外的小部件组件,如Lucius模板。有关更多信息,请参阅有关小部件的上一章。
如果你是用的是模板项目,你可以修改templates/default-layout.hamlettemplates/default-layout-wrapper.hamlet前者包含<body>标记的大部分内容,而后者包含HTML的其余部分,例如doctype和<head>标记。有关详细信息,请参阅这些文件

getMessage

尽管我们还没有讲到Session,但是我还是想在这里提一下getMessage,Web开发中的一种常见模式是在一个处理程序中设置消息并在另一个处理程序中显示它。例如,当用户POST了一个表单的时候,你可能想将他重定向到另一个"显示表格提交完成"显示页面。为了实现这一点,Yesod内置了一对函数:setMessage在Session中设置一些消息,getMessage获取这个消息(并清除它,所以他不会再次出现了)。建议您将getMessage的结果放入defaultLayout。

{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE QuasiQuotes           #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE TypeFamilies          #-}
import           Yesod
import Data.Time (getCurrentTime)

data App = App

mkYesod "App" [parseRoutes|
/ HomeR GET
|]

instance Yesod App where
    defaultLayout contents = do
        PageContent title headTags bodyTags <- widgetToPageContent contents
        mmsg <- getMessage
        withUrlRenderer [hamlet|
            $doctype 5

            <html>
                <head>
                    <title>#{title}
                    ^{headTags}
                <body>
                    $maybe msg <- mmsg
                        <div #message>#{msg}
                    ^{bodyTags}
        |]

getHomeR :: Handler Html
getHomeR = do
    now <- liftIO getCurrentTime
    setMessage $ toHtml $ "You previously visited at: " ++ show now
    defaultLayout [whamlet|<p>Try refreshing|]

main :: IO ()
main = warp 3000 App

在讨论Session时,我们将更详细地介绍getMessage / setMessage。

自定义错误页面

专业网站的标志之一是设计合理的错误页面。Yesod通过默认的defaultLayout显示错误页面。但有时候,你会想自定义它。为此,您只需要覆盖errorHandler方法:

{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} import Yesod data App = App mkYesod "App" [parseRoutes| / HomeR GET /error ErrorR GET /not-found NotFoundR GET |] instance Yesod App where errorHandler NotFound = fmap toTypedContent $ defaultLayout $ do setTitle "Request page not located" toWidget [hamlet| <h1>Not Found <p>We apologize for the inconvenience, but the requested page could not be located. |] errorHandler other = defaultErrorHandler other getHomeR :: Handler Html getHomeR = defaultLayout [whamlet| <p> <a href=@{ErrorR}>Internal server error <a href=@{NotFoundR}>Not found |] getErrorR :: Handler () getErrorR = error "This is an error" getNotFoundR :: Handler () getNotFoundR = notFound main :: IO () main = warp 3000 App 

我们自定义了一个404页面。当我们不想为每种错误类型编写自定义处理程序时,我们可以使用defaultErrorHandler。由于类型限制我们需要去前面加上fmap toTypedContent。(我们将在下一章中详细了解TypedContent。)事实上,您甚至可以使用重定向等特殊响应:

errorHandler NotFound = redirect HomeR errorHandler other = defaultErrorHandler other 

即使你可以做到这一点,我实际上并没有推荐这样的做法。 404应该是404。

外部CSS和Javascript

此处描述的功能自动包含在scaffolded站点中,因此您无需担心自己实现此功能。

Yesod类型类中最强大的方法之一是addStaticContent。Widget由多个组件组成,包括CSS和Javascript。CSS / JS到底是如何到达用户的浏览器的?默认情况下,它们分别在页面的<head><style><script>标记内提供。
这可能很简单,但效率很低。每个页面加载都需要从头开始加载CSS / JS,即使没有任何改变!我们真正想要的是将此内容存储在外部文件中,然后从HTML中引用它。
这就是addStaticContent的作用,他有三个参数。分别是文件的扩展名(js或者css),mime-type (text/css or text/javascript)还有文件内容自己。他返回三种可能的结果。

Nothing

没有发生文件缓存直接将内容嵌入HTML中,这是默认的行为。

Just (Left Text)

此内容保存在外部文件中,并使用给定的文本链接来引用它。

Just (Right (Route a, Query))

和上一个基本相同,但现在使用类型安全的URL以及一些查询字符串参数。如果要将静态文件存储在外部服务器(如CDN或内存支持的服务器)上,则Left结果很有用。Right的结果是更常用的,并与静态子网站能更好的结合。这是大多数应用程序的推荐方法,默认情况下由scaffolded站点提供。

你可能想知道:如果这是推荐的方法,为什么不是默认方法?问题在于它产生了许多不普遍存在的假设,例如静态子网站的存在和静态文件的位置。

scaffolded的addStaticContent提供了许多默认的东西来帮助你:

  • 它会使用hjsmin包自动压缩你的Javascript。
  • 他根据文件的内容的哈希值来命名文件,这同时意味着你可以把他设置为很长时间才过时而不用担心出问题。
  • 由于文件名基于哈希,如果文件名是相同的,那个两份文件名相同的文件可以合并为一个文件,scaffold代码会自动检查该文件是否存在,并且如果没有必要,可以避免代价高昂的磁盘I / O。

更智能的静态文件

Google的一个优化建议是:从单独的域提供静态文件。这种方法的优点是在检索静态文件时不会发送主域上设置的cookie,从而节省了一些带宽。为此,我们提供了urlRenderOverride方法。这个方法拦截普通的URL并设置一些特殊的规则。例如:

urlRenderOverride y (StaticR s) = Just $ uncurry (joinPath y (Settings.staticRoot $ settings y)) $ renderRoute s urlRenderOverride _ _ = Nothing 

这意味着静态路由是从特殊的静态节点提供的,您可以将其配置为不同的域名。这是类型安全URL的强大功能和灵活性的一个很好的例子:只需一行代码,您就可以在所有处理程序中更改静态路由的渲染。

认证/授权

对于简单的应用程序,检查每个Handler函数的响应权限可能比较简单,但是它不能很好的扩展,最终,你会想要一个更具声明性的方法。许多其他的系统为了解决这个问题,定义了ACLs,添加特殊的配置文件,和一些其他的写法。在Yesod中,只是普通的Haskell,三个函数用来解决这个问题:

isWriteRequest

定义当前请求是“读取”还是“写入”操作。默认情况下,Yesod遵循RESTful原则,并假设GET,HEAD,OPTIONS和TRACE请求是只读的,而所有其他请求都是可写的。

isAuthorized

一个路由参数(类型安全的URL),一个bool值表示请求是否是写操作,他返回的类型是AuthResult,可以是下面三种类型:

  • Authorized
  • AuthenticationRequired
  • Unauthorized

默认情况下,它会为所有请求返回Authorized。

authRoute

如过 isAuthorized 返回 AuthenticationRequired, 则重定向到给定的路线。如果未提供路由(默认值),则返回401“需要身份验证”消息。
这些方法可以和与yesod-auth软件包很好地结合,脚手架网站使用它来提供许多身份验证选项,例如OpenID,Mozilla Persona,电子邮件,用户名和Twitter。我们将在auth章节中介绍更具体的例子。

一些简单的设置

并非Yesod类型类中的所有内容都很复杂。一些方法是简单的功能。我们来看看列表:

maximumContentLength

为了防止拒绝服务(DoS)攻击,Yesod将限制请求主体的大小。有些时候,你会想要解除请求大小限制(例如,文件上传页面)。

fileUpload

根据请求的大小确定上传文件的处理方式。两种最常用的方法是将文件保存在内存中,或者流式传输到临时文件。默认情况下,小请求保留在内存中,大型请求存储在磁盘中。

shouldLogIO

确定是否应将给定的日志消息(具有关联的源和级别)发送到日志。这允许您将大量调试信息放入您的应用程序,但只在必要时打开它。

总结

转载于:https://my.oschina.net/mzui/blog/

今天的文章 Yesod - 类型类 (5)分享到此就结束了,感谢您的阅读。
编程小号
上一篇 2024-12-30 21:30
下一篇 2024-12-30 21:27

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/91923.html