express
- express是nodejs最常用的web server 框架
安装express
安装脚手架
npm i express-generator -g
创建项目
express blog_express
cd blog-express
npm i
npm start
配置package.json
npm i nodemon cross-env -D
"scripts": {
"start": "node ./bin/www",
"dev":"cross-env NODE_ENV=development nodemon ./bin/www"
},
介绍app.js
var createError = require('http-errors'); 对错误页面的处理
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser'); 对cookie的解析 通过req.cookies就可以直接访问cookie
var logger = require('morgan'); 记录日志
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev')); 使用logger日志
app.use(express.json()); 可以获取到post json的数据 req.body获取
app.use(express.urlencoded({ extended: false })); 获取post除了json之外格式的数据 都是绑定到req.body上
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
express-router
// app.js
app.use('/api/blog', blogRouter);
app.use('/api/user', userRouter);
//router.js
var express = require('express');
var router = express.Router();
router.post('/login', function(req, res, next) {
const {username,password} = req.body;
res.json({
errno:0,
data:{
username,
password
}
})
});
router.get('/detail', function(req, res, next) {
res.json({
errno:0,
data:'ok'
})
});
post
请求的数据在req.body
上get
请求的数据在req.query
上
express-中间件
在 Node.js 中被广泛使用,它泛指一种特定的设计模式、一系列的处理单元、过滤器和处理程序,以函数的形式存在,连接在一起,形成一个异步队列,来完成对任何数据的预处理和后处理。
其实中间件就是请求req
和响应res
之间的一个应用,请求浏览器向服务器发送一个请求后,服务器直接通过request
定位属性的方式得到通过request
携带过去的数据,就是用户输入的数据和浏览器本身的数据信息,这中间就一定有一个函数将这些数据分类做了处理,最后让request
对象调用使用,这个或者多个的处理函数就是我们所说得中间件。
原理分析
- app.use用来注册中间件,先收集起来。
- 遇到http请求,根据path和method判断触发那些。
- 实现next机制,即上一个通过next触发下一个
实现
- 创建存放所有路由的对象
(all,get,post.。。。)
,后期对不同的请求处理都会放到这里 - 声明对应处理的函数
use(),get(),post()
,register
函数统一处理path
信息,返回{path:'xxx',stack:['每个path对应中间件函数数组']}
use(),get(),post()
,将自己的info
信息存储到this.routes
中listen
监听端口callback
是createServer
回调,获取当前url
的路由和method
match
返回当前url
所需要执行的所有中间件集合
handle
核心方法 实现next()
调用各个中间件
const http = require('http');
const slice = Array.prototype.slice;
class LikeExpress{
constructor(){
// 静态属性
this.routes = {
all:[],
get:[],
post:[]
}
}
// 原型上定义的函数
//中间件公用的地方 定义在register里面
register(path){
const info = {};
if(typeof path === 'string'){
//如果第一个参数是路由的话
info.path = path;
// stack 以数组的形式存放路由后面的中间件
info.stack = slice.call(arguments,1);
}else{
// 如果是app.use((req,res,next)=>{}) 没有第一个参数的形式
info.path = '/';
info.stack = slice.call(arguments,0);
}
console.log(`info is ${info}`);
return info;
}
use(){
const info = this.register.apply(this,arguments);
this.routes.all.push(info);
}
get(){
const info = this.register.apply(this,arguments);
this.routes.get.push(info);
}
post(){
const info = this.register.apply(this,arguments);
this.routes.post.push(info);
}
// 监听事件
listen(...args){
const server = http.createServer(this.callback());
server.listen(...args);
}
// 定义server的回调函数
callback(){
return (req,res)=>{
// 定义res.json的使用
res.json = (data)=>{
res.setHeader('Content-type','application/json');
res.end(
JSON.stringify(data)
)
}
const url = req.url;
const method = req.method.toLowerCase();
const resultList = this.match(method,url);
this.handle(req, res, resultList)
}
}
// match 当前url所需要执行的所以中间件集合
match(method,url){
let stack = [];
if(url === '/favicon.ico'){
return stack;
}
// 获取routes
let curRoutes =[];
// 当前路由匹配到的中间件:所有的 all 以及当前url的method对应所有中间件
curRoutes = curRoutes.concat(this.routes.all);
curRoutes = curRoutes.concat(this.routes[method]);
curRoutes.forEach(routeInfo =>{
if(url.indexOf(routeInfo.path) === 0){
// url === '/api/get-cookie' 且 routeInfo.path === '/'
// url === '/api/get-cookie' 且 routeInfo.path === '/api'
// url === '/api/get-cookie' 且 routeInfo.path === '/api/get-cookie'
// 当前路由在curRoutes 中匹配对应自己的所有中间件
stack = stack.concat(routeInfo.stack)
}
})
return stack
}
// 核心的 next机制
handle(req,res,stack){
const next = ()=>{
const middleware = stack.shift();
if(middleware){
middleware(req, res, next);
}
}
next();
}
}
module.exports = () => {
return new LikeExpress()
}
调用
const express = require('./like-express2');
const app = express();
app.use((req,res,next)=>{
console.log('请求开始....',req.method,req.url);
next();
})
app.use((req,res,next)=>{
console.log('处理 cookie');
req.cookie = {
userId:'abc123'
}
next();
})
app.use('/api',(req,res,next)=>{
console.log('处理 /api 路由');
next();
})
app.get('/api',(req,res,next)=>{
console.log('get 处理 /api 路由');
next();
})
// 模拟登录验证
function loginCheck(req,res,next){
setTimeout(() => {
console.log('模拟登录成功');
next();
});
}
app.get('/api/get-cookie',loginCheck,(req,res,next)=>{
console.log('get 处理 /api/get-cookie 路由');
res.json({
errno:0,
data:req.cookie
})
})
app.listen(8000,()=>{
console.log(`server listen running http://localhost:8000`);
})
//访问 http://localhost:8000/api/get-cookie
//请求开始.... GET /api/get-cookie
//处理 cookie
//处理 /api 路由
//get 处理 /api 路由
//模拟登录成功
//get 处理 /api/get-cookie 路由
morgan日志
' dev 开发模式 combined线上模式 '
const ENV = process.env.NODE_ENV
if(ENV !== 'production'){
// 开发环境
app.use(logger('dev',{
stream:process.stdout //默认值
}));
}else{
// 线上环境
const logFileName = path.join(__dirname,'logs','access.log');
const writeStream = fs.createWriteStream(logFileName,{
flags:'a'
})
app.use(logger('combined',{
stream:writeStream
}));
}