【nginx】NJS 的简单实践

Nginx 是绝大部分前端工程师经常需要打交道的 web 服务器,凭借 事件驱动架构异步非阻塞 I/O 模型,在高并发场景下展现出卓越的性能表现。其核心功能覆盖静态资源托管、反向代理、负载均衡及请求过滤等,成为现代 Web 架构中的关键基础设施。 然而,原生 Nginx 依赖静态配置文件驱动,在动态业务逻辑处理层面存在显著局限,难以满足动态路由分发、实时请求改写、精细化鉴权等复杂场景需求。 为突破这一限制,OpenResty 作为集成 Nginx 与 LuaJIT 的增强套件,通过内置的 Lua 脚本执行环境实现了动态能力的系统性扩展:允许嵌入自定义 Lua 逻辑实现上下文感知的动态决策 ------ 例如通过解析 JWT 令牌进行权限校验,或依据请求参数动态调整后端服务集群的路由策略。OpenResty 通过预集成 lua-nginx-module 等核心组件,在保持 Nginx 高性能特性的同时,构建了完整的动态处理生态,使其能够作为生产级 API 网关支撑复杂业务场景。 但从前端工程师的技术栈视角来看,Lua 方案存在明显的适配门槛。首先是语言生态的壁垒:Lua 的语法体系(如变量作用域规则、函数定义方式)与前端工程师熟悉的 JavaScript 存在本质差异,异步处理模式也与 Promise/async/await 范式不兼容,导致学习曲线陡峭。其次是工具链的割裂:前端工程化常用的构建工具、转译工具均无法直接适配 Lua 脚本,需额外搭建独立的开发链路,增加了环境配置成本。此外,团队协作中,前端工程师与运维工程师的技术栈差异会导致逻辑交接效率低下,简单的路由规则调整都可能因语言隔阂产生沟通成本。

早期实践

为了解决沟通和交付效率,早期我们会采用 nodejs 或者 bun 去做一些中间层去做一些中间层进行转发或处理。举个例子:

客户端染色后,我们希望通过IP来区分一些用户权限,有权限的返回真实的响应,没权限的重定向到 baidu.com

如果只用 nginx 也可以做到,但就是配置不够灵活,如果 ip 有变化还得依靠运维同事帮忙,因此我们加了一层 nodejs 如图所示。 这么做有一个问题,就是得手动写一堆面对 海量流量 的处理策略,什么 熔断限流 甚至得写一些 负载均衡 的算法~~(后续写出来😪)~~

基于 NJS 的实践

今年7月,nginx 官方宣布新增了 quick.js 的引擎,支持了现代 JavaScript 的语法,门槛更低,但需要手动开启,后面有空会把镜像制作过程贴出来。

采用 NJS 的方案后,整个架构模式变成如下图所示,具体实践过程见下文。

前期准备

  1. 具备 qjs 的一个 nginx 镜像
  2. 一个 typescript 的开发环境

预期流程

一般来说,访问 ip 需要在数据库中动态维护,本文简单一些,用一个额外的路由(js模块)去mock。整个流程如下:

nginx.conf

nginx 复制代码
server {
        add_header Access-Control-Allow-Origin "*";
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
        add_header Access-Control-Allow-Headers "X-Requested-With, Content-Type";
        add_header Access-Control-Allow-Credentials "true";
        add_header Access-Control-Expose-Headers "Location";
        listen 80;

        js_engine qjs;
        js_import main from conf.d/ngc/njs/index.js;

        default_type application/json;
 
        location /test {
                js_content main.redirect.test;
        }

        location /auth {
                js_content main.redirect.getVersion;
        }

}

auth 路由

其作用是判断请求中的 ['X-Forwarded-For'] 是否属于 黑名单(banIps) 中 ;此外,如果它的 remoteAddress 不是来自 127.0.0.1 说明是外部访问,需要挂掉。

ts 复制代码
const banIps = ["2.2.2.2", "127.0.0.1"];
function getVersion(r: NginxHTTPRequest) {
    if(r.remoteAddress !== "127.0.0.1"){
        return r.return(500, "ip error")
    }
    r.headersOut['Content-Type'] = "text/plain; charset=UTF-8";
    const ip = r.headersIn['X-Forwarded-For'] || '127.0.0.1';
    if (banIps.includes(ip)) {
        return r.return(403, "没有权限");
    }
    return r.return(200, `有权限, ${r.headersIn['X-Forwarded-For']}`)
}

test 路由

其作用便是返回用户是否可见

ts 复制代码
async function test(r: NginxHTTPRequest) {
    const resp = await ngx.fetch("http://127.0.0.1:80/auth", {
        headers: r.headersIn,
    })
    const canSee =  resp.status === 200;
    if (!canSee) {
         //* 如果没有权限重定向到百度
        return r.return(302, "https://baidu.com");
    }
    //* 如果有权限展示 self
    r.headersOut["Content-Type"] = "text/plain; charset=UTF-8";
    r.return(200, "hi,siroi 👋👋")
}

typescript 配置

通常在使用 tsc --init 会生成一个 commonjs 的方案,这个需要留意,需要改成 esm 的格式,推荐使用 esnext

json 复制代码
{
  "compilerOptions": {
    "target": "esnext",                                
    "module": "esnext",                                
    "rootDir": ".",                                  
    "moduleResolution": "node",                      
    "baseUrl": ".",                                  
    "outDir": "./",                                  
    "esModuleInterop": true,                            
    "forceConsistentCasingInFileNames": true,            
    "strict": true,                                      
    "skipLibCheck": true                                
  },

  "include": [
    "**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

项目结构

实践结果

  1. 设置 ip 为 2.2.2.2(没有权限的❌)
  2. 设置 ip 为 2.2.2.3 (有权限的✔️)
相关推荐
静小谢39 分钟前
前后台一起部署,vite配置笔记base\build
前端·javascript·笔记
用户47949283569151 小时前
改了CSS刷新没反应-你可能不懂HTTP缓存
前端·javascript·面试
还好还好不是吗1 小时前
老项目改造 vue-cli 2.6 升级 rsbuild 提升开发效率300% upupup!!!
前端·性能优化
sumAll1 小时前
别再手动对齐矩形了!这个开源神器让 AI 帮你画架构图 (Next-AI-Draw-IO 体验)
前端·人工智能·next.js
OpenTiny社区1 小时前
2025OpenTiny星光ShowTime!年度贡献者征集启动!
前端·vue.js·低代码
wangan0942 小时前
不带圆圈的二叉树
java·前端·javascript
狗哥哥2 小时前
从零到一:打造企业级 Vue 3 高性能表格组件的设计哲学与实践
前端·vue.js·架构
疯狂平头哥2 小时前
微信小程序真机预览-数字不等宽如何解决
前端
Drift_Dream2 小时前
前端趣味交互:如何精准判断鼠标从哪个方向进入元素?
前端
hqk2 小时前
鸿蒙ArkUI:状态管理、应用结构、路由全解析
android·前端·harmonyos