在 Bluemix 上编写 Clojure Web 应用程序

栏目: 编程语言 · Clojure · 发布时间: 5年前

内容简介:准备好尝试函数式编程了吗?或许理想情况下,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 ,然后可以在浏览器中执行所有开发和部署工作。

在 GitHub 上获取代码

初识 Clojure

Clojure 是一种 Lisp 方言,可在 JVM 或 JavaScript 中执行。要从 Node.js 应用程序运行它,可使用 clojurescript-nodejs 包。

  1. 编辑 packages.json 文件,将 clojurescript-nodejs 添加到包列表中:
    "dependencies": {
        "express": "4.13.x",
    	"cfenv": "1.0.x",
    	"clojurescript-nodejs": "*"
    },
  2. 创建一个 Clojure 环境:
    // Get a Clojure environment
    var cljs = require('clojurescript-nodejs');
  3. 定义一个 Clojure 函数(参阅下一节了解):
    cljs.eval('(ns app (:require clojure.string)) ' +
    	'(defn h2 [str] (clojure.string/join ["<h2>" str "</h2>"]))');
  4. 使用 Clojure 代码响应 URL 请求(参阅下一节了解:
    app.get("/trivial", function(req, res) {
    res.send(cljs.eval('(ns app) (h2 "Hello, Clojure")'));
    });
  5. 运行应用程序并查看结果。

Clojure 函数的工作原理

如果您已了解 Clojure 甚至一种不同的 Lisp 方言,那么您应该对本文感到熟悉。

在上面的第 3 步中,当定义一个 Clojure 函数时, cljs.eval 函数收到一个字符串并将它计算为 Clojure 代码。在本例中,该字符串使用撇号 ( ' ) 分隔,以便也可以使用引号 ( " )。

cljs.eval('

下面的 Clojure 语句将执行两个操作:

  1. 将我们的名称空间定义为 app 。这很重要,因为只有在名称空间中才能定义变量、函数等。
  2. 导入 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 ) 并导入我们需要的两个库: joincljs.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 请求的主体中。

查看所有 3 种形式的用户输入

路径参数

在 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 个局部变量: queryMapbodyMapparamMap

(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 编程乐趣》


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

机器学习算法原理与编程实践

机器学习算法原理与编程实践

郑捷 / 电子工业出版社 / 2015-11 / 88.00

本书是机器学习原理和算法编码实现的基础性读物,内容分为两大主线:单个算法的原理讲解和机器学习理论的发展变迁。算法除包含传统的分类、聚类、预测等常用算法之外,还新增了深度学习、贝叶斯网、隐马尔科夫模型等内容。对于每个算法,均包括提出问题、解决策略、数学推导、编码实现、结果评估几部分。数学推导力图做到由浅入深,深入浅出。结构上数学原理与程序代码一一对照,有助于降低学习门槛,加深公式的理解,起到推广和扩......一起来看看 《机器学习算法原理与编程实践》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具