clojure_在IBM Cloud上编写Clojure Web应用程序

本文介绍如何在IBMCloud上使用Clojure与Node.js构建Web应用程序,包括在Node.js环境中运行Clojure代码的方法,以及如何处理来自用户的不同类型的输入。

准备将您的脚趾浸入功能性编程池中吗? 保罗·格雷厄姆(Paul Graham)关于Lisp的经典文章也许说服了您尝试一下。 也许您想学习一种新的做事方式。 或者,也许您想查看可以提取多少代码和数据。

理想情况下,您的Web应用程序需要在IBM Cloud服务器上运行所获得的性能和高可用性。 在本教程中,我将向您展示如何使用Node.js作为IBM Cloud Web应用程序的一部分运行Clojure程序。

构建和部署Clojure应用程序所需的内容

  • 一个IBM Cloud帐户(注册您的免费试用帐户,或者如果已经拥有一个帐户,则登录到IBM Cloud )。
  • HTML和JavaScript的使用知识。
  • MEAN应用程序堆栈(至少Node.js和Express)的工作知识。 如果您不熟悉它,可以在我的developerWorks系列文章中学习MEAN堆栈基础知识
  • 一个开发环境,支持将Node.js应用程序上载到IBM Cloud,例如Eclipse。

    在IBM Cloud中运行应用程序后,您可以快速添加Git存储库和持续交付管道以自动开发,测试和部署它。 要进行设置,请在您应用的“概述”页面上,选择添加Git Repo和管道 ,然后您就可以在浏览器中进行所有开发和部署。

你好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.cljs 在这里,逐行解释。

这些行定义名称空间( my-ns )并导入我们需要的两个库: clojure.stringcljs.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应用程序必须处理用户输入。 此输入通常以三种形式出现:在路径中,在查询中或在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
			])
		)
	)
)

将所有参数放在一起

通常,您不在乎参数来自何处; 您只关心他们的价值观。 因为参数具有键和值, 所以要使用的适当数据结构是map

下面是执行此操作的代码示例。 第一部分是函数定义。 因为这是一个命名函数,可以从其他地方调用它,所以它是用defn而不是fn定义的。

; Given a request object, return a map with all the parameters
(defn getParams [req]

let调用定义了可以在表达式的末尾使用的多个局部变量 (大致来说,它们在功能编程中实际上称为符号)。 在这种情况下,它定义了三个: queryMapbodyMapparamMap

(let [    ; Define local variables

这三个定义都使用js->clj函数将JavaScript对象转换为Clojure映射。

queryMap (js->clj (js* "req.query"))
		bodyMap (js->clj (js* "req.body"))
		paramMap (js->clj (js* "req.params"))		
	]

merge功能将三个不同的映射合并到一个映射中(如果键相同,则后面的映射中的值会覆盖前面的映射中的值)。 该表达式是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)创作的《克洛瑞尔的欢乐》


翻译自: https://www.ibm.com/developerworks/cloud/library/cl-clojure-node-bluemix-app/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值