vbenjs / vite-plugin-mock

A mock plugin for vite.use mockjs.
MIT License
625 stars 96 forks source link

Support `application/x-www-form-urlencoded` request params #57

Open guyskk opened 3 years ago

guyskk commented 3 years ago

目前请求体都当作JSON处理了,希望能支持 application/x-www-form-urlencoded 类型的参数。

写了一个实现,合适的话我提个PR?

import { IncomingMessage } from 'http';
import qs from 'qs';

export function parseRequest(req: IncomingMessage): Promise<any> {
  return new Promise((resolve) => {
    const contentType = (req.headers['content-type'] || '').toLowerCase();
    let body = '';
    req.on('data', function (chunk) {
      body += chunk;
    });
    req.on('end', function () {
      let data: any = null;
      if (contentType.includes('json')) {
        try {
          data = JSON.parse(body);
        } catch (err) {
          console.log(err);
        }
      } else if (contentType.includes('form')) {
        try {
          data = qs.parse(body);
        } catch (err) {
          console.log(err);
        }
      } else {
        data = body;
      }
      resolve(data);
    });
  });
}
mishengqiang commented 2 years ago

理论上应该有3个参数,body参数、query参数和params参数,body参数和query参数比较好理解,params参数我个人认为是类似VueRouter的路径参数,比如/api/user/:id那id就属于params参数。

我认为这3种参数应该同时解析出来提供给response函数,我看作者把query和params二选其一了。这种情况虽然在后端api中很少见但确实存在,在vue项目我们能经常见到query和params传参的情况,比如:/api/user/:id?name='小花'

mishengqiang commented 2 years ago

为了解决application/x-www-form-urlencoded参数的问题,不得不在rawResponse函数种处理,为了使用方便因此封装了几个函数,便于直接使用。sendResponse的回调函数可以同时返回query参数、params参数(需要传递mockUrl参数,值同url)和body参数。

使用案例:

import {
  sendResponse,
  resultSuccess,
  resultError,
} from "./../utils";

// 假如真实请求是`/api/user/getgetUserInfo/1?name='小花'`,body参数是`age=18&sex=女`
export default [
  {
    url: "/api/user/getUserInfo/:id",
    method: "post",
    rawResponse: async (req, res) => {
      await sendResponse(
        req,
        res,
        ({ url, body, query, params }) => {
          console.log(url, body, query, params); 
          // 这里的body将打印{age: "18", sex: "女"};
          // query将打印{name: "小花"}
          // params将打印{id: "1"}
          const item = data.list.find((item) => item.id === Number(params.id));
          if (item) {
            return resultSuccess(item);
          }
          return resultError("无此该用户");
        },
        "/api/user/getUserInfo/:id" // 想要获取params,这个参数必须传,值同url
      );
    },
  },
]

声明:下面代码结合了源代码和 @guyskk 的代码

工具函数:

import url from "url";
import Mock from "mockjs";
import qs from "qs";
import { match } from "path-to-regexp";

/**
 * 解析请求body参数
 * @param {IncomingMessage} req 请求
 * @returns body参数
 */
export function parseRequestBody(req) {
  return new Promise((resolve) => {
    const contentType = (req.headers["content-type"] || "").toLowerCase();
    let body = "";
    req.on("data", function (chunk) {
      body += chunk;
    });
    req.on("end", function () {
      let data = null;
      if (contentType.includes("json")) {
        try {
          data = JSON.parse(body);
        } catch (err) {
          console.log(err);
        }
      } else if (contentType.includes("form")) {
        try {
          data = qs.parse(body);
        } catch (err) {
          console.log(err);
        }
      } else {
        data = body;
      }
      resolve(data);
    });
  });
}

/**
 * 解析请求的query参数和params参数(如果有params参数指的是path路径上的参数,比如:api/getItem/:id,id就是属于params参数)
 * @param {IncomingMessage} req 请求
 * @param {string} mockUrl mock数据的url
 * @returns {query:Object,params:Object} query参数和params参数
 */
export function parseRequestQueryParams(req, mockUrl) {
  let queryParams = {};
  if (req.url) {
    queryParams = url.parse(req.url, true);
  }

  const query = queryParams.query || {};

  let params = {};
  if (mockUrl) {
    const urlMatch = match(mockUrl, {
      decode: decodeURIComponent,
    });
    params = queryParams.pathname ? urlMatch(queryParams.pathname).params : {};
  }

  return { query, params };
}

/**
 * 发送响应
 * @param {IncomingMessage} req 请求
 * @param {ServerResponse} res 响应
 * @param {(opt: { [key: string]: string; body: Record<string,any>; query:  Record<string,any>, headers: Record<string, any>; }) => any} callback 结果处理
 * @param {string} [mockUrl=''] mock数据的url
 */
export async function sendResponse(req, res, callback, mockUrl = "") {
  const body = await parseRequestBody(req);
  const { query, params } = parseRequestQueryParams(req, mockUrl);
  res.setHeader("Content-Type", "application/json");
  res.statusCode = 200; // 无法从源码中获取statusCode,因此这里写死了200,因为99%情况下是200
  const mockResponse =
    typeof callback === "function"
      ? callback({ url: req.url, body, query, params, headers: req.headers })
      : callback;
  res.end(JSON.stringify(Mock.mock(mockResponse)));
}