Skip to main content
Version: 4.x

中间件

中间件函数是为每个传入连接执行的函数。

¥A middleware function is a function that gets executed for every incoming connection.

中间件函数可用于:

¥Middleware functions can be useful for:

  • 记录

    ¥logging

  • 认证/授权

    ¥authentication / authorization

  • 速率限制

    ¥rate limiting

注意:每个连接仅执行该函数一次(即使该连接包含多个 HTTP 请求)。

¥Note: this function will be executed only once per connection (even if the connection consists in multiple HTTP requests).

info

如果你正在寻找 Express 中间件,请检查 本节

¥If you are looking for Express middlewares, please check this section.

注册中间件

¥Registering a middleware

中间件函数可以访问 套接字实例 和下一个注册的中间件函数。

¥A middleware function has access to the Socket instance and to the next registered middleware function.

io.use((socket, next) => {
if (isValid(socket.request)) {
next();
} else {
next(new Error("invalid"));
}
});

你可以注册多个中间件函数,它们将按顺序执行:

¥You can register several middleware functions, and they will be executed sequentially:

io.use((socket, next) => {
next();
});

io.use((socket, next) => {
next(new Error("thou shall not pass"));
});

io.use((socket, next) => {
// not executed, since the previous middleware has returned an error
next();
});

无论如何,请务必致电 next()。否则,连接将保持挂起状态,直到给定超时后关闭。

¥Please make sure to call next() in any case. Otherwise, the connection will be left hanging until it is closed after a given timeout.

重要的提示:当中间件执行时,Socket 实例并未实际连接,这意味着如果连接最终失败,则不会触发 disconnect 事件。

¥Important note: the Socket instance is not actually connected when the middleware gets executed, which means that no disconnect event will be emitted if the connection eventually fails.

例如,如果客户端手动关闭连接:

¥For example, if the client manually closes the connection:

// server-side
io.use((socket, next) => {
setTimeout(() => {
// next is called after the client disconnection
next();
}, 1000);

socket.on("disconnect", () => {
// not triggered
});
});

io.on("connection", (socket) => {
// not triggered
});

// client-side
const socket = io();
setTimeout(() => {
socket.disconnect();
}, 500);

发送凭证

¥Sending credentials

客户端可以使用 auth 选项发送凭据:

¥The client can send credentials with the auth option:

// plain object
const socket = io({
auth: {
token: "abc"
}
});

// or with a function
const socket = io({
auth: (cb) => {
cb({
token: "abc"
});
}
});

可以在服务器端的 handshake 对象中访问这些凭据:

¥Those credentials can be accessed in the handshake object on the server-side:

io.use((socket, next) => {
const token = socket.handshake.auth.token;
// ...
});

处理中间件错误

¥Handling middleware error

如果使用 Error 对象调用 next 方法,连接将被拒绝,并且客户端将收到 connect_error 事件。

¥If the next method is called with an Error object, the connection will be refused and the client will receive an connect_error event.

// client-side
socket.on("connect_error", (err) => {
console.log(err.message); // prints the message associated with the error
});

你可以将其他详细信息附加到 Error 对象:

¥You can attach additional details to the Error object:

// server-side
io.use((socket, next) => {
const err = new Error("not authorized");
err.data = { content: "Please retry later" }; // additional details
next(err);
});

// client-side
socket.on("connect_error", (err) => {
console.log(err instanceof Error); // true
console.log(err.message); // not authorized
console.log(err.data); // { content: "Please retry later" }
});

与 Express 中间件的兼容性

¥Compatibility with Express middleware

由于它们不绑定到通常的 HTTP 请求/响应周期,因此 Socket.IO 中间件并不真正与 Express 中间件 兼容。

¥Since they are not bound to a usual HTTP request/response cycle, Socket.IO middlewares are not really compatible with Express middlewares.

话虽如此,从 4.6.0 版本开始,底层引擎现在支持 Express 中间件:

¥That being said, starting with version 4.6.0, Express middlewares are now supported by the underlying engine:

io.engine.use((req, res, next) => {
// do something

next();
});

每个传入的 HTTP 请求(包括升级请求)都会调用中间件。

¥The middlewares will be called for each incoming HTTP requests, including upgrade requests.

express-session 为例:

¥Example with express-session:

import session from "express-session";

io.engine.use(session({
secret: "keyboard cat",
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}));

helmet 为例:

¥Example with helmet:

import helmet from "helmet";

io.engine.use(helmet());

如果中间件必须仅应用于握手请求(而不是每个 HTTP 请求),则可以检查 sid 查询参数是否存在。

¥If the middleware must be only applied to the handshake request (and not for each HTTP request), you can check for the existence of the sid query parameter.

passport-jwt 为例:

¥Example with passport-jwt:

io.engine.use((req, res, next) => {
const isHandshake = req._query.sid === undefined;
if (isHandshake) {
passport.authenticate("jwt", { session: false })(req, res, next);
} else {
next();
}
});