| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { gzipSync, gunzipSync } from 'zlib'; |
|
|
| |
|
|
| export function gzip(buf) { return gzipSync(buf); } |
|
|
| export function gunzip(buf) { return gunzipSync(buf); } |
|
|
| export function tryGunzip(buf) { |
| try { return gunzipSync(buf); } |
| catch { return null; } |
| } |
|
|
| |
|
|
| |
| |
| |
| export function wrapEnvelope(protoBuf, { compress = true } = {}) { |
| let payload = protoBuf; |
| let flags = 0; |
| if (compress && payload.length > 0) { |
| payload = gzipSync(payload); |
| flags |= 0x01; |
| } |
| const frame = Buffer.alloc(5 + payload.length); |
| frame[0] = flags; |
| frame.writeUInt32BE(payload.length, 1); |
| payload.copy(frame, 5); |
| return frame; |
| } |
|
|
| |
| |
| |
| export function wrapRequest(protoBuf) { |
| return wrapEnvelope(protoBuf, { compress: true }); |
| } |
|
|
| |
| |
| |
| export function endOfStreamEnvelope() { |
| const trailer = Buffer.from('{}'); |
| const frame = Buffer.alloc(5 + trailer.length); |
| frame[0] = 0x02; |
| frame.writeUInt32BE(trailer.length, 1); |
| trailer.copy(frame, 5); |
| return frame; |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| export function unwrapRequest(body, headers = {}) { |
| let buf = Buffer.isBuffer(body) ? body : Buffer.from(body); |
|
|
| |
| const encoding = headers['content-encoding'] || headers['connect-content-encoding'] || ''; |
| if (encoding === 'gzip') { |
| buf = gunzipSync(buf); |
| } |
|
|
| |
| if (buf.length >= 5) { |
| const flags = buf[0]; |
| const len = buf.readUInt32BE(1); |
| if (len === buf.length - 5 && (flags === 0 || flags === 1)) { |
| let payload = buf.subarray(5); |
| if (flags & 0x01) payload = gunzipSync(payload); |
| return payload; |
| } |
| } |
|
|
| return buf; |
| } |
|
|
| |
|
|
| |
| |
| |
| export class StreamingFrameParser { |
| constructor() { |
| this.buffer = Buffer.alloc(0); |
| } |
|
|
| push(chunk) { |
| this.buffer = Buffer.concat([this.buffer, chunk]); |
| } |
|
|
| |
| drain() { |
| |
| |
| const MAX_FRAME_SIZE = 16 * 1024 * 1024; |
| const frames = []; |
| while (this.buffer.length >= 5) { |
| const len = this.buffer.readUInt32BE(1); |
| if (len > MAX_FRAME_SIZE) { |
| throw new Error(`HTTP/2 frame size ${len} exceeds ${MAX_FRAME_SIZE}`); |
| } |
| if (this.buffer.length < 5 + len) break; |
|
|
| const flags = this.buffer[0]; |
| let payload = this.buffer.subarray(5, 5 + len); |
| if (flags & 0x01) { |
| try { payload = gunzipSync(payload); } |
| catch (err) { |
| |
| |
| |
| |
| throw new Error(`Connect frame decompression failed: ${err.message}`); |
| } |
| } |
|
|
| frames.push({ |
| flags, |
| isEndStream: !!(flags & 0x02), |
| payload, |
| }); |
| this.buffer = this.buffer.subarray(5 + len); |
| } |
| return frames; |
| } |
| } |
|
|
| |
|
|
| export function connectHeaders(extra = {}) { |
| return { |
| 'Content-Type': 'application/connect+proto', |
| 'Connect-Protocol-Version': '1', |
| 'Connect-Accept-Encoding': 'gzip', |
| 'User-Agent': 'connect-es/2.0.0', |
| ...extra, |
| }; |
| } |
|
|