Basic
任何新技术的第一步都是让它运行起来。 本章的目标是让您开始使用简单的Yesod应用程序,并介绍一些基本概念和术语。
Hello World
让我们写一个Yesod的Hello World作为开始吧:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
data HelloWorld = HelloWorld
mkYesod "HelloWorld" [parseRoutes|
/ HomeR GET
|]
instance Yesod HelloWorld
getHomeR :: Handler Html
getHomeR = defaultLayout [whamlet|Hello World!|]
main :: IO ()
main = warp 3000 HelloWorld
复制代码
如果您将该代码保存在helloworld.hs
中并使用runhaskell helloworld.hs
运行它,您将获得一个在端口3000上运行的Web服务器。注意,如果您按照快速入门指南并使用堆栈安装yesod,那么您将没有runhaskell
并且需要运行stack runghc helloworld.hs
。 如果您将浏览器指向http:// localhost:3000,您将获得以下HTML:
<!DOCTYPE html>
<html> <head> <title> </ title> </ head> <body> Hello World!</ body> </ html>
复制代码
我们将通过本章的其余部分回顾这个例子。
路由
像大多数现代Web框架一样,Yesod遵循前端控制器模式。 这意味着对Yesod应用程序的每个请求都在同一点进入,并从那里进行路由。 相比之下,在PHP和ASP等系统中,您通常会创建许多不同的文件,Web服务器会自动将请求定向到相关文件。 此外,Yesod使用声明式样式来指定路径。 在上面的示例中,这看起来像:
mkYesod "HelloWorld" [parseRoutes|
/ HomeR GET
|]
复制代码
mkYesod是一个模板Haskell函数,parseRoutes是一个QuasiQuoter。
在英语中,所有这些意味着:在HelloWorld应用程序中,创建一个路由。 我想称之为HomeR,它应该监听/(应用程序的根目录)的请求,并且应该回答GET请求。 我们将HomeR称为资源,这是“R”后缀的来源。 资源名称上的R后缀只是一个约定,但它是一个相当普遍遵循的约定。 它使得阅读和理解代码变得更容易一些。 mkYesod TH函数在这里生成了相当多的代码:路由数据类型,解析器/渲染函数,调度函数和一些帮助器类型。 我们将在路由章节中更详细地介绍这一点。 但是通过使用-ddump-splices GHC选项,我们可以立即查看生成的代码。 一个很清理的版本是:
instance RenderRoute HelloWorld where
data Route HelloWorld = HomeR
deriving (Show, Eq, Read)
renderRoute HomeR = ([], [])
instance ParseRoute HelloWorld where
parseRoute ([], _) = Just HomeR
parseRoute _ = Nothing
instance YesodDispatch HelloWorld where
yesodDispatch env req =
yesodRunner handler env mroute req
where
mroute = parseRoute (pathInfo req, textQueryString req)
handler =
case mroute of
Nothing -> notFound
Just HomeR ->
case requestMethod req of
"GET" -> getHomeR
_ -> badMethod
type Handler = HandlerT HelloWorld IO
复制代码
除了使用-ddump-splices之外,为应用程序生成Haddock文档通常也很有用,可以查看为您生成了哪些函数和数据类型。 我们可以看到RenderRoute类定义了一个关联的数据类型,为我们的应用程序提供了路由。在这个简单的例子中,我们只有一条路线:HomeR。在现实生活中,我们会有更多,它们将比我们的HomeR更复杂。 renderRoute采用路由并将其转换为路径段和查询字符串参数。同样,我们的示例很简单,因此代码同样简单:两个值都是空列表。 ParseRoute提供了反函数parseRoute。在这里,我们看到了依赖Template Haskell的第一个强烈动机:它确保路由的解析和呈现彼此正确对应。当手写时,这种代码很容易变得难以保持同步。依靠代码生成,我们让编译器(和Yesod)为我们处理这些细节。 YesodDispatch提供了一种获取输入请求并将其传递给适当的处理函数的方法。这个过程基本上是:
- 解析请求。
- 选择处理函数。
- 运行处理函数。
代码生成遵循一种简单的格式,用于将路由匹配到处理函数名称,我们将在下一节中对其进行描述。 最后,我们有一个定义Handler的简单类型同义词,使我们的代码更容易编写。 这里发生的事情比我们描述的要多得多。 生成的调度代码实际上使用视图模式语言扩展来提高效率,创建更多类型类实例,还有其他情况需要处理,例如子网站。 在我们阅读本书时,我们将深入了解细节,特别是在“理解请求”一章中。
Handler函数
所以我们有一个名为HomeR的路由,它响应GET请求。你如何定义你的回答?你编写了一个处理函数。 Yesod遵循这些函数的标准命名方案:它是小写的方法名称(例如,GET变为get),后跟路由名称。在这种情况下,函数名称将是getHomeR。 您在Yesod中编写的大多数代码都存在于处理函数中。这是您处理用户输入,执行数据库查询和创建响应的地方。在我们的简单示例中,我们使用defaultLayout函数创建响应。此函数包含在站点模板中给出的内容。默认情况下,它会生成一个带有doctype和html,head和body标签的HTML文件。正如我们在Yesod类型类章节中看到的那样,可以重写此函数以执行更多操作。 在我们的示例中,我们将[whamlet | Hello World!|]传递给defaultLayout。 whamlet是另一个准引用者。在这种情况下,它将Hamlet语法转换为Widget。 Hamlet是Yesod中的默认HTML模板引擎。与其兄弟Cassius,Lucius和Julius一起,您可以以完全类型安全和编译时检查的方式创建HTML,CSS和Javascript。我们将在莎士比亚的这一章中看到更多关于此的内容。 小部件是Yesod的另一个基石。它们允许您创建由HTML,CSS和Javascript组成的站点的模块化组件,并在整个站点中重用它们。我们将在小部件章节中详细介绍它们。
基础
在我们的示例中,“HelloWorld”这个词出现了很多次。每个Yesod应用程序都有一个基础数据类型。此数据类型必须是Yesod类型类的实例,它为声明控制应用程序执行的许多不同设置提供了一个中心位置。 在我们的例子中,这种数据类型非常无聊:它不包含任何信息。尽管如此,基础是我们的示例如何运行的核心:它将路由与实例声明联系在一起,并让它们全部运行。我们将在本书中看到基础会出现在很多地方。 但基础并不一定无聊:它们可以用来存储大量有用的信息,通常是需要在程序启动时初始化并在整个过程中使用的东西。一些非常常见的例子是: 数据库连接池。 从配置文件加载的设置。 HTTP连接管理器。 一个随机数发生器。 顺便说一下,Yesod(יסוד)这个词意味着希伯来语的基础。
运行
我们再次在主函数中提到HelloWorld。我们的基础包含在我们的应用程序中路由和响应请求所需的所有信息;现在我们只需将其转换为可以运行的东西。在Yesod中有用的功能是warp,它运行Warp网络服务器,并在指定端口上启用了许多默认设置(此处为3000)。 Yesod的一个功能是,您不会受限于单一部署策略。 Yesod构建于Web应用程序接口(WAI)之上,允许它在FastCGI,SCGI,Warp上运行,甚至可以使用Webkit库作为桌面应用程序运行。我们将在部署章节中讨论其中一些选项。在本章的最后,我们将解释开发服务器。 Warp是Yesod的首选部署选项。它是一款轻量级,高效的Web服务器,专为托管Yesod而开发。它也可以在Yesod之外用于其他Haskell开发(框架和非框架应用程序),以及许多生产环境中的标准文件服务器。
资源和类型安全的URL
在我们的hello世界中,我们只定义了一个资源(HomeR)。 Web应用程序通常更令人兴奋,因为它上面有多个页面。 让我们来看看:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
data Links = Links
mkYesod "Links" [parseRoutes|
/ HomeR GET
/page1 Page1R GET
/page2 Page2R GET
|]
instance Yesod Links
getHomeR = defaultLayout [whamlet|<a href=@{Page1R}>Go to page 1!|]
getPage1R = defaultLayout [whamlet|<a href=@{Page2R}>Go to page 2!|]
getPage2R = defaultLayout [whamlet|<a href=@{HomeR}>Go home!|]
main = warp 3000 Links
复制代码
总的来说,这与Hello World非常相似。 我们的基础现在是Links而不是HelloWorld,除HomeR资源外,我们还添加了Page1R和Page2R。 因此,我们还添加了两个处理函数:getPage1R和getPage2R。 唯一真正的新功能是在whamlet准引用中。 我们将在“莎士比亚”一章中深入研究语法,但我们可以看到: <a href=@{Page1R}>
转到第1页! 创建指向Page1R资源的链接。 这里要注意的重要一点是Page1R是一个数据构造函数。 通过使每个资源成为数据构造函数,我们有一个称为类型安全URL的功能。 我们只是创建一个普通的旧Haskell值,而不是将字符串拼接在一起来创建URL。 通过使用符号插值(@ {...}),Yesod会在将内容发送给用户之前自动将这些值呈现给文本URL。 我们可以通过再次查看-ddump-splices输出来了解如何实现它:
instance RenderRoute Links where
data Route Links = HomeR | Page1R | Page2R
deriving (Show, Eq, Read)
renderRoute HomeR = ([], [])
renderRoute Page1R = (["page1"], [])
renderRoute Page2R = (["page2"], [])
复制代码
在Links的Route关联类型中,我们为Page1R和Page2R提供了额外的构造函数。我们现在还可以更好地了解renderRoute的返回值。元组的第一部分给出了给定路径的路径。第二部分给出了查询字符串参数;对于几乎所有用例,这将是一个空列表。 很难高估类型安全URL的价值。在开发应用程序时,它们为您提供了极大的灵活性和稳健性。您可以随意移动网址,而不会破坏链接。在路由章节中,我们将看到路由可以采用参数,例如博客条目URL获取博客帖子ID。 假设您想要从数字邮政ID的路由切换到年/月/ slug设置。在传统的Web框架中,您需要遍历对博客发布路径的每个引用并进行适当更新。如果你错过了一个,你将在运行时拥有404。在Yesod中,您所做的就是更新您的路线并进行编译:GHC将精确定位需要纠正的每一行代码。
非HTML响应
Yesod可以提供您想要的任何类型的内容,并且对许多常用的响应格式提供一流的支持。 到目前为止,您已经看过HTML,但通过aeson包,JSON数据同样简单:
{-# LANGUAGE ExtendedDefaultRules #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
data App = App
mkYesod "App" [parseRoutes|
/ HomeR GET
|]
instance Yesod App
getHomeR = return $ object ["msg" .= "Hello World"]
main = warp 3000 App
复制代码
我们将在后面的章节中更详细地介绍JSON响应,包括如何根据Accept请求标头自动在HTML和JSON表示之间切换。
网站脚手架
安装Yesod将为您提供Yesod库以及yesod可执行文件。这个可执行文件接受一些命令,但是你想要熟悉的第一个命令是yesod init。它会问你一些问题,然后生成一个包含默认scaffolded站点的文件夹。在该文件夹中,您可以运行cabal install --only-dependencies来构建任何额外的依赖项(例如数据库后端),然后使用yesod devel来运行您的站点。 为了设置您的包装环境,请务必遵循快速入门指南。 脚手架网站为您提供了许多开箱即用的最佳实践,通过大多数生产Yesod网站使用的经过时间考验的方法设置文件和依赖项。然而,所有这些便利性都会妨碍实际学习Yesod。因此,本书的大部分内容都将避免使用脚手架工具,而是将Yesod作为库直接处理。但是,如果你要建立一个真实的网站,我强烈建议使用脚手架。 我们将在脚手架章节中介绍脚手架的结构。
开发服务器
解释语言优于编译语言的优势之一是快速原型设计:将更改保存到文件并点击刷新。 如果我们想对上面的Yesod应用程序进行任何更改,我们需要从头开始调用runhaskell,这可能有点单调乏味。 幸运的是,有一个解决方案:yesod devel自动重建并为您重新加载代码。 这可能是开发Yesod项目的好方法,当您准备好转向生产时,您仍然可以编译成非常高效的代码。 Yesod脚手架自动为您设置。 这为您提供了两全其美的优势:快速原型设计和快速生产代码。 设置您的代码以供yesod devel使用更加复杂,因此我们的示例将仅使用warp。 幸运的是,脚手架网站已完全配置为使用开发服务器,因此当您准备好移动到现实世界时,它将等待您。
总结
每个Yesod应用程序都是基于基础数据类型构建的。 我们将一些资源与该数据类型相关联并定义一些处理函数,Yesod处理所有路由。 这些资源也是数据构造函数,它允许我们使用类型安全的URL。 通过构建在WAI之上,Yesod应用程序可以运行许多不同的后端。 对于简单的应用程序,warp函数提供了使用Warp Web服务器的便捷方式。 为了快速开发,使用yesod devel是一个不错的选择。 当您准备好投入生产时,您可以充分发挥功能和灵活性来配置Warp(或任何其他WAI处理程序)以满足您的需求。 在Yesod开发时,我们为编码风格提供了许多选择:准引用或外部文件,warp或yesod devel等等。 本书中的示例将倾向于使用最容易复制和粘贴的选项,但是当您开始构建真正的Yesod应用程序时,可以使用功能更强大的选项。