- A+
Express
在 myApp.js
文件的前两行中,创建一个 Express 应用对象很简单。 这个对象有几种方法,一个基础的方法是 app.listen(port)
。 它处于运行状态时告诉服务器监听指定的端口。 出于测试的原因,需要应用在后台运行,所以在 server.js
中已经添加了这个方法。
在 Express 中,路由采用这种结构:app.METHOD(PATH, HANDLER)
, METHOD 是 http 请求方法的小写形式, PATH 是服务器上的相对路径(它可以是一个字符串,甚至可以是正则表达式), HANDLER 是匹配路由时 Express 调用的函数, 处理函数采用这种形式:function(req, res) {...}
,其中 req 是请求对象,res 是响应对象, 例如:
function(req, res) {
res.send('Response String');
} // “Response String”
app.get("/", function(req, res) {
res.send("Hello Express");
})
提供 HTML 文件
通过 res.sendFile(path)
方法给请求响应一个文件, 可以把它放到路由处理 app.get('/', ...)
中。 在后台,这个方法会根据想发送的文件的类型,设置适当的消息头信息来告诉浏览器如何处理它, 然后读取并发送文件, 此方法需要文件的绝对路径。 建议使用 Node. js 的全局变量 __dirname
来计算出这个文件的绝对路径:
absolutePath = __dirname + relativePath/file.ext
var express = require('express');
var app = express();
let absolutePath = __dirname + "/views/index.html";
app.get("/", function(req, res) {
res.sendFile(absolutePath);
});
module.exports = app;
提供静态资源
HTML 服务器通常有一个或多个用户可以访问的目录。 可以将应用程序所需的静态资源 (样式表、脚本、图片) 放在那里。在 Express 中可以使用中间件 express.static(path)
来设置此功能,它的参数 path
就是包含静态资源文件的绝对路径。
其实,中间件就是一个拦截路由处理方法并在里面添加一些信息的函数。 使用 app.use(path, middlewareFunction)
方法来加载一个中间件, 它的第一个参数 path
是可选的, 如果没设置第一个参数,那么所有的请求都会经过这个中间件处理。
app.use("/public", express.static(__dirname + "/public"));
在指定路由上提供 JSON
HTML 服务器提供 HTML 服务,而 API 提供数据服务。 REST(REpresentational State Transfer)API 允许以简单的方式进行数据交换,对于客户端不必要知道服务器的细节。 客户只需要知道资源在哪里(URL),以及想执行的动作(动词)。 GET 动词常被用来获取无需修改的信息。 如今,网络上的移动数据首选格式是 JSON, 简而言之,JSON 是一种可以方便地用字符串表示 JavaScript 对象的方式,因此它很容易传输。
来创建一个简单的 API,创建一个路径为 /json
且返回数据是 JSON 格式的路由, 可以像之前那样用 app.get()
方法来做。 然后在路由处理部分使用 res.json()
方法,并传入一个对象作为参数, 这个方法会结束请求响应循环(request-response loop),然后返回数据。 在后台,它把一个有效的 JavaScript 对象转化为字符串,然后会设置适当的消息头来告诉浏览器:“这是一个 JSON 数据”,最后将数据返回给客户端。 一个有效的对象通常是这种结构:{key: data}
, data
可以是数字、字符串、嵌套对象或数组, data
也可以是变量或者函数返回值,在这种情况下,它们先求值再转成字符串。
app.get("/json", function(req, res) {
res.json({
"message": "Hello json"
});
})
当向路由 /json
发送 GET 请求,将对象 {"message": "Hello json"}
以 JSON 格式返回给客户端, 浏览器访问 your-app-url/json
时,应该在屏幕上看到这个消息。
使用 .env 文件
.env
文件是一个用于将环境变量传给应用程序的隐藏文件, 这是一个除了开发者之外没人可以访问的私密文件,它可以用来存储保密或者隐藏的数据, 例如,它可以存储第三方服务的 API 密钥或者数据库 URI, 也可以使用它来存储配置选项, 通过设置配置选项,改变应用程序的行为,而无需重写一些代码。
在应用程序中通过 process.env.VAR_NAME
访问到环境变量。 process.env
对象是 Node 程序中的一个全局对象,可以给这个变量传字符串。 习惯上,变量名全部大写,单词之间用下划线分隔。 .env
是一个 shell 文件,因此不需要用给变量名和值加引号。 还有一点需要注意,当你给变量赋值时等号两侧不能有空格,例如:VAR_NAME=value
。 通常来讲,每一个变量定义会独占一行。
app.get('/json', (function(req,res) {
const mySecret = process.env['MESSAGE_STYLE'];
if( mySecret === "uppercase" ){
res.json({"message" : "Hello json".toUpperCase() });
} else {
res.json({"message" : "Hello json" });
}
}))
添加一个环境变量作为配置选项。
在项目根目录创建一个 .env
文件,并存储变量 MESSAGE_STYLE=uppercase
。
当向 /json
发 GET 请求时,如果 process.env.MESSAGE_STYLE
的值为 uppercase
,那么上一次挑战中的路由处理程序返回的对象的消息则应该大写。 响应对象应该是 {"message": "Hello json"}
or {"message": "HELLO JSON"}
,取决于 MESSAGE_STYLE
的值。
注意: 在本地工作,需要 dotenv
包。 它将环境变量从的 .env
文件加载到 process.env
中。 使用 npm install dotenv
安装它。 然后,在 myApp.js
文件的顶部,使用 require('dotenv').config()
导入和加载变量。
实现一个根级的请求记录中间件 Root-Level Request Logger Middleware
中间件函数是一个接收 3 个参数的函数,这 3 个参数分别是:请求对象、响应对象和在应用的请求-响应循环中的下一个函数。 中间件函数执行一些可能对应用程序产生一些效果的代码,通常还会在请求对象或者响应对象里添加一些信息, 它们也可以在满足某些条件时通过发送响应来结束循环, 如果在它们完成时没有发送响应,那么就会开始执行堆栈中的下一个函数, next()
将触发调用第 3 个参数。
请看以下示例:
function(req, res, next) {
console.log("I'm a middleware...");
next();
}
假设在某个路由上安装了这个中间件函数, 当一个请求与路由匹配时,它会显示字符串“I’m a middleware…”,然后它执行堆栈中的下一个函数。可以使用 app.use(<mware-function>)
方法。 在这种情况下,该函数将对所有请求执行,但也可以设置更具体的条件来执行, 例如,如果你希望某个函数只针对 POST 请求执行,可以使用 app.post(<mware-function>)
方法。 所有的 HTTP 动词(GET、DELETE、PUT……)都存在类似的方法。
app.use(function(req, res, next) {
console.log(req.method + " " + req.path + " - " + req.ip);
next();
})
注意: Express 按照函数在代码中出现的顺序来执行, 中间件也是如此。 如果想让中间件函数适用于所有路由,那么应该在路由之前配置好中间件。
通过链式调用中间件来创建时间服务 Chain Middleware
使用 app.METHOD(path, middlewareFunction)
可以在指定的路由挂载中间件, 也可以在路由定义中链式调用中间件。
请看以下示例:
app.get('/user', function(req, res, next) {
req.user = getTheUserSync();
next();
}, function(req, res) {
res.send(req.user);
});
此方法可用于将服务操作拆分为较小的单元, 这可以让应用拥有更好的结构,也便于在不同的位置上复用代码; 此方法还可用于对数据执行某些验证。 可以在每一个中间件堆栈中,阻止当前链的执行,并将控制权传递给专门设计用于处理错误的函数; 或者可以将控制权传递给下一个匹配的路由,以处理特殊情况。
从客户端获取路由参数 Route Parameter
在构建 API 时,要让用户说明他们想从服务中获取什么。 举个例子,如果客户请求数据库中存储的用户信息,需要一种方法让开发者知道用户对哪个数据库用户项感兴趣, 使用路由参数可以实现这个需求。 路由参数是由斜杠(/)分隔的 URL 命名段, 每一小段能捕获与其位置匹配的 URL 部分的值, 捕获的值能够在 req.params
对象中找到。
路由地址:'/user/:userId/book/:bookId' 实际请求 URL:'/user/546/book/6754' req.params:{userId: '546', bookId: '6754'}
app.get("/:word/echo", (req, res) => {
const { word } = req.params;
res.json({
echo: word
});
});
从客户端获取输入的查询数据 Query Parameter
从客户端获取输入的另一种常见方式是使用查询字符串对路由路径中的数据进行编码, 查询字符串使用标记(?)分隔,并且包含键值对 field=value, 每对键值使用连字号(&)分隔。 Express 能够从查询字符串中解析这些数据,并且把它放到 req.query
对象中。 有些字符(如百分号(%))不能在出现在 URL 中,它们在发送前必须以不同的格式进行编码。 如果使用 JavaScript 的 API,可以用特定的方法来编码/解码这些字符。
路由地址:'/library' 实际请求 URL:'/library?userId=546&bookId=6754' req.query:{userId: '546', bookId: '6754'}
注意: 在后面的练习中,我们将向相同的路由路径 /name
发送 POST 请求来接收数据。 如果愿意,可以使用app.route(path).get(handler).post(handler)
这中写法, 这种语法允许在同一路径路由上链式调用不同的请求方法, 可以节省一点打字时间,也可以让代码看起来更清晰。
app.get("/name", function(req, res) {
res.json({
name: `${req.query.first} ${req.query.last}`
});
})
body-parser
除了 GET 还有另一个常见的 HTTP 动词,即 POST。 POST 是使用 HTML 表单发送客户端数据的默认方法。 在 REST 规范中,POST 常用于发送数据以在数据库中创建新项目(新用户或新博客文章)。 在这个项目中没有使用数据库,但下面将学习如何处理 POST 请求。
在这种类型的请求中,数据不会出现在 URL 中,而是隐藏在请求正文中。 请求正文也是 HTML 请求的一部分,被称为负载。 即使数据在 URL 中是不可见的,也不意味着它是私有的。 要了解原因,请观察 HTTP POST 请求的原始内容:
POST /path/subpath HTTP/1.0
From: john@example.com
User-Agent: someBrowser/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
name=John+Doe&age=25
正如你所看到的,正文被编码成类似查询字符串的形式, 这是 HTML 表单使用的默认格式。 可以通过 Ajax 使用 JSON 来处理具有更复杂结构的数据。 还有另一种类型的编码:multipart/form-data, 它被用来上传二进制文件。 使用 URL 编码请求正文。 要解析来自 POST 请求的数据,必须安装 body-parser
包, 这个包包含一套可以解码不同格式数据的中间件。
var express = require('express');
var bodyParser = require("body-parser");
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
从 POST 请求中获取数据
在路径 /name
挂载一个 POST 处理方法, 和前面一样, 我们已经在 html 首页准备了一份表单, 它将提交与练习 10 相同的数据(查询字符串), 如果 body-parser 正确配置好了,那么就可以在 req.body
对象中找到请求的参数。 来看看一个常规的例子:
路由:POST '/library' URL 编码的请求正文:userId=546&bookId=6754 req.body:{userId: '546', bookId: '6754'}
响应和前面一样的 JSON 对象 {name: 'firstname lastname'}
。 可以使用首页应用提供的 html 表单,来测试 API 是否正常工作。
提示:除了 GET 和 POST,还有其他几种 http 方法。 按照惯例,http 动词和在服务端执行的某种操作之间有对应关系, 这种对应关系通常如下:
POST(有时候是 PUT)- 使用请求发送信息,以创建新资源;
GET - 读取不用修改的已存在的资源;
PUT 或者 PATCH(有时候是 POST)- 发送数据,以更新资源;
DELETE => 删除一个资源。
还有其他两种方法常用于与服务进行交互。 除了 GET 之外,上面列出的所有方法都可以负载数据(即数据都能放到消息正文中), 这些方法也可以使用 body-parser 中间件。
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: false}));
const path = require('path')
app.post("/name", function(req, res) {
var string = req.body.first + " " + req.body.last;
res.json({ name: string });
});
module.exports = app;