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目录,并执行如下命令:

  1. lein new luminus myapp

再切换到目录myapp,并执行如下命令:

  1. lein run

首次运行这个项目时,Leiningen将下载必要的依赖。过段时间后,你将看到一条消息,指出即将启动HTTP服务器。Luminus内置的HTTP服务器使用的默认端口为3000。请使用你喜欢的浏览器访问这个Web应用程序,其地址如下:http://localhost:3000

你将看到类似于下面的屏幕:

8.6 Web框架Luminus - 图1

请仔细阅读这个初始页面中的文字——这个页面详尽地介绍了框架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后,只需在其中执行如下命令即可:

  1. (start)

切换到Console选项卡,你将看到一条消息,指出服务器已启动。默认不会启动HTTP服务器。要启动它,请切换到REPL选项卡,并执行如下命令:

  1. (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块中添加如下条目:

  1. [myapp.routes.pretty-msg :as prettymsg]

另外,导入请求方法POST。为此,在:require块中找到下面这一行:

  1. [compojure.core :refer [defroutes GET]]

再在其中添加方法POST,使其类似于下面这样:

  1. [compojure.core :refer [defroutes GET POST]]

现在可以编写用户请求/monadtest时将调用的处理程序函数了。它将渲染一个HTML页面,并在该页面中渲染两个变量:prettymsg和msg,其中前者包含经过格式设置后的消息,而后者包含原始消息。为此,在文件myapp.routes.home.clj中,在以(defroutes home-routes打头的代码行前面添加如下代码:

  1. (defn monad-test-page [msg]
  2. (layout/render
  3. "monadtest.html" {:prettymsg (prettymsg/pretty-msg msg 10)
  4. :msg msg }))

再在defroutes home-routes块中添加两个条目,为新页面定义URL:

  1. (defroutes home-routes
  2. (GET "/" [] (home-page))
  3. (GET "/about" [] (about-page))
  4. (GET "/monadtest" [] (monad-test-page nil))
  5. (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。我们首先来定义让用户能够输入句子的表单,为此添加如下代码并将文件存盘:

  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="row">
  4. <div class="col-sm-12">
  5. <form name="input" action="/monadtest" method="POST">
  6. {% csrf-field %}
  7. Message: <input type="text" name="msg" value="{{ msg }}">
  8. <input type="submit" class="btn" value="Submit">
  9. </form>
  10. </div>
  11. </div>
  12. {% endblock %}

这是一个非常标准的HTML模板。要在生成的输出中替换传递给模板的变量,可使用语法{{ variable name }}。在上述代码中,最值得注意的可能是{% crsf-field %}。路由home-routes调用的中间件可防范跨站请求伪造(Cross-Site Request Forgery,CSRF)攻击,这是对网站和应用程序发起的一种常见攻击。使用Luminus提交表单时,必须在HTML中指定一个不可见的字段,其中包含该框架生成的一个令牌(token)。{% csrf-field %}宏就是负责完成这项任务的。

下面来添加渲染HTML输出的代码,为此在最后一个元素后面添加如下代码:

  1. <div class="row">
  2. <div class="col-sm-12">
  3. <p><h1>{{ prettymsg }}</h1></p>
  4. </div>
  5. </div>

确保当前没有运行任何myapp实例;如果必要,在Console选项卡中终止当前会话,并单击REPL选项卡的关闭图标将其关闭。然后,运行项目myapp:在Package Explorer中单击这个项目,并单击工具栏中的Run按钮,再选择Clojure Application,然后在REPL启动后输入(start)并按回车。重新切换到REPL选项卡,再执行命令(mount/start #'myapp.core/http-server)

在你喜欢的浏览器中,访问下面的新URL:

http://localhost:3000/monadtest

如果一切顺利,你将看到一个页面,可在其中输入并提交消息:

8.6 Web框架Luminus - 图2