Node知识体系之网络

今天我们的主题是关于网络方面的知识。

Node知识体系之网络

获取本地 IP

function get_local_ip() {
const interfaces = require('os').networkInterfaces();
let IPAdress = '';
for (const devName in interfaces) {
const iface = interfaces[devName];
for (let i = 0; i < iface.length; i++) {
const alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
IPAdress = alias.address;
}
}
}
return IPAdress;
}

TCP 客户端

NodeJS 使用 net 模块创建 TCP 连接和服务。

启动与测试 TCP

const assert = require('assert');
const net = require('net');
let clients = 0;
let expectedAssertions = 2;
const server = net.createServer(function (client) {
clients++;
const clientId = clients;
console.log('Client connected:', clientId);
client.on('end', function () {
console.log('Client disconnected:', clientId);
});
client.write('Welcome client: ' + clientId);
client.pipe(client);
});
server.listen(8000, function () {
console.log('Server started on port 8000');
runTest(1, function () {
runTest(2, function () {
console.log('Tests finished');
assert.equal(0, expectedAssertions);

server.close();
});
});
});
function runTest(expectedId, done) {
const client = net.connect(8000);
client.on('data', function (data) {
const expected = 'Welcome client: ' + expectedId;
assert.equal(data.toString(), expected);
expectedAssertions--;
client.end();
});
client.on('end', done);
}

UDP 客户端

利用 dgram 模块创建数据报 socket,然后利用 socket.send 发送数据。

文件发送服务

const dgram = require('dgram');
const fs = require('fs');
const port = 41230;
const defaultSize = 16;
function Client(remoteIP) {
const inStream = fs.createReadStream(__filename); // 从当前文件创建可读流
const socket = dgram.createSocket('udp4'); // 创建新的数据流 socket 作为客户端
inStream.on('readable', function () {
sendData(); // 当可读流准备好,开始发送数据到服务器
});
function sendData() {
const message = inStream.read(defaultSize); // 读取数据块
if (!message) {
return socket.unref(); // 客户端完成任务后,使用 unref 安全关闭它
}
// 发送数据到服务器
socket.send(message, 0, message.length, port, remoteIP, function () {

sendData();
}
);
}
}
function Server() {
const socket = dgram.createSocket('udp4'); // 创建一个 socket 提供服务
socket.on('message', function (msg) {
process.stdout.write(msg.toString());
});
socket.on('listening', function () {
console.log('Server ready:', socket.address());
});
socket.bind(port);
}
if (process.argv[2] === 'client') { // 根据命令行选项确定运行客户端还是服务端
new Client(process.argv[3]);
} else {
new Server();
}

HTTP 客户端

使用 http.createServer 和 http.createClient 运行 HTTP 服务。

启动与测试 HTTP

const assert = require('assert');
const http = require('http');
const server = http.createServer(function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' }); // 写入基于文本的响应头
res.write('Hello, world.'); // 发送消息回客户端
res.end();
});
server.listen(8000, function() {
console.log('Listening on port 8000');
});
const req = http.request({ port: 8000}, function(res) { // 创建请求
console.log('HTTP headers:', res.headers);
res.on('data', function(data) { // 给 data 事件创建监听,确保和期望值一致

console.log('Body:', data.toString());
assert.equal('Hello, world.', data.toString());
assert.equal(200, res.statusCode);
server.unref();
console.log('测试完成');
});
});
req.end();

重定向

HTTP 标准定义了标识重定向发生时的状态码,它也指出了客户端应该检查无限循环。

  • 300:多重选择
  • 301:永久移动到新位置
  • 302:找到重定向跳转
  • 303:参见其他信息
  • 304:没有改动
  • 305:使用代理
  • 307:临时重定向
const http = require('http');
const https = require('https');
const url = require('url'); // 有很多接续 URLs 的方法
// 构造函数被用来创建一个对象来构成请求对象的声明周期
function Request() {
this.maxRedirects = 10;

this.redirects = 0;
}
Request.prototype.get = function(href, callback) {
const uri = url.parse(href); // 解析 URLs 成为 Node http 模块使用的格式,确定是否使用 HTTPS
const options = { host: uri.host, path: uri.path };
const httpGet = uri.protocol === 'http:' ? http.get : https.get;
console.log('GET:', href);
function processResponse(response) {
if (response.statusCode >= 300 && response.statusCode < 400) { // 检查状态码是否在 HTTP 重定向范围
if (this.redirects >= this.maxRedirects) {
this.error = new Error('Too many redirects for: ' + href);
} else {
this.redirects++; // 重定向计数自增
href = url.resolve(options.host, response.headers.location); // 使用 url.resolve 确保相对路径的 URLs 转换为绝对路径 URLs
return this.get(href, callback);
}
}
response.url = href;
response.redirects = this.redirects;
console.log('Redirected:', href);
function end() {
console.log('Connection ended');
callback(this.error, response);
}
response.on('data', function(data) {
console.log('Got data, length:', data.length);
});
response.on('end', end.bind(this)); // 绑定回调到 Request 实例,确保能拿到实例属性
}
httpGet(options, processResponse.bind(this))
.on('error', function(err) {
callback(err);
});
};
const request = new Request();
request.get('http://google.com/', function(err, res) {
if (err) {
console.error(err);
} else {
console.log(`
Fetched URL: ${res.url} with ${res.redirects} redirects
`);
process.exit();

}
});

HTTP 代理

  • ISP 使用透明代理使网络更加高效
  • 使用缓存代理服务器减少宽带
  • Web 应用程序的 DevOps 利用他们提升应用程序性能
const http = require('http');
const url = require('url');
http.createServer(function(req, res) {
console.log('start request:', req.url);
const options = url.parse(req.url);
console.log(options);
options.headers = req.headers;
const proxyRequest = http.request(options, function(proxyResponse) { // 创建请求来复制原始的请求
proxyResponse.on('data', function(chunk) { // 监听数据,返回给浏览器
console.log('proxyResponse length:', chunk.length);
res.write(chunk, 'binary');
});
proxyResponse.on('end', function() { // 追踪代理请求完成
console.log('proxied request ended');
res.end();
});
res.writeHead(proxyResponse.statusCode, proxyResponse.headers); // 发送头部信息给服务器
});
req.on('data', function(chunk) { // 捕获从浏览器发送到服务器的数据
console.log('in request length:', chunk.length);
proxyRequest.write(chunk, 'binary');
});
req.on('end', function() { // 追踪原始的请求什么时候结束

console.log('original request ended');
proxyRequest.end();
});
}).listen(8888); // 监听来自本地浏览器的连接

封装 request-promise

const https = require('https');
const promisify = require('util').promisify;
https.get[promisify.custom] = function getAsync(options) {
return new Promise((resolve, reject) => {
https.get(options, (response) => {
response.end = new Promise((resolve) => response.on('end', resolve));
resolve(response);
}).on('error', reject);
});
};
const rp = promisify(https.get);
(async () => {
const res = await rp('https://jsonmock.hackerrank.com/api/movies/search/?Title=Spiderman&page=1');
let body = '';
res.on('data', (chunk) => body += chunk);
await res.end;
console.log(body);
})();

DNS 请求

使用 dns 模块创建 DNS 请求。

  • A:dns.resolve,A 记录存储 IP 地址
  • TXT:dns.resulveTxt,文本值可以用于在 DNS 上构建其他服务
  • SRV:dns.resolveSrv,服务记录定义服务的定位数据,通常包含主机名和端口号
  • NS:dns.resolveNs,指定域名服务器
  • CNAME:dns.resolveCname,相关的域名记录,设置为域名而不是 IP 地址
const dns = require('dns');
dns.resolve('www.chenng.cn', function (err, addresses) {
if (err) {
console.error(err);
}
console.log('Addresses:', addresses);
});

crypto 库加密解密

const crypto = require('crypto')
function aesEncrypt(data, key = 'key') {
const cipher = crypto.createCipher('aes192', key)
let crypted = cipher.update(data, 'utf8', 'hex')
crypted += cipher.final('hex')
return crypted
}
function aesDecrypt(encrypted, key = 'key') {
const decipher = crypto.createDecipher('aes192', key)
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
}

发起 HTTP 请求的方法

  • HTTP 标准库
  • 无需安装外部依赖
  • 需要以块为单位接受数据,自己监听 end 事件
  • HTTP 和 HTTPS 是两个模块,需要区分使用
  • Request 库
  • 使用方便
  • 有 promise 版本 request-promise
  • Axios
  • 既可以用在浏览器又可以用在 NodeJS
  • 可以使用 axios.all 并发多个请求
  • SuperAgent
  • 可以链式使用
  • node-fetch
  • 浏览器的 fetch 移植过来的


分享到:


相關文章: