uWebSockets.js icon indicating copy to clipboard operation
uWebSockets.js copied to clipboard

Proposal: Refactor DeclarativeResponse

Open webcarrot opened this issue 1 year ago • 8 comments

  • Allow to write blobs
  • Check text/blob size

PS. I like this ArrayBuffer based API 😉 PPS. In theory we can send blobs of byte length greater than > MAX U16 by calling write/5 multiple times.

webcarrot avatar Jun 16 '24 13:06 webcarrot

It blows up in size.

uNetworkingAB avatar Aug 07 '24 15:08 uNetworkingAB

@uNetworkingAB Should I close this one? (I use different implementation anyway.)

webcarrot avatar Aug 08 '24 15:08 webcarrot

It still needs binary support but I did not like the size blowing up like that. What kind of implementation do you use?

uNetworkingAB avatar Aug 18 '24 13:08 uNetworkingAB

Something like (playgound):

const TE = new TextEncoder();
const EU8A = new Uint8Array(0);

function toU8(data: string | Uint8Array | undefined): Uint8Array {
	if (!data) return EU8A;
	if (typeof data === 'string') return TE.encode(data);
	return data;
}

const enum OPC {
	END = 0,
	WRITE_HEADER = 1,
	WRITE_BODY_VALUE = 2,
	WRITE_QUERY_VALUE = 3,
	WRITE_HEADER_VALUE = 4,
	WRITE = 5,
	WRITE_PARAMETER_VALUE = 6,
}

const MAX_U8 = Math.pow(2, 8) - 1;

const MAX_U16 = Math.pow(2, 16) - 1;

const OPCODES = [
	Uint8Array.from([OPC.END]),
	Uint8Array.from([OPC.WRITE_HEADER]),
	Uint8Array.from([OPC.WRITE_BODY_VALUE]),
	Uint8Array.from([OPC.WRITE_QUERY_VALUE]),
	Uint8Array.from([OPC.WRITE_HEADER_VALUE]),
	Uint8Array.from([OPC.WRITE]),
	Uint8Array.from([OPC.WRITE_PARAMETER_VALUE]),
] as const;

export class DeclarativeResponse {
	#instructions: Array<Uint8Array> = [];
	#instructionsLength: number = 0;

	#appendOpCode(opcode: OPC) {
		this.#instructions.push(OPCODES[opcode]);
		this.#instructionsLength += 1;
	}

	#appendInstruction(text: string) {
		const bytes = TE.encode(text);
		const length = bytes.byteLength;
		if (length > MAX_U8) throw new RangeError(`Data byte length ${length} greater than ${MAX_U8}`);
		this.#instructions.push(Uint8Array.from([length]));
		this.#instructions.push(bytes);
		this.#instructionsLength += 1 + length;
	}

	#appendBytes(bytes: Uint8Array) {
		const length = bytes.byteLength;
		this.#instructions.push(Uint8Array.from([length & 0xff, (length >> 8) & 0xff]));
		if (length) this.#instructions.push(bytes);
		this.#instructionsLength += 2 + length;
	}

	writeHeader(key: string, value: string) {
		this.#appendOpCode(OPC.WRITE_HEADER);
		this.#appendInstruction(key);
		this.#appendInstruction(value);
		return this;
	}

	writeBody() {
		this.#appendOpCode(OPC.WRITE_BODY_VALUE);
		return this;
	}

	writeQueryValue(key: string) {
		this.#appendOpCode(OPC.WRITE_QUERY_VALUE);
		this.#appendInstruction(key);
		return this;
	}

	writeHeaderValue(key: string) {
		this.#appendOpCode(OPC.WRITE_HEADER_VALUE);
		this.#appendInstruction(key);
		return this;
	}

	write(data: string | Uint8Array) {
		const bytes = toU8(data);
		if (!bytes.byteLength) {
			return this;
		}
		if (bytes.byteLength > MAX_U16) {
			let offset = 0;
			while (offset < bytes.byteLength) {
				this.#appendOpCode(OPC.WRITE);
				this.#appendBytes(bytes.subarray(offset, offset + MAX_U16));
				offset += MAX_U16;
			}
		} else {
			this.#appendOpCode(OPC.WRITE);
			this.#appendBytes(bytes);
		}
		return this;
	}

	writeParameterValue(key: string) {
		this.#appendOpCode(OPC.WRITE_PARAMETER_VALUE);
		this.#appendInstruction(key);
		return this;
	}

	end(data?: string | Uint8Array): ArrayBufferLike {
		const bytes = toU8(data);
		if (bytes.byteLength > MAX_U16) {
			let offset = 0;
			while (offset < bytes.byteLength) {
				this.#appendOpCode(offset + MAX_U16 < bytes.byteLength ? OPC.WRITE : OPC.END);
				this.#appendBytes(bytes.subarray(offset, offset + MAX_U16));
				offset += MAX_U16;
			}
		} else {
			this.#appendOpCode(OPC.END);
			this.#appendBytes(bytes);
		}
		const output = new Uint8Array(this.#instructionsLength);
		let offset = 0;
		for (const chunk of this.#instructions) {
			output.set(chunk, offset);
			offset += chunk.byteLength;
		}
		return output.buffer;
	}
}

webcarrot avatar Aug 19 '24 09:08 webcarrot

I added this https://github.com/uNetworking/uWebSockets.js/commit/4c29320e4ed7ed042cff9d5111ec7b1555c274aa

uNetworkingAB avatar Aug 19 '24 11:08 uNetworkingAB

Ah never mind that's not it

uNetworkingAB avatar Aug 19 '24 11:08 uNetworkingAB

Maybe best to just take your variant

uNetworkingAB avatar Aug 19 '24 11:08 uNetworkingAB

You might be interested in whether it is possible to dynamically increase the buffer size. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/resize

uasan avatar Aug 19 '24 19:08 uasan

Clean-up dashboard.

webcarrot avatar May 20 '25 17:05 webcarrot