NodeJS超长总结 3

11/7/2018 Node

# 🌹🌹 🐱 🐶 🐭 🐘 🐳 ✈️ 🚄 🚗 ⚽️ 💆 🥚 🧒 🌹 🐯

上次总结到了http的实现。。。。另开一章,继续总结记录


# koa


# 开始一个koa

# 1. 简介

  • Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。
  • 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。
  • Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

# 2. 安装

  1. node 7.6以上版本
  2. 低版本用bable编译
$ nvm install 7
$ npm i koa
$ node my-koa-app.js
1
2
3

# 3. 应用程序

# hellow world:

const Koa = require('koa'); // 引入
const app = new Koa(); // 创建实力

app.use(async ctx => { // 
  ctx.body = 'Hello World';
});

app.listen(3000); // 
1
2
3
4
5
6
7
8

# 级联

const Koa = require('koa');
const app = new Koa();

// logger

app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.get('X-Response-Time');
  console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});

// x-response-time

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
});

// response

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# app.listen(...)

创建并返回 HTTP 服务器,将给定的参数传递给 Server#listen():

const Koa = require('koa');
const app = new Koa();
app.listen(3000);
1
2
3

这里的 app.listen(...) 方法只是以下方法的语法糖:

const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);
1
2
3
4

这意味着您可以将同一个应用程序同时作为 HTTP 和 HTTPS 或多个地址:

const http = require('http');
const https = require('https');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);
https.createServer(app.callback()).listen(3001);
1
2
3
4
5
6

# app.callback()

返回适用于 http.createServer() 方法的回调函数来处理请求。你也可以使用此回调函数将 koa 应用程序挂载到 Connect/Express 应用程序中。

# app.use(function)

将给定的中间件方法添加到此应用程序

# app.keys=

设置签名的 Cookie 密钥。这些被传递给 KeyGrip,但是你也可以传递你自己的 KeyGrip 实例。

app.keys = ['im a newer secret', 'i like turtle'];
app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha256');
1
2

这些密钥可以倒换,并在使用 { signed: true } 参数签名 Cookie 时使用。

ctx.cookies.set('name', 'tobi', { signed: true });
1

# app.context

  • app.context 是从其创建 ctx 的原型。您可以通过编辑 app.context 为 ctx 添加其他属性。
  • 这对于将 ctx 添加到整个应用程序中使用的属性或方法非常有用,这可能会更加有效(不需要中间件)和/或 更简单(更少的 require()),而更多地依赖于ctx,这可以被认为是一种反模式。

例如,要从 ctx 添加对数据库的引用:

app.context.db = db();

app.use(async ctx => {
  console.log(ctx.db);
});
1
2
3
4
5

注意

  • ctx 上的许多属性都是使用 getter ,setter 和 Object.defineProperty() 定义的。你只能通过在 app.context 上使用 Object.defineProperty() 来编辑这些属性(不推荐)。
  • 安装的应用程序目前使用其父级的 ctx 和设置。 因此,安装的应用程序只是一组中间件。

# 错误处理

// 默认情况下,将所有错误输出到 stderr,除非 app.silent 为 true。 当 err.status 是 404 或 err.expose 是 true 时默认错误处理程序也不会输出错误。 要执行自定义错误处理逻辑,如集中式日志记录,您可以添加一个 “error” 事件侦听器:
app.on('error', err => {
  log.error('server error', err)
});
// 如果 req/res 期间出现错误,并且 _无法_ 响应客户端,Context实例仍然被传递:
app.on('error', (err, ctx) => {
  log.error('server error', err, ctx)
});
1
2
3
4
5
6
7
8

当发生错误 并且 仍然可以响应客户端时,也没有数据被写入 socket 中,Koa 将用一个 500 “内部服务器错误” 进行适当的响应。在任一情况下,为了记录目的,都会发出应用级 “错误”。

# 4.上下文 Context/ctx

  • Koa Context 将 node 的 request 和 response 对象封装到单个对象中。
  • Context/ctx就是req和res的封装
app.use(async ctx => {
  ctx; // 这是 Context 
  // 会自动将一些api委托给 ctx.request或者 koa Response
  // 例如 ctx.type 和 ctx.length 委托给 response 对象,ctx.path 和 ctx.method 委托给 request。
  ctx.request; // 这是 koa Request 请求报文
  ctx.response; // 这是 koa Response 相应报文
});
1
2
3
4
5
6
7

# 5.API Context/ctx 具体方法和访问器. 详细参考官方文档

  • ctx.req node的request对象
  • ctx.res node的response对象
  • ctx.request koa 的 Request 对象.
  • ctx.response koa 的 Response 对象.
  • ctx.state:
    • 推荐的命名空间,用于通过中间件传递信息和你的前端视图。
    • ctx.state.user = await User.find(id);
  • ctx.app 应用程序实例引用
  • ctx.cookies.get(name, [options])
    • 通过 options 获取 cookie name: signed 所请求的cookie应该被签名
    • koa使用cookie模块,其中只需传递参数
  • ctx.cookies.set(name, value, [options])

# 自己实现一个koa


  • koa使用方法
let Koa = require('./koa/application');
let app = new Koa();
let logger = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, 1000);
  })
}
// return或者await 都可以达到同样的效果
let fs = require('fs');
app.use((ctx, next) => {
  ctx.body = fs.createReadStream('./1.txt');
  // console.log(1);
  // //throw new Error('出错了');
  // return next(); // 这个函数是异步函数
  // console.log(2);
});
app.use(async (ctx, next) => {
  console.log(3);
  await logger();
  next();
  console.log(4);
});
app.use(async (ctx, next) => {
  console.log(5);
  await next();
  console.log(6);
});
app.on('error', err => {
  console.log(err);
})
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  • application.js
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');
let Stream = require('stream');
let EventEmitter = require('events');
class Koa extends EventEmitter{
  constructor(){
    super();
    this.middlewares = [];
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
  }
  use(fn){
    this.middlewares.push(fn);
  }
  createContext(req,res){
    // 上下文就是一个对象而已
    let ctx = this.context;
    // ctx上拥有两个自定义的属性 request response
    ctx.request = this.request;
    ctx.response = this.response;
    // req和res是自己身上的
    ctx.req = ctx.request.req = req;
    ctx.res = ctx.response.res = res;
    return ctx;
  }
  compose(ctx,middlewares){
    function dispatch(index) {
      if(index === middlewares.length) return Promise.resolve();
      let fn = middlewares[index];
      return Promise.resolve(fn(ctx,()=>dispatch(index+1)));
      }
    return dispatch(0);
  }
  handleRequest(req,res){
    let ctx = this.createContext(req,res);
    let p = this.compose(ctx,this.middlewares); // 把函数组合起来
    p.then(() => {
      let body = ctx.body;// 当所有的函数都执行完后取出body的值,响应回去即可
      console.log(body instanceof Stream);
      if (body instanceof Stream){
        body.pipe(res);
      }else if(Buffer.isBuffer(body) || typeof body === 'string'){
        res.end(body);
      }else if(typeof body === 'object' ){
        res.end(JSON.stringify(body));
      }else{
        res.end(body);
      }
    }).catch(err => {
      this.emit('error', err);
    });
  }
  listen(...args){
    let server = http.createServer(this.handleRequest.bind(this));
    server.listen(...args);
  }
}
module.exports = Koa;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
  • context.js
// 上下文对象
let proto = {
  
}

// ctx.path
function defineGetter(target,property) {
  proto.__defineGetter__(property,function () {
    return this[target][property]
  });
}
function defineSetter(target,property) {
  proto.__defineSetter__(property,function (value) {
    this[target][property] = value
  })
}
// ctx.path = ctx.request.path;
defineGetter('request','path');
defineGetter('request','query');
defineGetter('response','body');
// ctx.body = ctx.response.body;
defineSetter('response','body')

module.exports = proto;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  • request.js
let request = {
  // 类似于 Object.definProperty 属性中的get
  get url(){
    return this.req.url 
  },
  get path(){
    let {pathname} = require('url').parse(this.req.url);
    return pathname
  },
  get query(){
    let { query } = require('url').parse(this.req.url,true);
    return query
  },
  get headers() {
    return this.req.headers
  }
}


module.exports = request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • response.js
let response = {
  get body(){
    return this._body;
  },
  set body(value){
    this._body = value;
  }
}
response.body
module.exports = response
1
2
3
4
5
6
7
8
9
10

# 获取请求参数


const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => {
    console.log(ctx.method); //获取请求方法
    console.log(ctx.url);    //获取请求URL
    console.log(ctx.query);  //获取解析的查询字符串对象
    console.log(ctx.querystring); //根据 ? 获取原始查询字符串.
    console.log(ctx.headers);//获取请求头对象
    ctx.body = ctx.url;
});

app.listen(3000, () => {
    console.log('server is starting at port 3000');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 获取请求体


const Koa = require('koa');
const querystring = require('querystring');
const app = new Koa();
app.use(async (ctx) => {
    if (ctx.method == 'GET') {
        ctx.set('Content-Type', 'text/html;charset=utf-8');
        ctx.body = (
            `
            <form method="POST">
               <input name="username" >
               <input type="submit">
            </form>
            `
        );
    } else if (ctx.method == 'POST') {
        ctx.set('Content-Type', 'application/json');
        ctx.body = await parseBody(ctx);
    } else {
        ctx.body = 'Not Allowed';
    }
});
function parseBody(ctx) {
    return new Promise(function (resolve, reject) {
        let buffers = [];
        ctx.req.on('data', function (data) {
            buffers.push(data);
        });
        ctx.req.on('end', function (data) {
            let body = buffers.toString();
            body = querystring.parse(body);
            resolve(body);
        });
        ctx.req.on('error', function (errdata) {
            reject(err);
        });
    });
}

app.listen(3000, () => {
    console.log('server is starting at port 3000');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 模板引擎 koa-views


npm i koa-views ejs -S koa-views 第三方模块 专门实现 模板引擎的

# 用法

// koa-views 第三方模块 专门实现 模板引擎的
let Koa = require('koa');
let views = require('koa-views') 
let app = new Koa();
let path = require('path');
app.use(views(path.join(__dirname, 'views'), {
  extension: 'ejs' // 自己引入ejs
}));
app.use(async (ctx,next)=>{
  // render方法返回的是一个promise 如果不写await
  await ctx.render('index',{name:'zfpx'});
});

app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 自己实现 koa-views

let Koa = require('koa');
// let views = require('koa-views') 
let app = new Koa();
let path = require('path');
// 自己简单实现 
function views(dir,{extension}) {
  return async (ctx,next)=>{
    // ctx.render是一个promise方法
    ctx.render = async function (p,obj) {
      let realPath = path.join(dir, p) + '.'+ extension;
      let fs = require('fs');
      let util = require('util');
      let readFile = util.promisify(fs.readFile);
      let str = await readFile(realPath,'utf8');
      ctx.body = require(extension).render(str, obj)
    }
    await next();
  }
}
app.use(views(path.join(__dirname, 'views'), {
  extension: 'ejs' // 自己引入ejs
}));
app.use(async (ctx,next)=>{
  // render方法返回的是一个promise 如果不写await
  await ctx.render('index',{name:'zfpx'});
});
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 自己实现一个简单的ejs

先复习下正则

let path = require('path');
let fs = require('fs');
let r = fs.readFileSync(path.join(__dirname, './1.html'), 'utf8');
let obj = { arr: [1, 2, 3] };

function render(str,obj) {
  let head = `let tmpl=''\r\n`;
  head += `with (obj) {\r\n`
  let content = 'tmpl+=`\r\n';
  str = str.replace(/<%=([\s\S]*?)%>/g,function () {
    return '${'+arguments[1]+'}'
  })
  content +=str.replace(/<%([\s\S]*?)%>/g,function () {
      return '`\r\n'+arguments[1] +"\r\ntmpl+=`"
  })
  let tail = '`}\r\n return tmpl';
  return head + content + tail
}
r = render(r,obj);
let fnStr = new Function('obj',r);
let result = fnStr(obj);
fs.writeFile('./1.js',result)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 静态资源中间件 koa-static


npm install --save koa-static

# 用法

let Koa = require('koa');
// let static = require('koa-static');
let app = new Koa();
let path = require('path');
app.use(static(path.join(__dirname,'public')));
app.use( (ctx,next) => {
    ctx.body = 'hello world'
})
app.listen(3000);
1
2
3
4
5
6
7
8
9

# 自己简单实现

let Koa = require('koa');
let app = new Koa();
let path = require('path');
let fs = require('fs');
let {promisify} = require('util');
let stat = promisify(fs.stat);
function static(root) {
  return async (ctx,next)=>{
    let realPath = path.join(root,ctx.path);
    try{
      let statObj = await stat(realPath);
      if(statObj.isDirectory()){
        let p = path.join(realPath,'index.html');
        await stat(p);
        ctx.body = fs.createReadStream(p);
      }else{
        ctx.body = fs.createReadStream(realPath);
      }
    }catch(e){
      return next();
    }
  }
}
app.use(static(path.join(__dirname,'public')));
app.use( (ctx,next) => {
    ctx.body = 'hello world'
})
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 使用中间件获取普通请求体 koa-bodyparser


npm i koa-bodyparser -S

# 用法

const Koa = require('koa');
const querystring = require('querystring');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
app.use(bodyParser());
app.use(async (ctx) => {
    if (ctx.method == 'GET') {
        ctx.set('Content-Type', 'text/html;charset=utf-8');
        ctx.body = (
            `
            <form method="POST">
               <input name="username" >
               <input type="submit">
            </form>
            `
        );
    } else if (ctx.method == 'POST') {
        ctx.set('Content-Type', 'application/json');
        ctx.body = ctx.request.body;
    } else {
        ctx.body = 'Not Allowed';
    }
});

app.listen(3000, () => {
    console.log('server is starting at port 3000');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 自己实现

let Koa = require('koa');
let app = new Koa();
let fs = require('fs');
let path = require('path');
app.use(async (ctx, next) => {
  if (ctx.path === '/' && ctx.method === 'GET') {
    ctx.set('Content-Type', 'text/html;charset=utf8');
    ctx.body = fs.createReadStream(path.join(__dirname, '1.html'));
  } else {
    await next();
  }
});
function bodyParser(ctx) {
  return new Promise((resolve, reject) => {
    let arr = [];
    ctx.req.on('data', function (data) {
      arr.push(data);
    });
    ctx.req.on('end', function () {
      resolve(Buffer.concat(arr));
    })
  })
}
app.use(async (ctx, next) => {
  if (ctx.path === '/login' && ctx.method === 'POST') {
    console.log('提交来了');
    // a=b
    ctx.set('Content-Type', 'text/plain;charset=utf8');
    ctx.body = await bodyParser(ctx);
  }
})
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 使用中间件获取包含文件的请求体


npm i koa-better-body -S

const Koa = require('koa');
const querystring = require('querystring');
const path = require('path');
const convert = require('koa-convert');
const bodyParser = require('koa-better-body');
const app = new Koa();
app.use(convert(bodyParser({
    uploadDir: path.join(__dirname, 'uploads'),
    keepExtensions: true
})));
app.use(async (ctx) => {
    if (ctx.method == 'GET') {
        ctx.set('Content-Type', 'text/html;charset=utf-8');
        ctx.body = (
            `
            <form method="POST" enctype="multipart/form-data">
               <input name="username" >
               <input name="avatar" type="file" >
               <input type="submit">
            </form>
            `
        );
    } else if (ctx.method == 'POST') {
        ctx.set('Content-Type', 'application/json');
        console.log(ctx.request.fields);
        ctx.body = ctx.request.body;
    } else {
        ctx.body = 'Not Allowed';
    }
});

app.listen(3000, () => {
    console.log('server is starting at port 3000');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
    username: 'zfpx',
    avatar: [File {
        domain: null,
        _events: {},
        _eventsCount: 0,
        _maxListeners: undefined,
        size: 78540,
        path: '\%uploads\%upload_b631c6cbae762214afbe18b6e18d9f68.png',
        name: 'mm.png',
        type: 'image/png',
        hash: null,
        lastModifiedDate: 2018 - 03 - 09 T09: 12: 20.679 Z,
        _writeStream: [WriteStream]
    }]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# cookie


  • ctx.cookies.get(name,[optins]):读取上下文请求中的cookie。
  • ctx.cookies.set(name,value,[options]):在上下文中写入cookie。
    • domain:写入cookie所在的域名
    • path:写入cookie所在的路径
    • maxAge:Cookie最大有效时长
    • expires:cookie失效时间
    • httpOnly:是否只用http请求中获得
    • overwirte:是否允许重写
app.use(async (ctx, next) => {
    console.log(ctx.url);

    if (ctx.url == '/write') {
        ctx.cookies.set('name', 'zfpx');
        ctx.body = 'write';
    } else {
        next();
    }
});
app.use(async (ctx) => {
    if (ctx.url == '/read') {
        ctx.body = ctx.cookies.get('name');
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# session


$ npm install koa-session

const Koa = require('koa');
const session = require('koa-session');
const app = new Koa();
app.keys = ['zfpx'];
app.use(session({}, app));
app.use(async (ctx) => {
    let visit = ctx.session.visit;
    if (visit) {
        visit = visit + 1;
    } else {
        visit = 1;
    }
    ctx.session.visit = visit;
    ctx.body = `这是你的第${visit}次访问`;
});
app.listen(3000);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# generator


npm install -g koa-generator

$ koa /tmp/foo && cd /tmp/foo
$ npm install
$ npm start
1
2
3

# form-data


const Koa = require('koa');
const views = require('koa-views');
const fs = require('fs');
let querystring = require('querystring');
let path = require('path');
let uuid = require('uuid');
const app = new Koa();
app.use(async (ctx, next) => {
    if (ctx.method == 'GET') {
        ctx.set('Content-Type', 'text/html;charset=utf8');
        ctx.body = (
            `
                <form id="userform" method="POST" enctype="multipart/form-data">
                  用户名:<input type="text"  name="username"> 
                  密码<input type="text" name="password"> 
                  头像<input type="file" name="avatar">
                  <input type="submit">
                 </form>
                `
        );
    } else if (ctx.method == 'POST') {
        let buffers = [];
        ctx.req.on('data', function (data) {
            buffers.push(data);
        });
        ctx.req.on('end', function () {
            let result = Buffer.concat(buffers);
            let type = ctx.headers['content-type'];
            let matched = type.match(/\bboundary=(.+)\b/);
            if (matched) {
                let seperator = '--' + matched[1];
                let body = process(seperator, result);
                ctx.body = body;
            } else {
                next();
            }
        });
        ctx.body = 'hello';
    } else {
        next();
    }

});
app.listen(3000);
Buffer.prototype.split = Buffer.prototype.split || function (sep) {
    let len = Buffer.byteLength(sep);
    let parts = [];
    let offset = 0;
    let pos = -1;
    while (-1 != (pos = this.indexOf(sep, offset))) {
        parts.push(this.slice(offset, pos));
        offset = pos + len;
    }
    parts.push(this.slice(offset));
    return parts;
}
function process(seperator, result) {
    let lines = result.split(seperator);
    lines = lines.slice(1, -1);
    let body = {};
    let files = [];
    lines.forEach(function (line) {
        let [desc, val] = line.split('\r\n\r\n');
        desc = desc.toString();
        val = val.slice(0, -2);
        if (desc.includes('filename')) {//如果是文件的话
            let [, line1, line2] = desc.split('\r\n');
            let obj1 = querystring.parse(line1, '; ');
            let obj2 = querystring.parse(line2, '; ');
            let filepath = path.join(__dirname, 'uploads', uuid.v4());
            fs.writeFileSync(filepath, val);
            files.push({
                ...obj1, filepath
            });
        } else {
            let matched = desc.match(/\bname=(.+)\b/);
            if (matched)
                body[matched[1]] = val.toString();
        }
    });
    return { body, files };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
Last Updated: 1/7/2020, 7:42:12 AM
    asphyxia
    逆时针向