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

通过 Nginx 日志恢复失败请求的若干问题 #26

Open
hexh250786313 opened this issue Nov 5, 2022 · 0 comments
Open

通过 Nginx 日志恢复失败请求的若干问题 #26

hexh250786313 opened this issue Nov 5, 2022 · 0 comments
Labels
post blog post type: POST

Comments

@hexh250786313
Copy link
Owner

hexh250786313 commented Nov 5, 2022

不要点开, 博客网站用的
博文标题图片

pic

博文置顶说明
工作上遇到了一个需求, 有一个接口因为 body 量相当大, 很容易请求失败, 加上是个静默接口用户不会有任何感知, 不会手动重新发起请求. 于是就需要我们从 nginx 日志上根据收集的参数信息进行接口请求恢复, 本文即记录做该需求中遇到的若干问题和解决方案

相关

问题综述

由于需要恢复请求, 所以使用脚本, 考虑到后续维护, 用 node 脚本进行开发, 脚本功能如下:

  • 从日志文件 (.csv) 中解析出相关的参数
  • 根据参数恢复请求
  • 输出结果日志

开发中遇到的问题:

  • 工作环境需要开启代理才能请求得到目标 url, 明明环境已经具有 http_proxyhttps_proxy 但 node 脚本却无法发出这个请求
  • 日志的 body 被转义并转码了, 恢复这些编码遇到了问题

node 中的代理

根据 nodejs/node#1490 得知 node 原生不实现代理功能, 也不读取 http_proxy 变量, 原因是加入 proxy 的话 node 开发团队就需要考虑安全 / SOCKS / 证书等等一系列问题, 于是不做

但是依然可以通过很多库实现这个功能, 例如: node-global-proxy

import proxy from "node-global-proxy";

export function setupProxy(address: string) {
  proxy.setConfig({ http: address, https: address });

  proxy.start();
}

setupProxy(proxyAddress); // 程序初始化时设置代理

// ...其他代码

编码问题

下面的字符串 "x\xC2\x9C\xC3", 只是一个代指, 不是一个有意义的值, 只是为了方便解释

这个问题就比较复杂了, 主要体现在两个地方:

  1. 转码失败. 运维把 nginx 日志的编码格式设置成了 utf-16, 根据 https://juejin.cn/post/7122019278991458334#heading-2 可知, js 的编码也是 utf-16, 是可以直接使用从日志中得到的字符串, 然而却出现了两种情况
    1. 直接给 js 变量赋值例如: const body = "x\xC2\x9C\xC3" 再使用这个 body 是能成功发出请求的, js 自动完成了转码, 其参数和客户端的请求参数完全一样
    2. 从 .csv 日志中通过 readline 解析出字符串 "x\xC2\x9C\xC3" 再赋值给 body, 却没有自动转码, 传的参数是 "x\xC2\x9C\xC3" 这个字符串而不是转码后的字符串
  2. 转义失败. node 中的 url 转义恢复: 熟悉前端开发可知请求参数经常需要先通过 encodeURIComponent 转义再发请求, 原因这里不提, 问题在于这次使用 decodeURIComponent 后居然无法正确恢复解析转义

转义失败问题

先说第二个问题, 直接使用 decodeURIComponent("x\xC2\x9C\xC3") 无效 (报错了), 那么也就意味着 decodeURIComponent 对这些初始 utf-16 编码的字符串依然无法正确解析

然后通过搜索找到了另一个函数: querystring.escape, 有效, 但是不是完全有效, 我发现这个函数只对部分的 utf-16 有用, 有的就无效, 这就有点奇怪了, 再仔细找原因, 发现了 node:querystring 文档里就有提到: https://nodejs.org/api/querystring.html#querystringunescapestr

By default, the querystring.unescape() method will attempt to use the JavaScript built-in decodeURIComponent() method to decode. If that fails, a safer equivalent that does not throw on malformed URLs will be used.
大概翻译下:
默认情况下 querystring.unescape() 方法会尝试用 decodeURIComponent() 进行转义, 如果失败了, 那么使用更安全的方法来解决那些长得不像 url 的字符串

也就是 pass 掉

最终找到了这个: https://stackoverflow.com/questions/37670485/how-do-i-decode-this-string-xc3-x99-xc3-xa9-xc2-x87-bx-xc2

使用 decodeURIComponent(escape(s)) 即可, 原来是还需要用 escape 先做一层转换呀, 不过值得一提的是 escape 函数已经被标记 deprecated

转码失败问题

上面提到过, js 是 utf-16 编码的, 理论上直接支持来自 nginx 的日志参数, 直接赋值也证明了这一点 (下面 nextFetch 会直接使用 body 发出 fetch 请求)

const body = "x\xC2\x9C\xC3";
console.log(body); // 被转码的字符串

nextFetch(body); // 成功

然而如果是用 node:readline 读文件中的 'x\xC2\x9C\xC3' 却无法直接使用:

// nginx-log.csv
x\xC2\x9C\xC3
let body = "";

const fileStream = createReadStream("./nginx-log.csv");
const rl = createInterface({
  input: fileStream,
  crlfDelay: Infinity,
});

rl.on("line", function (line) {
  body = line;
});

rl.on("close", function () {
  console.log(body); // 没有被转码的字符串 "x\xC2\x9C\xC3"
  nextFetch(body); // 失败
});

造成这个结果的原因其实是, js 代码编译过程中其实已经把第一种情况的字符串转码了, 到 runtime 时使用的已经是转码后的字符串

而第二种情况是此时 js 程序已经是 runtime 了, 此时得到的 line 自然不是代码的一部分, 就没有经过编译这个过程而未被转码, 只得到了原始的字符串

那么我们直接转码即可, 参考: https://gist.github.com/kiinlam/176ce20707336fa8278726e869e59cb1

export function decodeUtf16(s: string) {
  return s.replace(/\\x(\w{2})/g, function (_, $1) {
    return String.fromCharCode(parseInt($1, 16));
  });
}
@hexh250786313 hexh250786313 added the post blog post type: POST label Nov 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
post blog post type: POST
Projects
None yet
Development

No branches or pull requests

1 participant