[Nginx][njs]宝塔安装njs

NJS诞生

NJS是Nginx + JavaScript的缩写。是为了那些web开发不熟悉lua语言特意推出的,是Nginx官方根据JS语言规范实现的一个完全独立的JavaScript的执行引擎,所以NJS是有限实现了JavaScript,并不是一个完整的与 Node.JS (opens new window)和 Google V8 (opens new window)这样的 JavaScript 解析引擎。由于项目还在不断开发当中,请自行到 官方介绍页面 (opens new window)进行查看当前支持哪些 JS 的特性。

Nginx NJS 包含两个 Nginx 扩展模块,具体如下:

这两个模块目前还没有默认集成到 Nginx mainline 的发布里面,但是你可以通过代码编译安装或者作为 Nginx 动态模块来安装。

Nginx NJS 的安装

要求 nginx 的版本大于 1.9.11, 因为从该版本才开始支持 load_module 指令

#方法一: 动态加载 NJS 模块

注意: 不同版本的 nginx 需要相应版本的 NJS 模块.

  • 将 ngx_http_js_module.so 文件放在nginx 根目录的 modules 目录下,
  • 在 nginx.conf 中增加引入模块
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;

#方法二: 宝塔编译时增加模块

下载源码 https://github.com/nginx/njs

下载完毕后上传至服务器地址 哪个位置都行

通过宝塔软件商店直接选择编译安装nginx

其中新模块配置:

--add-module=你的njs地址/nginx
图片[1]-[Nginx][njs]宝塔安装njs-DoubleWorld’s

验证安装是否成功

在Nginx安装目录下输入nginx -V即可以看到扩展模块是否安装成功

#NJS 模块支持的指令及对应的处理阶段

处理阶段HTTP 模块Stream 模块
Access – Authentication and access controlauth_request and js_contentjs_access
Pre-read – Read/write payloadN/Ajs_preread
Filter – Read/write response during proxyjs_body_filter js_header_filterjs_filter
Content – Send response to clientjs_contentN/A
Log / Variables – Evaluated on demandjs_setjs_set
  • js_content,执行其中 JS 内容并输出
  • js_include,指定特定的文件内的 JS 代码处理请求
  • js_set,设置特定的 JS 变量
  • js_body_filter、js_header_filter,过滤请求
  • js_access,Nginx Access (opens new window)阶段执行的 JS 内容
  • js_filter,Nginx 输出的数据过滤阶段执行的 JS 内容
  • js_include,指定特定的文件内的 JS 代码处理请求
  • js_preread,Nginx Preread (opens new window)阶段执行的 JS 内容
  • js_set,设置特定的 JS 变量

#NJS 支持的指令

NJS 支持的指令并不多. 要实现复杂的功能需要与 Nginx 的其他指令结合一起使用.

js_body_filter 修改 response 的 body

Syntax:	js_body_filter function | module.function [buffer_type=string | buffer];
Default:	—
Context:	location, limit_except
This directive appeared in version 0.5.2.

/**
* 处理 response body 的函数
* @param { object } r - http 对象
* @param { buffer_type } data - 请求的 body 的数据
* @param { boolean } flags - 是否是最后一个数据块
*/
function filter(r, data, flags) {
    r.sendBuffer(data.toLowerCase(), flags);
}

js_content 处理请求的返回

Syntax:	js_content function | module.function;
Default:	—
Context:	location, limit_except

http {
    # 引入 js 模块
    js_import  http.js;             
    server {
        listen 80;
        location /content {
            # 通过 js_content 指令指定要执行的 js 函数
            js_content http.content;
        }
    }
}
// http.js 文件
function content(r) {
    r.status = 200;
    r.headersOut['Content-Type'] = "text/plain; charset=utf-8";
    r.headersOut['Content-Length'] = 12;
    r.sendHeader();
    r.send("hello world");
}

#NJS编程实践

直接拿Emby转直链来举例:

在emby.conf的最上方,引入了emby.js文件

js_path /etc/nginx/conf.d/;
js_import emby2Pan from emby.js;

然后在路径匹配上,制定了视频文件的路径匹配规则 ,还声明了emby的后端地址,以便于后面在js无法处理302跳转的时候,采用原路径匹配。

// 可以匹配 /emby/videos/34262/stream.mp4
 location ~* /videos/(.*)/stream {         
        js_content emby2Pan.redirect2Pan;
    }
    # for webClient download ,android is SyncService api
    location ~* /Items/(.*)/Download {
        js_content emby2Pan.redirect2Pan;
    }
    location @backend {
        proxy_pass $emby;
    }

通过emby.conf的配置能够将/emby/videos/34262/stream.mp4路径的地址,交由给emby.js中的redirect2Pan进行处理。

async function redirect2Pan(r) {

}

剩下的基本都是业务逻辑的处理,唯一要注意的地方是,如果没有匹配到路径,使用r.internalRedirect("@backend")可以指向服务器的原始路径:

更多的关于这个r对象的用法,可以查看官网:https://nginx.org/en/docs/njs/reference.html(opens new window)

    //根据实际情况修改下面4个设置
    const embyHost = 'http://embyserver:8096'; //这里默认emby/jellyfin的地址是宿主机,要注意iptables给容器放行端口
    const embyMountPath = '/mnt/share/pan';  // rclone 的挂载目录, 例如将od, gd挂载到/mnt目录下:  /mnt/onedrive  /mnt/gd ,那么这里 就填写 /mnt
    const alistPwd = '123456';      //alist password
    const alistApiPath = 'http://192.168.1.10:5244/api/public/path'; //访问宿主机上5244端口的alist api, 要注意iptables给容 器放行端口

    //fetch mount emby/jellyfin file path
    const regex = /[A-Za-z0-9]+/g;
    const itemId = r.uri.replace('emby', '').replace(/-/g, '').match(regex)[1]; 
    const mediaSourceId = r.args.MediaSourceId;
    let api_key = r.args.api_key;

    //infuse用户需要填写下面的api_key, 感谢@amwamw968
    if ((api_key === null) || (api_key === undefined)) {
        api_key = '34665a2b42e5456a9011dc2c7accc445';//这里填自己的emby/jellyfin API KEY
        r.warn(`api key for Infuse: ${api_key}`);
    }

    const itemInfoUri = `${embyHost}/Items/${itemId}/PlaybackInfo?MediaSourceId=${mediaSourceId}&api_key=${api_key}`;
    r.warn(`itemInfoUri: ${itemInfoUri}`);
    const embyRes = await fetchEmbyFilePath(itemInfoUri);
    if (embyRes.startsWith('error')) {
        r.error(embyRes);
        r.return(500, embyRes);
        return;
    }
    r.warn(`mount emby file path: ${embyRes}`);

    //fetch alist direct link
    const alistFilePath = embyRes.replace(embyMountPath, '');
    const alistRes = await fetchAlistPathApi(alistApiPath, alistFilePath, alistPwd);
    if (!alistRes.startsWith('error')) {
        r.warn(`redirect to: ${alistRes}`);
        r.return(302, alistRes);
        return;
    }
    if (alistRes.startsWith('error401')) {
        r.error(alistRes);
        r.return(401, alistRes);
        return;
    }
    if (alistRes.startsWith('error404')) {
        const filePath = alistFilePath.substring(alistFilePath.indexOf('/', 1));
        const foldersRes = await fetchAlistPathApi(alistApiPath, '/', alistPwd);
        if (foldersRes.startsWith('error')) {
            r.error(foldersRes);
            r.return(500, foldersRes);
            return;
        }
        const folders = foldersRes.split(',').sort();
        for (let i = 0; i < folders.length; i++) {
            r.warn(`try to fetch alist path from /${folders[i]}${filePath}`);
            const driverRes = await fetchAlistPathApi(alistApiPath, `/${folders[i]}${filePath}`, alistPwd);
            if (!driverRes.startsWith('error')) {
                r.warn(`redirect to: ${driverRes}`);
                r.return(302, driverRes);
                return;
            }
        }
        r.warn(`not found direct ${alistRes}`);
        r.internalRedirect("@backend");
        return;

    }
    r.warn(`not found direct ${alistRes}`);
    r.internalRedirect("@backend");
    return;
© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容