koa-bodyparser 实现

请求代理上下文context实现

前言

狭义中间件的上下文代理,除了在实例化 let app = new Koa() 的时候将属性或者方法挂载到app.context 中,供后续中间件使用。另外一种方式是在请求过程中在顶端中间件(一般在第一个中间件)使用,把数据或者方法挂载代理到ctx 供下游中间件获取和使用。

这里 请求代理上下文实现 最代表性是官方提供的koa-bodyparser 中间件,这里基于官方原版用最简单的方式实现koa-bodyparser最简单功能。

常见请求代理上下文context实现过程

  • 请求代理ctx
  • 直接app.use()
  • 在请求过程中过载方法或者数据到上下文ctx
  • 一般在大部分中间件前加载,供下游中间件获取挂载的数据或方法

实现步骤

  • step 01 app.use()在中间件最顶端
  • step 02 拦截post请求
  • step 03 等待解析表单信息
  • step 04 把表单信息代理到ctx.request.body上
  • step 05 下游中间件都可以在ctx.request.body中获取表单数据

实现源码

demo源码

https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-05-03

## 安装依赖
npm i

## 执行 demo
npm run start

## 最后启动chrome浏览器访问
##  http://127.0.0.1:3000

依赖

请求体数据流解析方法

module.exports = readStream;

function readStream(req) {
  return new Promise((resolve, reject) => {
    try {
      streamEventListen(req, (data, err) => {
        if (data && !isError(err)) {
          resolve(data);
        } else {
          reject(err);
        }
      });
    } catch (err) {
      reject(err);
    }
  });
}

function isError(err) {
  return Object.prototype.toString.call(err).toLowerCase() === '[object error]';
}

function streamEventListen(req, callback) {
  let stream = req.req || req;
  let chunk = [];
  let complete = false;

  // attach listeners
  stream.on('aborted', onAborted);
  stream.on('close', cleanup);
  stream.on('data', onData);
  stream.on('end', onEnd);
  stream.on('error', onEnd);

  function onAborted() {
    if (complete) {
      return;
    }
    callback(null, new Error('request body parse aborted'));
  }

  function cleanup() {
    stream.removeListener('aborted', onAborted);
    stream.removeListener('data', onData);
    stream.removeListener('end', onEnd);
    stream.removeListener('error', onEnd);
    stream.removeListener('close', cleanup);
  }

  function onData(data) {
    if (complete) {
      return;
    }
    if (data) {
      chunk.push(data.toString());
    }
  }

  function onEnd(err) {
    if (complete) {
      return;
    }

    if (isError(err)) {
      callback(null, err);
      return;
    }

    complete = true;
    let result = chunk.join('');
    chunk = [];
    callback(result, null);
  }
}

解读

const readStream = require('./lib/read_stream');
let strictJSONReg = /^[\x20\x09\x0a\x0d]*(\[|\{)/;

let jsonTypes = [
  'application/json'
];

let formTypes = [
  'application/x-www-form-urlencoded'
];

let textTypes = [
  'text/plain'
];

function parseQueryStr(queryStr) {
  let queryData = {};
  let queryStrList = queryStr.split('&');
  for (let [ index, queryStr ] of queryStrList.entries()) {
    let itemList = queryStr.split('=');
    queryData[ itemList[0] ] = decodeURIComponent(itemList[1]);
  }
  return queryData;
}

function bodyParser(opts = {}) {
  return async function(ctx, next) {
    // 拦截post请求
    if (!ctx.request.body && ctx.method === 'POST') {
      // 解析请求体中的表单信息
      let body = await readStream(ctx.request.req);
      let result = body;
      if (ctx.request.is(formTypes)) {
        result = parseQueryStr(body);
      } else if (ctx.request.is(jsonTypes)) {
        if (strictJSONReg.test(body)) {
          try {
            result = JSON.parse(body);
          } catch (err) {
            ctx.throw(500, err);
          }
        }
      } else if (ctx.request.is(textTypes)) {
        result = body;
      }

      // 将请求体中的信息挂载到山下文的request 属性中
      ctx.request.body = result;
    }
    await next();
  };
}

module.exports = bodyParser;

使用

const Koa = require('koa');
const fs = require('fs');
const path = require('path');
const body = require('../index');
const app = new Koa();

app.use(body());

app.use(async(ctx, next) => {
  if (ctx.url === '/') {
    // 当GET请求时候返回表单页面
    let html = fs.readFileSync(path.join(__dirname, './index.html'), 'binary');
    ctx.body = html;
  } else if (ctx.url === '/post' && ctx.method === 'POST') {
    // 当POST请求的时候,解析POST表单里的数据,并显示出来
    ctx.body = ctx.request.body;
  } else {
    // 其他请求显示404
    ctx.body = '<h1>404!!! o(╯□╰)o</h1>';
  }

  await next();
});

app.listen(3000, () => {
  console.log('[demo] is starting at port 3000');
});
<html>
  <head>
    <title>example</title>
  </head>
  <body>
    <div>
      <p>form post demo</p>
      <form method="POST" action="/post">
        <span>data</span>
        <textarea name="userName" ></textarea><br/> 
        <button type="submit">submit</button>
      </form>
    </div> 
  </body> 
</html>

附录

参考

https://github.com/koajs/bodyparser

results matching ""

    No results matching ""