Node.js躬行记(2)——文件系统和网络
一、文件系统
fs模块可与文件系统进行交互,封装了常规的POSIX函数。POSIX(Portable Operating System Interface,可移植操作系统接口)是UNIX系统的一个设计标准。fs模块提供了多个操作目录和文件的方法,大多会有同步和异步两种版本,同步的方法名称会以Sync为后缀。
1)目录处理
fs模块的readdir()方法可异步的读取目录内容,回调函数包含两个参数,第一个是错误对象,第二个是一个包含文件名称的数组,对应的同步方法是readdirSync()。在下面的示例中,当前目录包含两个文件,上一级目录包含一个目录名称。
const fs = require('fs');
fs.readdir('./', (err, files) => {
console.log(files); //[ 'demo.txt', 'index.js' ]
});
fs.readdir('../', (err, files) => {
console.log(files); //[ '1' ]
});
其它处理目录的方法还有opendir()、mkdir()等。
2)读写文件
在fs模块中,可使用批量方法readFile()将文件内容一次性的加载到内存中,如下所示。
const fs = require('fs');
fs.readFile('./origin.txt', (err, buf) => {
console.log(buf.toString()); //"hello Node.js"
});
对应的写入方法是writeFile(),如下所示,文件路径、写入内容和回调函数是必传的参数。如果文件不存在,那么会自动创建。
fs.writeFile('./target.txt', 'hello Node.js', (err) => {
if (err) throw err;
console.log('文件已被保存');
});
当文件很大时,像上面这样直接读取会有问题,可以改用流式方法createReadStream(),分批次的读取文件,如下所示,每次只读7个字节的内容。
const readable = fs.createReadStream('./origin.txt', {highWaterMark: 7});
readable.on("data", (chunk) => {
/*************/
/* "hello N"
/* "ode.js"
/************/
console.log(chunk.toString());
});
通过管道方法pipe()将origin.txt中的内容写入到target-stream.txt中,如下所示,对于不存在的文件,也会自动创建。
const writable = fs.createWriteStream('./target-stream.txt');
readable.pipe(writable);
3)文件描述
fs模块的stat()方法可读取文件的描述信息,如下所示。
fs.stat('./demo.txt', (err, stats) => {
console.log(stats);
});
回调函数中的stats参数是一个fs.Stats对象,其属性如下所示。它还包含一些判断方法,例如isDirectory()、isFile()等。
Stats {
dev: 195650,
mode: 33206,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: undefined,
ino: 36873221949682120,
size: 13,
blocks: undefined,
atimeMs: 1586227933993.0217,
mtimeMs: 1585882949091.0166,
ctimeMs: 1586227933995.0222,
birthtimeMs: 1586227933993.0217,
atime: 2020-04-07T02:52:13.993Z,
mtime: 2020-04-03T03:02:29.091Z,
ctime: 2020-04-07T02:52:13.995Z,
birthtime: 2020-04-07T02:52:13.993Z
}
fs模块还提供了fstat()方法,在功能上与stat()等价,只是fstat()方法的第一个参数是文件描述符。在POSIX系统中,文件描述符是一个正整数,它实际上是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当打开或创建一个文件时,就会被分配一个文件描述符。
在下面的示例中,open()方法的回调函数中包含一个fd参数(即文件描述符),搭配fstat()方法就能读取文件信息。
fs.open('./demo.txt', 'r', (err, fd) => {
fs.fstat(fd, (err, stats) => {
console.log(stats);
});
});
4)监控文件
fs模块提供了两种方法来监控文件:watch()和watchFile(),前者能监控文件或目录的更改,后者只能监控文件的更改。
watch()方法的监听器回调包含两个参数,第一个是事件类型(包括rename和change),第二个是触发事件的文件名称,如下所示。
fs.watch('./demo.txt', (eventType, filename) => {
console.log(eventType, filename);
});
执行node命令后,每次更改demo.txt文件,就会在控制台打印出下面这条语句。
$ node index.js
change demo.txt
虽然watch()方法的性能优于watchFile()方法,但是watch()不是一个跨平台的方法,其表现在各个平台中并非百分百一致(例如filename参数不能保证提供),而watchFile()是跨平台的。
5)异步文件
fs.promises是Node提供的一组备用的异步文件系统方法,它们会返回Promise对象而不是通过回调来处理结果。例如以Promise的方式使用stat()方法,如下所示。
const fsPromises = require('fs').promises;
fsPromises.stat('./demo.txt').then(stats => {
console.log(stats);
});
二、网络
Node可以处理的网络协议包括HTTP、TCP、HTTPS等,本节将会重点分析HTTP协议。Node中的http模块基于net、stream、events等模块,提供了一系列的网络传输的底层API,可创建HTTP服务器和客户端,包含重定向、代理、上传数据等功能。
1)HTTP服务器
通过http.createServer()方法就能创建HTTP服务器,其内部会调用net.Server()方法,返回一个http.Server实例。当服务被创建后,就能通过server.listen()方法监听一个端口。
在下面的示例中,监听的端口是8000,createServer()的回调函数包含两个参数:req和res,分别表示HTTP的请求和响应,前者是IncomingMessage类(一个可读流)的实例,后者是ServerResponse类(一个可写流)的实例。__dirname是一个全局变量,保存着当前文件所处的路径。
const http = require('http');
const fs = require('fs');
const server = http.createServer(function (req, res) {
fs.readFile(__dirname + "/index.html", function (err, data) {
if (err) {
res.statusCode = 500;
res.end(String(err));
} else {
res.end(data);
}
});
}).listen(8000);
在回调函数中,可修改或解析响应报文,例如像上面当发生错误时,将状态码改成500。在浏览器中访问http://localhost:8000,就能通过end()方法在页面中输出index.html的文件内容。
注意,必须调用响应对象的end()方法结束此次响应,如果省略,那么页面将会一直处于加载中,阻塞内容渲染。
http.Server包含一组事件,例如connection和request,如下所示。前者会在建立新的TCP流时触发,后者会在每次请求服务器时触发。
server.on("connection", function (socket) {
console.log(connection");
});
server.on("request", function (req, res) {
console.log('request');
});
2)重定向
3XX格式的状态码用于重定向,例如在Node中实现302跳转,如下所示。
http.createServer(function (req, res) {
if(req.url == '/strick') {
res.writeHead(302, {'Location': 'http://www.pwstrick.com'});
}
res.end();
}).listen(8000);
当浏览器访问http://localhost:8000/strick时,才会执行重定向。writeHead()方法可一次性设置响应的状态码和所有的首部。如果只想设置单个响应首部,可以使用setHeader()方法。
3)上传数据
在实际的业务开发中,免不了上传数据的需求,例如表单提交、图像上传等。下面是一张form表单,包含一个文本框和一个提交按钮。
<form action="http://localhost:8000" method="post">
<input type="text" name="name" />
<button type="submit">提交</button>
</form>
点击提交按钮,会将整张表单提交到HTTP服务器(代码如下所示),假设文本框中输入的内容为“咖啡机strick”。
const http = require('http');
const querystring = require('querystring'); const server = http.createServer(function (req, res) {
//声明Content-Type响应首部,以免中文乱码
res.setHeader("Content-Type", "application/json; charset=utf-8");
const arr = [];
req.on("data", chunk => {
arr.push(chunk);
});
req.on("end", () => {
const buf = Buffer.concat(arr); //拼接Buffer数据
const params = querystring.decode(buf.toString());
console.log(params.name); //"咖啡机strick"
res.write(JSON.stringify(params)); //响应数据
res.end();
});
}).listen(8000);
在data事件中接收数据并添加到arr数组中,然后在end事件中由Buffer.concat()方法拼接Buffer数据。querystring模块的decode()方法可将查询字符串解析成一个对象。write()方法会将响应数据发送给客户端。
4)客户端服务
http模块提供了request()方法,可让客户端向服务器发起请求。在下面的示例中,postData变量是要发送的数据,options变量是各种配置参数,包括请求首部的信息。
const http = require('http');
const querystring = require('querystring'); const postData = querystring.stringify({
'name': '咖啡机strick'
});
const options = {
hostname: 'localhost',
port: 8000,
path: '/',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = http.request(options, (res) => {
const code = res.statusCode; //状态码
const headers = JSON.stringify(res.headers); //响应首部
res.setEncoding('utf8'); //为可读流设置字符编码
res.on('data', (chunk) => {
console.log(chunk); //响应数据
});
res.on('end', () => {
console.log('响应中已无数据');
});
});
req.write(postData);
req.end();
注意,在请求最后需要调用end()方法,结束本次请求。Node还提供了一个基于request()的便捷方法:get(),它会自动调用req.end()方法,并且请求方法是GET。
5)代理
代理是一种特殊的网络服务,允许一个客户端通过这个服务与目标服务器进行间接的连接,也就是在它们之间建立了一层中转,可用来过滤广告、控制内部资源的访问权限、保障终端的隐私或安全等。
http模块可搭建出一个简易的HTTP代理服务器,下面示例引用自《Node.js硬实战》技巧52一节。
const http = require('http');
const url = require('url');
http.createServer(function (req, res) {
const options = url.parse(req.url); //一个URL对象
options.headers = req.headers; req.on('data', chunk => { //将请求的原始数据发送给代理
proxyRequest.write(chunk);
});
req.on('end', chunk => { //完成原始请求
proxyRequest.end();
}); //复制原始请求
const proxyRequest = http.request(options, proxyResponse => {
proxyResponse.on('data', chunk => { //将响应的原始数据发送给客户端
res.write(chunk);
});
proxyResponse.on('end', chunk => { //完成代理请求
res.end();
});
//将响应报文发送给客户端
res.writeHead(proxyResponse.statusCode, proxyResponse.headers);
});
}).listen(8000);
url模块的parse()方法能将字符串转换成一个URL对象。在绑定请求对象的data事件后,就能将客户端的请求数据发送给代理。通过http.request()方法发起一次代理请求,并将服务器的响应数据发送给客户端。
Node.js躬行记(2)——文件系统和网络的更多相关文章
- Node.js躬行记(1)——Buffer、流和EventEmitter
一.Buffer Buffer是一种Node的内置类型,不需要通过require()函数额外引入.它能读取和写入二进制数据,常用于解析网络数据流.文件等. 1)创建 通过new关键字初始化Buffer ...
- Node.js躬行记(4)——自建前端监控系统
这套前端监控系统用到的技术栈是:React+MongoDB+Node.js+Koa2.将性能和错误量化.因为自己平时喜欢吃菠萝,所以就取名叫菠萝系统.其实在很早以前就有这个想法,当时已经实现了前端的参 ...
- Node.js躬行记(6)——自制短链系统
短链顾名思义是一种很短的地址,应用广泛,例如页面中有一张二维码图片,包含的是一个原始地址(如下所示),如果二维码中的链接需要修改,那么就得发代码替换掉. 原始地址:https://github.com ...
- Node.js躬行记(15)——活动规则引擎
在日常的业务开发中,会包含许多的业务规则,一般就是用if-else硬编码的方式实现,这样就会增加逻辑的维护成本,若无注释,可能都无法理解规则意图. 因为一旦规则有所改变,那么就需要修改代码再发布代码, ...
- Node.js躬行记(19)——KOA源码分析(上)
本次分析的KOA版本是2.13.1,它非常轻量,诸如路由.模板等功能默认都不提供,需要自己引入相关的中间件. 源码的目录结构比较简单,主要分为3部分,__tests__,lib和docs,从名称中就可 ...
- Node.js躬行记(21)——花10分钟入门Node.js
Node.js 不是一门语言,而是一个基于 V8 引擎的运行时环境,下图是一张架构图. 由图可知,Node.js 底层除了 JavaScript 代码之外,还有大量的 C/C++ 代码. 常说 Nod ...
- Node.js躬行记(23)——Worker threads
Node.js 官方提供了 Cluster 和 Child process 创建子进程,通过 Worker threads 模块创建子线程.但前者无法共享内存,通信必须使用 JSON 格式,有一定的局 ...
- Node.js躬行记(3)——命令行工具
一.自定义 创建一个空目录,然后通过npm init命令初始化package.json文件,并按提示输入相关信息或直接回车使用默认信息,生成的内容如下所示. { "name": & ...
- Node.js躬行记(24)——低代码
低代码开发平台(LCDP)是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台.让具有不同经验水平的开发人员可以通过图形化的用户界面,通过拖拽组件和模型驱动的逻辑来创建网页和移动应用程序 ...
随机推荐
- python字符串分段组合(更新)
描述 获得输入的一个字符串s,以字符减号(-)分割s,将其中首尾两段用加号(+)组合后输出. ...
- E. Count The Blocks
E. Count The Blocks 这是一个计数题,又把我卡自闭了...之前也碰到过类似的题目,这次居然还没有写出来,感觉自己还是太菜了,加油补题吧. 题目大意: 给你一个数字 \(n\),代表的 ...
- 面试官:你说你懂动态代理,那你知道为什么JDK中的代理类都要继承Proxy吗?
之前我已经写过了关于动态代理的两篇文章,本来以为这块应该没啥问题,没想到今天又被难住了- 太难了!!! 之前文章的链接: 动态代理学习(一)自己动手模拟JDK动态代理. 动态代理学习(二)JDK动态代 ...
- 海外网站如何通过代理IP进行采集?
海外网站如何通过代理IP进行采集? 我们在做爬虫的时候,经常会遇到这种情况,爬虫最初运行的时候,数据是可以正常获取的,一切看起来都那么的美好,然而,不一会儿,就可能会出现403 Forbidden , ...
- Badboy录制脚本时,提示脚本错误的解决方法
如下,录制时发生错误:
- Mysql常用sql语句(18)- union 全连接
测试必备的Mysql常用sql语句系列 https://www.cnblogs.com/poloyy/category/1683347.html 前言 其实Mysql并没有全连接,Oracle才有全连 ...
- mybatis collection的使用
Mybatis collection的使用 今天学习了mybatis中的collection使用,作为记录以后使用.首先看一下javabean的结构! public class Article { ...
- 00004-form 表单的清空、重置 (jquery)
form 表单的清空.重置 $(':input').not(':button, :submit, :reset').val('').removeAttr('selected').removeAttr( ...
- 用JetBrains PyCharm 开发工具写一个简单python案例
import urllib.request import re #解析html的内容 def getHtml(url): page=urllib.request.urlopen(url) html=p ...
- Unity2D模拟控制位移
using UnityEngine; using System.Collections; public class PlayerController : MonoBehaviour { public ...