Skip to content

Commit

Permalink
feat: add a "connection_error" event
Browse files Browse the repository at this point in the history
The "connection_error" event will be emitted when one of the following
errors occurs:

- Transport unknown
- Session ID unknown
- Bad handshake method
- Bad request
- Forbidden
- Unsupported protocol version

Syntax:

```js
server.on("connection_error", (err) => {
  console.log(err.req);		// the request object
  console.log(err.code);	// the error code, for example 1
  console.log(err.message);	// the error message, for example "Session ID unknown"
  console.log(err.context);     // some additional error context
});
```

Related:

- socketio/socket.io#3819
- #576
  • Loading branch information
darrachequesne committed Apr 30, 2021
1 parent 887ba06 commit 7096e98
Show file tree
Hide file tree
Showing 4 changed files with 305 additions and 65 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,24 @@ The main server/manager. _Inherits from EventEmitter_.
- Fired when a new connection is established.
- **Arguments**
- `Socket`: a Socket object
- `connection_error`
- Fired when an error occurs when establishing the connection.
- **Arguments**
- `error`: an object with following properties:
- `req` (`http.IncomingMessage`): the request that was dropped
- `code` (`Number`): one of `Server.errors`
- `message` (`string`): one of `Server.errorMessages`
- `context` (`Object`): extra info about the error

| Code | Message |
| ---- | ------- |
| 0 | "Transport unknown"
| 1 | "Session ID unknown"
| 2 | "Bad handshake method"
| 3 | "Bad request"
| 4 | "Forbidden"
| 5 | "Unsupported protocol version"


##### Properties

Expand Down
148 changes: 102 additions & 46 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,37 +109,60 @@ class Server extends EventEmitter {
const transport = req._query.transport;
if (!~this.opts.transports.indexOf(transport)) {
debug('unknown transport "%s"', transport);
return fn(Server.errors.UNKNOWN_TRANSPORT, false);
return fn(Server.errors.UNKNOWN_TRANSPORT, { transport });
}

// 'Origin' header check
const isOriginInvalid = checkInvalidHeaderChar(req.headers.origin);
if (isOriginInvalid) {
const origin = req.headers.origin;
req.headers.origin = null;
debug("origin header invalid");
return fn(Server.errors.BAD_REQUEST, false);
return fn(Server.errors.BAD_REQUEST, {
name: "INVALID_ORIGIN",
origin
});
}

// sid check
const sid = req._query.sid;
if (sid) {
if (!this.clients.hasOwnProperty(sid)) {
debug('unknown sid "%s"', sid);
return fn(Server.errors.UNKNOWN_SID, false);
return fn(Server.errors.UNKNOWN_SID, {
sid
});
}
if (!upgrade && this.clients[sid].transport.name !== transport) {
const previousTransport = this.clients[sid].transport.name;
if (!upgrade && previousTransport !== transport) {
debug("bad request: unexpected transport without upgrade");
return fn(Server.errors.BAD_REQUEST, false);
return fn(Server.errors.BAD_REQUEST, {
name: "TRANSPORT_MISMATCH",
transport,
previousTransport
});
}
} else {
// handshake is GET only
if ("GET" !== req.method)
return fn(Server.errors.BAD_HANDSHAKE_METHOD, false);
if (!this.opts.allowRequest) return fn(null, true);
return this.opts.allowRequest(req, fn);
if ("GET" !== req.method) {
return fn(Server.errors.BAD_HANDSHAKE_METHOD, {
method: req.method
});
}

if (!this.opts.allowRequest) return fn();

return this.opts.allowRequest(req, (message, success) => {
if (!success) {
return fn(Server.errors.FORBIDDEN, {
message
});
}
fn();
});
}

fn(null, true);
fn();
}

/**
Expand Down Expand Up @@ -186,9 +209,15 @@ class Server extends EventEmitter {
this.prepare(req);
req.res = res;

const callback = (err, success) => {
if (!success) {
sendErrorMessage(req, res, err);
const callback = (errorCode, errorContext) => {
if (errorCode !== undefined) {
this.emit("connection_error", {
req,
code: errorCode,
message: Server.errorMessages[errorCode],
context: errorContext
});
sendErrorMessage(req, res, errorCode, errorContext);
return;
}

Expand Down Expand Up @@ -231,6 +260,15 @@ class Server extends EventEmitter {
const protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default
if (protocol === 3 && !this.opts.allowEIO3) {
debug("unsupported protocol version");
this.emit("connection_error", {
req,
code: Server.errors.UNSUPPORTED_PROTOCOL_VERSION,
message:
Server.errorMessages[Server.errors.UNSUPPORTED_PROTOCOL_VERSION],
context: {
protocol
}
});
sendErrorMessage(
req,
req.res,
Expand All @@ -244,6 +282,15 @@ class Server extends EventEmitter {
id = await this.generateId(req);
} catch (e) {
debug("error while generating an id");
this.emit("connection_error", {
req,
code: Server.errors.BAD_REQUEST,
message: Server.errorMessages[Server.errors.BAD_REQUEST],
context: {
name: "ID_GENERATION_ERROR",
error: e
}
});
sendErrorMessage(req, req.res, Server.errors.BAD_REQUEST);
return;
}
Expand All @@ -266,6 +313,15 @@ class Server extends EventEmitter {
}
} catch (e) {
debug('error handshaking to transport "%s"', transportName);
this.emit("connection_error", {
req,
code: Server.errors.BAD_REQUEST,
message: Server.errorMessages[Server.errors.BAD_REQUEST],
context: {
name: "TRANSPORT_HANDSHAKE_ERROR",
error: e
}
});
sendErrorMessage(req, req.res, Server.errors.BAD_REQUEST);
return;
}
Expand Down Expand Up @@ -304,9 +360,15 @@ class Server extends EventEmitter {
this.prepare(req);

const self = this;
this.verify(req, true, function(err, success) {
if (!success) {
abortConnection(socket, err);
this.verify(req, true, (errorCode, errorContext) => {
if (errorCode) {
this.emit("connection_error", {
req,
code: errorCode,
message: Server.errorMessages[errorCode],
context: errorContext
});
abortConnection(socket, errorCode, errorContext);
return;
}

Expand Down Expand Up @@ -469,52 +531,46 @@ Server.errorMessages = {
/**
* Sends an Engine.IO Error Message
*
* @param {http.ServerResponse} response
* @param {code} error code
* @param req - the request object
* @param res - the response object
* @param errorCode - the error code
* @param errorContext - additional error context
*
* @api private
*/

function sendErrorMessage(req, res, code) {
const headers = { "Content-Type": "application/json" };

const isForbidden = !Server.errorMessages.hasOwnProperty(code);
if (isForbidden) {
res.writeHead(403, headers);
res.end(
JSON.stringify({
code: Server.errors.FORBIDDEN,
message: code || Server.errorMessages[Server.errors.FORBIDDEN]
})
);
return;
}
if (res !== undefined) {
res.writeHead(400, headers);
res.end(
JSON.stringify({
code: code,
message: Server.errorMessages[code]
})
);
}
function sendErrorMessage(req, res, errorCode, errorContext) {
const statusCode = errorCode === Server.errors.FORBIDDEN ? 403 : 400;
const message =
errorContext && errorContext.message
? errorContext.message
: Server.errorMessages[errorCode];

res.writeHead(statusCode, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
code: errorCode,
message
})
);
}

/**
* Closes the connection
*
* @param {net.Socket} socket
* @param {code} error code
* @param {string} errorCode - the error code
* @param {object} errorContext - additional error context
*
* @api private
*/

function abortConnection(socket, code) {
function abortConnection(socket, errorCode, errorContext) {
socket.on("error", () => {
debug("ignoring error from closed connection");
});
if (socket.writable) {
const message = Server.errorMessages.hasOwnProperty(code)
? Server.errorMessages[code]
: String(code || "");
const message = errorContext.message || Server.errorMessages[errorCode];
const length = Buffer.byteLength(message);
socket.write(
"HTTP/1.1 400 Bad Request\r\n" +
Expand Down
11 changes: 11 additions & 0 deletions test/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,14 @@ exports.eioc = eioc;
*/

require("s").extend();

exports.createPartialDone = (done, count) => {
let i = 0;
return () => {
if (++i === count) {
done();
} else if (i > count) {
done(new Error(`partialDone() called too many times: ${i} > ${count}`));
}
};
};
Loading

0 comments on commit 7096e98

Please sign in to comment.