feng xiaohan
TCP 实现 HTTP 服务

在传输层使用 TCP 一层去模拟返回应用层 HTTP 的报文。

后端(node)

import net from "net";

const html: string = `<h1>BLG</h1>`;

const header = [
  "HTTP/1.1 200 OK",
  "Content-Type: text/html",
  `Content-Length: ${html.length}`,
  "Date: Mon, 27 Jul 2009 12:28:53 GMT",
  `\r\n`,
  html,
];

const server = net.createServer((socket) => {
  socket.on("data", (data) => {
    console.log(data.toString());
    if (/GET/.test(data.toString())) {
      socket.write(headers.join("\r\n"));
      socket.end();
    }
  });
});

// 监听 TCP 服务
server.listen(8080, () => {
  console.log("server is running", server.address());
});
WebSocket

是一种在单个 TCP 连接上进行全双工通信的网络协议。HTML5 的新特性,实现客户端和服务端相互之间的实时通信。

相比于 HTTP 协议的请求,使用 WebSocket 可以建立持久的连接,允许服务器主动向客户端推送数据,避免不必要的轮询请求,提高了实时性和效率。

使用场景

  • 实时性要求比较高的应用:比如在线聊天,游戏,数据可视化等。
  • 需要频繁交换数据的应用:在线编辑器,文档管理器等。
  • 需要推送服务的应用:比如实时数据监控、通知系统等。
  • 跨平台应用:比如桌面应用程序、移动应用程序等。

使用

后端

node

  • 安装 ws 和其声明文件。
  • 新建一个 WebSocket 的服务(ws.Server)。
  • 监听客户端的连接。

默认是使用点对点的方式,可以修改为广播的方式,让连接到这个 socket 的客户端都能通过服务端的广播消息,收到对方客户端的消息。(群聊)

  • 遍历 clients,获取每一个连接上的 client,发送消息。

前端

  • 构造一个 WebSocket 的实例,该实例需要传入一个用于建立连接的 url,以 ws 或 wss 开头(不用 http/https)。
  • 在这个实例对象上监听 open()。

发送消息前后端都使用 send(),接收消息默认监听 message 事件。

心跳检测

在实际使用过程中,由干网络波动、弱信号等原因,可能会导 WebSocket 连接断开。为了避免这种请求,可以通过心跳检测机制来检测 WebSocket 连接是否正常,当发现连接断开时尝试重连。

心跳自定义的。

const state = {
  HEART: 1,
  MESSAGE: 2,
};
// ...
let heartInreaval = null;
const heartCheck = () => {
  if (socket.readyState === ws.OPEN) {
    // socket.send("ping");
    socket.send(
      JSON.stringify({
        type: state.HEART,
        message: "心跳检测",
      })
    );
  } else {
    clearInterval(heartInreaval);
  }
};
heartInreaval = setInterval(heartCheck, 5000);
XSS

Cross-site scripting。跨站脚本攻击,利于 Web 网络安全的漏洞,注入恶意的 js 脚本进行攻击。一般分为三种:

XSS 类型

反射型 XSS

攻击者必须以某种方式诱导用户访问一个精心设计的 URL(恶意链接),该连接中可能包含一个 script 标签,攻击者通过注入 js 来实施攻击,获取用户数据等。

特性

  • 即时性。不经过服务器存储,直接通过 HTTP 的 GET 和 POST 请求就能完成一次攻击,拿到用户隐私数据;

存储型 XSS

存储型(HTML 注入型/持久型)最严重的 XSS 攻击。将 js 脚本直接注入到数据库中,使得需要查看数据的用户都会被遭受攻击。主要发生在社区的评论区,留言板,HTML 电子邮件等,通过写入 script 标签,注入 js 脚本来实施攻击,如果前端和后端都没有进行 js 的过滤,该代码就会被注入进数据库中。

DOM 型 XSS

主要是利用 JS 和 HTML 的交互性质以及 DOM 对象实现的。客户端的脚本程序可以动态地检查和修改页面内容,而不依赖于服务器端的数据。
如果用户在客户端输入的数据包含了恶意的 JavaScript 脚本,而这些脚本没有经过适当的过滤和消毒,就会遭到攻击,如:

  • loaction
  • innerHTML(可以使用 textContent)
  • document.write
  • v-html
  • eval

预防 XSS

  • 输入过滤:在 Web 应用程序中对用户输入的数据进行过滤和校验,确保只接收合法的数据;

    使用 xss 的 npm 包,利用第三方工具来防止 xss 注入。

  • 输出转译:在向 Web 页面中输出用户数据时,对其内容进行适当的编码和转义处理;
  • 设置 CSP(Content Security Policy):通过设置响应报文头中的 CSP 策略,限制页面中可执行的脚本来源、样式表、图像、字体等,以此减少恶意代码执行的可能性。(前后端都可以设置)
    • 前端:meta 标签里设置 http-equiv=”Content-Security-Policy”
    • 后端:设置请求头

      例如机制外部脚本执行,外部资源加载,禁止页面嵌入式 iframe。

XSS 靶场

小练习。https://xssaq.com/yx/level1.php

XSS 漏洞扫描工具

  • wvs
  • 椰树
  • safe3
  • Xelenium
  • w3af – Kali
  • vega – Kali
  • burp
  • Kail 操作系统
前端网络状态

前端网络状态

前端判断网络是否离线

  • navigator.onLine

true:在线;false:离线

  • 在 window 监听 online/offine 事件:
window.addEventListener("online", function () {
  console.log("online");
});
window.addEventListener("offline", function () {
  console.log("online");
});

区分强网和弱网环境

使用 navigator.connection,它会返回一个 NetworkInformation 的对象,该对象包含:

  • downlink:当前网络连接的估计下行速度
  • downlinkMax:设备网络连接的最大下行速度
  • effectiveType:当前网络连接的估计速度,类型(如 slow-2g、2g、39、4g 等)
  • rtt:当前网络连接的估计往返时间(单位为毫秒)
  • saveData:是否处于数据节省模式
navigator.sendBeacon

前端发送请求的方法有哪些:

  • AJAX 的 XMLHttpRequest(axios)
  • fetch
  • SSE
  • Websocket
  • jsonp
  • image 的 src

navigator.sendBeacon

应用场景

  • 发送心跳检测包。
  • 做埋点。可以使用 navigator.sendBeacon 在页面关闭或卸载时记录用户的在线时间,pv(page view:页面的浏览量或点击量),uv(unique visitor:独立访客,即访客量),错误日志上报,按钮点击数等。
  • 发送用户反馈。如用户意见、bug 报告等,以便进行产品的优化和改进,

对比 AJAX 和 fetch 的优势

  • 不受页面卸载过程的影响,确保数据可靠发送。
  • 异步执行,不阻塞页面关闭或跳转。
  • 能发送跨域请求。

    使用 HTML5 新增的 ping 类型,ping 类型的请求不需要等待服务端的返回,能支持发送跨域请求。

ping 类型请求

请求类型 ping 是 HTML5 新增的,并且是 sendBeacon 特有的请求类型,该请求只能携带少量数据,但是他不需要等待服务端响应(因此非常适合做埋点)。

缺点

  • 只能发送 post 请求。
  • 只能传输少量数据(64kb 以内),并且只能传输 ArrayBuffer、ArrayBufferView、Blob、DOMString、FormData 或 URLSerchParams 类型的数据,不能传送 json。
  • 没有办法去定义请求头(比如携带 token 之类的)。
  • 如果处理危险的网络环境(比如公共网络),或者开启了广告屏蔽的插件,该请求将失效。

使用

前端

  • 在某个监听函数中,使用 navigator.sendBeacon()来发起请求,第一个参数为 url,第二个参数为要传送的数据。

后端

设置 post 请求(由于 sendBeacon 只能发送 post 请求),并返回简单数据。

计算机网络

OSI 七层模型

该模型为参考模型

TCP/IP 五(四)层模型

该模型为参考模型的实施。

物理层(比特流)

直接和物理介质打交道的。物理层的设备有网卡,网线,集线器,中继器,调制解调器。

物理层的通信(信道)

有线信道

  • 明线:明线是指平行架设在电线杆上的架空线路。(已逐渐被电缆所替代)
    • 优点:传输损耗低;
    • 缺点:易受天气和环境的影响,对外界噪声干扰比较敏感;
  • 对称电缆:由多对双绞线组成的线缆。
  • 同轴电缆:一共有四层,从内到外分别是中心导体、绝缘体、外层导体(带屏蔽)和外皮。同轴电缆能以低损耗的方式传输模拟信号和数字信号,适用于各种应用(如电视广播系统、长途电话传输系统、计算机系统之间短距离跳线以及局域网互联)。
  • 光纤:光导纤维是由玻璃或塑料制成的纤维,利用光在这些纤维中以全反射原理传输的光传导工具。

无线信道

  • 无线电波:以辐射无线电波为传输方式无线信道主要有地波传输,天波传输和视距传输。例如:卫星通讯,电台广播。(常说的 wifi)

小结

在物理层根据传输方式的不同(电、光、无线电波等方式),会获取他们对应的传输信号,再将其转化为 010101 的电信号,单位为比特(bit),所以物理层传输的是比特流

比如电压根据伏度不同转换 01 数字不同。

数据链路层(数据帧)

在物理层已经可以拿到 01010 的比特流了,但是不知道给谁发过去,谁来接收,接收后用来干什么,所以数据链路层需要将比特流组装成一个数据帧(主要是使用 MAC 地址来组帧)。

建立逻辑连接、进行硬件地址寻址、差错校验等功能。将比特组合成字节进而组合成帧,用 MAC 地址访问介质。

MAC 地址:每个网卡的唯一标识。windows 寻找物理地址(MAC 地址):ipconfig/all

有了 MAC 地址和数据帧,就可以进行传播。MAC 地址保证了消息的发送者和接收者,并且知道了数据的内容进行分组。数据链路层以广播的方式在局域网内传播,该局域网内所有的计算机都能接收到消息

交互方式可以使用交换机等。

网络层(数据包)

网络层控制数据链路层与传输层之间的信息转发,建立、维持和终止网络的连接。数据链路层的数据在网络层被转换为数据包,然后通过路径选择、分段组合、顺序、进出路由等控制,将信息从一个网络设备传递到另一个网络设备中。
它主要做了两件事:

  • 寻址:对网络层而言使用 IP 地址来唯一标识互联网上的设备,网络层依靠 IP 地址进行相互通信。

    在数据链路层中已经获得了 MAC 地址,但是 Mac 地址众多,查找起来很麻烦。所以会有一个网络地址——IP

  • 路由:不同网路之间通信的需要借助的设备。

    在同一个网络中的内部通信并不需要网络层设备,仅仅靠数据链路层就可以完成相互通信,对于不同的网络之间相互通信则必须借助路由器等三层设备。

使用互联网的用户在同一个网段中时会产生广播风暴,在该网段中的所有用户都会无差别的进行数据的传播或接收,所以要将用户划分,让他们在不同的网段中,在自己的小网段中进行广播。

互联网就是将无数的子网络构成一个巨大的网络。子网络是网络划分的概念,而子网段是指划分出的具体 IP 地址范围。子网络是在逻辑上划分网络,而子网段是在物理上划分网络。

为了能区分不同的网段,在网络层引入了一个网络地址——IP,也称网址。网络地址帮助我们确定计算机所在的子网络,MAC 地址则将数据包送到该子网络中的目标网卡。
而这一层有一个规范网络地址的协议,就叫做 IP 协议,遵守这个协议的地址就被称为 IP 地址。

有 IPV4 和 IPV6,IPv4 地址长度为 32 位,通常用点分十进制表示(例如,192.168.1.1);IPv6 地址长度为 128 位,通常用冒号分隔的十六进制表示(例如,2001:0db8:85a3:0000:0000:8a2e:0370:7334)。

传输层(数据段)

主要是定义端口号,控流和校验。

传输协议

TCP

面向连接,三次握手四次挥手,是可靠的传输协议。但是速度会降低。

UDP

具有较好的实时性,效率高于 TCP,但是是不可靠的。由于其速度快,常用于直播。

会话层(报文)

在发送方和接收方之间进行通信时创建、维持、之后终止或断开连接的地方。

表示层(报文)

表示层主要做了几件重要的事情:安全,压缩,也是程序在网络中的一个翻译官。

  • 安全:发送方数据发送之前进行加密,在接受者的表示层进行解密。
  • 翻译:图片。音频等文件格式进行解码和编码。

    ASCII 图片是人类能读懂的计算机需要转换成计算机能读懂的编码。

应用层(报文)

使用最多的一层。例如 ajax 调用接口发送 http 请求,域名系统 DNS,邮件协议 SMTP,webSocket 长连接,SSH 协议等。

报文,比如 f12 的 network 里的数据就叫报文,包括请求头、响应体之内的。

Vite 的依赖预构建

快速识别引入第三方包

浏览器原生识别相对路径或者绝对路径,类似import _ from "lodash"的方式浏览器是无法识别的。

如果浏览器帮我们识别引入,会导致浏览器需要加载非常巨大的依赖文件,会让其速度非常慢!

vite 的功能就是识别第三方包,只需要使用 vite 启动即可(默认去找根目录下的 index.html)。

依赖预加载

前言

vite 能够识别非绝对路径或者相对路径的引用,是因为它进行了路径补全

vite 会对 lodash 进行预处理,并把它放在了 node_modules/.vite/deps 目录下。

是 vite 在考虑另外一个问题(模块规范兼容性问题)的时候顺便把这个问题解决了!

依赖预构建

浏览器只认识 esmodule 的包,但是在早期的很多包是以 commonjs 规范导出的,所以需要工具来处理这些包。

vite 处理这些包步骤:

  1. 找到项目中的依赖包,比如 lodash;
  2. 利用 esbuild(对 js 语法进行处理的一个库)将其他规范的代码转换成 esmodule 规范的代码,将其放在 node_modules/.vite/deps 目录下;
  3. 将将 esmodule 规范的各个模块进行统一集成导出。

解决问题

  • 统一包为 esmodule 规范,浏览器可以识别。
  • 对路径的处理上可以直接使用 .vite/deps,方便路径重写。
  • 优化了网络多包传输的性能问题。

关闭依赖预构建

在 vite.config.js 中添加如下配置:

import { defineConfig } from "vite";

export default defineConfig({
  optimizeDeps: {
    exclude: ["lodash-es"],
  },
});

关闭依赖依赖预构建后,访问地址直接在 node_modules 中,浏览器会请求很多依赖文件,这也是原生 esmodule 规范不敢支持直接加载 node_modules 的原因之一。

环境变量

根据当前的代码环境变化的变量就叫做环境变量。我们可以设置生产环境和开发环境的 base_url,来请求不同环境的接口。

运行环境

node.js —— process.env

process.env是 Nodejs 提供的 API,其返回一个对象,包含了当前 Shell 的所有环境变量。

vite.config.js 运行在 node 环境中,所以可以识别process.env变量,而process.env.NODE_ENV就是当前 Node 运行环境变量。

浏览器环境 —— import.meta.env

在浏览器环境中,Vite 在一个特殊的import.meta.env 对象上暴露环境变量。

这些变量在 vite.config.js 中无法访问到的。

自定义环境变量

dotenv

vite 中内置了 dotenv,dotenv 会从环境目录下自动读取.env 文件:

.env:默认所有环境都会加载
.env.[mode]:只在指定模式下加载

加载的环境变量也会通过 import.meta.env字符串形式暴露给客户端源码。为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。

注:所以在其中不用写""也行。

在指定模式下运行时,.env 在 .env.[mode] 中的同名变量会被 .env.[mode] 覆盖,而其他变量则会被进行合并。

指定运行模式(运行加载的自定义环境变量文件)

运行命令时以 –mode 参数指定运行模式,如:

"scripts": {
  "dev": "vite", // 默认开发模式 development
  "build": "vite build", // build 默认为生产模式
  "test": "vite --mode test"
},

指定的--mode xxx会去寻找对应的.env.xxx 文件,并在浏览器中将import.meta.env设置为 xxx 模式。如果没有找到这个文件,则只加载.env中的环境变量。

注意:自定义模式(test)和设置的.env.xxx(.env.test)只在 vite 经过处理的浏览器端生效,Nodejs 是不会承认它的,也就是说只能在使用import.meta.env时才能读取到自定义环境变量,而 process.env.NODE_ENV 中没有,并且通过loadEnv()方法也只能获取到开发环境和生产环境。(测试了一下即使在浏览器中 MOED=test,但是process.env.NODE_ENV还是 development)

更改.env 文件的默认入口地址

一般来说 .env 文件都是建立在根目录下的,但如果环境文件太多,都散落在根目录下会让目录变得混乱。

import { defineConfig } from "vite";
export default defineConfig({
  envDir: "env",
});

这样所有的文件都可以放在根目录下的 env 文件夹下。

更改环境变量的 VITE_前缀

以 envPrefix 开头的环境变量会通过 import.meta.env 暴露在你的客户端源码中。

import { defineConfig } from "vite";
export default defineConfig({
  envPrefix: "FER",
});

获取所有的环境变量

vite.config.js 中的环境变量获取默认以设置的 envPrefix 为开头,可以使用 Vite 内置的 API 方法loadEnv()获取所有的环境变量:

const env1 = loadEnv(process.env.NODE_ENV, process.cwd() + "\\env", ""); // 将所有的配置文件放在 env 文件夹下,这样能获取到 node 环境中(env)的所有变量
const env2 = loadEnv(process.env.NODE_ENV, process.cwd() + "\\env"); // 默认获取到以 VITE\_ 开头的环境变量
const env3 = loadEnv(process.env.NODE_ENV, process.cwd() + "\\env", "FER_"); // 获取到以 FER\_ 开头的环境变量
  • "":表示需要获取的环境变量的前缀,默认为 VITE_开头。
  • 对于不同的 NODE_ENV,只会获取对应模式(只有 production 和 development)的环境变量。
不同环境的 vite 配置文件集成

vite 是运行在 node 环境中的,但是 vite.config.js 却支持 esm 形式的代码,这是因为 vite 在读取 vite.config.js 时,会率先通过 node 解析文件语法, 在加载文件之前进行预处理(如果发现你是 esmodule 规范会直接将你的 esmodule 规范进行替换变成 commonjs 规范)。

指定配置文件

可以通过--config在 vite 运行的时候指定配置文件入口:

vite --config vite.config.js

配置文件语法提示

  • defineConfig 工具函数
    适用于 vscode。

    import { defineConfig } from "vite";
    export default defineConfig({});
    
  • jsdoc 注释

    Vite 本身附带 TypeScript 类型,所以你可以通过 IDE 和 jsdoc 的配合来实现智能提示

    /** @type {import('vite').UserConfig} */
    export default {
      // ...
    };
    

    好像不起作用?

  • TS 配置文件

    import type { UserConfig } from "vite";
    export default {
      // ...
    } satisfies UserConfig;
    

多环境配置文件集成

基本配置

import { defineConfig } from "vite";

import viteBaseConfig from "./vite.base.config";
import viteDevConfig from "./vite.dev.config";
import viteProdConfig from "./vite.prod.config";

export default defineConfig(({ command, mode, isSsrBuild }) => {
  if (command === "serve") {
    return {
      ...viteBaseConfig,
      ...viteDevConfig,
    };
  } else {
    return {
      ...viteBaseConfig,
      ...viteProdConfig,
    };
  }
});

策略模式

const envResolver = {
  serve: () => {
    console.log("开发环境");
    return Object.assign({}, viteBaseConfig, viteDevConfig);
  },
  build: () => {
    console.log("生产环境");
    return Object.assign({}, viteBaseConfig, viteProdConfig);
  },
};
export default defineConfig(({ command, mode }) => {
  return envResolver[command]();
});
静态资源

json 文件、图片、打包后的文件(dist)都是常见的静态资源,静态资源都是一开始就被载入的;而动态资源则通过后续在页面中发起请求后获取的。

静态资源文件夹

vite 默认静态资源存放于为位于项目根目录下的 public 文件夹,该目录中的资源在开发时能直接通过 / 根路径访问到,并且打包时会被完整复制到目标目录的根目录下。

静态资源处理

vite 引入静态资源文件

vite 针对不同的静态资源,在导入 js 文件中时:

  • css 文件:直接引入生效;
  • js 文件:引入可输出为内部导出函数;
  • 图片、媒体和字体文件类型:引入可以默认输出其文件所在的 url 地址;
  • json 文件:引入可以默认输出为对象;

    vite 将其预处理为对象的键值对形式。

将资源引入为 url

一般来说图片、媒体和字体文件类型的文件会自动引入为 url,而 js 或其他文件则不会(引入为内部导出函数和对象),如果想让它们引入为 url 则可以使用 assetsInclude 选项,来添加内部静态资源路径问题。

import { defineConfig } from "vite";
export default defineConfig({
  assetsInclude: ["./src/plugin.ts", "**/*.gltf"],
});

在 vite 的内建支持的资源类型列表中已经包括了很多引入 url 地址使用的资源文件,所以其目的(assetsInclude )主要是针对于那些没在里面的文件但是又需要通过 url 引入。(只是在下的猜测)

内建支持的资源类型列:

  • images:’apng’,’bmp’,’png’,’jpe?g’,’jfif’,’pjpeg’,’pjp’,’gif’,’svg’,’ico’,’webp’,’avif’
  • media:’mp4’,’webm’,’ogg’,’mp3’,’wav’,’flac’,’aac’,’opus’,’mov’,’m4a’,’vtt’
  • fonts:’woff2?’,’eot’,’ttf’,’otf’
  • other:’webmanifest’,’pdf’,’txt’

强制将资源引入为 url

未被包含在内部列表或 assetsInclude 中的资源,可以使用 ?url 后缀显式导入为一个 URL。

import testCss from "./assets/test.css?url ";
console.log("css-path", testCss);

vite 版本 5.3.1:主要是针对 css,可恶 css 的油盐不进,直接引入和放在 assetsInclude 中都不行。

将资源引入为源文件

资源可以使用 ?raw 后缀声明作为字符串(源文件)引入。

import testCss from "./assets/test.css?raw ";
import testPng from "./assets/box.png?raw";
import testJson from "./assets/test.json?raw";

console.log("json-raw", testJson);
console.log("png-raw", testPng);
console.log("css-raw", testCss);

css、js、json 都将其内容以文本形式输出。jpg、mp4 文件以流的形式输出。svg 以 html 文本(svg 标签)输出。