Express 是一个简洁、灵活的 node.js Web 应用开发框架, 它提供一系列强大的特性,帮助你创建各种 Web 和移动设备应用。丰富的 HTTP 快捷方法和任意排列组合的 Connect 中间件,让你创建健壮、友好的 API 变得既快速又简单。此文章会讲解express常用方法以及实现的原理.
1.Express介绍
Express 是一个小巧且灵活的 Node.js Web应用框架,它有一套健壮的特性,可用于开发单页、多页和混合Web应用。
2.express的应用
2.1 安装express
- npm安装1$ npm install express
2.2 使用express
- 创建http服务123456//引入expressvar express = require('express');//执行express**函数var app = express();//监听端口app.listen(3000);
2.3 express的get方法
- 根据请求路径来处理客户端发出的GET请求
- 第一个参数path为请求的路径
- 第二个参数为处理请求的回调函数1app.get(path,function(req, res));
2.3.1 get方法的使用
- get方法12345678910111213141516//引入expressvar express = require('./express');//执行express函数var app = express();//监听端口app.get('/hello', function (req,res) {res.end('hello');});app.get('/world', function (req,res) {res.end('world');});app.get('*', function (req,res) {res.setHeader('content-type','text/plain;charset=utf8');res.end('没有找到匹配的路径');});app.listen(3000);
2.3.2 get方法实现
|
|
2.3.3 增加 * 匹配
- 使用 * 匹配所有路径1234var route = app.routes.find(function (item) {- return item.path==pathname&&item.method==method;+ return (item.path==pathname||item.path=='*')&&item.method==method;});
2.4 express的post方法
- 根据请求路径来处理客户端发出的POST请求
- 第一个参数path为请求的路径
- 第二个参数为处理请求的回调函数1app.post(path,function(req,res));
2.4.1 post的方法使用
post方法的使用
123456789101112//引入expressvar express = require('./express');//执行express函数var app = express();//监听端口app.post('/hello', function (req,res) {res.end('hello');});app.post('*', function (req,res) {res.end('post没找到');});app.listen(3000);通过linux命令发送post请求
1$ curl -X POST http://localhost:3000/hello
2.4.2 post的实现
- 增加所有请求的方法123456var methods = ['get','post','delete','put','options'];methods.forEach(function (method) {app[method] = function (path,fn) {app.routes.push({method:method,path:path,fn:fn});};});
2.5 express的all方法
- 监听所有的请求方法,可以匹配所有的HTTP动词
- 根据请求路径来处理客户端发出的所有请求
- 第一个参数path为请求的路径
- 第二个参数为处理请求的回调函数 1app.all(path,function(req, res));
2.5.1 all的方法使用
- all方法123456789var express = require('./express');var app = express();app.all('/hello', function (req,res) {res.end('hello');});app.all('*', function (req,res) {res.end('没找到');});app.listen(3000);
2.5.2 注册所有方法
- 增加all方法匹配所有method 1+ var methods = ['get','post','delete','put','options','all'];
2.5.3 all方法的实现
- 对all方法进行判断1234var route = app.routes.find(function (item) {- return (item.path==pathname||item.path=='*')&&item.method==method;+ return (item.path==pathname||item.path=='*')&&(item.method==method||method=='all');});
2.6 中间件
中间件就是处理HTTP请求的函数,用来完成各种特定的任务,比如检查用户是否登录、检测用户是否有权限访问等,它的特点是:
- 一个中间件处理完请求和响应可以把相应数据再传递给下一个中间件
- 回调函数的next参数,表示接受其他中间件的调用,函数体中的next(),表示将请求数据继续传递
- 可以根据路径来区分返回执行不同的中间件
2.6.1 中间件的使用方法
- 增加中间件1234567891011121314var express = require('express');var app = express();app.use(function (req,res,next) {console.log('过滤石头');next();});app.use('/water', function (req,res,next) {console.log('过滤沙子');next();});app.get('/water', function (req,res) {res.end('water');});app.listen(3000);
2.6.2 use方法的实现
在路由数组中增加中间件
1234567app.use = function (path,fn) {if(typeof fn !='function'){fn = path;path = '/';}app.routes.push({method:'middle',path:path,fn:fn});}app方法中增加Middleware判断
123456789101112131415161718192021222324252627- var route = app.routes.find(function (item) {- return item.path==pathname&&item.method==method;- });- if(route){- route.fn(req,res);- }var index = 0;function next(){if(index>=app.routes.length){return res.end(`CANNOT ${method} ${pathname}`);}var route = app.routes[index++];if(route.method == 'middle'){if(route.path == '/'||pathname.startsWith(route.path+'/')|| pathname==route.path){route.fn(req,res,next)}else{next();}}else{if((route.path==pathname||route.path=='*')&&(route.method==method||route.method=='all')){route.fn(req,res);}else{next();}}}next();
2.6.3 错误中间件
- next中可以传递错误,默认执行错误中间件123456789101112131415161718var express = require('express');var app = express();app.use(function (req,res,next) {console.log('过滤石头');next('stone is too big');});app.use('/water', function (req,res,next) {console.log('过滤沙子');next();});app.get('/water', function (req,res) {res.end('water');});app.use(function (err,req,res,next) {console.log(err);res.end(err);});app.listen(3000);
2.6.4 错误中间件的实现
- 对错误中间件进行处理123456789101112131415161718192021222324252627function next(err){if(index>=app.routes.length){return res.end(`CANNOT ${method} ${pathname}`);}var route = app.routes[index++];+ if(err){+ if(route.method == 'middle'&&route.fn.length==4){+ route.fn(err,req,res,next);+ }else{+ next(err);+ }+ }else{if(route.method == 'middle'){if(route.path == '/'||pathname.startsWith(route.path+'/')|| pathname==route.path){route.fn(req,res,next)}else{next();}}else{if((route.path==pathname||route.path=='*')&&(route.method==method||route.method=='all')){route.fn(req,res);}else{next();}}+ }}
2.7 获取参数和查询字符串
- req.hostname 返回请求头里取的主机名
- req.path 返回请求的URL的路径名
- req.query 查询字符串12345//http://localhost:3000/?a=1app.get('/',function(req,res){res.write(JSON.stringify(req.query))res.end(req.hostname+" "+req.path);});
2.7.1 具体实现
- 对请求增加方法123+ req.path = pathname;+ req.hostname = req.headers['host'].split(':')[0];+ req.query = urlObj.query;
2.8 获取params参数
- req.params 匹配到的所有路径参数组成的对象1234app.get('/water/:id/:name/home/:age', function (req,res) {console.log(req.params);res.end('water');});
2.8.1 params实现
- 增加params属性1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556methods.forEach(function (method) {app[method] = function (path,fn) {var config = {method:method,path:path,fn:fn};if(path.includes(":")){//是路径参数 转换为正则//并且增加paramsvar arr = [];config.path = path.replace(/:([^\/]+)/g, function () {arr.push(arguments[1]);return '([^\/]+)';});config.params = arr;}app.routes.push(config);};});+ if(route.params){+ var matchers = pathname.match(new RegExp(route.path));+ if(matchers){+ var params = {};+ for(var i = 0; i<route.params.length;i++){+ params[route.params[i]] = matchers[i+1];+ }+ req.params = params;+ route.fn(req,res);+ }else{+ next();+ }+}else{if((route.path==pathname||route.path=='*')&&(route.method==method||route.method=='all')){route.fn(req,res);}else{next();}+}//如果不等于就是路由if(item.params){//带paramsvar arr = pathname.match(new RegExp(item.path));if(arr){var obj = {};for(var i = 1; i<=item.params.length;i++){obj[item.params[i-1]] = arr[i];}req.params = obj;item.fn(req,res);}else{next();}}else{if(item.path==pathname||item.path=='*'&&item.method ==method){item.fn(req,res);}else{next();}}
2.9 express中的send方法
参数为要响应的内容,可以智能处理不同类型的数据,在输出响应时会自动进行一些设置,比如HEAD信息、HTTP缓存支持等等
1res.send([body]);当参数是一个字符串时,这个方法会设置Content-type为
text/html
123app.get('/', function (req,res) {res.send('<p>hello world</p>');});当参数是一个Array或者Object,这个方法返回json格式
123456app.get('/json', function (req,res) {res.send({obj:1});});app.get('/arr', function (req,res) {res.send([1,2,3]);});当参数是一个number类型,这个方法返回对应的状态码短语
1234app.get('/status', function (req,res) {res.send(404); //not found//res.status(404).send('没有找到');设置短语});
2.9.1 send方法的实现
- 自定义send方法 12345678910111213res.send = function (msg) {if(typeof msg=='string'||Buffer.isBuffer(msg)){res.contentType('text/html');res.end(msg);}else if(typeof msg=='object'){res.contentType('application/json');res.end(JSON.stringify(msg));}else if(typeof msg == 'number'){res.contentType('text/plain');var status_code = require('_http_server').STATUS_CODESres.end(status_code[msg]);}};
3. 模板的应用
3.1 安装ejs
- npm安装ejs1$ npm install ejs
3.2 设置模板
- 使用ejs模版123456var express = require('express');var path = require('path');var app = express();app.set('view engine','ejs');app.set('views',path.join(__dirname,'views'));app.listen(3000);
3.3 渲染html
- 配置成html格式123app.set('view engine','html')app.set('views',path.join(__dirname,'views'));app.engine('html',require('ejs').__express);
3.4 渲染视图
- 第一个参数 要渲染的模板
- 第二个参数 渲染所需要的数据123app.get('/', function (req,res) {res.render('hello',{title:'hello'},function(err,data){});});
3.5 模板的实现
- 读取模版渲染1234567891011121314res.render = function (tmpl,obj,fn) {tmpl = tmpl+(tmpl.endsWith('.ejs')?'':'.ejs');var filepath = path.join(app.get('views'),tmpl);fs.readFile(filepath,'utf8', function (err,data) {data = data.replace(/<%=(\w+)%>/g,function () {return obj[arguments[1]];});if(typeof fn =='function'){fn(err,data);}else{res.end(data);}})};
4.静态文件服务器
- 如果要在网页中加载静态文件(css、js、img),就需要另外指定一个存放静态文件的目录,当浏览器发出非HTML文件请求时,服务器端就会到这个目录下去寻找相关文件12345var express = require('express');var app = express();var path = require('path');app.use(express.static(path.join(__dirname,'public')));app.listen(3000);
4.1 静态文件服务器实现
- 配置静态服务器12345678910function static(p){return function (req,res,next) {var exists = fs.existsSync(p);if(exists){fs.createReadStream(path.join(p,req.path)).pipe(res);}else{next();}}}
5.重定向
- redirect方法允许网址的重定向,跳转到指定的url并且可以指定status,默认为302方式。
- 参数1 状态码(可选)
- 参数2 跳转的路径1res.redirect([status], url);
5.1 redirect使用
- 使用重定向123app.get('/', function (req,res) {res.redirect('http://www.baidu.com')});
5.2 redirect的实现
- 302重定向123456789app.use(function (req,res,next) {res.redirect = function (url) {console.log(url);res.statusCode = 302;res.setHeader('Location','http://www.baidu.com');res.end('');};next();});
6. 接收post响应体
- 安装body-parser1$ npm install body-parser
6.1 使用body-parser
- 接收请求体中的数据12345678app.get('/login', function (req,res) {res.sendFile('./login.html',{root:__dirname})});app.post('/user', function (req,res) {console.log(req.body);res.send(req.body);});app.listen(3000);
6.2 req.body的实现
- 实现bodyParser12345678910111213141516function bodyParser () {return function (req,res,next) {var result = '';req.on('data', function (data) {result+=data;});req.on('end', function () {try{req.body = JSON.parse(result);}catch(e){req.body = require('querystring').parse(result);}next();})}};