0%

敲黑板

vagrant 命令行报错 Encoding::UndefinedConversionError

使用 vagrant 命令时提示 process_builder.rb:44:in `encode!’: “\xE5” to UTF-8 in conversion from ASCII-8BIT to UTF-8 to UTF-16LE (Encoding::UndefinedConversionError)

本例使用的 vagrant 版本为 2.2.10, 安装目录:<br/> D:\HashiCorp\Vagrant\embedded\gems\2.2.10\gems\childprocess-4.0.0\lib\childprocess\windows\process_builder.rb 第 44 行

修改 newstr.encode!(‘UTF-16LE’) 为 <br/> newstr.encode!(‘UTF-16LE’, invalid: :replace, undef: :replace, replace: ‘?’) 参考连接

vagrant 自制 box 启动时 Authentication failure

  • vagrant 2.3.6
  • virtualBox 7.0.8
  • win 11

Authentication failure 是 ssh 登录证书错误, 但虚拟机已经启动完成

  • .ssh/authorized_keys.vagrant/machines/default/virtualbox/private_key 有变化(内容改动/文件存在)
1
2
3
4
5
6
7
8
9
10
11
12
13
...
==> default: Forwarding ports...
default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
default: SSH address: 127.0.0.1:2222
default: SSH username: vagrant
default: SSH auth method: private key
default: Warning: Authentication failure. Retrying...
default: Warning: Authentication failure. Retrying...
default: Warning: Authentication failure. Retrying...
default: Warning: Authentication failure. Retrying...
...
阅读全文 »

shell 重置密码

  1. 开机按 e 键进入内核编辑
  2. 光标移动到倒数第二段 Linux16 末尾添加 init=/bin/sh
  3. ctrl + x 进行引导启动, 成功进入命令提示界面
  4. 输入 mount -o remount, rw / 挂载根目录
  5. 使用 passwd 命令修改指定用户密码
  6. 直到提示 passwd: all authentication tokens updated successfully.
  7. 输入 touch /.autorelabel 回车
  8. 输入 exec /sbin/init 回车重启系统

命令

  • export 导出全局变量
  • declare 声明变量
  • unset 删除变量
  • local 声明局部变量,一般用于函数内部

单中括号是 POSIX 标准兼容的适用于所有 Unix/Linux 系统, 其他一切双方括号, 双圆括号不是 POSIX 标准兼容的但广泛用于 Bash、Zsh等Shell

PowerShell

dir env: | Get-childItem env: 查看所有的环境变量

$env:[NAME] 查看指定环境变量

设置环境变量

$env:[NAME=VALUE] 仅在当前会话窗口中有效

[System.Environment]::SetEnvironmentVariable(NAME, VALUE, ‘User|Machine’) 永久生效, User 表示用户级别, Machine 表示系统级别

删除环境变量

Remove-Item env:[NAME] 仅在当前会话窗口中有效

[System.Environment]::SetEnvironmentVariable(NAME, $null, “User|Machine”) 永久生效, 将环境变量的设置为 $null

阅读全文 »

敲黑板

启动 nginx 失败

命令行提示错误 98: address already in use

查找系统进程中已存在的 nginx 进程号, 使用 kill -9 $PID 关闭进程后重启 nginx 服务

内置变量

  • $nginx_version nginx 版本

  • $connection_requests TCP 链接当前的请求数量

  • $proxy_protocol_addr 获取代理访问服务器的客户端地址,如果是直接访问,该值为空字符串

    阅读全文 »

Docker 网络

Docker 网络架构源自一种叫作容器网络模型(CNM)的方案, 该方案是开源的并且支持插接式连接

Libnetwork 是 Docker 对 CNM 的一种实现, 提供了 Docker 核心网络架构的全部功能. 不同的驱动可以通过插拔的方式接入 Libnetwork 来提供定制化的网络拓扑

CNM 定义了 3 个基本要素:沙盒(Sandbox)、终端(Endpoint)和网络(Network)

  • 沙盒是一个独立的网络栈, 其中包括以太网接口、端口、路由表以及 DNS 配置
  • 终端就是虚拟网络接口。就像普通网络接口一样,终端主要职责是负责创建连接. 在 CNM 中, 终端负责将沙盒连接到网络
  • 网络是 802.1d 网桥(类似大家熟知的交换机)的软件实现. 因此, 网络就是需要交互的终端的集合, 并且终端之间相互独立

docker 网络采用 veth-pair 技术, 每次启动容器时会自动创建一对虚拟网络设备接口, 一端连着网络协议栈, 一端彼此相连, 停止容器时自动删除, docker0 网卡作为中间的桥梁, 常见的网络模式包含 bridge, host, none, container, overlay 等.

阅读全文 »

敲黑板

docker

客户端/服务器架构

  • 所有的容器操作都需要通过 dockerd 守护进程中转到 OCI(容器运行时)执行, dockerd 一旦崩溃, 所有依赖 dockerd 的容器都会失控, dockerd 会持续占用系统资源.
  • docker 默认以 root 用户运行 dockerd, 如果容器逃逸, 攻击者可以直接获取主机的 root 权限,风险极高.

podman

无守护进程架构

  • 容器操作采用直接调用 OCI(容器运行时) 的模式, 每个容器操作都是独立的进程, 互不依赖. 无单点故障, 资源占用更低.
  • Rootless 原生, 基于 linux 的用户命名空间、挂载命名空间等原生能力, 实现无需 root 权限的容器运行. 普通用户可直接创建和管理容器, 容器内的 根用户 仅映射到主机的普通用户.

安装

  • Package docker-ce is not available, but is referred to by another package.

    如果提示未发现可用的 docker-ce 包时,检查系统镜像源是否正确(如果不能翻墙时, 使用国内的镜像源修改 /etc/apt/source.list)

  • Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.24/images/json: dial unix /var/run/docker.sock: connect: permission denied

    执行 docker 相关命令时提示, 表示 docker 权限不足

    • 使用 sudo 命令运行 docker 命令

    • 将当前用户加入到 docker 组中

1
2
3
4
[root@localhost ~]# cat /etc/group | grep docker
docker:x:994:vagrant
[root@localhost ~]# cat /etc/gshadow | grep docker
docker:!::vagrant
1
2
3
4
5
6
7
8
9
10
groupadd docker # 添加 docker 用户组
gpasswd -a $USER docker # 添加登陆用户到 docker 用户组中
newgrp docker # 更新用户组
或者
usermod -aG docker $USER # 给用户添加一个新的附属组
newgrp docker # 重新登陆组

systemctl restart docker # 重启 docker 服务

systemctl enable docker # 设置 docker 守护进程开机启动
阅读全文 »

ArrayBuffer

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区, 可以理解为一个字节数组. 不能直接操作 ArrayBuffer, 需要通过 类型化数组对象(TypedArray)DataView 操作
ArrayBuffer 构造函数创建一个以字节为单位的固定长度的新 ArrayBuffer, 或者从现有的数据中获取数组缓冲区(例如: Base64 字符串或者 Blob 类文件对象 )

1
2
var buffer = new ArrayBuffer(10); // 创建一个 10 字节的缓冲区
var i8a = new Int32Array(buffer); // 并使用 Int32Array 视图引用它

TypedArray

不能实例化

描述底层 二进制数据缓冲区(ArrayBuffer) 的类数组视图, TypedArray 没有可用的全局属性和构造函数, 其为所有类型化数组的子类提供了实用方法的通用接口, 当创建 TypedArray 子类(例如 Int8Array) 的实例时, 在内存中会创建数组缓冲区, 如果将 ArrayBuffer 实例作为构造函数参数时, 则使用该 ArrayBuffer.

  • Int8Array -128 到 127, 1 字节, 8 位有符号整型(补码)
  • Uint8Array 0 到 255, 1 字节, 8 位无符号整型
  • Uint8ClampedArray 0 到 255, 1 字节, 8 位无符号整型(一定在 0 - 255 之间)
  • Int16Array -32768 到 32767, 2 字节, 16 位有符号整型(补码)
  • Uint16Array 0 到 65535, 2 字节, 16 位无符号整型
  • Int32Array -2147483648 到 2147483647, 4 字节, 32 位有符号整型(补码)
  • Uint32Array 0 到 4294967295, 4 字节, 32 位无符号整型
  • Float32Array -3.4E38 到 3.4E38 并且 1.2E-38 是最小的正数, 4 字节, 32 位 IEEE 浮点数(7 位有效数字,例如 1.234567)
  • Float64Array -1.8E308 到 1.8E308 并且 5E-324 是最小的正数, 8 字节, 64 位 IEEE 浮点数(16 位有效数字,例如 1.23456789012345)
  • BigInt64Array -263 到 263 - 1, 8 字节, 64 位有符号整型(补码)
  • BigUint64Array 0 到 264 - 1, 8 字节, 64 位无符号整型
1
2
var ia = new Int8Array(10); // Int8Array(length) 创建一个长度为 10 的 Int8Array
ia[0] = 42;

参数

实例化子类时可传入以下参数

  • typedArray, 描述底层二进制数据缓冲区的类数组视图
  • object, 描述底层二进制数据缓冲区的类数组视图
  • length, 描述底层二进制数据缓冲区的字节长度
  • buffer,byteOffset,length 创建一个指定字节长度的 ArrayBuffer
1
2
3
var ia = new Int8Array(10); //  创建一个 10 个元素的 Int8Array
var ia = new Int8Array([1, 2, 3]); // 创建一个 3 个元素的 Int8Array
var ia = new Int8Array(new ArrayBuffer(10), 1, 8); // 创建一个 8 个元素的 Int8Array
阅读全文 »

–no-sandbox –disable-web-security –user-data-dir=C:\chromedata

fetch() 表单上传时, 不能设置 Content-Type 头, 否则会丢失文件边界

浏览器引擎

浏览器 渲染引擎 js 引擎
IE Trident JScript(IE3.0-IE8.0) / Chakra(IE9~)
Chrome webkit / Blink V8
Safari webkit Nitro(SquirrelFish)
Firefox Gecko ~Monkey 系列(SpiderMonkey / TraceMonkey / JaegerMonkey / OdinMonkey)
Opera WebKit / Blink Carakan

改变原数组的方法

  • pop 从数组中删除最后一个元素,并返回该元素的值(数组为空时返回 undefined). 此方法更改数组的长度
  • push 将一个或多个元素添加到数组的末尾, 并返回该数组的新长度
  • shift 从数组中删除第一个元素,并返回该元素的值(数组为空则返回 undefined). 此方法更改数组的长度
  • unshift 将一个或多个元素添加到数组的开头. 并返回该数组的新长度(该方法修改原有数组)
  • reverse 将数组中元素的位置颠倒, 并返回该数组. 该方法会改变原数组
  • sort 用原地算法对数组的元素进行排序, 并返回数组
    • compareFunction 用来指定按某种顺序进行排列的函数, 省略则按照转换为的字符串的 unicode 位点进行排序
      • firstEl 第一个用于比较的元素
      • secondEl 第二个用于比较的元素
  • splice 通过删除或替换现有元素或者原地添加新的元素来修改数组, 并以数组形式返回被修改的内容. 此方法会改变原数组
    • start 指定修改的开始位置
    • deleteCount 整数, 表示要移除的数组元素的个数, 如果为 0 或者负数, 则不移除元素
    • item1, item2 要添加进数组的元素,从 start 位置开始, 不指定则删除数组元素
  • fill 用一个固定值填充一个数组中从起始索引到终止索引内的全部元素. 不包括终止索引. 返回修改后的数组
    • value 用来填充数组元素的值
    • start 起始索引, 默认值为 0
    • end 终止索引, 默认值为 this.length
  • copyWithin 浅复制数组的一部分到同一数组中的另一个位置, 并返回它, 不会改变原数组的长度. 返回改变后的数组
    • target 整数, 复制序列到该位置, 如果是负数, target 将从末尾开始计算. 如果 target 大于等于 arr.length, 将会不发生拷贝
    • start 整数, 开始复制元素的起始位置, 如果是负数, start 将从末尾开始计算. 如果 start 被忽略, copyWithin 将会从 0 开始复制
    • end 整数, 开始复制元素的结束位置, copyWithin 将会拷贝到该位置, 但不包括 end 这个位置的元素. 如果是负数, end 将从末尾开始计算. 如果忽略则复制到数组结尾
阅读全文 »

TreeWalker

表示文档子树中的节点和它们的位置

  • root 表示对象的根节点
  • whatShow, 默认值: 0xFFFFFFFF, 表示位掩码的 unsigned long, 由 NodeFilter 的常用属性组合而成, 此参数便于筛选出特定类型的节点
    • 0xFFFFFFFF, NodeFilter.SHOW_ALL 显示所有节点
    • 0x2 , NodeFilter.SHOW_ATTIBUTE 显示 Attr 节点
    • 0x80 , NodeFilter.SHOW_COMMENT 显示 Comment 节点
    • 0x1 , NodeFilter.SHOW_ELEMENT 显示 Element 节点
    • 0x4 , NodeFilter.SHOW_TEXT 显示 Text 节点
  • filter, 回调函数或包含 acceptNode() 方法的对象, 其返回值为 NodeFilter.FILTER_ACCEPT, NodeFilter.FILTER_REJECT 或 NodeFilter.FILTER_SKIP
    • NodeFilter.FILTER_ACCEPT 包含此节点
    • NodeFilter.FILTER_REJECT 不包含以此节点为根的子树中的任意节点
    • NodeFilter.FILTER_SKIP 不包含此节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 返回新创建的 TreeWalker 对象 
const tw = document.createTreeWalker(root, whatToShow, filter);

const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_ElEMENT, (node) =>
node.classList.contains('no-escape')
? NodeFilter.FILTER_REJECT
: node.closest('.escape')
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_SKIP
);
while (treeWalker.nextNode()) {
for (const node of treeWalker.currentNode.childNodes) {
if (node.nodeType === Node.TEXT_NODE && /\S/.test(node.data)) {
// 排除仅含空白符的文本节点
node.data = encodeURI(node.data.replace(/\s+/g, " "));
}
}
}
阅读全文 »

函数防抖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @method debounce 函数防抖
* @param {Function} fn 执行方法
* @param {Number} delay 延迟时间 默认 300 毫秒
* @returns {Function}
*/
const debounce = function (fn, delay) {
if (typeof fn !== 'function') throw new Error('fn is not Function');
delay = delay >= 0 ? delay : 300;

let timer;
return function () {
let context = this;
let args = arguments;

if (timer) clearTimeout(timer);

timer = setTimeout(() => {
fn.apply(context, args);
}, delay);
};
};

函数节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @method throttle 函数节流
* @param {Function} fn 执行方法
* @param {Number} delay 延迟时间 默认 300 毫秒
* @returns {Function}
*/
const throttle = function (fn, delay) {
if (typeof fn !== 'function') throw new Error('fn is not Function');
delay = delay >= 0 ? delay : 300;

var previous = 0;
return function () {
var _this = this;
var args = arguments;
var now = new Date();
if (now - previous > delay) {
fn.apply(_this, args);
previous = now;
}
};
};
阅读全文 »

RSC(React Server Component)
ISR(Incremental Static Regeneration)

React Server Component

异步组件 是服务器组件的一个新特性, 允许在渲染中 await.

RSC 是一种新型的组件, 它在打包之前在独立于客户端应用程序或 SSR 服务器的环境中提前渲染, 在 next.js 中, 渲染工作进一步按路由段划分, 以实现流式和部分渲染

  • 数据获取, 将数据获取移动到更靠近数据源的服务器上, 可以减少获取渲染所需数据的时间以及客户端需要发出的请求数量来提高性能
  • 安全性, 在服务器上保留敏感数据和逻辑, 例如 token 和 API keys, 而不会将它们暴露给客户端
  • 缓存, 通过在服务器上渲染, 结果可以被缓存并在后续请求和跨用户中重用, 减少每个请求的渲染和数据获取量
  • 性能, 减少所需的客户端 javascript 的数量, 对于弱网环境或设备较弱的用户来说需要下载、解析和执行的客户端 javascript 较少
  • 初始化页面加载和首次内容绘制(FCP), 在服务器上生成 HTML, 允许用户立即查看页面而无需等客户端下载、解析和执行渲染页面所需要的 javascript
  • SEO 和 社交网络共享(SNS), 渲染的 HTML 可供搜索引擎机器人用来检索页面, 社交网络机器人可用于为页面生成社交卡预览
  • 流式传输, 服务器组件允许将渲染工作分成块, 并在准备就绪时将其流式传输到客户端. 这允许用户提前查看页面的部分内容, 而无需等待整个页面在服务器上渲染

渲染策略

  • Static Rendering, 路由在构建时渲染, 或在数据重新验证后在后台渲染, 结果被缓存. 以优化用户和服务器请求之间共享渲染的结果
  • Dynamic Redering, 在请求时渲染路由, 针对个性化的数据或只有在请求时才知道的信息
  • Streaming, 从服务器逐步渲染 UI, 并在准备就绪时流式传输给客户端, 允许用户在整个内容完整渲染之前立即看到页面的部分内容.
    有助于提高初始化页面加载性能, 以及依赖于较慢数据获取的 UI. 可以使用 Suspense 组件和 loading.tsx 开启

渲染流程:

  1. 按单个路由段和 Suspense Boundaries 拆分块
  2. 每个块使用 RSC Payload 和 客户端组件 JavaScript 指令渲染 HTML, 然后返回给客户端
  3. 客户端立即显示路由的快速非交互式页面预览, 这仅适用于初始化页面加载
  4. RSC Payload 用于协调客户端和 RSC 树并更新 DOM, JavaScript 指令用于 hydrate 客户端组件并使应用程序具有交互性

RSC Payload

渲染 RSC 树的紧凑二进制表示形式, 包含

  • RSC 的渲染结果
  • 客户端组件应渲染的占位符及其 JavaScript 文件的引用
  • 从 RSC 传递给客户端组件的任何信息

Server Function

是一个在服务器上执行的异步函数, 它们可以在服务器和客户端组件之间调用, 以处理 Next.js 应用程序中的表单和提交和数据突变

当使用 ‘use server’ 指令定义服务器函数时, 将自动创建一个指向服务器函数的引用, 并将该引用传递给客户端组件. 当在客户端组件调用该函数时, React 向服务器发送一个请求来执行该函数, 并返回结果

  • 不限于 <form>, 可以从 Event Handlers、useEffect、第三方库和其他表单元素 <button> 调用
  • 与 Next.js cache 和 revalidate 集成, 当调用一个 action 时, Next.js 可以在单个服务器往返中返回更新的 UI 和新数据
  • 使用 HTTP 的 POST 方法调用
  • 接收的参数 和 返回值 必须可由 React 序列化
  • 可以在应用程序的任何地方重复使用
  • 从其使用的 layout 和 page 继承 运行时
  • 从其使用的 layout 和 page 继承路由段设置, 包含 maxDuration 等字段

为什么使用 POST

  • 幂等性:POST 用于创建/修改数据,支持非幂等操作
  • 请求体:POST 可以携带复杂的序列化数据
  • 语义正确:Server Functions 通常执行有副作用的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// app/invoices/page.tsx
'use client';
import React, { useActionState } from 'react'
import { createInvoice } from './action'

export type InitialStateType = { message: string }

const initialState: InitialStateType = { message: ''}
export default function Page() {
const [state, formAction, pending] = useActionState(createInvoice, initialState)

return (
<form action={formAction}>
<input type="text" name="customerId" className='border-1 border-blue-300 rounded-2xl py-2 px-4 my-4' placeholder='customer id' /><br />
<p className='text-red-500 text-xl'>{state?.message}</p>
<input type="text" name="amount" className='border-1 border-blue-300 rounded-2xl py-2 px-4 my-4' placeholder='amount' /><br />
<input type="checkbox" name="status" className='w-4 h-6 border-blue-300 border-1 rounded-2xl' /><br />
<button type="submit" disabled={pending} className='border-1 border-blue-300 rounded-2xl py-2 px-4 text-2xl text-gray-600 enabled:hover:border-blue-700 enabled:hover:text-white cursor-pointer'>Submit</button>
<br />
</form>
)
}

// app/invoices/action.ts
'use server';
import { redirect } from "next/navigation";
import type { InitialStateType } from './page'

export async function createInvoice(prevState: InitialStateType, formData: FormData) {
const customerId = formData.get('customerId');
const amount = formData.get('amount');
const status = formData.get('status');
console.log(customerId, amount, status);
// mutate data
// revaliate cache
await new Promise((resolve) => setTimeout(resolve, 3000));
// 如果 customerId 不符合条件则返回提示信息
if (!customerId || Number(customerId) > 100) {
return { ...prevState, message: 'Please enter a valid customer ID' };
}
redirect('/');
}
阅读全文 »