Skip to content

Commit 3ca986e

Browse files
committed
update
1 parent cbaeea3 commit 3ca986e

File tree

6 files changed

+341
-13
lines changed

6 files changed

+341
-13
lines changed
File renamed without changes.

NET网络通信/Fetch/koa-server/package.json renamed to NET网络通信/Fetch/koa-service/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "koa-server",
2+
"name": "koa-service",
33
"version": "1.0.0",
44
"description": "测试在关闭Web浏览器标签前,接收发过来的HTTP请求的服务端",
55
"scripts": {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>SSE</title>
8+
<style>
9+
h1 {
10+
text-align: center;
11+
}
12+
</style>
13+
</head>
14+
15+
<body>
16+
<h1>Server-Sent Events 服务端推送!</h1>
17+
<hr />
18+
<h3><a href="http://localhost:666/api/v1/sse">SSE</a></h3>
19+
<ul id="ul">
20+
<li><b id="msg"></b></li>
21+
</ul>
22+
<script>
23+
let index = 1;
24+
const msg = document.querySelector("#msg");
25+
// 创建SSE对象,连接SSE服务器的事件流端点,路由为/api/v1/sse
26+
const source = new EventSource("http://localhost:666/api/v1/sse");
27+
console.log(source);
28+
29+
// 监听SSE打开事件
30+
source.onopen = function (event) {
31+
console.log("onopen", event);
32+
msg.innerHTML = event.type;
33+
};
34+
35+
source.onmessage = function (event) {
36+
console.log("onmessage", event.data);
37+
msg.innerHTML = event.type;
38+
const { time, message } = JSON.parse(event.data);
39+
const li = document.createElement("li");
40+
li.innerHTML = `${time} ${message}`
41+
ul.append(li);
42+
};
43+
44+
source.addEventListener('update', (event) => {
45+
console.log(JSON.parse(event.data));
46+
msg.innerHTML = event.type;
47+
const { time, message } = JSON.parse(event.data);
48+
const li = document.createElement("li");
49+
li.innerHTML = `${time} ${message}`;
50+
ul.append(li);
51+
});
52+
53+
source.onerror = function (event) {
54+
console.log("onerror", event);
55+
msg.innerHTML = event.type;
56+
// source.close(); // 注:如果不使用source.close()方法来断开连接,浏览器会自动重连的哦!
57+
const li = document.createElement("li");
58+
li.innerHTML = `错误重连 ${ index++} 次`;
59+
ul.append(li);
60+
61+
};
62+
63+
source.onclose = function (event) {
64+
console.log("onclose", event);
65+
msg.innerHTML = event.type;
66+
};
67+
</script>
68+
69+
</body>
70+
71+
</html>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import Koa from "koa";
2+
import Cors from "koa2-cors";
3+
import KoaRouter from "@koa/router";
4+
import bodyParser from "koa-bodyparser";
5+
6+
let App = new Koa();
7+
8+
// 路由中间件
9+
const Router = new KoaRouter(
10+
{ prefix: "/api/v1" }
11+
);
12+
13+
// 请数据解析中间件
14+
App.use(bodyParser());
15+
16+
// 跨域处理中间件
17+
App.use(
18+
Cors({
19+
origin: function (ctx) {
20+
// 设置允许来自指定域名请求
21+
if (ctx.url === "/test") {
22+
return "*"; // 允许来自所有域名请求
23+
}
24+
// return "http://localhost:8080"; //只允许http://localhost:8080这个域名的请求
25+
return "*";
26+
},
27+
maxAge: 5, //指定本次预检请求的有效期,单位为秒。
28+
credentials: true, //是否允许发送Cookie
29+
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], //设置所允许的HTTP请求方法
30+
// allowHeaders: ["Content-Type", "Authorization", "Accept"], //设置服务器支持的所有头信息字段
31+
// exposeHeaders: ["WWW-Authenticate", "Server-Authorization"], //设置获取其他自定义字段
32+
})
33+
);
34+
35+
// 默认get路由
36+
Router.get("/", async (ctx, next) => {
37+
ctx.body = "欢迎使用 Koa Service 666 端口启动成功!";
38+
await next();
39+
})
40+
41+
// get
42+
.get("/sse", async (ctx, next) => {
43+
// 必须禁用Koa的默认响应处理,否则然会覆盖SSE的响应头
44+
ctx.respond = false;
45+
46+
// 设置SSE必须的头部信息
47+
ctx.res.writeHead(200, {
48+
"Content-Type": "text/event-stream",
49+
"Cache-Control": "no-cache", // 禁用缓存
50+
"Connection": "keep-alive", // 连接控制为持久连接
51+
"X-Accel-Buffering": "no", // 如果是Nginx反向代理,防止代理服务器缓冲
52+
53+
// "Access-Control-Allow-Origin": "*", // 允许跨域
54+
// "Access-Control-Allow-Credentials": "true",
55+
// "Access-Control-Allow-Methods": "PUT,POST,GET,DELETE,OPTIONS",
56+
// "Access-Control-Allow-Headers": "Content-Type,Access-Control-Allow-Headers,Authorization,X-Requested-With",
57+
// "X-Powered-By": "3.2.1",
58+
});
59+
60+
// 每秒向客户端发送数据(当前时间)
61+
const interval = setInterval(() => {
62+
// 发送SSE事件, 类型为update
63+
ctx.res.write(`event: update\n`);
64+
65+
// 自定义SSE发送事件,类型为message 发送数据
66+
ctx.res.write(`data: ${JSON.stringify({
67+
time: new Date().toLocaleTimeString(),
68+
message: `服务端推送数据${Math.random()}`,
69+
})}\n\n`); // 数据格式‌:每条消息以\n\n结尾,支持自定义事件类型
70+
}, 1000);
71+
72+
// 监听当客户端断开连接时,清除定时器
73+
ctx.res.on("close", () => {
74+
clearInterval(interval);
75+
console.log("客户端已断开连接!");
76+
ctx.res.end();
77+
});
78+
})
79+
80+
81+
// 全局中间件
82+
App.use(async (ctx, next) => {
83+
ctx.set("Access-Control-Allow-Origin", "*"); //允许来自所有域名请求(不携带cookie请求可以用*,如果有携带cookie请求必须指定域名)
84+
// console.log(ctx);
85+
86+
await next();
87+
if (404 == ctx.status) {
88+
ctx.body = 404;
89+
}
90+
})
91+
92+
.use(Router.routes())
93+
94+
.listen(666, "127.0.0.1", () => {
95+
console.log(
96+
"\n\nKoa服务器已启动, 监听 http://127.0.0.1:666 端口,API前缀为 /api/v1!"
97+
);
98+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "koa-service",
3+
"version": "1.0.0",
4+
"description": "SSE-ServerSentEvents Koa服务端",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [
10+
"SSE;Koa"
11+
],
12+
"author": "沐枫",
13+
"license": "ISC",
14+
"dependencies": {
15+
"@koa/router": "^14.0.0",
16+
"koa": "^3.1.0",
17+
"koa-bodyparser": "^4.4.1",
18+
"koa2-cors": "^2.0.6"
19+
}
20+
}

NET网络通信/SSE-Server-Sent Events/从 WebSocket 到 SSE,实时通信的轻量化趋势.md

Lines changed: 151 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,55 @@ Server-Sent Events (SSE) 是一种允许服务器通过**单个、持久的 HTTP
3434

3535
在前端,你不需要引入任何第三方库。浏览器原生提供了 `EventSource` API,使用起来极其简单:
3636

37-
![图片](D:\GitHub\JavaScript\NET网络通信\SSE-Server-Sent Events\EventSourceAPI.png)
38-
39-
40-
41-
就是这么简单!没有复杂的连接状态管理,没有心跳检测,更没有手动重连逻辑。浏览器为你搞定了一切。
37+
```js
38+
let index = 1;
39+
const msg = document.querySelector("#msg");
40+
41+
// 创建SSE对象,连接SSE服务器的事件流端点,路由为/api/v1/sse
42+
const source = new EventSource("http://localhost:666/api/v1/sse");
43+
console.log(source);
44+
45+
// 监听SSE打开事件
46+
source.onopen = function (event) {
47+
console.log("onopen", event);
48+
msg.innerHTML = event.type;
49+
};
50+
51+
source.onmessage = function (event) {
52+
console.log("onmessage", event.data);
53+
msg.innerHTML = event.type;
54+
const { time, message } = JSON.parse(event.data);
55+
const li = document.createElement("li");
56+
li.innerHTML = `${time} ${message}`
57+
ul.append(li);
58+
};
59+
60+
source.addEventListener('update', (event) => {
61+
console.log(JSON.parse(event.data));
62+
msg.innerHTML = event.type;
63+
const { time, message } = JSON.parse(event.data);
64+
const li = document.createElement("li");
65+
li.innerHTML = `${time} ${message}`;
66+
ul.append(li);
67+
});
68+
69+
source.onerror = function (event) {
70+
console.log("onerror", event);
71+
msg.innerHTML = event.type;
72+
// source.close(); // 注:如果不使用source.close()方法来断开连接,浏览器会自动重连的哦!
73+
const li = document.createElement("li");
74+
li.innerHTML = `错误重连 ${ index++}`;
75+
ul.append(li);
76+
};
77+
78+
source.onclose = function (event) {
79+
console.log("onclose", event);
80+
msg.innerHTML = event.type;
81+
};
82+
83+
```
84+
85+
**就是这么简单!没有复杂的连接状态管理,没有心跳检测,更没有手动重连逻辑。浏览器为你搞定了一切。**
4286

4387

4488

@@ -59,13 +103,108 @@ Server-Sent Events (SSE) 是一种允许服务器通过**单个、持久的 HTTP
59103

60104
### 实战演示:一个简单的实时时钟
61105

62-
让我们看看用 Node.js (Express) 实现一个 SSE 服务有多简单。
63-
64-
**后端 (server.js):**
65-
66-
![图片](https://mmbiz.qpic.cn/sz_mmbiz_png/btsCOHx9LAMdwee30vUSm8nnb7HbwRCjfUlLgCsxwphKiasc3qdWXKxq8LsWzqibtQSQCxVP4QXUTm9KM2WdWJbA/640?wx_fmt=png&from=appmsg&tp=wxpic&wxfrom=5&wx_lazy=1#imgIndex=1)
67-
68-
106+
让我们看看用 Node.js (Koa) 实现一个 SSE 服务有多简单。
107+
108+
**后端 (koa-service.js):**
109+
110+
```js
111+
import Koa from "koa";
112+
import Cors from "koa2-cors";
113+
import KoaRouter from "@koa/router";
114+
import bodyParser from "koa-bodyparser";
115+
116+
let App = new Koa();
117+
118+
// 路由中间件
119+
const Router = new KoaRouter(
120+
{ prefix: "/api/v1" }
121+
);
122+
123+
// 请数据解析中间件
124+
App.use(bodyParser());
125+
126+
// 跨域处理中间件
127+
App.use(
128+
Cors({
129+
origin: function (ctx) {
130+
// 设置允许来自指定域名请求
131+
if (ctx.url === "/test") {
132+
return "*"; // 允许来自所有域名请求
133+
}
134+
// return "http://localhost:8080"; //只允许http://localhost:8080这个域名的请求
135+
return "*";
136+
},
137+
maxAge: 5, //指定本次预检请求的有效期,单位为秒。
138+
credentials: true, //是否允许发送Cookie
139+
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], //设置所允许的HTTP请求方法
140+
// allowHeaders: ["Content-Type", "Authorization", "Accept"], //设置服务器支持的所有头信息字段
141+
// exposeHeaders: ["WWW-Authenticate", "Server-Authorization"], //设置获取其他自定义字段
142+
})
143+
);
144+
145+
// 默认get路由
146+
Router.get("/", async (ctx, next) => {
147+
ctx.body = "欢迎使用 Koa Service 666 端口启动成功!";
148+
await next();
149+
}).get("/sse", async (ctx, next) => {
150+
// 必须禁用Koa的默认响应处理,否则然会覆盖SSE的响应头
151+
ctx.respond = false;
152+
153+
// 设置SSE必须的头部信息
154+
ctx.res.writeHead(200, {
155+
"Content-Type": "text/event-stream",
156+
"Cache-Control": "no-cache", // 禁用缓存
157+
"Connection": "keep-alive", // 连接控制为持久连接
158+
"X-Accel-Buffering": "no", // 如果是Nginx反向代理,防止代理服务器缓冲
159+
160+
// "Access-Control-Allow-Origin": "*", // 允许跨域
161+
// "Access-Control-Allow-Credentials": "true",
162+
// "Access-Control-Allow-Methods": "PUT,POST,GET,DELETE,OPTIONS",
163+
// "Access-Control-Allow-Headers": "Content-Type,Access-Control-Allow-Headers,Authorization,X-Requested-With",
164+
// "X-Powered-By": "3.2.1",
165+
});
166+
167+
// 每秒向客户端发送数据(当前时间)
168+
const interval = setInterval(() => {
169+
// 发送SSE事件, 类型为update
170+
ctx.res.write(`event: update\n`);
171+
172+
// 自定义SSE发送事件,类型为message 发送数据
173+
ctx.res.write(`data: ${JSON.stringify({
174+
time: new Date().toLocaleTimeString(),
175+
message: `服务端推送数据${Math.random()}`,
176+
})}\n\n`); // 数据格式‌:每条消息以\n\n结尾,支持自定义事件类型
177+
}, 1000);
178+
179+
// 监听当客户端断开连接时,清除定时器
180+
ctx.res.on("close", () => {
181+
clearInterval(interval);
182+
console.log("客户端已断开连接!");
183+
ctx.res.end();
184+
});
185+
})
186+
187+
188+
// 全局中间件
189+
App.use(async (ctx, next) => {
190+
ctx.set("Access-Control-Allow-Origin", "*"); //允许来自所有域名请求(不携带cookie请求可以用*,如果有携带cookie请求必须指定域名)
191+
// console.log(ctx);
192+
193+
await next();
194+
if (404 == ctx.status) {
195+
ctx.body = 404;
196+
}
197+
})
198+
199+
.use(Router.routes())
200+
201+
.listen(666, "127.0.0.1", () => {
202+
console.log(
203+
"\n\nKoa服务器已启动, 监听 http://127.0.0.1:666 端口,API前缀为 /api/v1!"
204+
);
205+
});
206+
207+
```
69208

70209
后端代码清晰明了:设置头部,然后在一个循环里用 `res.write()` 发送格式化的数据即可。
71210

0 commit comments

Comments
 (0)