Platform-specific ERR_INTERNAL_ASSERTION HTTP error #47162
-
Versionv19.1.0 (from nix, stable 22.11) PlatformLinux 9dbe5639fcc3 5.15.0-1030-gcp #37~20.04.1-Ubuntu SMP Mon Feb 20 04:30:57 UTC 2023 x86_64 GNU/Linux (more specifically, Replit) SubsystemNo response What steps will reproduce the bug?I've been trying to proxy HTTP requests over a WebSocket connection, but however, I've been getting this weird error that, for some reason, only happens when running my code on the aforementioned platform. Requests are converted to their raw TCP equivalent (through a custom Duplex class), sent over the connection, and forwarded to the actual HTTP server. I'm not sure of the exact cause, but below is the code that proxies the connection and my custom Duplex implementation. These are just the suspected culprits and aren't guaranteed to be the root causes of the issue.\ Code used to forward/proxy HTTP requests: (everything is written in TypeScript) export async function forwardHTTP(req: http.IncomingMessage, res: http.ServerResponse) {
const headers = req.headers,
route = new URL.URL(req.url!, `http://${req.headers.host!}`),
agent = new http.Agent()
;(agent as any).createConnection = (options: http.RequestOptions, callback: (err: Error, socket: Duplex) => void) => {
const cId = BACKEND!.getNextConnectionId(),
packet = new SNewConnectionPacket(),
tunnel = new UpstreamConnection(cId, BACKEND!)
packet.channelId = cId
packet.ip = req.socket.remoteAddress
packet.port = req.socket.remotePort
BACKEND!.handler.writePacket(packet, 0)
;(tunnel as any).setTimeout = () => true
;(tunnel as any).setNoDelay = () => true
callback(null as any, tunnel as any)
return tunnel as any
}
const httpConnection = http.request(route, {
agent: agent,
method: req.method,
headers: headers
})
httpConnection.on('response', remote => {
res.writeHead(remote.statusCode!, remote.statusMessage, remote.headers)
remote.pipe(res)
remote.once('close', () => res.end())
res.once('close', () => res.end())
})
let isEnded = false
req.pipe(httpConnection)
res.once('close', () => {
if (isEnded) return
isEnded = true
if (httpConnection.socket) {
httpConnection.socket!.end()
} else {
httpConnection.destroy()
}
})
httpConnection.once('close', () => {
if (isEnded) return
isEnded = true
req.socket!.end()
})
} Duplex implementation: export class UpstreamConnection extends Duplex {
backend: RemoteBackend
channelId: number
isClosed: boolean = false
_dataCb?: Function
constructor(connectionId: number, self: RemoteBackend) {
super()
this.channelId = connectionId
this.backend = self
const cb = (id: number, data: Buffer) => {
if (id == this.channelId) {
this.emit('data', data)
}
}
this.backend.handler.on('packet', cb.bind(this))
this._dataCb = cb
this.backend.connections.push(this)
this.backend.emit('connectionOpen', this)
}
public _write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null | undefined) => void): void {
const data = chunk instanceof Buffer ? chunk : Buffer.from(chunk as string, encoding)
this.backend.handler.writeRaw(data, this.channelId)
callback()
}
public _read(): void {
// handled by this._readCb
}
public _destroy(error?: Error | null, callback?: (error: Error | null) => void): void {
const destroyPacket = new SConnectionEndPacket()
destroyPacket.channelId = this.channelId
this.backend.handler.writePacket(destroyPacket, 0)
this.backend.handler.removeListener("packet", this._dataCb! as any)
this.backend.connections = this.backend.connections.splice(this.backend.connections.indexOf(this), 1)
this.isClosed = true
if (callback) callback(error ?? null)
}
public end(cb?: (() => void) | undefined): this;
public end(chunk: any, cb?: (() => void) | undefined): this;
public end(chunk: any, encoding?: BufferEncoding | undefined, cb?: (() => void) | undefined): this;
public end(chunk?: unknown, encoding?: unknown, cb?: unknown): this {
if (chunk != null) {
this.write(chunk, encoding as any)
}
this._destroy(null)
if (cb != null) (cb as Function)()
return this
}
} Packages used in code (all on NPM): How often does it reproduce? Is there a required condition?I've tried to run the same code/server on both other Linux and Windows, all on the same version of node.js I've discovered this bug on (via nvm), but to no avail. The error simply can't be replicated outside the problematic platform. Everything works as normal, and the request gets successfully proxied. After some digging around, I found out that this only happened on the 7th proxied request. What is the expected behavior? Why is that the expected behavior?I expected the request to get successfully proxied without issue, as shown by running the same code/server on other platforms. What do you see instead?The below error, with a reference to this repo's issues page:
Additional informationNo response |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
I've converted this to a discussion because in all likelihood the bug is in your code, not in node. That specific assert checks that |
Beta Was this translation helpful? Give feedback.
I've converted this to a discussion because in all likelihood the bug is in your code, not in node.
That specific assert checks that
socket.writable == false
after calling end() / destroySoon(). IOW, you're probably not faithfully emulating how a real socket object behaves.