- 卷积神经网络学习笔记——ZFNet(Tensorflow实现)
- 24年3月使用VS22编译TelegramDesktop
- STM32中RFID模块(MFRC522)简单应用
- java反序列化-CC1
原文链接:https://hi.imzlh.top/2024/07/08.cgi 。
首先,njs似乎在国内外都不受关注,资料什么的只有 官网参考手册,出了个问题只能看到Github Issue 所以,这篇文章将我的探索过程展示给大家,njs对于可用存储空间较小的设备真的很友好,相比较于NodeJS、Deno这种80M起步的运行环境真的很轻量 但是,这里有几点需要提一下,入坑需谨慎:
addEventListener
seek()
,返回的是UInt8Array
虽然njs不支持TypeScript,但是不影响我们使用TypeScript为代码添加类型检查 NJS官方开发了TypeScript类型定义,开箱即用 将定义放在type文件夹中,然后使用三斜杠ref语法引入 。
入口上,我们不能使用export function语法(前文提到过),需要定义一个入口函数然后使用默认导出 。
async function main(h:NginxHTTPRequest){
// ...
}
export default { main }
注意 这个时候不能使用njs-cli运行,会显示SyntaxError: Illegal export statement 解决办法:njs -c "import M from './main.js'; M.main();" 。
提示 Nginx的Buffer和NodeJS的Buffer很像,我就不多介绍了 。
使用NJS的目标就是代替NginxLUA模块,NJS复用Nginx的事件循环,因此支持异步操作 异步操作用的最多的就是文件IO,即fs 使用fs有两种方式(这一点上和NodeJS很像) 。
import FS from 'fs';
const FS = require('fs');
FS内有两种,一种是同步IO(不建议,但API简单)和异步IO(共享Nginx的EventLoop) 下面我们以异步IO为例:
access最大的作用是确保文件是如你所想的,要知道,Permission Denied很烦人 这个是官方的实例:
import fs from 'fs'
fs.promises.access('/file/path', fs.constants.R_OK | fs.constants.W_OK)
.then(() => console.log('has access'))
.catch(() => console.log('no access'))
|
),官方提供了fs.constants
fs.constants
里有一些预设变量,方便使用
注意 这个函数最大的坑就是没有返回值,如果没有权限就抛出错误,千万别忘记catch 。
这个函数很关键,用于打开文件 。
open(path: Buffer|string, flags?: OpenMode, mode?: number): Promise<NjsFsFileHandle>;
Buffer
这个函数重点是返回的结果。什么?看不起?好,那么我们尝试读取文件的一段 我们先看一下结构 。
buffer
传入一个Buffer用于缓冲。当读取完毕时,这个Buffer里有我们想要的数据buf_offset
这个Buffer开始填充的位置。可以用这个实现一个Buffer读取指定大小的内容read_len
读取长度,但是如果超出了Nginx的Buffer大小,这个数值相对于实际读取的大小会偏大pos
这个是我们今天的重头戏pos
null
,不改变文件指针位置,从当前位置开始读取NjsFsBytesRead
,其中有两个元素
bytesRead
,读取的长度buffer
,就是你传入的bufferfs.promises.stat()
的结果buffer
老规矩,写入的数据Bufferbuf_offset
这个Buffer开始读取的位置read_len
从这个Buffer读取用于写入长度,但是如果超出了Nginx的Buffer大小,这个数值相对于实际读取的大小会偏大pos
和上面read()
的pos参数一致NjsFsBytesWritten
,其中有两个元素
bytesWritten
,写入的长度buffer
,就是你传入的bufferstring
等待写入的字符串pos
和上面read()
的pos参数一致encoding
编码格式,可选 utf8
hex
base64
base64url
这是TypeScript定义 。
interface NjsFsFileHandle {
close(): Promise<void>;
fd: number;
read(buffer: NjsBuffer, offset: number, length: number, position: number | null): Promise<NjsFsBytesRead>;
stat(): Promise<NjsStats>;
write(buffer: NjsBuffer, offset: number, length?: number, position?: number | null): Promise<NjsFsBytesWritten>;
write(buffer: string, position?: number | null, encoding?: FileEncoding): Promise<NjsFsBytesWritten>;
}
关于使用,可以见 https://github.com/imzlh/vlist-njs/blob/master/main.ts#L130,实现纯粹文件拷贝 。
const st = await fs.promises.open(from,'r'),
en = await fs.promises.open(to,'w');
while(true){
// 读取64k 空间
const buf = new Uint8Array(64 * 1024),
readed = await st.read(buf, 0, 64 * 1024, null);
// 读取完成
if(readed.bytesRead == 0) break;
// 防漏式写入
let writed = 0;
do{
const write = await en.write(buf, writed, readed.bytesRead - writed, null);
writed += write.bytesWritten;
}while(writed != readed.bytesRead);
}
虽然我们建议返回填满string的数组,但是返回填充了Buffer的数组也不是不行 。
readdir(path, option)
path
路径,同样可以是Bufferoption
Object对象
encoding
编码格式,可选 utf8
(返回字符串) buffer
(返回Buffer
)withFileTypes
自带stat文件类型的扫描,指定为true,返回的就是NjsDirent[]
了
isBlockDevice()
isCharacterDevice()
isDirectory()
isFIFO()
isFile()
isSocket()
isSymbolicLink()
name
文件(夹)名realpath(path, option)
path
路径,同样可以是Bufferoption
Object对象
encoding
编码格式,可选 utf8
(返回字符串) buffer
(返回Buffer
)注意跨文件系统(磁盘)移动不能使用rename(),instead,请拷贝后再删除 实用技巧 什么?你告诉我你不会判断是否跨文件系统(磁盘)?stat()啊 。
const from = await fs.promises.stat('...'),
to = await fs.promises.stat('...');
// 相同dev使用rename
if(from.dev == to.dev){
await fs.promises.rename(...);
}else{
// copy()
await fs.promises.unlink('...');
}
实例参考:https://github.com/imzlh/vlist-njs/blob/master/main.ts#L622 。
rename(from, to)
from
路径,除了string同样可以是Bufferto
路径,同理,除了string同样可以是Buffercatch
错误情况unlink(path: PathLike): Promise<void>;
path
路径,同样可以是Bufferrmdir(path: PathLike, options?: { recursive?: boolean; }): Promise<void>;
path
路径,同样可以是Bufferoptions
recursive
递归删除,相当于大名鼎鼎的rm -r
rm -rf /
stat(path: PathLike, options?: { throwIfNoEntry?: boolean; }): Promise<NjsStats>;
path
路径,同样可以是Bufferoptions
Object对象
throwIfNoEntry
true
,文件不存在时直接报错,否侧返回 undefineddev
: number 处于的文件系统IDino
: number inode数量mode
: number 文件模式,8进制nlink
: number 这个文件实际地址硬链接数量,即引用数uid
: number 所有者User IDgid
: number 所有者Group IDrdev
: number 这个文件代表文件系统时表示此文件代表的文件系统IDsize
: number 文件大小blksize
: numberblocks
: numberatimeMs
: number 最后访问时间戳mtimeMs
: number 最后修改文件修饰(模式)时间戳ctimeMs
: number 最后修改时间戳birthtimeMs
: number 创建时间atime
: Date;mtime
: Date;ctime
: Date;birthtime
: Date;symlink(target: PathLike, path: PathLike): Promise<void>;
target
目标(要创建软连接的)文件路径,同样可以是Bufferpath
新建的软连接的路径,同样可以是BufferreadFile(path: Buffer|string): Promise<Buffer>;
readFile(path: Buffer|string, options?: {
flag?: "a" | "ax" | "a+" | "ax+" | "as" | "as+" | "r" | "r+" | "rs+" | "w" | "wx" | "w+" | "wx+"
}): Promise<Buffer>;
readFile(path: Buffer|string, options: {
flag?: "a" | "ax" | "a+" | "ax+" | "as" | "as+" | "r" | "r+" | "rs+" | "w" | "wx" | "w+" | "wx+",
encoding?: "utf8" | "hex" | "base64" | "base64url"
} | "utf8" | "hex" | "base64" | "base64url"): Promise<string>;
writeFile(path: Buffer|string, data: string | Buffer | DataView | TypedArray | ArrayBuffer, options?: {
mode?: number;
flag?: "a" | "ax" | "a+" | "ax+" | "as" | "as+" | "r" | "r+" | "rs+" | "w" | "wx" | "w+" | "wx+"
}): Promise<void>;
不多作介绍,看定义就行 。
请求,就是传入主函数的一个参数,函数由export导出和js_import导入以供nginx调用 这个是函数定义(main.js) 。
async main(h:NginxHTTPRequest):any;
这个是导出(main.js) 。
export { main };
这个是导入(nginx http) 。
js_import SCRIPT from 'main.js';
这个是使用(nginx location) 。
location /@api/{
js_content SCRIPT.main;
}
这样,每当请求/@api/时,main()就会被调用,所有Promise完成时VM会被回收 这里讲4个很常用的技巧 。
h.args 是一个数组,官方是这么说的 。
Since 0.7.6, duplicate keys are returned as an array, keys are case-sensitive, both keys and values are percent-decoded. For example, the query string a=1&b=%32&A=3&b=4&B=two%20words is converted to r.args as: {a: "1", b: ["2", "4"], A: "3", B: "two words"} 。
args会自动解码分割,允许重复且重复的会变成一个Array。 这里就很重要了,每一个请求你都需要检查你需要的arg是不是Array或string而不能认为只要不是undefined就是string,下面的代码就是最好的反例 。
if(typeof h.args.action != 'string')
return h.return(400,'invaild request: Action should be defined');
当请求/@api/?action=a&action=b时,这个函数会错误报错,事实上Action已经定义 。
h.headersIn和h.headersOut是Nginx分割好的Header,你可以直接使用 但是这两个常量有很大的限制,必须是Nginx内部专门定义的Header才会出现 其中,headersIn的定义是这样的 。
readonly 'Accept'?: string;
readonly 'Accept-Charset'?: string;
readonly 'Accept-Encoding'?: string;
readonly 'Accept-Language'?: string;
readonly 'Authorization'?: string;
readonly 'Cache-Control'?: string;
readonly 'Connection'?: string;
readonly 'Content-Length'?: string;
readonly 'Content-Type'?: string;
readonly 'Cookie'?: string;
readonly 'Date'?: string;
readonly 'Expect'?: string;
readonly 'Forwarded'?: string;
readonly 'From'?: string;
readonly 'Host'?: string;
readonly 'If-Match'?: string;
readonly 'If-Modified-Since'?: string;
readonly 'If-None-Match'?: string;
readonly 'If-Range'?: string;
readonly 'If-Unmodified-Since'?: string;
readonly 'Max-Forwards'?: string;
readonly 'Origin'?: string;
readonly 'Pragma'?: string;
readonly 'Proxy-Authorization'?: string;
readonly 'Range'?: string;
readonly 'Referer'?: string;
readonly 'TE'?: string;
readonly 'User-Agent'?: string;
readonly 'Upgrade'?: string;
readonly 'Via'?: string;
readonly 'Warning'?: string;
readonly 'X-Forwarded-For'?: string;
这个是headersOut 。
'Age'?: string;
'Allow'?: string;
'Alt-Svc'?: string;
'Cache-Control'?: string;
'Connection'?: string;
'Content-Disposition'?: string;
'Content-Encoding'?: string;
'Content-Language'?: string;
'Content-Length'?: string;
'Content-Location'?: string;
'Content-Range'?: string;
'Content-Type'?: string;
'Date'?: string;
'ETag'?: string;
'Expires'?: string;
'Last-Modified'?: string;
'Link'?: string;
'Location'?: string;
'Pragma'?: string;
'Proxy-Authenticate'?: string;
'Retry-After'?: string;
'Server'?: string;
'Trailer'?: string;
'Transfer-Encoding'?: string;
'Upgrade'?: string;
'Vary'?: string;
'Via'?: string;
'Warning'?: string;
'WWW-Authenticate'?: string;
'Set-Cookie'?: string[];
其中最需要注意的是h.headersOut['Set-Cookie']是一个数组 当然,大部分情况下这些Header足够你玩了,但是有的时候还是需要自定义的,这个时候raw开头的变量上场了 。
readonly rawHeadersIn: Array<[string, string|undefined]>;
readonly rawHeadersOut: Array<[string, string|undefined]>;
这些都是按照数组 [key, value] 排的,你可以用下面的代码快速找到你想要的 。
const headers = {} as Record<string, Array<string>>;
h.rawHeadersIn.forEach(item => item[0] in headers ? headers[item[0]].push(item[1]) : headers[item[0]] = [item[1]])
h['X-user-defined'][0]; // 你想要的
如果是自定义输出的话,第一个想到的是不是应该也是h.rawHeadersOut? 然而,我发现 官方的示例 中用的不是rawHeadersOut而是headersOut 的确,我在rawHeadersOut这些东西的定义下面都发现了 。
[prop: string]: string | string[] | undefined;
这个让rawHeaders系列更加意味不明了,我也不清楚官方的做法 总之用 headersOut 准没错 。
这个函数发送的是整个请求,调用后这个请求就结束了 。
return(status: number, body?: NjsStringOrBuffer): void;
这三个函数是用来搭配响应的,但是我不清楚 官方的用意。 嘛,大部分时间还是别这么玩吧 。
sendHeader(): void;
send(part: NjsStringOrBuffer): void;
finish(): void;
internalRedirect(uri: NjsStringOrBuffer): void;
parent?: NginxHTTPRequest;
subrequest(uri: NjsStringOrBuffer, options: NginxSubrequestOptions & { detached: true }): void;
subrequest(uri: NjsStringOrBuffer, options?: NginxSubrequestOptions | string): Promise<NginxHTTPRequest>;
subrequest(uri: NjsStringOrBuffer, options: NginxSubrequestOptions & { detached?: false } | string,
callback:(reply:NginxHTTPRequest) => void): void;
subrequest(uri: NjsStringOrBuffer, callback:(reply:NginxHTTPRequest) => void): void;
是不是很心动?的确,你可以使用subrequest分割任务,internalRedirect快速服务文件,parent在子请求内直接操纵响应 举个例子,你验证完Token想要发送给客户端一个文件 。
nginx.conf
location /@files/{
internal;
alias /file/;
}
file.js 。
// ....
h.internalRedirect('/@files/' + file_path);
// 这个时候客户端就接收到了`/files/{file_path}`这个文件
请注意这一句话 。
*** if it has not been written to a temporary file. 。
详情请参看我的这篇踩坑文章 https://hi.imzlh.top/2024/07/09.cgi 总之,这是Nginx的Buffer,而客户端的上传如果大于client_body_buffer_size会被写入文件并暴露在变量中 h.variables.request_body_file 。
readonly requestBuffer?: Buffer;
readonly requestText?: string;
需要注意的是,下面的两项是subrequest返回的内容而不会写入客户端Buffer 想要给客户端则需要这样: r.return(res.status, res.responseText) 这个是Nginx官方的例子 。
readonly responseBuffer?: Buffer;
readonly responseText?: string;
error(message: NjsStringOrBuffer): void;
log(message: NjsStringOrBuffer): void;
warn(message: NjsStringOrBuffer): void;
这些很好理解,就是log warn error三个等级的日志 。
这些函数是js_body_filter才能使用的,对于新手像我一样找不到为什么出错的很致命 。
sendBuffer(data: NjsStringOrBuffer, options?: NginxHTTPSendBufferOptions): void;
done(): void;
httpVersion: string
HTTP版本号method: string
HTTP方法,是大写的remoteAddress: string
客户端地址uri: string
请求的URL,在subrequest则是subrequest的URLvariables: NginxVariables
Nginx变量,是UTF8字符串rawVariables: NginxRawVariables
Nginx变量,不同的是值是Buffer
NJS有一个全局命名空间njs.*,这里面的东西全局可用不分场合 。
version: string
njs版本version_number: number
njs版本,字符串版本on(event: "exit", callback: () => void): void
VM退出时的回调dump(value: any, indent?: number): string
pre打印,输出到日志还有一个命名空间叫做ngx.*,这里面的东西与nginx相关 东西太多,我就介绍最重要的 。
fetch(init: NjsStringOrBuffer | Request, options?: NgxFetchOptions): Promise<Response>
fetch
API,只是第二个参数大缩水了
body?: string
headers?: NgxHeaders
method?: string
verify?: boolean
是否验证SSL证书,默认验证,不符合会报错log(level: number, message: NjsStringOrBuffer): void
ngx.INFO
ngx.WARN
ngx.ERR
readonly shared: NgxGlobalShared
shared
就是解决方法js_shared_dict_zone
定义string
或number
,由js_shared_dict_zone
定义worker_id
工作进程的ID,对于定时任务指定很有效最后此篇关于njs最详细的入门手册:NginxJavaScriptEngine的文章就讲到这里了,如果你想了解更多关于njs最详细的入门手册:NginxJavaScriptEngine的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
HBase —— 入门 HBase介绍 HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”
Hive —— 入门 Hive介绍 Apache Hive是一款建立在Hadoop之上的开源数据仓库系统,可以将存储在Hadoop文件中的结构化、半结构化数据文件映射为一张数据库表,基于表提供了一
零:前端目前形势 前端的发展史 HTML(5)、CSS(3)、JavaScript(ES5、ES6):编写一个个的页面 -> 给后端(PHP、Python、Go、Java) ->
在本教程中,您将了解在计算机上运行 JavaScript 的不同方法。 JavaScript 是一种流行的编程语言,具有广泛的应用程序。 JavaScript 以前主要用于使网页具有交
嗨,亲爱的读者们! 今天我要给大家分享一些关于Python爬虫的小案例。你是否曾为了获取特定网页上的数据而烦恼过?或者是否好奇如何从网页中提取信息以供自己使用?那么,这篇文章将会给你一些启示和灵感。
我曾经是一个对编程一窍不通的小白,但因为对互联网世界的好奇心和求知欲的驱使,我踏入了编程的殿堂。在学习的过程中,我发现了一门神奇的编程语言——Python。Python有着简洁、易读的语法,让初学者能
关闭。这个问题是opinion-based 。目前不接受答案。 想要改进这个问题吗?更新问题,以便 editing this post 可以用事实和引文来回答它。 . 已关闭 8 年前。 Improv
我想创建一个像https://apprtc.appspot.com/?r=04188292这样的应用程序。我对 webrtc 了解一点,但无法掌握 google app-engine。如何为 java
我刚刚开始使用 Python 并编写了一个简单的周边程序。但是,每当我在终端中键入 python perimeter.py 时,都会收到以下错误,我不知道如何解决。 >>> python perime
Redis有5个基本数据结构,string、list、hash、set和zset。它们是日常开发中使用频率非常高应用最为广泛的数据结构,把这5个数据结构都吃透了,你就掌握了Redis应用知识的一半了
创建发布web项目 具体步骤: 1.在开发工具中创建一个dynamic web project helloword 2.在webContent中创建index.html文件 3.发布web应用到
如果你在 Ubuntu 上使用终端的时间很长,你可能会希望调整终端的字体和大小以获取一种良好的体验。 更改字体是一种最简单但最直观的 Linux 的终端自定义 的方法。让我
1. 前言 ADODB 是 Active Data Objects Data Base 的简称,它是一种 PHP 存取数据库的函式组件。现在 SFS3 系统 (校园自由软件交流网学务系统) 计划的
我对 neo4j 完全陌生,我很抱歉提出这样一个基本问题。我已经安装了neo4j,我正在使用shell“localhost:7474/webadmin/#/console/” 我正在寻找一个很好的例子
我正在阅读 ios 4 的核心音频,目的是构建一个小测试应用程序。 在这一点上,我对所有 api 的研究感到非常困惑。理想情况下,我想知道如何从两个 mp3 中提取一些样本到数组中。 然后在回调循环中
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是无关紧要的,因
我下载了 GNUStep并安装了它,但是我不确定在哪里可以找到 IDE。有谁知道什么程序可以用作 GNUStep IDE/从哪里获取它们?否则,有没有人知道有关如何创建和编译基本 GNUStep 程序
我正在尝试开始使用 Apache Solr,但有些事情我不清楚。通读tutorial ,我已经设置了一个正在运行的 Solr 实例。我感到困惑的是 Solr 的所有配置(架构等)都是 XML 格式的。
请问有没有关于如何开始使用 BruTile 的文档? 我目前正在使用 SharpMap,我需要预缓存切片以加快进程 最佳答案 我今天正在研究这个:)Mapsui项目site严重依赖 SharpMap
尽我所能,我无法让 CEDET 做任何事情。 Emacs 24.3。我下载了最新的 CEDET 快照。我从他的底部(不是这样)Gentle Introduction 中获取了 Alex Ott 的设置
我是一名优秀的程序员,十分优秀!