Web前端学习笔记——NodeJS之Express

目录

Express

Express 介绍

起步

安装

Hello World

基本路由

处理静态资源

使用模板引擎

配置使用 art-template 模板引擎

其它常见模板引擎

解析表单 post 请求体

使用 Session

路由

路由方法

路由路径

路由处理方法

app.route()

express.Router

在 Express 中获取客户端请求参数的三种方式

查询字符串参数

请求体参数

动态的路径参数

中间件

一个简单的中间件例子:打印日志

中间件的组成

中间件分类

中间件应用

错误处理

常用 API

express

Application

Request

Response

Router


Express

Edit on github

原生的 http 模块在某些方面表现不足以应对我们的开发需求,所以我们就需要使用框架来加快我们的开发效率,框架的目的就是提高效率,让我们的代码更高度统一。 在 Node 中,有很多 Web 开发框架,我们这里以学习 Express为主。

Express 介绍

  • Express 是一个基于 Node.js 平台,快速、开放、极简的 web 开发框架。

起步

安装

参考文档:http://expressjs.com/en/starter/installing.html

# 创建并切换到 myapp 目录
mkdir myapp
cd myapp

# 初始化 package.json 文件
npm init -y

# 安装 express 到项目中
npm i express

Hello World

参考文档:http://expressjs.com/en/starter/hello-world.html

// 0. 加载 Express
const express = require('express')

// 1. 调用 express() 得到一个 app
//    类似于 http.createServer()
const app = express()

// 2. 设置请求对应的处理函数
//    当客户端以 GET 方法请求 / 的时候就会调用第二个参数:请求处理函数
app.get('/', (req, res) => {
  res.send('hello world')
})

// 3. 监听端口号,启动 Web 服务
app.listen(3000, () => console.log('app listening on port 3000!'))

基本路由

参考文档:http://expressjs.com/en/starter/basic-routing.html

路由(Routing)是由一个 URI(或者叫路径标识)和一个特定的 HTTP 方法(GET、POST 等)组成的,涉及到应用如何处理响应客户端请求。

每一个路由都可以有一个或者多个处理器函数,当匹配到路由时,这个/些函数将被执行。

路由的定义的结构如下:

app.METHOD(PATH, HANDLER)

其中:

  • app 是 express 实例
  • METHOD 是一个 HTTP 请求方法
  • PATH 是服务端路径(定位标识)
  • HANDLER 是当路由匹配到时需要执行的处理函数

下面是一些基本示例。

Respond with Hello World! on the homepage:

// 当你以 GET 方法请求 / 的时候,执行对应的处理函数
app.get('/', function (req, res) {
  res.send('Hello World!')
})

Respond to POST request on the root route (/), the application’s home page:

// 当你以 POST 方法请求 / 的时候,指定对应的处理函数
app.post('/', function (req, res) {
  res.send('Got a POST request')
})

Respond to a PUT request to the /user route:

app.put('/user', function (req, res) {
  res.send('Got a PUT request at /user')
})

Respond to a DELETE request to the /user route:

app.delete('/user', function (req, res) {
  res.send('Got a DELETE request at /user')
})

For more details about routing, see the routing guide.

处理静态资源

参考文档:http://expressjs.com/en/starter/static-files.html

// 开放 public 目录中的资源
// 不需要访问前缀
app.use(express.static('public'))

// 开放 files 目录资源,同上
app.use(express.static('files'))

// 开放 public 目录,限制访问前缀
app.use('/public', express.static('public'))

// 开放 public 目录资源,限制访问前缀
app.use('/static', express.static('public'))

// 开放 publi 目录,限制访问前缀
// path.join(__dirname, 'public') 会得到一个动态的绝对路径
app.use('/static', express.static(path.join(__dirname, 'public')))

使用模板引擎

参考文档:

我们可以使用模板引擎处理服务端渲染,但是 Express 为了保持其极简灵活的特性并没有提供类似的功能。

同样的,Express 也是开放的,它支持开发人员根据自己的需求将模板引擎和 Express 结合实现服务端渲染的能力。

配置使用 art-template 模板引擎

参考文档:

这里我们以 art-template 模板引擎为例演示如何和 Express 结合使用。

安装:

npm install art-template express-art-template

配置:

// 第一个参数用来配置视图的后缀名,这里是 art ,则你存储在 views 目录中的模板文件必须是 xxx.art
// app.engine('art', require('express-art-template'))

// 这里我把 art 改为 html
app.engine('html', require('express-art-template'))

使用示例:

app.get('/', function (req, res) {
  // render 方法默认会去项目的 views 目录中查找 index.html 文件
  // render 方法的本质就是将读取文件和模板引擎渲染这件事儿给封装起来了
  res.render('index.html', {
    title: 'hello world'
  })
})

如果希望修改默认的 views 视图渲染存储目录,可以:

// 第一个参数 views 是一个特定标识,不能乱写
// 第二个参数给定一个目录路径作为默认的视图查找目录
app.set('views', 目录路径)

其它常见模板引擎

JavaScript 模板引擎有很多,并且他们的功能都大抵相同,但是不同的模板引擎也各有自己的特色。

大部分 JavaScript 模板引擎都可以在 Node 中使用,下面是一些常见的模板引擎。

  • ejs
  • handlebars
  • jade
    • 后改名为 pug
  • nunjucks

解析表单 post 请求体

参考文档:

在 Express 中没有内置获取表单 POST 请求体的 API,这里我们需要使用一个第三方包:body-parser

安装:

npm install --save body-parser

配置:

var express = require('express')
// 0. 引包
var bodyParser = require('body-parser')

var app = express()

// 配置 body-parser
// 只要加入这个配置,则在 req 请求对象上会多出来一个属性:body
// 也就是说你就可以直接通过 req.body 来获取表单 POST 请求体数据了
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())

使用:

app.use(function (req, res) {
  res.setHeader('Content-Type', 'text/plain')
  res.write('you posted:\n')
  // 可以通过 req.body 来获取表单 POST 请求体数据
  res.end(JSON.stringify(req.body, null, 2))
})

使用 Session

参考文档:https://github.com/expressjs/session

安装:

npm install express-session

配置:

// 该插件会为 req 请求对象添加一个成员:req.session 默认是一个对象
// 这是最简单的配置方式,暂且先不用关心里面参数的含义
app.use(session({
  // 配置加密字符串,它会在原有加密基础之上和这个字符串拼起来去加密
  // 目的是为了增加安全性,防止客户端恶意伪造
  secret: 'itcast',
  resave: false,
  saveUninitialized: false // 无论你是否使用 Session ,我都默认直接给你分配一把钥匙
}))

使用:

// 添加 Session 数据
req.session.foo = 'bar'

// 获取 Session 数据
req.session.foo

提示:默认 Session 数据是内存存储的,服务器一旦重启就会丢失,真正的生产环境会把 Session 进行持久化存储。


路由

参考文档:

一个非常基础的路由:

var express = require('express')
var app = express()

// respond with "hello world" when a GET request is made to the homepage
app.get('/', function (req, res) {
  res.send('hello world')
})

路由方法

// GET method route
app.get('/', function (req, res) {
  res.send('GET request to the homepage')
})

// POST method route
app.post('/', function (req, res) {
  res.send('POST request to the homepage')
})

路由路径

This route path will match requests to the root route, /.

app.get('/', function (req, res) {
  res.send('root')
})

This route path will match requests to /about.

app.get('/about', function (req, res) {
  res.send('about')
})

This route path will match requests to /random.text.

app.get('/random.text', function (req, res) {
  res.send('random.text')
})

Here are some examples of route paths based on string patterns.

This route path will match acd and abcd.

app.get('/ab?cd', function (req, res) {
  res.send('ab?cd')
})

This route path will match abcd, abbcd, abbbcd, and so on.

app.get('/ab+cd', function (req, res) {
  res.send('ab+cd')
})

This route path will match abcd, abxcd, abRANDOMcd, ab123cd, and so on.

app.get('/ab*cd', function (req, res) {
  res.send('ab*cd')
})

This route path will match /abe and /abcde.

app.get('/ab(cd)?e', function (req, res) {
  res.send('ab(cd)?e')
})

Examples of route paths based on regular expressions:

This route path will match anything with an “a” in the route name.

app.get(/a/, function (req, res) {
  res.send('/a/')
})

This route path will match butterfly and dragonfly, but not butterflyman, dragonflyman, and so on.

app.get(/.*fly$/, function (req, res) {
  res.send('/.*fly$/')
})

动态路径

Route path: /users/:userId/books/:bookId
Request URL: http://localhost:3000/users/34/books/8989
req.params: { "userId": "34", "bookId": "8989" }

定义动态的路由路径:

app.get('/users/:userId/books/:bookId', function (req, res) {
  res.send(req.params)
})

路由处理方法

app.route()

express.Router

Create a router file named router.js in the app directory, with the following content:

const express = require('express')

const router = express.Router()

router.get('/', function (req, res) {
  res.send('home page')
})

router.get('/about', function (req, res) {
  res.send('About page')
})

module.exports = router

Then, load the router module in the app:

const router = require('./router')

// ...

app.use(router)

在 Express 中获取客户端请求参数的三种方式

例如,有一个地址:/a/b/c?foo=bar&id=123

查询字符串参数

获取 ?foo=bar&id=123

console.log(req.query)

结果如下:

{
  foo: 'bar',
  id: '123'
}

请求体参数

POST 请求才有请求体,我们需要单独配置 body-parser 中间件才可以获取。 只要程序中配置了 body-parser中间件,我们就可以通过 req.body 来获取表单 POST 请求体数据。

req.body
// => 得到一个请求体对象

动态的路径参数

在 Express 中,支持把一个路由设计为动态的。例如:

// /users/:id 要求必须以 /users/ 开头,:id 表示动态的,1、2、3、abc、dnsaj 任意都行
// 注意::冒号很重要,如果你不加,则就变成了必须 === /users/id
// 为啥叫 id ,因为是动态的路径,服务器需要单独获取它,所以得给它起一个名字
// 那么我们就可以通过 req.params 来获取路径参数
app.get('/users/:id', (req, res, next) => {
  console.log(req.params.id)
})

// /users/*/abc
// req.params.id
app.get('/users/:id/abc', (req, res, next) => {
  console.log(req.params.id)
})

// /users/*/*
// req.params.id
// req.params.abc
app.get('/users/:id/:abc', (req, res, next) => {
  console.log(req.params.id)
})

// /*/*/*
// req.params.users
app.get('/:users/:id/:abc', (req, res, next) => {
  console.log(req.params.id)
})

// /*/id/*
app.get('/:users/id/:abc', (req, res, next) => {
  console.log(req.params.id)
})

中间件

参考文档:

Express 的最大特色,也是最重要的一个设计,就是中间件。一个 Express 应用,就是由许许多多的中间件来完成的。

为了理解中间件,我们先来看一下我们现实生活中的自来水厂的净水流程。

自来水厂净水过程

在上图中,自来水厂从获取水源到净化处理交给用户,中间经历了一系列的处理环节,我们称其中的每一个处理环节就是一个中间件。这样做的目的既提高了生产效率也保证了可维护性。

一个简单的中间件例子:打印日志

app.get('/', (req, res) => {
  console.log(`${req.method} ${req.url} ${Date.now()}`)
  res.send('index')
})

app.get('/about', (req, res) => {
  console.log(`${req.method} ${req.url} ${Date.now()}`)
  res.send('about')
})

app.get('/login', (req, res) => {
  console.log(`${req.method} ${req.url} ${Date.now()}`)
  res.send('login')
})

在上面的示例中,每一个请求处理函数都做了一件同样的事情:请求日志功能(在控制台打印当前请求方法、请求路径以及请求时间)。

针对于这样的代码我们自然想到了封装来解决:

app.get('/', (req, res) => {
  // console.log(`${req.method} ${req.url} ${Date.now()}`)
  logger(req)
  res.send('index')
})

app.get('/about', (req, res) => {
  // console.log(`${req.method} ${req.url} ${Date.now()}`)
  logger(req)
  res.send('about')
})

app.get('/login', (req, res) => {
  // console.log(`${req.method} ${req.url} ${Date.now()}`)
  logger(req)
  res.send('login')
})

function logger (req) {
  console.log(`${req.method} ${req.url} ${Date.now()}`)
}

这样的做法自然没有问题,但是大家想一想,我现在只有三个路由,如果说有10个、100个、1000个呢?那我在每个请求路由函数中都手动调用一次也太麻烦了。

好了,我们不卖关子了,来看一下我们如何使用中间件来解决这个简单的小功能。

app.use((req, res, next) => {
  console.log(`${req.method} ${req.url} ${Date.now()}`)
  next()
})

app.get('/', (req, res) => {
  res.send('index')
})

app.get('/about', (req, res) => {
  res.send('about')
})

app.get('/login', (req, res) => {
  res.send('login')
})

function logger (req) {
  console.log(`${req.method} ${req.url} ${Date.now()}`)
}

上面代码执行之后我们发现任何请求进来都会先在服务端打印请求日志,然后才会执行具体的业务处理函数。那这个到底是怎么回事?

中间件的组成

中间件的组成

中间件函数可以执行以下任何任务:

  • 执行任何代码
  • 修改 request 或者 response 响应对象
  • 结束请求响应周期
  • 调用下一个中间件

中间件分类

  • 应用程序级别中间件
  • 路由级别中间件
  • 错误处理中间件
  • 内置中间件
  • 第三方中间件

应用程序级别中间件

不关心请求路径:

var app = express()

app.use(function (req, res, next) {
  console.log('Time:', Date.now())
  next()
})

限定请求路径:

app.use('/user/:id', function (req, res, next) {
  console.log('Request Type:', req.method)
  next()
})

限定请求方法:

app.get('/user/:id', function (req, res, next) {
  res.send('USER')
})

多个处理函数:

app.use('/user/:id', function (req, res, next) {
  console.log('Request URL:', req.originalUrl)
  next()
}, function (req, res, next) {
  console.log('Request Type:', req.method)
  next()
})

多个路由处理函数:

app.get('/user/:id', function (req, res, next) {
  console.log('ID:', req.params.id)
  next()
}, function (req, res, next) {
  res.send('User Info')
})

// handler for the /user/:id path, which prints the user ID
app.get('/user/:id', function (req, res, next) {
  res.end(req.params.id)
})

最后一个例子:

app.get('/user/:id', function (req, res, next) {
  // if the user ID is 0, skip to the next route
  if (req.params.id === '0') next('route')
  // otherwise pass the control to the next middleware function in this stack
  else next()
}, function (req, res, next) {
  // render a regular page
  res.render('regular')
})

// handler for the /user/:id path, which renders a special page
app.get('/user/:id', function (req, res, next) {
  res.render('special')
})

路由级别中间件

创建路由实例:

var router = express.Router()

示例:

var app = express()
var router = express.Router()

// a middleware function with no mount path. This code is executed for every request to the router
router.use(function (req, res, next) {
  console.log('Time:', Date.now())
  next()
})

// a middleware sub-stack shows request info for any type of HTTP request to the /user/:id path
router.use('/user/:id', function (req, res, next) {
  console.log('Request URL:', req.originalUrl)
  next()
}, function (req, res, next) {
  console.log('Request Type:', req.method)
  next()
})

// a middleware sub-stack that handles GET requests to the /user/:id path
router.get('/user/:id', function (req, res, next) {
  // if the user ID is 0, skip to the next router
  if (req.params.id === '0') next('route')
  // otherwise pass control to the next middleware function in this stack
  else next()
}, function (req, res, next) {
  // render a regular page
  res.render('regular')
})

// handler for the /user/:id path, which renders a special page
router.get('/user/:id', function (req, res, next) {
  console.log(req.params.id)
  res.render('special')
})

// mount the router on the app
app.use('/', router)

另一个示例:

var app = express()
var router = express.Router()

// predicate the router with a check and bail out when needed
router.use(function (req, res, next) {
  if (!req.headers['x-auth']) return next('router')
  next()
})

router.get('/', function (req, res) {
  res.send('hello, user!')
})

// use the router and 401 anything falling through
app.use('/admin', router, function (req, res) {
  res.sendStatus(401)
})

错误处理中间件

app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

内置中间件

  • express.static serves static assets such as HTML files, images, and so on.
  • express.json parses incoming requests with JSON payloads. NOTE: Available with Express 4.16.0+
  • express.urlencoded parses incoming requests with URL-encoded payloads. NOTE: Available with Express 4.16.0+

官方支持的中间件列表:

第三方中间件

官方中间件资源:http://expressjs.com/en/resources/middleware.html

早期的 Express 内置了很多中间件。后来 Express 在 4.x 之后移除了这些内置中间件,官方把这些功能性中间件以包的形式单独提供出来。这样做的目的是为了保持 Express 本身极简灵活的特性,开发人员可以根据自己的需求去灵活的定制。下面是官方提供的一些常用的中间件解决方案。

Middleware module Description Replaces built-in function (Express 3)
body-parser Parse HTTP request body. See also: bodyco-body, and raw-body. express.bodyParser
compression Compress HTTP responses. express.compress
connect-rid Generate unique request ID. NA
cookie-parser Parse cookie header and populate req.cookies. See also cookies and keygrip. express.cookieParser
cookie-session Establish cookie-based sessions. express.cookieSession
cors Enable cross-origin resource sharing (CORS) with various options. NA
csurf Protect from CSRF exploits. express.csrf
errorhandler Development error-handling/debugging. express.errorHandler
method-override Override HTTP methods using header. express.methodOverride
morgan HTTP request logger. express.logger
multer Handle multi-part form data. express.bodyParser
response-time Record HTTP response time. express.responseTime
serve-favicon Serve a favicon. express.favicon
serve-index Serve directory listing for a given path. express.directory
serve-static Serve static files. express.static
session Establish server-based sessions (development only). express.session
timeout Set a timeout period for HTTP request processing. express.timeout
vhost Create virtual domains. express.vhost

中间件应用

输出请求日志中间件

功能:实现为任何请求打印请求日志的功能。

logger.js 定义并导出一个中间件处理函数:

module.exports = (req, res, next) => {
  console.log(`${req.method} -- ${req.path}`)
  next()
}

app.js 加载使用中间件处理函数:

app.use(logger)

统一处理静态资源中间件

功能:实现 express.static() 静态资源处理功能

static.js 定义并导出一个中间件处理函数:

const fs = require('fs')
const path = require('path')

module.exports = function static(pathPrefix) {
  return function (req, res, next) {
    const filePath = path.join(pathPrefix, req.path)
    fs.readFile(filePath, (err, data) => {
      if (err) {
        // 继续往后匹配查找能处理该请求的中间件
        // 如果找不到,则 express 会默认发送 can not get xxx
        return next()
      }
      res.end(data)
    })
  }
}

app.js 加载并使用 static 中间件处理函数:

// 不限定请求路径前缀
app.use(static('./public'))
app.use(static('./node_modules'))

// 限定请求路径前缀
app.use('/public', static('./public'))
app.use('/node_modules', static('./node_modules'))

错误处理

参考文档:

常用 API

参考文档:

express

  • express.json
  • express.static
  • express.Router
  • express.urlencoded()

Application

  • app.set
  • app.get
  • app.locals

Request

  • req.app
  • req.query
  • req.body
  • req.cookies
  • req.ip
  • req.hostname
  • Req.method
  • req.params
  • req.path
  • req.get()

Response

  • res.locals
  • res.append()
  • res.cookie()
  • res.clearCookie()
  • res.download()
  • res.end()
  • res.json()
  • res.jsonp()
  • res.redirect()
  • res.render()
  • res.send()
  • res.sendStatus()
  • res.set()
  • res.status()

Router

  • router.all()
  • router.METHOD()
  • router.use()
发布了296 篇原创文章 · 获赞 562 · 访问量 50万+

猜你喜欢

转载自blog.csdn.net/tichimi3375/article/details/82842691