bhttp-js
Version:
A BHTTP (Binary Representation of HTTP Messages) Encoder and Decoder
245 lines (244 loc) • 9.66 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./consts.js", "./errors.js"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BHttpEncoder = void 0;
const consts = __importStar(require("./consts.js"));
const errors = __importStar(require("./errors.js"));
class EncoderContext {
buf;
p = 0;
framingIndicator = 0;
headerSize;
body;
constructor() {
this.buf = new Uint8Array(0);
this.headerSize = 0;
this.body = new Uint8Array(0);
}
calculateVliSize(v) {
if (v < 64) {
return 1;
}
if (v < 16384) {
return 2;
}
if (v < 1073741824) {
return 4;
}
if (v <= Number.MAX_SAFE_INTEGER) {
return 8;
}
throw new errors.NotSupportedError("Over MAX_SAFE_INTEGER length value is not supported.");
}
}
class RequestEncoderContext extends EncoderContext {
request;
url;
constructor(request) {
super();
this.request = request;
this.url = new URL(request.url);
}
async setup() {
// Load requestBody.
this.body = new Uint8Array(await this.request.arrayBuffer());
// Setup the output buffer.
this.buf = new Uint8Array(this.calculateEncodedRequestSize());
}
calculateEncodedRequestSize() {
let len = 1; // framing indicator
// Request Control Data
len += 1; // this.calculateVliSize(src.method.length);
len += this.request.method.length;
len += this.calculateVliSize(this.url.protocol.length - 1);
len += this.url.protocol.length - 1;
len += this.calculateVliSize(this.url.host.length);
len += this.url.host.length;
len += this.calculateVliSize(this.url.pathname.length + this.url.search.length);
len += this.url.pathname.length;
len += this.url.search.length;
// Known Length Headers
this.headerSize = 0;
this.request.headers.forEach((value, key) => {
this.headerSize += this.calculateVliSize(key.length);
this.headerSize += key.length;
this.headerSize += this.calculateVliSize(value.length);
this.headerSize += value.length;
});
len += this.calculateVliSize(this.headerSize);
len += this.headerSize;
// Known Length Content
len += this.calculateVliSize(this.body.byteLength);
len += this.body.byteLength;
// Known Length Trailers
len += 1; // The trailer size = 0;
// No padding
return len;
}
}
class ResponseEncoderContext extends EncoderContext {
response;
constructor(response) {
super();
this.response = response;
}
async setup() {
// Load responseBody.
this.body = new Uint8Array(await this.response.arrayBuffer());
// Setup the output buffer.
this.buf = new Uint8Array(this.calculateEncodedResponseSize());
}
calculateEncodedResponseSize() {
let len = 1; // framing indicator
// Response Control Data
len += 2;
// Known Length Headers
this.headerSize = 0;
this.response.headers.forEach((value, key) => {
this.headerSize += this.calculateVliSize(key.length);
this.headerSize += key.length;
this.headerSize += this.calculateVliSize(value.length);
this.headerSize += value.length;
});
len += this.calculateVliSize(this.headerSize);
len += this.headerSize;
// Known Length Content
len += this.calculateVliSize(this.body.byteLength);
len += this.body.byteLength;
// Known Length Trailers
len += 1; // The trailer size = 0;
// No padding
return len;
}
}
class BHttpEncoder {
_te;
constructor() {
this._te = new TextEncoder();
}
async encodeRequest(src) {
// Setup RequestEncoderContext.
const ctx = new RequestEncoderContext(src);
await ctx.setup();
// Do BHTTP encoding.
return this.encodeKnownLengthRequest(ctx);
}
async encodeResponse(src) {
// Setup RequestEncoderContext.
const ctx = new ResponseEncoderContext(src);
await ctx.setup();
// Do BHTTP encoding.
return this.encodeKnownLengthResponse(ctx);
}
encodeKnownLengthRequest(ctx) {
this.encodeVli(ctx, 0);
// Request Control Data
this.encodeVliAndValue(ctx, ctx.request.method);
this.encodeVliAndValue(ctx, ctx.url.protocol.slice(0, ctx.url.protocol.length - 1));
this.encodeVliAndValue(ctx, ctx.url.host);
this.encodeVliAndValue(ctx, ctx.url.pathname + ctx.url.search);
// Known Length Headers
this.encodeVli(ctx, ctx.headerSize);
ctx.request.headers.forEach((value, key) => {
this.encodeVliAndValue(ctx, key);
this.encodeVliAndValue(ctx, value);
});
// Known Length Content
this.encodeVli(ctx, ctx.body.byteLength);
ctx.buf.set(ctx.body, ctx.p);
ctx.p += ctx.body.byteLength;
// Known Length Trailers
this.encodeVli(ctx, 0);
// No padding
return ctx.buf;
}
encodeKnownLengthResponse(ctx) {
this.encodeVli(ctx, 1);
// Response Control Data
this.encodeVli(ctx, ctx.response.status);
// Known Length Headers
this.encodeVli(ctx, ctx.headerSize);
ctx.response.headers.forEach((value, key) => {
this.encodeVliAndValue(ctx, key);
this.encodeVliAndValue(ctx, value);
});
// Known Length Content
this.encodeVli(ctx, ctx.body.byteLength);
ctx.buf.set(ctx.body, ctx.p);
ctx.p += ctx.body.byteLength;
// Known Length Trailers
this.encodeVli(ctx, 0);
// No padding
return ctx.buf;
}
encodeVliAndValue(ctx, v) {
this.encodeVli(ctx, v.length);
ctx.buf.set(this._te.encode(v), ctx.p);
ctx.p += v.length;
return;
}
encodeVli(ctx, v) {
if (v < 64) {
ctx.buf[ctx.p++] = consts.VLI_LEN_1 + v;
return;
}
if (v < 16384) {
ctx.buf[ctx.p++] = consts.VLI_LEN_2 + (v >> 8);
ctx.buf[ctx.p++] = consts.VLI_MASK_LSB & v;
return;
}
if (v < 1073741824) {
ctx.buf[ctx.p++] = consts.VLI_LEN_4 + (v >> 24);
ctx.buf[ctx.p++] = consts.VLI_MASK_LSB & (v >> 16);
ctx.buf[ctx.p++] = consts.VLI_MASK_LSB & (v >> 8);
ctx.buf[ctx.p++] = consts.VLI_MASK_LSB & v;
return;
}
if (v <= Number.MAX_SAFE_INTEGER) {
// ctx.buf[ctx.p++] = consts.VLI_LEN_8 + (v >> 56);
ctx.buf[ctx.p++] = consts.VLI_LEN_8;
ctx.buf[ctx.p++] = consts.VLI_MASK_LSB & (v >> 48);
ctx.buf[ctx.p++] = consts.VLI_MASK_LSB & (v >> 40);
ctx.buf[ctx.p++] = consts.VLI_MASK_LSB & (v >> 32);
ctx.buf[ctx.p++] = consts.VLI_MASK_LSB & (v >> 24);
ctx.buf[ctx.p++] = consts.VLI_MASK_LSB & (v >> 16);
ctx.buf[ctx.p++] = consts.VLI_MASK_LSB & (v >> 8);
ctx.buf[ctx.p++] = consts.VLI_MASK_LSB & v;
return;
}
throw new errors.NotSupportedError("Over MAX_SAFE_INTEGER-length value is not supported.");
}
}
exports.BHttpEncoder = BHttpEncoder;
});