内容简介:准备好尝试函数式编程了吗?或许理想情况下,Web 应用程序需要高性能和高可用性,可通过在 Bluemix 服务器上运行来实现此目的。在本教程中,我将介绍如何使用 Node.js 运行作为 Bluemix Web 应用程序一部分的
准备好尝试函数式编程了吗?或许 Paul Graham 的经典 Lisp 文章 会说服您试一试。您可能想了解一种新的做事方式。或者想看看您能多大程度地抽象化您的代码和数据。
理想情况下,Web 应用程序需要高性能和高可用性,可通过在 Bluemix 服务器上运行来实现此目的。在本教程中,我将介绍如何使用 Node.js 运行作为 Bluemix Web 应用程序一部分的 Clojure 程序。
构建和部署您的 Clojure 应用程序需要做的准备工作
- 一个 Bluemix 帐户(注册 免费试用帐户 ,如果您已已有一个帐户, 请登录到 Bluemix )。
- HTML 和 JavaScript 的应用知识。
- MEAN 应用程序堆栈(至少包括 Node.js 和 Express)的应用知识。如果不熟悉 MEAN 堆栈,可以在我的 developerWorks 系列文章中了解它的基础知识 。
-
一个支持将 Node.js 应用程序上传到 Bluemix 的开发环境,比如 Eclipse。
有了一个在 Bluemix 中运行的应用程序后,可以快速添加 Git 存储库和持续交付管道,以便自动开发、测试和部署它。要执行此设置,可在应用程序的 Overview 页面上选择 Add Git Repo And Pipeline ,然后可以在浏览器中执行所有开发和部署工作。
初识 Clojure
Clojure 是一种 Lisp 方言,可在 JVM 或 JavaScript 中执行。要从 Node.js 应用程序运行它,可使用 clojurescript-nodejs 包。
-
编辑 packages.json 文件,将
clojurescript-nodejs
添加到包列表中:"dependencies": { "express": "4.13.x", "cfenv": "1.0.x", "clojurescript-nodejs": "*" },
-
创建一个 Clojure 环境:
// Get a Clojure environment var cljs = require('clojurescript-nodejs');
-
定义一个 Clojure 函数(参阅下一节了解):
cljs.eval('(ns app (:require clojure.string)) ' + '(defn h2 [str] (clojure.string/join ["<h2>" str "</h2>"]))');
-
使用 Clojure 代码响应 URL 请求(参阅下一节了解:
app.get("/trivial", function(req, res) { res.send(cljs.eval('(ns app) (h2 "Hello, Clojure")')); });
- 运行应用程序并查看结果。
Clojure 函数的工作原理
如果您已了解 Clojure 甚至一种不同的 Lisp 方言,那么您应该对本文感到熟悉。
在上面的第 3 步中,当定义一个 Clojure 函数时, cljs.eval
函数收到一个字符串并将它计算为 Clojure 代码。在本例中,该字符串使用撇号 ( '
) 分隔,以便也可以使用引号 ( "
)。
cljs.eval('
下面的 Clojure 语句将执行两个操作:
-
将我们的名称空间定义为
app
。这很重要,因为只有在名称空间中才能定义变量、函数等。 -
导入
clojure.string
库。此库包含字符串操作函数,它们在生成 HTML 时很有用。
(ns app (:require clojure.string))
下面的 Clojure 语句是实际的函数定义。因为这是我们定义的第一个函数,所以让我们详细分析一下。第一部分定义一个名为 h2
的函数。
(defn h2
下面的部分指定该函数将拥有一个名为 str
的参数。请注意,此部分使用了方括号 ( [ ]
) 而不是典型的括号。Clojure 与传统 Lisp 的区别在于,它使用不同括号类型来表示不同实体。方括号定义矢量,矢量充当着一个列表(放在普通的圆括号中的项的集合),但支持更快地访问中间元素和更快地在末尾插入项。
[str]
接下来,从库中调用函数的语法为 <library name>/<function>
。这部分从 clojure.string
调用 join
。该函数接收一个矢量并返回一个包含所有项的字符串。在本例中,它将该字符串放在 h2 标签中:
(clojure.string/join ["<h2>" str "</h2>"])
完整的函数定义是一个列表。它以最后的圆括号结束。
)
在上面的第 4 步中, app.get
调用中的 Clojure 代码更加简单。它首先将自己识为别 app
名称空间的一部分,然后调用之前定义的 h2
函数。 cljs.eval
函数返回最后一个表达式(在本例中为 h2
调用)的结果。然后通过 res.send
将该值发送给用户。
res.send(cljs.eval('(ns app) (h2 "Hello, Clojure")'));
在 Clojure 中编写完整的应用程序
对于更复杂的任务,更简单的方法是在 Clojure 中编写整个应用程序,仅在 JavaScript 中编写一小段桩代码。
JavaScript 桩代码
下面是不完整的 app.js。第一行是一个编辑器指令,它的用途是避免有错误出现:
/*eslint-env node*/
接下来,创建一个 Clojure 环境:
// Get a Clojure environment var cljs = require('clojurescript-nodejs');
不计算字符串,而是计算一个文件:
// Evaluate the application library cljs.evalfile("app.cljs");
Clojure 应用程序
app.cljs
的第一部分等效于模板自带的 app.js。下面将逐行解释它。
这些行定义了名称空间 ( my-ns
) 并导入我们需要的两个库: join
和 cljs.nodejs
。刚才已导入第一个库,但第二个库是通过 :as node
放入一个矢量中的,以便指定该名称空间的剩余部分中的代码将该包作为 node
。
(ns my-ns (:require clojure.string [cljs.nodejs :as node] ) )
Clojure 注释以分号 ( ;
) 开头。
; Get the application environment
此行显示了您在 Clojure 中用来与底层 JavaScript 代码进行通信的两种机制。 js/
语法用于访问 JavaScript 全局变量,比如 require
。代码段 (js/require "cfenv")
等效于 require("cfenv")
。要在 Clojure 中调用某个对象的一个方法,语法为 (.<
method
> <
object
> <
parameters, as needed
>)
。代码段 (.getAppEnv (js/require "cfenv"))
等效于 require("cfenv").getAppEnv()
。
最后, def
定义了一个全局变量。它计算第二个参数的值,并将该值赋给第一个参数。整行等效于 var appEnv = require("cfenv").getAppEnv()
:
(def appEnv (.getAppEnv (js/require "cfenv")))
此行类似于 appEnv
行,但没有方法调用:
; Create an express application (def express (js/require "express"))
此行展示了访问 JavaScript 的另一种方法: (js* <expression>)
运行该 JavaScript 表达式,并返回值。此行等效于 var app = require("express")()
:
(def app (js* "require('express')()"))
此行展示了使用 JavaScript 的另一种机制。在您拥有一个对象时,语法 (aget <object> <attribute>)
可获取该对象的一个属性。与 (.<method> <object> <parameters, as needed>)
语法相结合,整行表示 app.use(express.static("public"))
,此代码指定在公共目录中提供这些静态文件。
; Static files (.use app ((aget express "static") "public"))
下面的代码示例指定了对路径 /trivial 的 GET 请求的响应。创建一个匿名函数的语法为 (
fn [<
parameters
>] (<
expression
>)
)
。将此与调用 JavaScript 方法的方式相结合,等效于 app.get(
"/
trivial
",
f
unction(req, res) {res.send(
"
h
ello")})
:
; Respond to a request (.get app "/trivial" (fn [req res] (.send res "Hello")))
这等效于在 JavaScript 中启动应用程序监听在 appEnv
中指定的端口的代码:
; Start listening (.listen app (aget appEnv "port") "0.0.0.0" (fn [] (.log js/console (aget appEnv "url")) ) )
响应用户
无论用户输入是什么都返回一个固定响应的 Web 应用程序不是很有用。实际的 Web 应用程序需要处理用户输入。此输入通常有 3 种形式:位于路径、查询或 HTTP 请求的主体中。
路径参数
在 Express(Node.js HTTP 服务器库)中,通过指定 :<
keyword
>
作为路径组件来指示路径参数。然后在 req.params
中显示结果。以下是相关代码。
首先,创建一个包含返回到表单页面的链接的字符串。这样做是为了让版面好看,同时为了让用户容易返回。
; Go back to the form (def goBackForm "<hr /><a href=\"/form.html\">Go back</a>");
下面这个实际调用接收对 /process-path/<
whatever
>
的所有 GET 请求:
; Process a part of the path (.get app "/process-path/:param"
这是在请求该 URL 时调用的函数。它串联两个字符串来调用 res.send
:参数值,以及返回到表单页面的链接的 HTML(之前已定义为 goBackForm
)。
(fn [req res] (.send res (clojure.string/join [
请记住, (*js <
expression
>)
将该表达式计算为 JavaScript,并将值返回到 Clojure。这样就能轻松地读取 JavaScript 变量,比如 req.params.param
。
(js* "req.params.param") goBackForm ]) ) ) )
查询参数
查询参数更容易处理。不需要使用冒号指定参数的名称;浏览器已通过 HTML 创建它们。
; Respond to GET requests with a query (.get app "/process-form" (fn [req res] (.send res (clojure.string/join [ (js* "req.query.get") goBackForm ]) ) ) )
主体参数
主体中的参数(由 POST 和 PUT 方法使用)需要执行更多处理。首先,需要将 process-body
添加到 packages.json 文件。然后,在编码主体条目时,需要将它们作为中间件来处理。这一步后,可采用访问其他方法的相同方式访问实际参数。
; Parse the body (.post app "*"
JavaScript 表达式返回一个函数。函数在 JavaScript 和 Clojure 中均可有效地用作返回值和函数参数。
(js* "require('body-parser').urlencoded({extended: true})") ) ; Respond to POST requests (.post app "/process-form" (fn [req res] (.send res (clojure.string/join [ (js* "req.body.post") goBackForm ]) ) ) )
结合使用所有参数
通常不需要关心参数来自何处;只需要关心它们的值。 因为参数有一个键和一个值, 所以要使用的相应数据结构是一个映射 。
以下是一个结合使用所有参数的示例。第一部分是一个函数定义。因为这是一个可从其他位置调用的命名函数,所以它使用 defn
而不是 fn
定义。
; Given a request object, return a map with all the parameters (defn getParams [req]
let
调用 定义了一些可在一个或多个表达式中使用的局部变量
(大体上讲,在函数式编程中,它们实际上称为符号)。在本例中,定义了 3 个局部变量: queryMap
、 bodyMap
和 paramMap
。
(let [ ; Define local variables
所有 3 个定义都使用 js->clj
函数将 JavaScript 对象转换为 Clojure 映射。
queryMap (js->clj (js* "req.query")) bodyMap (js->clj (js* "req.body")) paramMap (js->clj (js* "req.params")) ]
merge
函数接受 3 个不同的映射并将它们合并到一个映射中(如果键相同,后面映射中的值将会覆盖前面映射中的值)。此表达式是 let
的结果,所以是整个函数的结果。
(merge queryMap bodyMap paramMap) ) ; end of let ) ; end of defn
下面是实际响应请求的调用:
; Respond requests with parameters from all over (.all app "/merge/:pathparam" (fn [req res]
这是 let
的另一种用法。由于拥有命令式编程背景,我发现在大部分处理工作都通过定义随后使用的符号来完成时,函数更容易编写和理解。
(let [ ; local variables
调用我以前定义的函数来获取参数。
params (getParams req) ] (.send res (clojure.string/join [
现在,我们想使用 JSON.stringify
将映射转换为 JSON。为此,使用 clj->js
将映射转换回 JavaScript 对象:
(.stringify js/JSON (clj->js params)) goBackForm ]) ; end of clojure.string/join ) ; end of res.send ) ; end of let ) ; end of fn [req res] ) ; end of app.all
其他 API 的备忘单
用于 Node.js 和 Express 包的相同 工具 也可用于任何其他 API 或包。
-
要使用某个包,请使用
(def <var
name
> (js/require "<
package name
>"))
。 -
要调用某个方法,请使用
(.<
method
> <
object
> <
parameters, if any
>)
。 -
要使用来自 JavaScript 的全局变量,请使用
js/<
variable
>
。 -
如果所有其他操作都失败,可使用此语法计算 JavaScript 表达式:
(js*
"<
JavaScript expression
>")
。
结束语
本教程介绍了在 Clojure 中编写 Web 应用程序的基础知识。下一步是学习如何实际使用函数式编程特性,让您的应用程序变得更完美。介绍该主题的图书有很多;我最喜欢的是 Michael Fogus 和 Chris Houser 合著的 《Clojure 编程乐趣》 。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 在Docker上编译OpenJDK 8
- 云上自动化 vs 云上编排
- 在 CentOS 7.8 上编译 PHP 7.4.x
- FreeBSD上编写x86 Shellcode初学者指南
- OpenmediaVault-4.1.3上编译Linux Kernel 4.14.118
- CentOS上编译OpenJDK8源码及在Eclipse上调试HotSpot虚拟机源码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。