node-express的使用

2,182 阅读15分钟

一、什么是express?

Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架它提供一系列强大的特性,帮助你创建各种 Web 和移动设备应用。

二、EXPRESS特点

  • Web 应用程序 Express 是一个保持最小规模的灵活的 Node.js Web 应用程序开发框架,为 Web 和移动应用程序提供一组强大的功能。
  • API 使用您所选择的各种 HTTP 实用工具和中间件,快速方便地创建强大的 API。
  • 性能 Express 提供精简的基本 Web 应用程序功能,而不会隐藏您了解和青睐的 Node.js 功能。

三、快速入门

安装EXPRESS

首先假设您已经安装了node, 新建一个项目目录 mkdir myExpress ,
cd myExpress npm init 接下来在 myapp 目录下安装 Express 并将其保存到依赖列表中 npm install express --save

以上命令会将 Express 框架安装在当前目录的 node_modules 目录中, node_modules 目录下会自动创建 express 目录。以下几个重要的模块是需要与 express 框架一起安装的:

  • body-parser  body-parser是一个HTTP请求体解析中间件,用于处理 JSON, Raw, Text 和 URL 编码的数据。 bodyParser.json()--解析JSON格式 ---bodyParser.raw()--解析二进制格式 ---bodyParser.text()--解析文本格式 ---bodyParser.urlencoded()-- 创建 application/x-www-form-urlencoded 解析,formData格式的解析

  • cookie-parser  这就是一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。

  • multer  node.js 中间件,用于处理 enctype='multipart/form-data'(设置表单的MIME编码)的表单数据。

npm install body-parser cookie-parser multer 

3.1 第一个例子Hello world

新建index.js配置文件,代码如下 创建一个Express应用。express()是一个express模块导出的入口函数

const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(3003, () => console.log('Example app listening on port 3003!'))

3.2 请求和响应

Express 应用使用回调函数的参数: request 和 response 对象来处理请求和响应的数据。

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

request 和 response 对象的具体介绍:

3.2.1 Request 对象 

request 对象表示 HTTP 请求,包含了请求查询字符串,参数,内容,HTTP 头部等属性。常见属性有:

req.app:当callback为外部文件时,用req.app访问express的实例
req.baseUrl:获取路由当前安装的URL路径
req.body / req.cookies:获得「请求主体」/ Cookies
req.fresh / req.stale:判断请求是否还「新鲜」
req.hostname / req.ip:获取主机名和IP地址
req.originalUrl:获取原始请求URL
req.params:获取路由的parameters
req.path:获取请求路径
req.protocol:获取协议类型
req.query:获取URL的查询参数串
req.route:获取当前匹配的路由
req.subdomains:获取子域名
req.accepts():检查可接受的请求的文档类型
req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages:返回指定字符集的第一个可接受字符编码
req.get():获取指定的HTTP请求头
req.is():判断请求头Content-Type的MIME类型

3.2.2 Response 对象

response 对象表示 HTTP 响应,即在接收到请求时向客户端发送的 HTTP 响应数据。常见属性有:

res.app:同req.app一样
res.append():追加指定HTTP头
res.set()在res.append()后将重置之前设置的头
res.cookie(name,value [,option]):设置Cookie
opition: domain / expires / httpOnly / maxAge / path / secure / signed
res.clearCookie():清除Cookie
res.download():传送指定路径的文件
res.get():返回指定的HTTP头
res.json():传送JSON响应
res.jsonp():传送JSONP响应
res.location():只设置响应的Location HTTP头,不设置状态码或者close response
res.redirect():设置响应的Location HTTP头,并且设置状态码302
res.render(view,[locals],callback):渲染一个view,同时向callback传递渲染后的字符串,如果在渲染过程中有错误发生next(err)将会被自动调用。callback将会被传入一个可能发生的错误以及渲染后的页面,这样就不会自动输出了。
res.send():传送HTTP响应
res.sendFile(path [,options] [,fn]):传送指定路径的文件-会自动根据文件extension设定Content-Type
res.set():设置HTTP头,传入object可以一次设置多个头
res.status():设置HTTP状态码
res.type():设置Content-Type的MIME类型

四、编写用于Express应用程序的中间件

中间件函数是能够访问请求对象(req)、响应对象(res)和应用程序请求-响应周期中的next函数的函数。next函数是Express路由器中的一个函数,当调用该函数时,它将执行当前中间件之后的中间件。

中间件功能可以执行以下任务:

  • 执行任何代码。
  • 更改请求和响应对象。
  • 结束请求-响应循环。
  • 调用堆栈中的下一个中间件。

如果当前中间件函数没有结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件函数。否则,请求将挂起。

从版本4开始。x, Express不再依赖于Connect。以前包含在Express中的中间件功能现在位于单独的模块中

第三方中间件

4.1 cookie-parser

  • cookie设置:使用Express的内置方法res.cookie。
  • cookie解析:使用cookie-parser中间件。
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
const cors = require('cors'); 

// app.use(cookieParser()); //不签名

// 初始化中间件,传入的第一个参数为singed secret
app.use(cookieParser('123456')); //签名
  //使用cors跨域中间件
//   app.use(cors());
  app.use(proxy)

app.use(function (req, res, next) {
  console.log(req.cookies.username); 
  console.log(req.signedCookies.username);
  // 传入第三个参数 {signed: true},表示要对cookie进行摘要计算
  res.cookie('username', 'laney',{ maxAge: 1000*60*60*60,signed: true});
  next();
});

app.get('/info', (req, res) =>{ 
    res.send('Hello World!');

    console.log(req.cookies.username); // 第二次访问,输出laney
});

app.listen(3000);

服务器可能需要拿到 Cookie,这时需要服务器显式指定Access-Control-Allow-Credentials字段, 告诉浏览器可以发送 Cookie

同时,开发者必须在 AJAX 请求中打开withCredentials属性。

res.setHeader('Access-Control-Allow-Credentials', 'true');
true 允许携带cookie
false 不携带

需要注意的是,如果服务器要求浏览器发送 Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨域)原网页代码中的document.cookie也无法读取服务器域名下的 Cookie。

function proxy(req, res,next){

            //跨域处理
            // 注意: http://192.168.1.29:8080为静态资源访问的地址
            res.setHeader('Access-Control-Allow-Origin', 'http://192.168.1.29:8080'); 
    //      res.setHeader('Access-Control-Allow-Origin', '*');  //允许任何源
            //允许任何源 ,如果服务器要求浏览器发送 Cookie,这是不能设置为*
    
            res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');  //允许任何方法
            // res.setHeader('Access-Control-Allow-Methods', '*'); 
    
             res.setHeader('Access-Control-Allow-Headers', 'X-Token,Content-Type')
            // res.setHeader('Access-Control-Allow-Headers', '*');   //允许任何类型

            res.setHeader('Access-Control-Allow-Credentials', 'true');
    
            next();  //next 方法就是一个递归调用
    
    }

是否使用了axios?

axios默认是不允许设置cookie的,所以如果用了,需要你单独启动一下全局设置 axios.defaults.withCredentials = true;//让ajax携带cookie 在入口的页面设置即可全局使用。

axios.defaults.withCredentials = true;  //true 允许携带cookie,  false 不携带

node后台启动时是否设置了允许携带cookie? res.header('Access-Control-Allow-Credentials', 'true');

作用:方便操作客户端中的cookie值。

  1. 安装cookie-parser第三方cookie操作模块
yarn add cookie-parser

  1. 引入
const cookieParser = require('cookie-parser');
app.use(cookieParser('123456')); //使用cookie中间件,传入签名123456进行加密
  1. 设置cookie,需要设置signed签名
res.cookies('key','value',option)

其中option要求是要json格式:有以下选项

  • domain: 域名。设置子域名(二级域名)是否可以访问cookie。 例:domain:'.主域名' name=value:键值对,可以设置要保存的 Key/Value,注意这里的 name 不能和其他属性项的名字一样
  • expires: 过期时间(秒),在设置的某个时间点后该 Cookie 就会失效,如 expires=Wednesday, 09-Nov-99 23:12:40 GMT
  • maxAge: 最大失效时间(毫秒),设置在多少后失效
  • secure: 当 secure 值为 true 时, cookie 在 HTTP 中是无效,在 HTTPS 中才有效
  • path: 表示 cookie 影响到的路由,如 path=/。如果路径不能匹配时,浏览器则不发送这个 Cookie
  • httpOnly:默认为false,建议设置为true, 客户端将无法通过document.cookie读取到 COOKIE 信息,可防止 XSS 攻击产生
  • signed: 表示是否签名(加密) cookie, 设为 true 会对这个 cookie 签名,这样就需要用res.signedCookies 访问它,前提需要设置上面中间件app.use传参 。 未签名则用 res.cookies 访问
  • 被篡改的签名 cookie 会被服务器拒绝,并且 cookie值会重置为它的原始值
res.cookie('cart', { items: [1,2,3] }, { maxAge: 10000*4,signed:true,httpOnly:true });
res.cookie('username', "laney", { maxAge: 10000*2,signed:true});
  1. 获取cookie
//签名的cookie:
console.log(req.signedCookies);
console.log(req.signedCookies.username);

//没签名的cookie
console.log(req.cookies.username); 

4.2 cookie签名的作用

主要是出于安全考虑,防止cookie被篡改,增强安全性

基于前面的例子展开。假设网站通过nick这个cookie来区分当前登录的用户是谁。在前面例子中,登录用户的cookie中,username对应的值如下:(decode后的)

s:laney.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0 此时,有人试图修改这个cookie值,来达到伪造身份的目的。比如修改成xiaoming:

s:xiaoming.uVofnk6k+9mHQpdPlQeOfjM8B5oa6mppny9d+mG9rD0 当网站收到请求,对签名cookie进行解析,发现签名验证不通过。由此可判断,cookie是伪造的。

仅通过username这个cookie的值来判断登录的是哪个用户,这是一个非常糟糕的设计。虽然在秘钥未知的情况下,很难伪造签名cookie。但用户名相同的情况下,签名也是相同的。这种情况下,其实是很容易伪造的。

另外,开源组件的算法是公开的,因此秘钥的安全性就成了关键,要确保秘钥不泄露。

五、路由

路由是指确定应用程序如何响应到特定端点的客户机请求,该端点是URI(或路径)和特定的HTTP请求方法(GET、POST等)。 每个路由可以有一个或多个处理程序函数,这些函数在匹配路由时执行。

app.METHOD(PATH, HANDLER)

说明: app是express的一个实例。 METHOD是HTTP请求方法(小写),比如post,get,put等。 PATH是服务器上的路径。 HANDLER是在匹配路由时执行的函数。

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

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

5.1 路由处理程序 -- Route handlers

您可以提供多个回调函数,它们的行为类似于中间件来处理请求。唯一的例外是,这些回调可能调用next('route')来绕过剩余的路由回调。您可以使用此机制对路由施加前置条件,然后如果没有理由继续当前路由,则将控制权传递给后续路由。

app.get('/example/b', function (req, res, next) {
  console.log('the response will be sent by the next function ...')
  next()
}, function (req, res) {
  res.send('Hello from B!')
})

5.2 响应头方法 -- Response methods

下表中响应对象(res)上的方法可以向客户机发送响应,并终止请求-响应周期。如果没有从路由处理程序调用这些方法,客户机请求将挂起。

方法描述
res.download()提示要下载的文件。
res.end()结束响应过程。
res.json()发送一个JSON响应
res.jsonp()发送一个支持JSONP的JSON响应。
res.redirect()重定向请求
res.render()呈现视图模板
res.send()发送各种类型的响应
res.sendFile()以流的形式发送文件
res.sendStatus()设置响应状态代码,并将其字符串表示形式作为响应体发送

5.3 app.route()

您可以使用app.route()为路由路径创建可链接的路由处理程序。由于路径是在单个位置指定的,因此创建模块化路由很有帮助,减少冗余和拼写错误也是如此。

app.route('/book')
  .get(function (req, res) {
    res.send('Get a random book')
  })
  .post(function (req, res) {
    res.send('Add a book')
  })
  .put(function (req, res) {
    res.send('Update the book')
  })

5.4 可挂载路由处理程序 -- express.Router

使用express来创建模块化的可挂载路由处理程序。路由器实例是一个完整的中间件和路由系统;因此,它经常被称为“迷你应用程序”。 下面的示例创建一个路由器作为模块,在其中加载一个中间件函数,定义一些路由,并将路由器模块挂载在主应用程序中的路径上。 在app目录中创建一个名为birds.js的路由器文件,内容如下:

var express = require('express');
var router = express.Router();

// 路由器的中间件
router.use(function timeLog (req, res, next) {
  console.log('Time: ', Date.now())
  next()
})
// 首页路由
router.get('/home', function (req, res) {
  res.send('Birds home page')
})
// 关于我们
router.get('/about', function (req, res) {
  res.send('About birds')
})
module.exports = router;

然后在app中加载路由器模块

var birds = require('./birds')
// ...
app.use('/birds', birds)

该应用程序现在将能够处理/birds/home和/birds/about的请求,以及调用特定于路由的timeLog中间件功能。

六、利用 Express 托管静态文件

Express 提供了内置的中间件 express.static 来设置静态文件如:图片, CSS, JavaScript 等。 你可以使用 express.static 中间件来设置静态文件路径。例如,如果你将图片, CSS, JavaScript 文件放在 public 目录下,你可以这么写:

app.use(express.static('public'));

现在,你就可以访问 public 目录中的所有文件了:

http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css

Express 在静态目录查找文件,因此,存放静态文件的目录名不会出现在 URL 中。 如果要使用多个静态资源目录,请多次调用 express.static 中间件函数:

app.use(express.static('public'))
app.use(express.static('files'))

为express提供的文件创建虚拟路径前缀(其中路径实际上并不存在于文件系统中)。静态函数,为静态目录指定一个挂载路径,如下所示:

app.use('/static', express.static('public'))

现在,你就可以通过带有 /static 前缀地址来访问 public 目录中的文件了。

http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css

如果您从另一个目录运行express应用程序,使用您想要服务的目录的使用path模块更更安全 app.use('/static', express.static(path.join(__dirname, 'public')))

七、Express 应用程序生成器

通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。 express-generator 包含了 express 命令行工具。通过如下命令即可局部安装:

安装:yarn add express-generator

当然你也可以选择全局安装, 但是配置相对麻烦点

注意:如果选择全局安装,express 4.0以上的 版本需要同时全局安装express和express-generator,需要这2个安装后还是提示以下错误,就需要配置环境变量
win10安装express后仍报错:'express' 不是内部或外部命令,也不是可运行的程序 或批处理文件
修改完,一定记得重启控制台服务
环境变量的配置参考:https://blog.csdn.net/m0_37750720/article/details/82937463

当使用npm安装一些全局的软件包时,不知道安装到了什么位置,可以使用命令 npm root -g 进行查询,通常默认会保存在以下位置:

-h 参数可以列出所有可用的命令行参数: npx express -h 使用express命令创建项目

npx express --view=ejs myapp
npx express --view=ejs --css=less myapp   //支持less的预加载写法

命令创建了一个名称为 myapp 的 Express 应用。此应用将在当前目录下的 myapp 目录中创建,并且设置为使用 ejs 模板引擎(view engine)

然后安装所有依赖包:

cd myapp
npm install
node bin/www

如果要实时监听配置文件可以使用 nodemon bin/www,前提时候需要全局安装 nodemon

然后在浏览器中打开 http://localhost:3000/ 网址就可以看到这个应用了。

通过生成器创建的应用一般都有如下目录结构:
├── app.js   //入口文件(主文件) 总路由 (其他的路由 要由它来分配)
├── bin   //启动目录 里面包含了一个启动文件 www 默认监听端口是 3000 (不用)
│   └── www
├── package.json  //包描述文件  最重要的是 依赖的模板列表 dependencies
                        //依赖列表里面的所有模板 可以通过 cnpm i  一次性全部安装
├── public   //所有的前端静态资源  html css image  js
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
放的是 路由 文件 (默认有两个)
            路由主要定义 url 和 资源 的映射关系 ( 一一对应关系 )
            主要用来接收前端发送的请求 响应数据给前端

│   ├── index.js
│   └── users.js
└── views   //主要放置 ejs 后端模板文件
    ├── error.pug
    ├── index.pug
    └──  layout.pug
node_modules:  //所有安装的依赖模块 都在这个文件夹里面

通过 Express 应用生成器创建应用只是众多方法中的一种。你可以不使用它,也可以修改它让它符合你的需求。

通过pm2启动项目,安装:

npm install pm2 -g

pm2 start app.js 注意:pm2是在后台启动的,还可以操作其他命令

7.1 使用Express的模板引擎

模板引擎允许您在应用程序中使用静态模板文件。在运行时,模板引擎用实际值替换模板文件中的变量,并将模板转换为发送给客户机的HTML文件。这种方法使设计HTML页面变得更容易。

使用Express的一些流行模板引擎是Pug、Mustache和EJS。Express应用程序生成器使用Jade作为默认值,但它还支持其他一些功能。

npm install ejs--save app.set('views', '.views');  //指定视图目录 app.set('view engine', ejs) // 注册模板引擎

八、仿写一个简版Express

1. 体验express

const express = require('./common/myexpress.js');
const app = express();
app.get('/',(req,res) => {
    res.end('Hello world')
})
app.get('/users',(req,res) => {
    res.end(JSON.stringify({name:'abcooooo'}))
})
app.get('/list',(req,res) => {
    res.end(JSON.stringify({name:'list'}))
})
app.listen(3200 , () => {
    console.log('Example listen at 3200')
})

2. 实现myexpress

const http = require('http');
const url = require('url');
let routers = [];
class Application {
    get(path,hander){
        routers.push({
            path,
            method:'get',
            hander
        });
    }
    listen(){
        const server = http.createServer(function(req,res){
            const {pathname} = url.parse(req.url,true);
            const {method,headers} = req;
            console.log(method);
            //在路由表routers通过pathname,找到回调,然后执行   
            var tet = routers.find(v=>{
            return  v.path == pathname && req.method.toLowerCase()==v.method
        })
          tet && tet.hander(req,res); 
          
         })
         //在Application原型上添加listen方法匹配路径, 执行对应的hander
       
         server.listen(...arguments)
        //  server.listen(3200,() => {
        //     console.log('Example listen at 3200')
        // })
       
    }
    
}
module.exports = function(){
    return new Application();
}