用 Node 编写的最常见的应用类型是 web 应用。我们已经在第 2 章中看到了如何编写一个简单的 HTTP 服务器。在本章中,我们将更深入地使用 Express.js 框架来了解如何构建真正的 web 应用。
在第 2 章中,我们看到了如何编写一个真正基本的 web 服务器:
代码清单 32
const http
= require('http')
const
server = http.createServer((request, response) => {
response.writeHead(200, {'Content-Type': 'text/html'})
response.end('<h1>Hello from Node.js</h1>')
})
server.listen(8000)
这段代码创建了一个监听端口 8000 的 HTTP 服务器。无论什么请求到达服务器,响应总是<h1>Hello from Node.js</h1>
。我们在这里使用end
方法,因为我们只有一个字符串要发送给客户端。一般来说,我们可以多次使用response.write
,并且只能在最后调用response.end()
来关闭连接。
request
对象包含关于请求的所有信息,因此我们可以使用它来决定我们必须向客户端发送什么样的响应。
response
对象用于构建响应。我们用它来添加状态代码(200
)和Content-Type
标题。
如果我们想构建一些不那么琐碎的东西,我们可以检查request.url
来决定将哪些内容发送给客户端。
例如,我们可以这样写,根据请求的网址发送不同的响应:
代码清单 33
const http = require('http')
const server =
http.createServer((request, response) => {
response.writeHead(200,
{'Content-Type': 'text/html'})
if (request.url === '/about') {
response.write('<h1>About Node.js</h1>')
} else {
response.write('<h1>Hello
from Node.js/h1>')
}
response.end();
})
server.listen(8000)
request
对象有很多其他属性和方法来分析请求。例如,attribute
方法包含请求的类型(GET、POST、PUT、…)。
http
模块非常容易使用,加上 HTTP 协议也很容易,我们可以考虑直接使用http
模块编写自己的 web 应用。但是,即使很容易,Node 社区已经创建了许多库和框架来加快 web 应用的开发。第一个也是最著名的是 Express.js
Express.js 是一个最小的 web 框架,它建立在组成中间件的思想上(这个思想来自带有 Rack 的 Ruby 世界)。即使 Express 非常小,Node 社区也已经构建了大量插件来为 Express 添加功能,这样我们就可以在应用中准确地选择我们需要的东西。
最小的 Express 应用是这样的:
代码清单 34
const
express = require('express')
const app =
express()
app.get('/',
(req, res) => {
res.send('Hello World!')
});
app.listen(8000,
() = > {
console.log('Example app listening on port 8000!');
});
在运行这段代码之前,我们需要使用npm
在我们的node_modules
文件夹中安装 Express:
代码清单 35
> npm
install express
代码需要express
模块,这是一个用于创建应用的函数。通过应用实例,我们可以定义调用路由时要使用的路由和回调。
在前面的例子中,我们已经定义了一个响应 HTTP GET / (root)的单一路由,我们使用字符串Hello World!
进行响应
这个例子和之前使用了 raw http
模块的例子没有太大的区别,但是结构稍微有点不同,更容易维护和扩展。
从这里我们可以使用 HTTP 方法get
、post
、put
等添加更多路由。,并指定路由必须匹配的模式。
例如,一个基本的 CRUD API 可能是这样的:
代码清单 36
const express = require('express')
const app = express()
app.get('/users', (req, res) =>
{
// get all users
})
app.get('/users/:id', (req, res)
=> {
// get the user with the
specified id
})
app.post('/users', (req, res)
=> {
// create a new user
})
app.put('/users/:id', (req, res)
=> {
// update the user with
specified id
})
app.delete('/users/:id', (req,
res) => {
// delete the user with specified
id
})
app.listen(8000, () = > {
console.log('Example app
listening on port 8000!');
})
|
在一个app
对象上,我们附加了指定所用方法的各种路线。这个应用编程接口在假设的用户资源上工作,并公开一组端点来操纵用户。请注意访问单个用户的get
和post
。在这些情况下,URL 将包含用户的id
(类似于/users/42
),并且在路线声明中,Express 使用:id
(冒号 id)来指定该部分是可变的。在请求中,我们将在req.params.id
中找到id
的值。
为了构建应用编程接口响应,我们可以使用回调中可用的res
对象。例如,要返回用户列表,我们可以简单地使用方法send
将用户数组发送给客户端:
代码清单 37
const
express = require('express')
const app =
express()
app.get('/users',
(req, res) => {
const
users = [{id: 1, name: 'Emanuele'}, {id: 2, name: 'Tessa'}]
res.send(users)
})
app.listen(8000, () = > {
console.log('Example app listening on port 8000!');
})
Express.js 将users
数组视为一个对象,将其序列化为 JSON 数组(默认情况下),并设置所有必要的头和状态代码来组成正确的响应。
如果我们使用curl
调用 API:
代码清单 38
curl -i http://localhost:8000/users
答案是:
代码清单 39
HTTP/1.1
200 OK
X-Powered-By:
Express
Content-Type:
application/json; charset=utf-8
Content-Length:
52
ETag:
W/"34-TWdsT6/ZO3egRrB46eWd+w"
Date: Tue,
23 Feb 2016 16:23:37 GMT
Connection:
keep-alive
[{"id":1,"name":"emanuele"},{"id":2,"name":"Tessa"}]
|
有时我们需要配置响应,例如,指定特定的状态代码或特定的头。res
对象有一系列方法可以做到这一点。
如果我们需要指定一个特定的状态代码,我们可以使用res.status(code)
,其中code
是我们想要的实际状态代码。默认情况下是 200。
send
和json
方法有一个重载来指定状态代码:
代码清单 40
res.json(200,
users)
res.send(200, users)
要添加标题,我们使用方法res.set(headerName, headerValue)
。
对于某些标头,存在一种特殊方法。例如,创建资源的应用编程接口在location
头中返回新创建的资源的网址是一个很好的做法:
代码清单 41
app.post('/users',
(req, res) => {
// create
a new user
const user = createUser(req.body)
res.location(`/users/${user.id}`)
res.status(201)
})
这个例子展示了一个使用createUser
函数创建新用户的帖子的可能实现。然后我们添加位置头,并将状态代码 201(已创建)发送给客户端。在这种情况下,身体是空的。
到目前为止,我们已经看到了如何使用 Express.js 创建一个简单的 API,但是 Express 不仅仅是关于 API 的,它允许我们使用模板引擎创建带有 HTML 页面的完整 web 应用。
如果我们需要使用服务器端模板引擎呈现一个 HTML 页面,那么 Jade 是 Express.js 的众多可用选项之一
要开始使用 Jade,我们首先需要安装它:
代码清单 42
npm install jade
然后,我们需要在应用对象上设置几个选项:
代码清单 43
app.set('view engine', 'jade')
app.set('views', './views')
第一个set
指定我们要用的视图引擎是 Jade。第二个告诉 app,视图文件将在当前项目的./views
文件夹中。
完成这两项设置后,我们准备创建第一个模板,并定义一个路由器来呈现它。
创建模板相当容易;Jade 的语法非常简单,很容易学习。例如,我们可以在./views
文件夹中创建新文件hello.jade
:
代码清单 44
html
head
title=
title
body
h1=
messageTitle
div(class='container')
= messageText
这是一个 Jade 模板,可编译为:
代码清单 45
<html>
<head>
<title>Hey</title>
</head>
<body>
<h1>Hello noders</h1>
<div class="container">
lorem ipsum....
</div>
</body>
</html>
编译过程由 Express with Jade 管理,以响应对所选路由的调用。在我们的例子中,我们可以定义一条路线:
代码清单 46
app.get('/hello', function (req,
res) {
res.render('hello', {
title: 'Hey',
messageTitle: 'Hello noders',
messageText: 'lorem ipsum....'
})
})
该路径响应/hello
并呈现名为hello
的视图(与文件名匹配)。render
方法接收我们想要传递给视图的对象。该对象由三个属性组成:title
、messageTitle
和messageText
。
所以从浏览器调用路线/hello
会呈现一个 HTML 页面,如下所示:
图 11:快递中的你好世界
Jade 是一种全模板语言,支持构建 HTML 页面所需的所有语义。我们可以使用条件句、服务器端代码和变量。好的一点是你不需要关闭标签,所以 Jade 文件相当紧凑。
如前所述,Express 是一个最小框架,基于中间件的思想。中间件是对象的请求和响应通过的一堆函数。每个中间件可以对请求和响应做任何它需要做的事情,并将它传递给下一个中间件。这是一种责任链。
|
有了这个强大的想法,我们可以用我们需要的中间件来构建堆栈。例如,如果我们需要管理一个 cookie 来识别用户,我们可以使用一个检查 cookie 的中间件,如果它无效,那么我们可以用一个向客户端返回 HTTP 401 状态的短路来断开这个链。
写中间件很容易;它只是一个具有三个参数的函数:请求、响应和下一个要调用的中间件:
代码清单 47
function myMiddleware(req, res,
next) {
// do what you need and if everything is ok
next()
}
app.use(myMiddleware)
我们能用中间件做什么?
中间件功能可以执行一些代码,或者对请求或响应对象进行更改。最后,他们可以结束请求-响应周期或调用下一个函数来继续中间件链。
例如,使用中间件,我们可以编写一个记录器:
代码清单 48
function
requestLogger(req, res, next) {
console.log(`${req.method} ${req.originalUrl}`)
next()
}
app.use(requestLogger)
这个简单的中间件功能将请求的方法和网址记录到控制台。
功能中间件的思想非常强大,其易用性是 Express.js 成功的原因之一。
大多数 Express.js 插件都是作为插件实现的,我们可以在npmjs.com上找到很多中间件来满足每个应用的需求,从安全性到日志记录。
可以使用app.use
功能为整个应用设置中间件,或者直接在路由定义上通过该功能的每条路由设置中间件:
代码清单 49
function checkPermissionMiddleware(req,
res, next){
// verify permissions. If ok call next()
if (ok){
next()
} else {
res.status(401)
}
}
app.post('/users', checkPermissionMiddleware, (req, res) => {
// create a new user
const user = createUser(req.body)
res.location(`/users/${user.id}`)
res.status(201)
})
在这种配置下,checkPermissionMiddleware
功能将在每次post
请求到路线/users
之前被调用。它可以检查req
对象中是否有正确的授权头来管理继续请求。如果它们存在,它将调用下一个中间件(实际上是在/users
路线上定义的函数),否则它将返回一个 401,甚至不调用后续的中间件。
中间件模块化的简单性给了我们很大的灵活性,增强了安全性,并且不影响性能。在最后这个例子中,如果用户没有访问资源的权限,那么危险的代码甚至不会被执行。