-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfeed.xml
More file actions
1115 lines (845 loc) · 44.1 KB
/
Copy pathfeed.xml
File metadata and controls
1115 lines (845 loc) · 44.1 KB
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" version="2.0">
<channel>
<title>爱学习的好孩子</title>
<link>http://localhost/</link>
<atom:link href="http://localhost/feed.xml" rel="self" type="application/rss+xml"/>
<description>活动老、学到老,绝不投机取巧,坚持日拱一卒,进一寸有一寸的欢喜。</description>
<lastBuildDate>Sat, 15 Jun 2024 09:43:06 GMT</lastBuildDate>
<language>en</language>
<generator>Lume v2.1.2</generator>
<item>
<title>WebSocket server with Oak</title>
<link>http://localhost/archive/websocket-server-with-oak/</link>
<guid isPermaLink="false">http://localhost/archive/websocket-server-with-oak/</guid>
<content:encoded>
<![CDATA[A WebSocket server written in `Oak` is different from the `native` WebSocket server only for the setup phase. This is because Oak uses similar looking, but different APIs for the WebSocket setup. Once the WebSocket connection is established, the other APIs like `onopen`, `onmessage`, `onclose`, `onerror` etc. are the **same** whether it’s Oak or native.
## Setup phase
The setup phase consists of two simple steps:
- Check if connection can be upgraded (`isUpgradable`)
- If upgradable, upgrade it (`upgrade`)
These two simple APIs are part of the Oak’s Context object. These APIs take care of upgrading the underlying HTTP connection to a two-way WebSocket connection.
# Example
<lume-code>
```ts { title="server.ts" }
import { Application } from "jsr:@oak/oak@16.1.0/application";
import { Router } from "jsr:@oak/oak@16.1.0/router";
const app = new Application({ logErrors: false });
const router = new Router();
router.get("/wss", (ctx) => {
if (!ctx.isUpgradable) {
ctx.throw(501);
}
const ws = ctx.upgrade();
ws.onopen = () => {
console.log("Connected to client");
ws.send("Hello from server!");
};
ws.onmessage = (m) => {
console.log("Got message from client: ", m.data);
ws.close();
};
ws.onclose = () => console.log("Disconncted from client");
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen({ port: 8000 });
```
```ts { title="client.ts" }
const ws = new WebSocket("ws://localhost:8000/wss");
ws.onopen = () => console.log("Connected to server");
ws.onmessage = (m) => {
console.log("Got message from server: ", m.data);
ws.send(`bye`);
};
ws.onclose = () => console.log("Disconnected from server");
```
</lume-code>
# 评论]]>
</content:encoded>
<pubDate>Sat, 15 Jun 2024 01:57:54 GMT</pubDate>
</item>
<item>
<title>Linux中~(tilde)和$HOME的区别</title>
<link>http://localhost/linux/linuxzhong-tilde-he-homedequbie/</link>
<guid isPermaLink="false">http://localhost/linux/linuxzhong-tilde-he-homedequbie/</guid>
<content:encoded>
< 中介绍`~`和由它构成的`tilde-prefix`在`shell`中是如何被处理的,用代码举例解释:
1. 没有`引号`或者`任何字符`在`~`之前的时候,才构成所谓`tilde-prefix`。
```shell
# "~"前有引号
$ echo "~"
~
# "~"前有其他字符串
$ echo test~
test~
```
2. 这个`tilde-prefix`中没有任何字符被引号包围的时候,`~`后的字符被当作可能的登录名
```shell
# 由于后面"test"被引号包围,所以"~"被当作字符串处理
$ echo ~"test"
~test
```
3. 如果登录名是`空字符串`,`~`被替换为`shell`变量`$HOME`
```shell
$ echo ~
/home/xiaofei
$ echo ~/foo
/home/xiaofei/foo
```
4. 否则`tilde-prefix`被替换为特定登录名的`$HOME`
```shell
# ~root被当作用户root的home目录
$ echo ~root/foo
/root/foo
# ~xiaofei被当作用户xiaofei的home目录
$ echo ~xiaofei/foo
/home/xiaofei/foo
# 由于"test"用户名不存在,因此"~"被当作字符串处理
$ echo ~test
~test
```
5. 其他一些用法
```shell
# 当前目录
$ pwd
/home/xiaofei/test
# "~+"被替换为当前目录
$ echo ~+/foo
/home/xiaofei/test/foo
# "~-"被替换为上一次所在目录
$ echo ~-/foo
/home/xiaofei/foo
```
虽然该文档中也提到,在指定`$PATH`等环境变量时用`~`是OK的,但是从上述问题来看,`~`在读入另一个程序中进行处理的时候,可能出现意想不到的`Bug`,因此环境变量中尽量用完整的`绝对路径`!
# 评论]]>
</content:encoded>
<pubDate>Fri, 14 Jun 2024 13:56:15 GMT</pubDate>
</item>
<item>
<title>Linux的kill命令用法</title>
<link>http://localhost/linux/linuxdekillminglingyongfa/</link>
<guid isPermaLink="false">http://localhost/linux/linuxdekillminglingyongfa/</guid>
<content:encoded>
<![CDATA[# 概要
```shell
kill [-s 信号说明符 | -n 信号编号 | -信号说明符] pid | 任务说明符 ... 或 kill -l [信号说明符]
向一个任务发送一个信号。
```
## 选项
```shell
-s sig 信号名称。
-n sig 信号名称对应的数字。
-l 列出信号名称。如果在该选项后提供了数字那么假设它是信号名称对应的数字。
-L 等价于-l选项。
```
## 参数
- pid: 进程ID
- jobspec: 作业标识符
## 返回值
返回状态为成功除非给出了非法选项、执行出现错误。
# 例子
```shell
# 列出所有信号名称:
[user2@pc] kill -l
[user2@pc] kill -l 9
KILL
```
下面是常用的信号。
| 信号 | 数字 | 说明 |
|---|---|---|
| HUP | 1 | 终端挂断 |
| INT | 2 | 中断,同 `Ctrl` + `C`
| QUIT | 3 | 退出,同 `Ctrl` + `\`
| KILL | 9 | 强制终止
| TERM | 15 | 终止
| CONT | 18 | 继续(与STOP相反,fg/bg命令)
| STOP | 19 | 暂停,同`Ctrl` + `Z`
> [!tip]
> 只有第`9`种信号`SIGKILL`才可以无条件终止进程,其他信号进程都有权利忽略。
```shell
# 以下发送KILL信号的形式等价
[user2@pc] kill -s SIGKILL PID
[user2@pc] kill -s KILL PID
[user2@pc] kill -n 9 PID
[user2@pc] kill -9 PID
[user2@pc] sleep 90 &
[1] 178420
# 终止作业标识符为1的作业
[user2@pc] kill -9 %1
[user2@pc] jobs -l
[1]+ 178420 KILLED ssh 192.168.1.4
[user2@pc] sleep 90 &
[1] 181357
# 发送停止信号
[user2@pc] kill -s STOP 181357
[user2@pc] jobs -l
[1]+ 181537 Stopped (signal) sleep 90
# 发送继续信号。
[user2@pc] kill -s CONT 181357
[user2@pc] jobs -l
[1]+ 181537 Running sleep 90 &
```
# 注意
- `bash`的作业控制命令包括`bg fg kill wait disown suspend`。
- 该命令是`bash`内建命令,相关的帮助信息请查看`help`命令。
# 评论]]>
</content:encoded>
<pubDate>Fri, 14 Jun 2024 11:57:16 GMT</pubDate>
</item>
<item>
<title>How to access path & query params in Oak?</title>
<link>http://localhost/archive/how-to-access-path-query-params-in-oak/</link>
<guid isPermaLink="false">http://localhost/archive/how-to-access-path-query-params-in-oak/</guid>
<content:encoded>
<![CDATA[> `P` - indicates `path` param
>
> `Q` - indicates `query` param
比如:
```txt
http://localhost:8080/products/101/review/201?sort=recent&limit=100
P P Q Q
```
我们写一个例子来说明:
```ts
import { Application } from "jsr:@oak/oak@16.1.0/application";
import { Router } from "jsr:@oak/oak@16.1.0/router";
const app = new Application();
const router = new Router();
router.get("/books", (ctx) => {
console.log("QUERY PARAMS:");
ctx.request.url.searchParams.forEach((v, k) => console.log(`${k}:${v}`));
console.log("PATH PARAMS:");
console.log(ctx.params);
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen({ port: 80 });
```
然后运行`deno run --allow-net app.ts`
```shell
$ curl "http://localhost/products/101/images/201?a=b&c=d"
QUERY PARAMS:
a:b
c:d
PATH PARAMS:
{ prdId: "101", imgId: "201" }
```
# 评论]]>
</content:encoded>
<pubDate>Fri, 14 Jun 2024 09:28:06 GMT</pubDate>
</item>
<item>
<title>Linux终端Tab补全提示忽略大小写</title>
<link>http://localhost/archive/linuxzhongduantabbuquantishihuluedaxiaoxie/</link>
<guid isPermaLink="false">http://localhost/archive/linuxzhongduantabbuquantishihuluedaxiaoxie/</guid>
<content:encoded>
<![CDATA[只需要在用户目录下创建`.inputrc`文件,并输入`set completion-ignore-case on`,重启终端即可。
```shell
echo "set completion-ignore-case on" > .inputrc
```
# 评论]]>
</content:encoded>
<pubDate>Fri, 14 Jun 2024 03:53:54 GMT</pubDate>
</item>
<item>
<title>FFmpeg</title>
<link>http://localhost/archive/ffmpeg/</link>
<guid isPermaLink="false">http://localhost/archive/ffmpeg/</guid>
<content:encoded>
<![CDATA[# 提取视频中的音频
```shell
# 提取完整音频
ffmpeg -i sample.mp4 sample.mp3
# 提取指定时间段的音频
# ss指定开始时间戳,t指定编码持续时间
ffmpeg -i sample.mp4 -ss 00:03:05 -t 00:00:45.0 -q:a 0 -map a sample.mp3
# 重新编码
ffmpeg -i input-video.avi -vn -acodec copy output-audio.aac
```
# 评论]]>
</content:encoded>
<pubDate>Thu, 13 Jun 2024 07:04:46 GMT</pubDate>
</item>
<item>
<title>Fetch</title>
<link>http://localhost/archive/fetch/</link>
<guid isPermaLink="false">http://localhost/archive/fetch/</guid>
<content:encoded>
<![CDATA[# 一、基本用法
```js
fetch(url, [option]);
```
`fetch`的功能与`XMLHttpRequest`基本相同,但有三个主要的差异。
(1)`fetch`使用`Promise`,不使用回调函数,因此大大简化了写法,写起来更简洁。
(2)`fetch`采用模块化设计,API分散在多个对象上(Response 对象、Request 对象、Headers 对象),更合理一些。
(3)`fetch`通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。
在用法上,`fetch`默认发出`GET`请求,返回一个`Promise`对象。比如:
```js
fetch("https://api.github.com/users/ruanyf")
.then((response) => response.json())
.then((json) => console.log(json))
.catch((err) => console.log("Request Failed", err));
```
上面示例中,`fetch()`接收到的`response`是一个`Stream`对象,`response.json()`是一个异步操作,取出所有内容,并将其转为
JSON 对象。
`Promise`可以使用`await`语法改写,使得语义更清晰。
```js
async function getJSON() {
let url = "https://api.github.com/users/ruanyf";
try {
let response = await fetch(url);
return await response.json();
} catch (error) {
console.log("Request Failed", error);
}
}
```
> 注意:上面示例中,`await`语句必须放在`try...catch`里面,这样才能捕捉异步操作中可能发生的错误。
后文都采用`await`的写法,不使用`.then()`的写法。
# 二、Response对象
## 2.1 Response对象的同步属性
`fetch`请求成功以后,得到的是一个`Response`对象,它对应服务器的`HTTP`回应。
前面说过,`Response`包含的数据通过`Stream`接口异步读取,但是它还包含一些同步属性,对应`HTTP`回应的标头信息`Headers`,可以立即读取。
```js
async function fetchText() {
let response = await fetch("/readme.txt");
console.log(response.status);
console.log(response.statusText);
}
```
上面示例中,`response.status`和`response.statusText`就是同步属性,可以立即读取。具体列表如下:
| 标头信息属性 | 类型 | 说明 |
| ---------- | ------ | ----------------------------------- |
| ok | bool | 表示请求是否成功,true对应状态码200到299,false对应其他 |
| status | number | HTTP的状态码 |
| statusText | string | HTTP的状态信息 |
| url | string | 请求的URL。如果URL存在跳转,该属性返回的是最终URL |
| redirected | bool | 表示请求是否发生过跳转 |
| type | string | 请求的类型 |
`type`可能的值如下:
| 参数 | 说明 |
| -------------- | ----------------------------------------------------------------------------------- |
| basic | 普通请求,即同源请求 |
| cors | 跨域请求 |
| error | 网络错误,主要用于 Service Worker |
| opaque | 如果`fetch`请求的`type`属性设为`no-cors`,就会返回这个值,详见请求部分。表示发出的是`简单的跨域请求`,类似`<form>`表单的那种跨域请求。 |
| opaqueredirect | 如果fetch请求的`redirect`属性设为manual,就会返回这个值,详见请求部分。 |
## 2.2 判断请求是否成功
::: danger 注意
`fetch`发出请求以后,有一个很重要的注意点:只有`网络错误`,或者`无法连接`时才会报错,其他情况都不会报错,而是认为请求成功。
也就是说,即使服务器返回的状态码是`4xx`或`5xx`,`fetch`也不会报错(即`Promise`不会变为`rejected`状态)。 :::
只有通过`Response.status`属性,得到`HTTP`回应的真实状态码,才能判断请求是否成功。请看下面的例子。
```js
async function fetchText() {
let response = await fetch("/readme.txt");
if (response.status >= 200 && response.status < 300) {
return await response.text();
} else {
throw new Error(response.statusText);
}
}
```
上面示例中,`response.status`属性只有等于`2xx`才能认定请求成功。这里不用考虑网址跳转(状态码为
3xx),因为`fetch`会将跳转的状态码自动转为`200`。
另一种方法是判断`response.ok`是否为`true`。
if (response.ok) { // 请求成功 } else { // 请求失败 }
## 2.3 headers属性
`Response`对象还有一个`Response.headers`属性,指向一个`Headers`对象,对应`HTTP`回应的所有标头,可以用`for...of`进行遍历。
```js
const response = await fetch(url);
for (let [key, value] of response.headers) {
console.log(`${key} : ${value}`);
}
// 或者
for (let [key, value] of response.headers.entries()) {
console.log(`${key} : ${value}`);
}
```
### Headers对象的方法
| 方法名 | 说明 |
| ----------------- | --------------------------------- |
| Headers.get() | 根据指定的键名,返回键值 |
| Headers.has() | 返回一个布尔值,表示是否包含某个标头 |
| Headers.set() | 将指定的键名设置为新的键值,如果该键名不存在则会添加 |
| Headers.append() | 添加标头 |
| Headers.delete() | 删除标头 |
| Headers.keys() | 返回一个遍历器,可以依次遍历所有键名。 |
| Headers.values() | 返回一个遍历器,可以依次遍历所有键值。 |
| Headers.entries() | 返回一个遍历器,可以依次遍历所有键值对([key, value]) |
| Headers.forEach() | 依次遍历标头,每个标头都会执行一次参数函数 |
上面的有些方法可以修改标头,那是因为继承自`Headers`接口。对于`HTTP回应`来说,修改标头意义不大,况且很多标头是只读的,浏览器不允许修改。
这些方法中,最常用的是`response.headers.get()`,用于读取某个标头的值。
```js
let res1 = await fetch(url);
res1.headers.get("Content-Type"); // application/json; charset=utf-8
// 键名
for (let key of myHeaders.keys()) {
console.log(key);
}
// 键值
for (let value of myHeaders.values()) {
console.log(value);
}
let res2 = await fetch(url);
res2.headers.forEach(
(value, key) => console.log(key, ":", value),
);
```
## 2.4 读取内容的方法
Response对象根据服务器返回的不同类型的数据,提供了不同的读取方法。
| 方法名 | 说明 |
| ------------- | ------------------------------------------------------------------- |
| text() | 得到文本字符串 |
| json() | 得到`JSON`对象 |
| blob() | 得到二进制`Blob`对象,用于获取二进制文件 |
| formData() | 得到`FormData`表单对象,主要用在`Service Worker`里面,拦截用户提交的表单,修改某些数据以后,再提交给服务器。 |
| arrayBuffer() | 得到二进制`ArrayBuffer`对象,主要用于获取流媒体文件。 |
上面5个读取方法都是`异步`的,返回的都是`Promise`对象。必须等到异步操作结束,才能得到服务器返回的完整数据。
```js
const res1 = await fetch("/users.html");
const body = await res1.text();
document.body.innerHTML = body;
// 读取图片flower.jpg显示在网页上
const res2 = await fetch("flower.jpg");
const myBlob = await res2.blob();
const objectURL = URL.createObjectURL(myBlob);
const myImage = document.querySelector("img");
myImage.src = objectURL;
// 直接获取图片的Base64编码
let myBlob = await res.blob()
function getBase64() {
return new Promise(function(resolve, reject) {
let reader = new FileReader();
reader.readAsDataURL(myBlob);
reader.onloadend = () => resolve(reader.result)
reader.onerror = () => reject('reader error');
});
}
let base64data = await getBase64();
console.log(base64data)
// 获取音频文件song.ogg然后在线播放
const audioCtx = new window.AudioContext();
const source = audioCtx.createBufferSource();
const res3 = await fetch("song.ogg");
const buffer = await res3.arrayBuffer();
const decodeData = await audioCtx.decodeAudioData(buffer);
source.buffer = buffer;
source.connect(audioCtx.destination);
source.loop = true;
```
## 2.5 Response.clone()
`Stream`对象只能读取一次,读取完就没了。这意味着,前一节的五个读取方法,`只能使用一个`,否则会报错。
```js
let text = await response.text();
let json = await response.json(); // 报错
```
上面示例先使用了`response.text()`,把`Stream`读完了。后面再调用`response.json()`就没有内容可读了,所以报错。
`Response`对象提供`clone()`方法,创建Response对象的`副本`,实现多次读取。
```js
const response1 = await fetch("flowers.jpg");
// 复制Response对象
const response2 = response1.clone();
const myBlob1 = await response1.blob();
const myBlob2 = await response2.blob();
image1.src = URL.createObjectURL(myBlob1);
image2.src = URL.createObjectURL(myBlob2);
```
`Response`对象还有一个`redirect()`方法,用于将`Response`结果重定向到指定的`URL`。该方法一般只用在`Service Worker`里面,这里就不介绍了。
## 2.6 Response.body 属性
`Response.body`属性是`Response`对象暴露出的底层接口,返回一个`ReadableStream`对象供用户操作。
它可以用来分块读取内容,应用之一就是`显示下载的进度`。
```js
const response = await fetch("flower.jpg");
const reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
console.log(`Received ${value.length} bytes`);
}
```
上面示例中,`response.body.getReader()`方法返回一个遍历器。这个遍历器的`read()`方法每次返回一个对象,表示本次读取的内容块。
这个对象的`done`属性是一个`bool`,用来判断有没有读完。`value`属性是一个`arrayBuffer`数组,表示内容块的内容,而`value.length`属性是当前块的大小。
# 三、fetch的第二个参数
## fetch配置对象的完整API
```js
const response = fetch(url, {
method: "GET",
headers: {
"Content-Type": "text/plain;charset=UTF-8",
},
body: undefined,
referrer: "about:client",
referrerPolicy: "no-referrer-when-downgrade",
mode: "cors",
credentials: "same-origin",
cache: "default",
redirect: "follow",
integrity: "",
keepalive: false,
signal: undefined,
});
```
`fetch()`请求的底层用的是`Request()`对象的接口,参数完全一样,因此上面的`API`也是`Request()`的`API`。
这些属性里面,headers、body前面已经给过示例了,下面是其他属性的介绍。
### method
`method`属性指定`HTTP`请求的方法,`POST`、`DELETE`、`PUT`都在这个属性设置。
### cache
`cache`属性指定如何处理缓存。可能的取值如下:
| 值 | 说明 |
| -------------- | ------------------------------------ |
| default | 默认值,先在缓存里面寻找匹配的请求 |
| no-store | 直接请求远程服务器,并且不更新缓存 |
| reload | 直接请求远程服务器,并且更新缓存 |
| no-cache | 将服务器资源跟本地缓存进行比较,有新的版本才使用服务器资源,否则使用缓存 |
| force-cache | 缓存优先,只有不存在缓存的情况下,才请求远程服务器 |
| only-if-cached | 只检查缓存,如果缓存里面不存在,将返回504错误 |
### mode
mode属性指定请求的模式。可能的取值如下:
| 值 | 说明 |
| ----------- | ------------------------------------------------------------------ |
| cors | 默认值,允许跨域请求 |
| same-origin | 只允许同源请求 |
| no-cors | 只限于`GET`、`POST`和`HEAD`,并且只能使用有限的几个简单标头,不能添加跨域的复杂标头,相当于提交表单所能发出的请求。 |
> no-cors可以用于简单的跨域请求。
### credentials
`credentials`属性指定是否发送`Cookie`。可能的取值如下:
| 值 | 说明 |
| ----------- | --------------------------- |
| same-origin | 默认值,同源请求时发送Cookie,跨域请求时不发送。 |
| include | 不管同源请求,还是`跨域`请求,一律发送Cookie。 |
| omit | 一律不发送。 |
> `跨域`请求发送`Cookie`,需要将`credentials`属性设为`include`。
### signal
`signal`属性指定一个`AbortSignal`实例,用于取消`fetch`请求。
### keepalive
`keepalive`属性用于页面卸载时,告诉浏览器在后台保持连接,继续发送数据。
一个典型的场景就是,用户离开网页时,脚本向服务器提交一些用户行为的统计信息。这时,如果不用`keepalive`属性,数据可能无法发送,因为浏览器已经把页面卸载了。
```js
window.onunload = function () {
fetch("/analytics", {
method: "POST",
body: "statistics",
keepalive: true,
});
};
```
### redirect
`redirect`属性指定HTTP跳转的处理方法。可能的取值如下:
| 值 | 说明 |
| ------ | --------------------------------------------------------------------------------------- |
| follow | 默认值,跟随HTTP跳转 |
| error | 如果发生跳转,fetch()就报错 |
| manual | 不跟随HTTP跳转,但是`response.url`属性会指向新的URL,`response.redirected`属性会变为`true`,由开发者自己决定后续如何处理跳转。 |
### integrity
`integrity`属性指定一个`哈希`值,用于检查HTTP回应传回的数据是否等于这个预先设定的哈希值。
比如,下载文件时,检查文件的 SHA-256 哈希值是否相符,确保没有被篡改。
```js
fetch("http://site.com/file", {
integrity: "sha256-abcdef",
});
```
### referrer
`referrer`属性用于设定请求的`referer`标头。
这个属性可以为任意字符串,也可以设为`空字符串`(即不发送referer标头)。
### referrerPolicy
`referrerPolicy`属性用于设定`Referer`标头的规则。可能的取值如下:
| 值 | 说明 |
| ------------------------------- | ---------------------------------------------------------- |
| no-referrer-when-downgrade | 默认值,总是发送Referer标头,除非从HTTPS页面请求HTTP资源时不发送。 |
| no-referrer | 不发送Referer标头。 |
| origin | Referer标头只包含域名,不包含完整的路径。 |
| origin-when-cross-origin | 同源请求Referer标头包含完整的路径,跨域请求只包含域名。 |
| same-origin | 跨域请求不发送Referer,同源请求发送。 |
| strict-origin | Referer标头只包含域名,HTTPS 页面请求 HTTP 资源时不发送Referer标头。 |
| strict-origin-when-cross-origin | 同源请求时Referer标头包含完整路径,跨域请求时只包含域名,HTTPS 页面请求 HTTP 资源时不发送该标头。 |
| unsafe-url | 不管什么情况,总是发送Referer标头。 |
# 四、一些实例
## 发送POST请求
```js
const response = await fetch(url, {
method: "POST",
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
},
body: "foo=bar&lorem=ipsum",
});
const json = await response.json();
```
> 注意,有些标头不能通过headers属性设置,比如`Content-Length`、`Cookie`、`Host`等等。它们是由浏览器自动生成,无法修改。
## 提交JSON数据
```js
const user = { name: "John", surname: "Smith" };
const response = await fetch("/article/fetch/post/user", {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify(user),
});
```
## 提交表单
```js
const form = document.querySelector("form");
const response = await fetch("/users", {
method: "POST",
body: new FormData(form),
});
```
## 文件上传
如果表单里面有文件选择器,可以用前一个例子的写法,上传的文件包含在整个表单里面,一起提交。
另一种方法是用脚本添加文件,构造出一个表单,进行上传,请看下面的例子。
```js
const input = document.querySelector('input[type="file"]');
const data = new FormData();
data.append("file", input.files[0]);
data.append("user", "foo");
fetch("/avatars", {
method: "POST",
body: data,
});
```
上传二进制文件时,不用修改标头的`Content-Type`,浏览器会自动设置。
## 直接上传二进制数据
`fetch`也可以直接上传二进制数据,将`Blob`或`arrayBuffer`数据放在`body`属性里面。
```js
let blob = await new Promise((resolve) =>
canvasElem.toBlob(resolve, "image/png")
);
let response = await fetch("/article/fetch/post/image", {
method: "POST",
body: blob,
});
```
## 取消fetch请求
如果中途想要取消`fetch`请求,需要使用`AbortController`对象。
```js
let controller = new AbortController();
let signal = controller.signal;
fetch(url, {
signal,
});
signal.addEventListener("abort", () => console.log("abort!"));
controller.abort(); // 取消
console.log(signal.aborted); // true
```
`controller.abort()`方法用于发出取消信号。这时会触发`abort`事件,这个事件可以监听,也可以通过`controller.signal.aborted`属性判断取消信号是否已经发出。
下面是一个1秒后自动取消请求的例子。
```js
let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);
try {
let response = await fetch("/long-operation", {
signal: controller.signal,
});
} catch (err) {
if (err.name == "AbortError") {
console.log("Aborted!");
} else {
throw err;
}
}
```
# 参考链接
- [Network requests: Fetch](https://javascript.info/fetch)
- [node-fetch](https://github.com/node-fetch/node-fetch)
- [Introduction to fetch()](https://developers.google.com/web/updates/2015/03/introduction-to-fetch)
- [Using Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
- [Javascript Fetch API: The XMLHttpRequest evolution](https://developerhowto.com/2019/09/14/javascript-fetch-api/)
# 评论]]>
</content:encoded>
<pubDate>Thu, 13 Jun 2024 07:03:47 GMT</pubDate>
</item>
<item>
<title>Deno中如何获取操作系统和硬件信息?</title>
<link>http://localhost/archive/denozhongruhehuoqucaozuoxitongheyingjianxinxi/</link>
<guid isPermaLink="false">http://localhost/archive/denozhongruhehuoqucaozuoxitongheyingjianxinxi/</guid>
<content:encoded>
<![CDATA[Runtime APIs Runtime
Deno.version
Deno.build
# 评论]]>
</content:encoded>
<pubDate>Wed, 12 Jun 2024 14:52:05 GMT</pubDate>
</item>
<item>
<title>Deno中如何获取用户目录、临时目录?</title>
<link>http://localhost/archive/denozhongruhehuoquyonghumulu-linshimulu/</link>
<guid isPermaLink="false">http://localhost/archive/denozhongruhehuoquyonghumulu-linshimulu/</guid>
<content:encoded>
<![CDATA[```ts
// 获取用户目录
console.log(Deno.env.get("HOME"));
console.log(Deno.env.get("TEMP"));
// 创建临时文件
const tempFilePath = await Deno.makeTempFile();
console.log("Temp file path:", tempFilePath);
```
类似的例子我们可以在官网查询。
https://docs.deno.com/examples/temporary-files
# 评论]]>
</content:encoded>
<pubDate>Wed, 12 Jun 2024 14:28:07 GMT</pubDate>
</item>
<item>
<title>puppeteer</title>
<link>http://localhost/archive/puppeteer/</link>
<guid isPermaLink="false">http://localhost/archive/puppeteer/</guid>
<content:encoded>
<是一个优秀的浏览器。
## 安装
```shell
PUPPETEER_PRODUCT=chrome deno run -A --unstable https://deno.land/x/puppeteer@16.2.0/install.ts
```
经常在使用脚本自动安装时会出错,这个时候最好手动下载可执行文件。
可以根据提示`Uncaught Error: Could not find browser revision 1022525.`获得具体版本编号。
通常下载链接类似:
`https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/1022525/chrome-linux.zip`,这个里面的`1022525`是版本号。
# 基础知识
我们通过一个简单的实例来做一些了解。
```js
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({
headless: false, //有浏览器界面启动
defaultViewport: null,
timeout: 30000, // 默认超时为30秒,设置为0则表示不设置超时
slowMo: 500, //放慢浏览器执行速度,方便测试观察
args: ["--no-sandbox"],
});
const page = await browser.newPage();
await page.goto("https://www.baidu.com");
await page.close();
await browser.close();
})();
```
> `puppeteer.launch`每次都会启动一个`Chrome`实例。
除此之外,我们也可以使用`puppeteer.connect`连接一个已经存在的`Chrome`实例。
首先启动实例。找到chrome的安装目录,通常在`C:\Program Files (x86)\Google\Chrome\Application`下面。然后从命令行启动:
```shell
$ chrome.exe --remote-debugging-port=9222 --user-data-dir=C:\tmp
```
这样会在`9222`端口启动一个调试实例。`user-data-dir`好像必须要配置,否则连接不成功?然后可以在`http://127.0.0.1:9222/json/version`查看到实例信息。
```txt
// 新建一个标签页(空白页或者带参数默认加载URL)
http://localhost:9222/json/new
http://localhost:9222/json/new?http://www.baidu.com
// 关闭一个标签页,传入该页面的id。
http://localhost:9222/json/close/477810FF-323E-44C5-997C-89B7FAC7B158
// 激活标签页。
http://localhost:9222/json/activate/477810FF-323E-44C5-997C-89B7FAC7B158
// 获取所有开打标签的信息。
http://loacalhost:9222/json/list
```
更详细的协议信息可以[查看官网](https://chromedevtools.github.io/debugger-protocol-viewer/1-2/)
```js
const puppeteer = require("puppeteer");
let request = require("request-promise-native");
(async () => {