Skip to content

Commit

Permalink
Add option to omit default headers, cleaner clones
Browse files Browse the repository at this point in the history
  • Loading branch information
blakeembrey committed Jun 15, 2019
1 parent f095811 commit 025d0bc
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 115 deletions.
136 changes: 81 additions & 55 deletions src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,44 +66,11 @@ function streamToArrayBuffer(stream: ReadableStream) {
*/
export class Body implements CommonBody<RawBody> {
$rawBody: RawBody | null | typeof kBodyUsed | typeof kBodyDestroyed;
headers: Headers;

constructor(body: CreateBody, headers: Headers) {
constructor(body: CreateBody) {
const rawBody = body === undefined ? null : body;

this.$rawBody = rawBody;
this.headers = headers;

if (rawBody === null) return;

if (typeof rawBody === "string") {
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "text/plain");
}

if (!headers.has("Content-Length")) {
headers.set("Content-Length", byteLength(rawBody).toString());
}

return;
}

// Default to "octet stream" for raw bodies.
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "application/octet-stream");
}

if (rawBody instanceof ArrayBuffer) {
if (!headers.has("Content-Length")) {
headers.set("Content-Length", rawBody.byteLength.toString());
}

return;
}

if (rawBody instanceof ReadableStream) return;

throw new TypeError("Unknown body type");
}

get bodyUsed() {
Expand Down Expand Up @@ -172,10 +139,10 @@ export class Body implements CommonBody<RawBody> {
if (rawBody instanceof ReadableStream) {
const [selfRawBody, clonedRawBody] = rawBody.tee();
this.$rawBody = selfRawBody;
return new Body(clonedRawBody, this.headers.clone());
return new Body(clonedRawBody);
}

return new Body(rawBody, this.headers.clone());
return new Body(rawBody);
}

destroy(): Promise<void> {
Expand All @@ -194,25 +161,35 @@ export class Body implements CommonBody<RawBody> {
export class Request extends Body implements CommonRequest<RawBody> {
url: string;
method: string;
headers: Headers;
trailer: Promise<Headers>;
readonly signal: Signal;

constructor(input: string | Request, init: RequestOptions = {}) {
// Clone request or use passed options object.
const opts = typeof input === "string" ? init : input.clone();
const headers = new Headers(init.headers || opts.headers);
const rawBody =
init.body || (opts instanceof Request ? getRawBody(opts) : null);

super(rawBody, headers);
const req = typeof input === "string" ? undefined : input.clone();
const rawBody = init.body || (req ? getRawBody(req) : null);
const headers =
req && !init.headers
? req.headers
: getDefaultHeaders(
rawBody,
init.headers,
init.omitDefaultHeaders === true
);

super(rawBody);

this.url = typeof input === "string" ? input : input.url;
this.method = init.method || opts.method || "GET";
this.signal = init.signal || opts.signal || new Signal();
this.method = init.method || (req && req.method) || "GET";
this.signal = init.signal || (req && req.signal) || new Signal();
this.headers = headers;
this.trailer = Promise.resolve<HeadersInit | undefined>(
init.trailer || opts.trailer
).then(x => new Headers(x));
this.trailer =
req && !init.trailer
? req.trailer
: Promise.resolve<HeadersInit | undefined>(init.trailer).then(
x => new Headers(x)
);

// Destroy body on abort.
once(this.signal, "abort", () => this.destroy());
Expand All @@ -223,7 +200,8 @@ export class Request extends Body implements CommonRequest<RawBody> {

return new Request(this.url, {
body: getRawBody(cloned),
headers: cloned.headers,
headers: this.headers.clone(),
omitDefaultHeaders: true,
method: this.method,
signal: this.signal,
trailer: this.trailer.then(x => x.clone())
Expand All @@ -237,21 +215,26 @@ export class Request extends Body implements CommonRequest<RawBody> {
export class Response extends Body implements CommonResponse<RawBody> {
status: number;
statusText: string;
headers: Headers;
trailer: Promise<Headers>;

get ok() {
return this.status >= 200 && this.status < 300;
}

constructor(body?: CreateBody, opts: ResponseOptions = {}) {
const headers = new Headers(opts.headers);
constructor(body?: CreateBody, init: ResponseOptions = {}) {
const headers = getDefaultHeaders(
body,
init.headers,
init.omitDefaultHeaders === true
);

super(body, headers);
super(body);

this.status = opts.status || 200;
this.statusText = opts.statusText || "";
this.status = init.status || 200;
this.statusText = init.statusText || "";
this.headers = headers;
this.trailer = Promise.resolve<HeadersInit | undefined>(opts.trailer).then(
this.trailer = Promise.resolve<HeadersInit | undefined>(init.trailer).then(
x => new Headers(x)
);
}
Expand All @@ -262,8 +245,51 @@ export class Response extends Body implements CommonResponse<RawBody> {
return new Response(getRawBody(cloned), {
status: this.status,
statusText: this.statusText,
headers: cloned.headers,
headers: this.headers.clone(),
omitDefaultHeaders: true,
trailer: this.trailer.then(x => x.clone())
});
}
}

/**
* Get default headers for `Request` and `Response` instances.
*/
function getDefaultHeaders(
rawBody: CreateBody,
init: HeadersInit | undefined,
omitDefaultHeaders: boolean
) {
const headers = new Headers(init);

if (rawBody === null || rawBody === undefined) return headers;

if (typeof rawBody === "string") {
if (!omitDefaultHeaders && !headers.has("Content-Type")) {
headers.set("Content-Type", "text/plain");
}

if (!omitDefaultHeaders && !headers.has("Content-Length")) {
headers.set("Content-Length", byteLength(rawBody).toString());
}

return headers;
}

// Default to "octet stream" for raw bodies.
if (!omitDefaultHeaders && !headers.has("Content-Type")) {
headers.set("Content-Type", "application/octet-stream");
}

if (rawBody instanceof ArrayBuffer) {
if (!omitDefaultHeaders && !headers.has("Content-Length")) {
headers.set("Content-Length", rawBody.byteLength.toString());
}

return headers;
}

if (rawBody instanceof ReadableStream) return headers;

throw new TypeError("Unknown body type");
}
5 changes: 4 additions & 1 deletion src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export type CommonBodyConstructor<T, U extends T> = {
*/
export interface CommonBody<T = unknown> {
$rawBody: T | null | typeof kBodyUsed | typeof kBodyDestroyed;
headers: Headers;
readonly bodyUsed: boolean;
json(): Promise<any>;
text(): Promise<string>;
Expand All @@ -65,6 +64,7 @@ export interface CommonRequestOptions<T> {
body?: T;
signal?: Signal;
headers?: HeadersInit;
omitDefaultHeaders?: boolean;
trailer?: HeadersInit | Promise<HeadersInit>;
}

Expand All @@ -74,6 +74,7 @@ export interface CommonRequestOptions<T> {
export interface CommonRequest<T = unknown> extends CommonBody<T> {
url: string;
method: string;
headers: Headers;
trailer: Promise<Headers>;
readonly signal: Signal;
clone(): CommonRequest<T>;
Expand All @@ -86,6 +87,7 @@ export interface CommonResponseOptions {
status?: number;
statusText?: string;
headers?: HeadersInit;
omitDefaultHeaders?: boolean;
trailer?: HeadersInit | Promise<HeadersInit>;
}

Expand All @@ -95,6 +97,7 @@ export interface CommonResponseOptions {
export interface CommonResponse<T = unknown> extends CommonBody<T> {
status: number;
statusText: string;
headers: Headers;
trailer: Promise<Headers>;
clone(): CommonResponse<T>;
}
52 changes: 50 additions & 2 deletions src/node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,54 @@ describe("node", () => {
expect(req.headers.get("Test")).toEqual("1");
expect(req.headers.get("Other")).toEqual(null);
});

it("should initialize default headers", () => {
const req = new Request("", {
body: "test"
});

expect(req.headers.get("Content-Type")).toEqual("text/plain");
expect(req.headers.get("Content-Length")).toEqual("4");
});

it("should skip default header initialization", () => {
const req = new Request("/", {
body: "test",
omitDefaultHeaders: true
});

expect(req.headers.get("Content-Length")).toEqual(null);

const clonedReq = req.clone();

expect(clonedReq.headers.get("Content-Length")).toEqual(null);

const initReq = new Request(req);

expect(initReq.headers.get("Content-Length")).toEqual(null);
});

it("should clone new header instances", () => {
const req = new Request("/", {
headers: {
"Test": "true"
}
})

expect(req.headers.get("test")).toEqual("true");

const clonedReq = req.clone();
clonedReq.headers.set("Test", "false");

expect(req.headers.get("test")).toEqual("true");
expect(clonedReq.headers.get("test")).toEqual("false");

const initReq = new Request(req);
initReq.headers.set("Test", "false");

expect(req.headers.get("test")).toEqual("true");
expect(initReq.headers.get("test")).toEqual("false");
})
});

describe("body", () => {
Expand Down Expand Up @@ -66,8 +114,8 @@ describe("node", () => {

const fn = jest.fn();

reqClone.signal.on('abort', fn);
req.signal.emit('abort');
reqClone.signal.on("abort", fn);
req.signal.emit("abort");

expect(fn).toHaveBeenCalled();
});
Expand Down
Loading

0 comments on commit 025d0bc

Please sign in to comment.