Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

是否可以支持mock数据低优先级 #60

Open
gapkukb opened this issue Dec 5, 2023 · 10 comments
Open

是否可以支持mock数据低优先级 #60

gapkukb opened this issue Dec 5, 2023 · 10 comments

Comments

@gapkukb
Copy link

gapkukb commented Dec 5, 2023

很多时候随着项目的迭代,大部分接口都是通过后台的接口获取,mock数据的常见情况是在新的需求中,后台接口未实现之前。
目前绝大部分的mock插件都是本地数据优先。

能否做到优先匹配server.proxy中的地址,如果返回404再走mock接口,
或者开放函数接口,由开发人员自行决定返回值
考虑以下方案:

mockDevServerPlugin({
priority:'before'|'after'
})

@pengzhanbo
Copy link
Owner

emmm.... 我理解你的想法。

在vite 中, server.proxy 是通过 http-proxy 实现的。但请求被 http-proxy 拦截并代理后,无论请求是否成功,请求都已经被消费。这表示,如果优先由 http-proxy 处理,即使 404 或 500 之类的错误码,请求也已经结束,不能继续被消费,也就是不会转到 mock 插件内进行处理。

而且假设可以实现, 被 http-proxy 代理的请求,等待响应的时间是不可控的,哪些状态码被认为是失败需要转到 本地mock 也是可能不止 404、4xx、5xx 等。这进一步引入了复杂度。

按照目前 vite 的实现,该功能是否能通过其他方式实现,我还需要进一步研究。

可替代的方法:

  1. 通过 enabled: false 禁用当前接口使用 本地mock数据,交给 vite 的 proxy 代理。
  2. 在 mock 文件中, 通过 fetch 或者 axios 进行请求,判断是否使用本地数据:
export default defineMock({
  url: '/api/url',
  body: async () => {
    try {
      const res = await fetch('http://example.com/api/url')
      return await res.json()
    } catch {
      return { message: 'local mock data' }
    }
  }
})

@gapkukb
Copy link
Author

gapkukb commented Dec 6, 2023

的确是这样,目前我也卡在这个点上。

http-proxy的请求被消费完成后,在进入mock的时候,可以使用middleware拿到req,和res.如果是无body的请求,这可以很好的工作以实现预期.

但是,如果是携带body的请求,由于请求进入mock前已经被消费了(complete and destoryed)。无法使用on('data') on('end')等监听事件来提取body和response. 请求会被挂起无响应

关于这一点,你有什么主意吗,比如active这个已经消费掉的请求;或者重新产生请求,再使用ProxyRes.pipe()返回 (试过了但失败了)?

对于你最后的示例,只合适接口不多的时候,如果大量的接口使用这种重复劳动很显然是吃力的。

@pengzhanbo
Copy link
Owner

在本插件中,提供了一种 请求复原 方式,将被 mock 消费后的 request 恢复并提交给 http-proxy

如果要实现 http-proxy -> mock request 的优先级顺序,在 vite 中, 可能就不能再使用 内置的 http-proxy 来处理,而是由插件全权进行 代理拦截,在插件内重新引入 http-proxy 以便完全的控制其行为;然后在此基础上实现 优先级的可配置。

但是我不确定这样做的带来的收益是否是正向的。

在我理解的 需要使用 mock request 的场景, 一般是:

  • 开发时, 接口数据的可靠性,获取在不同条件下的预期的数据;
  • 开发时,数据能够快速响应,减少接口等待,提高效率;
  • 联调时,即时切换到联调接口;
  • e2e 测试时,数据可靠性,可测试性;

这也是为什么 mock request 优先于 http-proxy 的原因; 但是两者的优先级翻转后,其行为反而导致了 开发时数据的不可预估,等待时间不可控等。

对于你最后的示例,只合适接口不多的时候,如果大量的接口使用这种重复劳动很显然是吃力的。

对于这个 大量接口的重复劳动问题,可以通过 createDefineMock 方法来批量处理:

比如,批量关闭某个 服务下的所有接口:

import { createDefineMock } from 'vite-plugin-mock-dev-server'

const defineMock = createDefineMock((mock) => {
  if (mock.url.startsWith('/api/services-a')) {
    mock.enabled = false
  }
})

export default defineMock({
  url: '/api/services-a'
})

通过预处理,借助 白名单 机制等,就可以批量控制 mock 的不同行为。

@pfdgithub
Copy link
Contributor

你这个需求跟我之前的需求正好相反😂
我是优先使用本地 mock 文件,没有则使用 proxy 中转的后端接口。
你可以参考一下,把对本地 mock 文件的检查,替换为对远程接口的检查(比如用 axios.head 检查一下接口是否存在)
如果检查通过则调用 next() 调用后续的 proxy 中间件,如果检查不通过则使用本地 mock 文件返回。

// mock/_.mock.js

import fs from "fs";
import path from "path";
import colors from "picocolors";
import process from "process";
import { defineMock } from "vite-plugin-mock-dev-server";

const print = (type, msg) => {
  const date = colors.dim(new Date().toLocaleTimeString());
  const tag = colors.bold("[vite:mock-file]");

  if (type === "error") {
    console.error(`${date} ${colors.red(tag)} ${msg}`);
  } else if (type === "warn") {
    console.warn(`${date} ${colors.yellow(tag)} ${msg}`);
  } else {
    console.info(`${date} ${colors.cyan(tag)} ${msg}`);
  }
};

export default defineMock([
  {
    url: "/(.*)",
    response: async (req, res, next) => {
      if (!req.url) {
        return next();
      }

      const reqUrl = new URL(req.url, "http://localhost");
      const mockPath = path.join(process.cwd(), "mock", reqUrl.pathname);
      const mockFile = path.normalize(mockPath) + ".json";

      try {
        const mockContent = await fs.promises.readFile(mockFile, {
          encoding: "utf-8",
        });
        res.end(mockContent);
        print("info", `matched file: ${mockFile}`);
      } catch (err) {
        if (err.code === "ENOENT") {
          print("warn", `unmatch file: ${mockFile}`);
          // res.writeHead(404).end();
          next();
        } else {
          print("error", err);
          next(err);
        }
      }
    },
  },
]);

@luvletterldl
Copy link

还有一种情况,比如比较复杂的老项目很久没迭代了,想要快速更新mock数据为线上的一些数据,可以增加一个mock模式的开关为录制(默认为mock),mode: 'record' | 'mock', 开启录制默认在页面中代理请求vite自带的请求server的target,假设为线上环境,这时候就拿真实的response数据替代本地的mock数据(写mock文件),这样有个好处就是在页面上操作就能更新本地mock数据,然后把mode改为'mock'就还是原样从本地代理。实现的难点就在于设置这个范式来兼容本地mock的加载和从server真实接口响应数据写数据更新本地的mock上,我目前也在摸索,大家有兴趣也可以继续沟通下。

@pengzhanbo
Copy link
Owner

pengzhanbo commented Jul 26, 2024

还有一种情况,比如比较复杂的老项目很久没迭代了,想要快速更新mock数据为线上的一些数据,可以增加一个mock模式的开关为录制(默认为mock),mode: 'record' | 'mock', 开启录制默认在页面中代理请求vite自带的请求server的target,假设为线上环境,这时候就拿真实的response数据替代本地的mock数据(写mock文件),这样有个好处就是在页面上操作就能更新本地mock数据,然后把mode改为'mock'就还是原样从本地代理。实现的难点就在于设置这个范式来兼容本地mock的加载和从server真实接口响应数据写数据更新本地的mock上,我目前也在摸索,大家有兴趣也可以继续沟通下。

实现并不复杂,在 http-proxy 中拦截响应数据流然后以接口地址为 json 文件名 输出到单独的 record 目录下,将这些 记录转换为 mock 配置即可。

但这种功能并不需要插件去处理,你可以先关闭 mock 插件,然后简单的写一个 vite 插件 来实现 拦截与转换输出为 mock 文件,完成后禁用这个插件重新启用 mock 插件即可。

或者也不用写插件,在 server.proxy 配置中添加 响应流事件监听即可,这里要注意请求流可能被消费而导致客户端响应失败

@luvletterldl
Copy link

luvletterldl commented Jul 26, 2024

请求流可能被消费而导致客户端响应失败

是的,如何记录的同时还返回客户端数据是我现在想要解决的问题

@pengzhanbo
Copy link
Owner

是的,如何记录的同时还返回客户端数据是我现在想要解决的问题

可以参考插件 请求复原 这部分的实现

@luvletterldl
Copy link

是的,如何记录的同时还返回客户端数据是我现在想要解决的问题

可以参考插件 请求复原 这部分的实现
image

十分感谢您的思路,我想我的问题应该解决了

@pengzhanbo
Copy link
Owner

十分感谢您的思路,我想我的问题应该解决了

不客气,能解决问题就好

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants