8.6 Web框架Luminus
Luminus是一个微框架,用于快速开发功能强大的Clojure Web应用程序。它是可全面配置的,提供了强大的数据库支持,无论是传统SQL还是NoSQL数据库。这个框架易于上手,使用内置的Leiningen模板时尤其如此。强烈建议你在鼓捣Luminus的同时参阅其文档(http://www.luminusweb.net)。
在接下来的几节中,我们将使用模板myapp新建一个项目,再对其进行运行和探索。
8.6.1 创建Luminus项目
正如你见到的,Counterclockwise能够根据Leiningen模板生成项目,但存在一个问题:创建项目时,Counterclockwise使用其内置的Leiningen版本,但这个版本可能不是最新的。编写本书期间,根据luminus myapp模板生成项目时Counterclockwise会引发异常。为解决这个问题,可使用最新的Leiningen版本创建一个项目,再手动将其导入到Counterclockwise中。本章就将这样做。
在命令提示符(终端)中,切换到Eclipse workspace目录,并执行如下命令:
lein new luminus myapp
再切换到目录myapp,并执行如下命令:
lein run
首次运行这个项目时,Leiningen将下载必要的依赖。过段时间后,你将看到一条消息,指出即将启动HTTP服务器。Luminus内置的HTTP服务器使用的默认端口为3000。请使用你喜欢的浏览器访问这个Web应用程序,其地址如下:http://localhost:3000。
你将看到类似于下面的屏幕:

请仔细阅读这个初始页面中的文字——这个页面详尽地介绍了框架Luminus。另外,请单击链接about。
8.6.2 将项目导入Counterclockwise
要导入这个Leiningen项目,并生成一个与Counterclockwise兼容的Eclipse IDE项目,请执行如下操作。
- 在Eclipse IDE中,右击Package Explorer的空白区域并选择Import…。在出现的Import对话框中,选择Projects from Folder or Archive,再单击Next按钮。
- 在出现的对话框中,单击文本框Import source旁边的Directory…按钮。切换到workspace目录(通常位于你的用户主目录中),并选择子目录myapp。确保选中了复选框Search for nested projects和Detect and configure project natures,再单击Finish按钮。
Eclipse IDE将发现这个项目与Counterclockwise兼容,进而生成相应的Eclipse IDE项目。
要在Eclipse IDE中运行这个项目,请单击Package Explorer中的项目名,再单击工具栏中的Run按钮,以启动REPL(可能出现一个对话框,让你选择项目类型,请选择Clojure Application)。启动REPL后,只需在其中执行如下命令即可:
(start)
切换到Console选项卡,你将看到一条消息,指出服务器已启动。默认不会启动HTTP服务器。要启动它,请切换到REPL选项卡,并执行如下命令:
(mount/start #'myapp.core/http-server)
现在你可再次访问端口3000处的页面了。要停止这个服务器,请单击Console选项卡中的终止(Terminate)按钮。
8.6.3 探索Luminus项目
下表列出了为这个项目生成的最重要的文件和目录。
| 文件/目录 | 描述 |
|---|---|
| project.clj | 与往常一样,project.clj为Leiningen构建文件 |
| profiles.clj | 用于存储运行系统所需的数据,如数据库连接凭证。这个文件被配置成不包含在Git版本控制管理器中 |
| src/myapp/config.clj |
这是配置文件。它尝试从命令行参数、Java系统属性或子目录env的配置文件中加载设置。可针对如下情形定义不同的设置:dev(开发期间使用的设置)、prod(用于生产环境的设置)和test(运行单元测试时使用的设置)
|
| src/myapp/core.clj |
包含main方法,即JVM入口函数。它包含启动服务器时调用的函数
|
| src/myapp/layout.clj | 包含视图渲染逻辑。默认实现确保模板引擎能够加载子目录/resource中的模板。另外,它还定义了引发异常时加载的错误页面 |
| src/myapp/middleware.clj |
中间件是包装函数,它们在调用请求处理程序前对请求进行处理。使用模板myapp时,生成的一个中间件函数是保护应用程序免受著名Web攻击的中间件函数
|
| src/myapp/handler.clj | 这个文件指定有哪些路由可用,以及为每条路由加载哪个中间件。由于URL被关联到路由(参见下一个文件),因此可在多个URL之间共享中间件 |
| src/myapp/routes/home.clj | 在这个文件中,定义了URL。对于每个URL,都指定了一个在用户请求它时将调用的处理程序函数。处理程序函数可使用模板引擎来渲染页面。将一组URL关联到一条路由 |
| resources/ | 这个目录包含静态素材。只有目录resources/public中的文件和子目录可供HTTP服务器使用,其他文件仅供应用程序内部使用 |
| resources/templates/ | 这个目录包含src/myapp/routes/home.clj使用的HTML模板。对于HTML文件,Luminus默认将模板系统Selmer作为模板引擎 |
| resources/public/ | 前面说过,这个目录中的一切都可供HTTP服务器使用。所有前端文件都必须存储在这个目录中,这包括图像、JavaScript文件、CSS样式表等 |
8.6.4 在Web应用程序中添加页面
作为练习,下面来给这个应用程序添加一个页面。对这个页面的要求如下。
- 其URL必须为/monadtest。
- 它应使用既有路由
home-routes。这条路由调用对应用程序进行保护,使其免受特定Web攻击的中间件。 - 它将使用一个HTML页面,这个页面包含一个表单,并使用函数
pretty-msg来渲染输入的文本。
这个页面将使用本章前面编写的函数pretty-msg。
在构建文件project.clj中,添加依赖org.clojure/algo.monads:复制文件exploring-monads/project.clj中相应的依赖行,并将其粘贴到文件myapp/project.clj的:dependencies部分。将这个文件存盘后,Counterclockwise将下载这个依赖并将其添加到项目中。
接下来,必须复制项目exploring-monad的文件core.clj,并对其重命名。
- 复制项目exploring-monads中的文件src/exploring-monads/core.clj:在Package Explorer中选择这个文件,再右击它并选择Copy。
- 右击项目myapp的目录src/clj/myapp.routes并选择Paste。
- 右击粘贴的文件src/clj/myapp.routes/core.clj,并选择Refactor>Rename,再输入文件名pretty_msg.clj(注意这里是下划线而不是连字符)并单击OK。
- 打开文件pretty_msg.clj,并将其中的命名空间定义从
(ns monad-test.core)改为(ns myapp.routes.pretty-msg),再将这个文件存盘。
打开文件myapp.routes.home.clj,并在其:require块中添加如下条目:
[myapp.routes.pretty-msg :as prettymsg]
另外,导入请求方法POST。为此,在:require块中找到下面这一行:
[compojure.core :refer [defroutes GET]]
再在其中添加方法POST,使其类似于下面这样:
[compojure.core :refer [defroutes GET POST]]
现在可以编写用户请求/monadtest时将调用的处理程序函数了。它将渲染一个HTML页面,并在该页面中渲染两个变量:prettymsg和msg,其中前者包含经过格式设置后的消息,而后者包含原始消息。为此,在文件myapp.routes.home.clj中,在以(defroutes home-routes打头的代码行前面添加如下代码:
(defn monad-test-page [msg](layout/render"monadtest.html" {:prettymsg (prettymsg/pretty-msg msg 10):msg msg }))
再在defroutes home-routes块中添加两个条目,为新页面定义URL:
(defroutes home-routes(GET "/" [] (home-page))(GET "/about" [] (about-page))(GET "/monadtest" [] (monad-test-page nil))(POST "/monadtest" [msg] (monad-test-page msg)))
我们将这些条目添加到了home-routes块中,这意味着对URL /monadtest的GET和PUT请求都将使用路由home-routes,而这条路由调用有助于保护应用程序的中间件。请保存对这个文件所做的修改。
最后,添加HTML页面。为此,右击Package Explorer中的目录resources/templates并选择New>Other。在打开的向导中,选择General>File并单击Next按钮,再将文件名设置为monadtest.html。我们首先来定义让用户能够输入句子的表单,为此添加如下代码并将文件存盘:
{% extends "base.html" %}{% block content %}<div class="row"><div class="col-sm-12"><form name="input" action="/monadtest" method="POST">{% csrf-field %}Message: <input type="text" name="msg" value="{{ msg }}"><input type="submit" class="btn" value="Submit"></form></div></div>{% endblock %}
这是一个非常标准的HTML模板。要在生成的输出中替换传递给模板的变量,可使用语法{{ variable name }}。在上述代码中,最值得注意的可能是{% crsf-field %}。路由home-routes调用的中间件可防范跨站请求伪造(Cross-Site Request Forgery,CSRF)攻击,这是对网站和应用程序发起的一种常见攻击。使用Luminus提交表单时,必须在HTML中指定一个不可见的字段,其中包含该框架生成的一个令牌(token)。{% csrf-field %}宏就是负责完成这项任务的。
下面来添加渲染HTML输出的代码,为此在最后一个元素后面添加如下代码:
<div class="row"><div class="col-sm-12"><p><h1>{{ prettymsg }}</h1></p></div></div>
确保当前没有运行任何myapp实例;如果必要,在Console选项卡中终止当前会话,并单击REPL选项卡的关闭图标将其关闭。然后,运行项目myapp:在Package Explorer中单击这个项目,并单击工具栏中的Run按钮,再选择Clojure Application,然后在REPL启动后输入(start)并按回车。重新切换到REPL选项卡,再执行命令(mount/start #'myapp.core/http-server)。
在你喜欢的浏览器中,访问下面的新URL:
http://localhost:3000/monadtest
如果一切顺利,你将看到一个页面,可在其中输入并提交消息:

