You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
7.3 KiB
212 lines
7.3 KiB
"use strict"; |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.uServer = void 0; |
|
const debug_1 = require("debug"); |
|
const server_1 = require("./server"); |
|
const transports_uws_1 = require("./transports-uws"); |
|
const debug = (0, debug_1.default)("engine:uws"); |
|
class uServer extends server_1.BaseServer { |
|
init() { } |
|
cleanup() { } |
|
/** |
|
* Prepares a request by processing the query string. |
|
* |
|
* @api private |
|
*/ |
|
prepare(req, res) { |
|
req.method = req.getMethod().toUpperCase(); |
|
const params = new URLSearchParams(req.getQuery()); |
|
req._query = Object.fromEntries(params.entries()); |
|
req.headers = {}; |
|
req.forEach((key, value) => { |
|
req.headers[key] = value; |
|
}); |
|
req.connection = { |
|
remoteAddress: Buffer.from(res.getRemoteAddressAsText()).toString() |
|
}; |
|
res.onAborted(() => { |
|
debug("response has been aborted"); |
|
}); |
|
} |
|
createTransport(transportName, req) { |
|
return new transports_uws_1.default[transportName](req); |
|
} |
|
/** |
|
* Attach the engine to a µWebSockets.js server |
|
* @param app |
|
* @param options |
|
*/ |
|
attach(app /* : TemplatedApp */, options = {}) { |
|
const path = (options.path || "/engine.io").replace(/\/$/, "") + "/"; |
|
app |
|
.any(path, this.handleRequest.bind(this)) |
|
// |
|
.ws(path, { |
|
compression: options.compression, |
|
idleTimeout: options.idleTimeout, |
|
maxBackpressure: options.maxBackpressure, |
|
maxPayloadLength: this.opts.maxHttpBufferSize, |
|
upgrade: this.handleUpgrade.bind(this), |
|
open: ws => { |
|
ws.transport.socket = ws; |
|
ws.transport.writable = true; |
|
ws.transport.emit("drain"); |
|
}, |
|
message: (ws, message, isBinary) => { |
|
ws.transport.onData(isBinary ? message : Buffer.from(message).toString()); |
|
}, |
|
close: (ws, code, message) => { |
|
ws.transport.onClose(code, message); |
|
} |
|
}); |
|
} |
|
handleRequest(res, req) { |
|
debug('handling "%s" http request "%s"', req.getMethod(), req.getUrl()); |
|
this.prepare(req, res); |
|
req.res = res; |
|
const callback = (errorCode, errorContext) => { |
|
if (errorCode !== undefined) { |
|
this.emit("connection_error", { |
|
req, |
|
code: errorCode, |
|
message: server_1.Server.errorMessages[errorCode], |
|
context: errorContext |
|
}); |
|
this.abortRequest(req.res, errorCode, errorContext); |
|
return; |
|
} |
|
if (req._query.sid) { |
|
debug("setting new request for existing client"); |
|
this.clients[req._query.sid].transport.onRequest(req); |
|
} |
|
else { |
|
const closeConnection = (errorCode, errorContext) => this.abortRequest(res, errorCode, errorContext); |
|
this.handshake(req._query.transport, req, closeConnection); |
|
} |
|
}; |
|
if (this.corsMiddleware) { |
|
// needed to buffer headers until the status is computed |
|
req.res = new ResponseWrapper(res); |
|
this.corsMiddleware.call(null, req, req.res, () => { |
|
this.verify(req, false, callback); |
|
}); |
|
} |
|
else { |
|
this.verify(req, false, callback); |
|
} |
|
} |
|
handleUpgrade(res, req, context) { |
|
debug("on upgrade"); |
|
this.prepare(req, res); |
|
// @ts-ignore |
|
req.res = res; |
|
this.verify(req, true, async (errorCode, errorContext) => { |
|
if (errorCode) { |
|
this.emit("connection_error", { |
|
req, |
|
code: errorCode, |
|
message: server_1.Server.errorMessages[errorCode], |
|
context: errorContext |
|
}); |
|
this.abortRequest(res, errorCode, errorContext); |
|
return; |
|
} |
|
const id = req._query.sid; |
|
let transport; |
|
if (id) { |
|
const client = this.clients[id]; |
|
if (!client) { |
|
debug("upgrade attempt for closed client"); |
|
res.close(); |
|
} |
|
else if (client.upgrading) { |
|
debug("transport has already been trying to upgrade"); |
|
res.close(); |
|
} |
|
else if (client.upgraded) { |
|
debug("transport had already been upgraded"); |
|
res.close(); |
|
} |
|
else { |
|
debug("upgrading existing transport"); |
|
transport = this.createTransport(req._query.transport, req); |
|
client.maybeUpgrade(transport); |
|
} |
|
} |
|
else { |
|
transport = await this.handshake(req._query.transport, req, (errorCode, errorContext) => this.abortRequest(res, errorCode, errorContext)); |
|
if (!transport) { |
|
return; |
|
} |
|
} |
|
res.upgrade({ |
|
transport |
|
}, req.getHeader("sec-websocket-key"), req.getHeader("sec-websocket-protocol"), req.getHeader("sec-websocket-extensions"), context); |
|
}); |
|
} |
|
abortRequest(res, errorCode, errorContext) { |
|
const statusCode = errorCode === server_1.Server.errors.FORBIDDEN |
|
? "403 Forbidden" |
|
: "400 Bad Request"; |
|
const message = errorContext && errorContext.message |
|
? errorContext.message |
|
: server_1.Server.errorMessages[errorCode]; |
|
res.writeStatus(statusCode); |
|
res.writeHeader("Content-Type", "application/json"); |
|
res.end(JSON.stringify({ |
|
code: errorCode, |
|
message |
|
})); |
|
} |
|
} |
|
exports.uServer = uServer; |
|
class ResponseWrapper { |
|
constructor(res) { |
|
this.res = res; |
|
this.statusWritten = false; |
|
this.headers = []; |
|
} |
|
set statusCode(status) { |
|
this.writeStatus(status === 200 ? "200 OK" : "204 No Content"); |
|
} |
|
setHeader(key, value) { |
|
this.writeHeader(key, value); |
|
} |
|
// needed by vary: https://github.com/jshttp/vary/blob/5d725d059b3871025cf753e9dfa08924d0bcfa8f/index.js#L134 |
|
getHeader() { } |
|
writeStatus(status) { |
|
this.res.writeStatus(status); |
|
this.statusWritten = true; |
|
this.writeBufferedHeaders(); |
|
} |
|
writeHeader(key, value) { |
|
if (key === "Content-Length") { |
|
// the content length is automatically added by uWebSockets.js |
|
return; |
|
} |
|
if (this.statusWritten) { |
|
this.res.writeHeader(key, value); |
|
} |
|
else { |
|
this.headers.push([key, value]); |
|
} |
|
} |
|
writeBufferedHeaders() { |
|
this.headers.forEach(([key, value]) => { |
|
this.res.writeHeader(key, value); |
|
}); |
|
} |
|
end(data) { |
|
if (!this.statusWritten) { |
|
// status will be inferred as "200 OK" |
|
this.writeBufferedHeaders(); |
|
} |
|
this.res.end(data); |
|
} |
|
onData(fn) { |
|
this.res.onData(fn); |
|
} |
|
onAborted(fn) { |
|
this.res.onAborted(fn); |
|
} |
|
}
|
|
|