express nodejs Web应用开发框架
安装
- npm install express ejs body-parser --save
使用
//引用express模块,他是一个函数let express = require('express');//express函数执行后,返回的是一个http的监听函数app,就是http.createServer中的函数let app = express();//在app函数上扩展了一个listen的方法,可以监听端口app.listen(3000,()=>{ console.log(`服务启动在3000端口`)})//=>剖析app.listen都做了什么? 启动了一个http服务,把监听函数传进去,并且监听了端口。app.listen = (...args)=>{ require('http').createServer(this).listen(...args)}复制代码
主要功能:
- 路由控制
- 参数获取(req扩展的方法)
- 中间件
- send 和sendFile(res扩展的方法)
- 静态文件服务
- 模版解析
- 重定向
主要功能分析
/* app监听函数上,扩展了很多方法,包括get post put...等,restful风格中的动词; app.all("*",cb) 表示匹配任何动词 任何路径 app.方法名(路径,cb) => 匹配的是只是请求路径pathname,并且不包含?参数,而且是严格匹配; 特点:从上到下匹配,如果匹配到了并且结束响应,就不会继续向下继续走了。*/let express = require('express');let app = express();app.listen(3000,()=>{ console.log(`服务运行在3000端口`)})app.get('/signin',(req,res)=>{ res.setHeader('Content-Type','text/plain;charset=utf8') res.end('登录')})app.post('/signup',(req,res)=>{ res.setHeader('Content-Type','text/plain;charset=utf8'); res.end(post注册)})//all 表示所有动词方法, * 表示匹配任何路径。一般放到最后app.all('*',(req,res)=>{ res.end(404)})复制代码
/* req.query =>url.parse(req.url,true).query 取?参数 req.path = >url.parse(req.url,true).pathname 取路径 req.url //请求路径+?参数 req.headers //是一个对象,key和value全部是小写的 req.method //请求方法,全部是大写的 req.params //路径参数,后续会讲到 app.param('id',(req,res,next))根据路径参数拦截请求*/let express = require('express')let app = express();app.listen(3000)app.get('/user',(req,res)=>{ console.log(req.query.id) console.log(req.url) console.log(req.path) console.log(req.headers) console.log(req.method) if(req.query.id){ //查一个用户 }else{ //查询所有用户 }})--------//利用路径参数,区分是查一个用户还是查所有用户let express = require('express');let app = express();app.listen(3000)app.get('/user',(req,res)=>{ res.end('select All')})// /user/1/qiaoshi//id表示占位符,必须有,但是可以随便写;app.get('/user/:id/:name',(req,res)=>{ console.log(res.params) // {id:1,name:qiaoshi} res.end(`select one ${req.params.id}${req.params.name}`)})//js实现 提取路径参数;let url = '/user/1/xiaoming';let path = '/user/:id/:name';let keys = []let newPath = path.replace(/:([^\/]+)/g,function(...args){ keys.push(args[1]) return '([^\/]+)';})let reg = new RegExp(newPath);let values = reg.exec(url);let params = {};keys.forEach((key,index)=>{ params[key] = values[index+1]})console.log(params)---------//利用app.param中间件,拦截路径参数,进行过滤,还可以进行其他操作let express = require('express')let app =express();app.listen(3000)app.param('id',(req,res,next)=>{ req.params.id = `你的学号是${req.params.id}` //res.end(); · next();//调用next,就继续向下匹配;})app.param('name',(req,res,next)=>{ req.params.name = '你的名字是${req.params.name}' next()})app.get('/user/:id/:name',(req,res)=>{ res.header('Content-Type','text/plain;charset=utf-8') res.end(req.params.id + req.params.name)})复制代码
/*功能:1.可以进行权限判断 2.可以对req res的属性进行扩充 3.中间件放到最终目标钱穆庵 4.中间件默认情况下,匹配路径'/';也可以指定匹配以什么开头的,以目录级数为单位; 特殊:调用next传递了参数 =>错误中间件,四个参数=> app.use((req,res,next,err)=>{console.log(err)}) 如果res.end,关闭了响应,在调用next,会继续匹配,但是响应已经关闭,返回给了客户端,后续的匹配毫无意义;*/let express =require('express');let app = express();app.listen(3000);//app.use((req,res,next){})//不写路径,默认匹配'/',匹配任何路径;app.use('/water',(req,res,next)=>{ console.log('过滤石头'); req.stone = 'too big' next();//不调用next,就不会继续向下走})app.use('/water',function(req,res,next)=>{ console.log('过滤沙子'); req.sand = ' too small' next();})app.use((req,res,next)=>{ //利用中间件匹配所有,统一设置响应头编码和mime类型 res.setHeader('Content-Type','text/plain;charset=utf8'); next()})app.get('/water',(req,res)=>{ //会过滤 console.log('过滤完成') console.log(req.stone,req.sand) res.end('水')})app.get('/water/a',(req,res)=>{ //会过滤 console.log('过滤完成') console.log(req.stone,req.sand) res.end('水')})app.get('/food',function (req, res) { //不会通过 /water的中间件进行过滤 console.log(req.stone,req.sand) //undefined undefined res.end('食物')})//错误中间件,拥有四个参数,fn.length = 4; //触发:当前面的中间件 调用next传递了参数时,就认为报错了,就会执行错误中间件app.use((err,req,res,next)=>{ console.log(err)})复制代码
//中间件装饰模式-就是在已有方法的基础上包装一层,生成新的方法,但不影响已有方法的功能,同时还扩展了自己的js逻辑let express = require('express');let app = express();app.listen(3000)app.use('/eat',(req,res,next)=>{ let t = new Date().getTime(); let end = res.end; res.end = (...args)=>{ end.call(res,...args); console.log(new Date().getTime() - t)//计算每个接口请求的耗时 }})app.get('/eat/water',(req,res)=>{ for(let i = 0 ;i<10000;i++){ } res.end('water')})app.get('/eat/food',(req,res)=>{ for(let i = 0 ;i<10000000;i++){ } res.end('food')})复制代码
- js模拟express 中间件 next方法思路:
function app (){ }//每次调用use方法,都会将方法存到数组中;app.middleWares = []app.use = function(cb){ this.middleWares.push(cb)}app.use((req,res,next)=>{ console.log(1) next()})app.use((req,res,next)=>{ console.log(2) next()})app.use((req,res,next)=>{ console.log(3)})//默认调用数组第一项,将next方法传递给数组中的方法;如果调用next 会继续执行数组中的下一项let i = 0;function next(){ app.middleWares[i](null,null,next); i++;}next();复制代码
- res 扩展的方法 send sendFile json sendStatus
/*res.json({name:1}) =>res.end(JSON.stringify({name:1})) 以前只能写入buffer和字符串,现在直接json,而且不用考虑utf8编码问题res.sendStatus(200) = > res.statusCode = 200;res.end('ok') res.send() =>res.end() res.setHeader() //支持返回文本、json、状态吗等,最常用,不用考虑编码;res.sendFile() => fs读取流然后pie;// 直接返回文件,不过要填绝对路径;//sendFile不能通过../去查找上一级,root不支持。//如果需要返回上一级的文件,需要用path模块拼接绝对路径;path.join(__dirnmae,'..','index.html)//res.header('Content-Type','text/plain;charset=utf8'); =>res.setHeader() 设置响应头*/let express = require('express');let app = express();app.listen(3000);app.get('/json',(req,res)=>{ res.json({ name:'小明'})})app.get('/',(req,res)=>{ res.sendFile('./index.html',{ root:__dirname}); //等价于res.sendFile(__dirname+'/index.html') res.sendFile(require('path').join(__dirname,'..','index.html'))//上一级目录的文件})app.get('/status',(req,res)=>{ res.sendStatus(200) //OK 404 =>not found});//js模拟扩展实现send方法app.use(function(req,res,next){ req.mySend = function(data){ if(typeof data =='object'){ res.setHeader('Content-Type','application/json;charset=utf8') res.end(JSON.stringify(data)) return } if(typeof data =='string'){ res.setHeader('Content-Type','text/plain;charset=utf8') res.end(data) return } if(typeof data = 'number'){ res.statusCode = data; let _http_server = require('_http_server');// res.end(_http_server.STATUS_CODES[data])//状态码信息映射表 return } next() } })app.get('/send',(req,res)=>{ //res.send({name:'小明'})//send方法会自己判断传入的值是什么类型 //res.send(200) //res.send('中文') res.mySend(404)})复制代码