0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 17:34:47 -05:00

Improve fetch headers (#853)

This commit is contained in:
ztplz 2018-09-30 22:31:50 +08:00 committed by Ryan Dahl
parent 77e876388b
commit b635553fd7
3 changed files with 195 additions and 54 deletions

View file

@ -13,7 +13,8 @@ See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License. and limitations under the License.
*******************************************************************************/ *******************************************************************************/
export type HeadersInit = string[][] | Record<string, string>; export type HeadersInit = Headers | string[][] | Record<string, string>;
export type URLSearchParamsInit = string | string[][] | Record<string, string>;
type BodyInit = type BodyInit =
| Blob | Blob
| BufferSource | BufferSource
@ -74,7 +75,7 @@ export interface ProgressEventInit extends EventInit {
total?: number; total?: number;
} }
interface URLSearchParams { export interface URLSearchParams {
/** /**
* Appends a specified key/value pair as a new search parameter. * Appends a specified key/value pair as a new search parameter.
*/ */
@ -101,7 +102,20 @@ interface URLSearchParams {
* If there were several values, delete the others. * If there were several values, delete the others.
*/ */
set(name: string, value: string): void; set(name: string, value: string): void;
/**
* Sort all key/value pairs contained in this object in place
* and return undefined. The sort order is according to Unicode
* code points of the keys.
*/
sort(): void; sort(): void;
/**
* Returns a query string suitable for use in a URL.
*/
toString(): string;
/**
* Iterates over each name-value pair in the query
* and invokes the given function.
*/
forEach( forEach(
callbackfn: (value: string, key: string, parent: URLSearchParams) => void, callbackfn: (value: string, key: string, parent: URLSearchParams) => void,
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any

View file

@ -22,85 +22,86 @@ import {
import { TextDecoder } from "./text_encoding"; import { TextDecoder } from "./text_encoding";
import { DenoBlob } from "./blob"; import { DenoBlob } from "./blob";
interface Header { // ref: https://fetch.spec.whatwg.org/#dom-headers
name: string;
value: string;
}
export class DenoHeaders implements Headers { export class DenoHeaders implements Headers {
private readonly headerList: Header[] = []; private headerMap: Map<string, string> = new Map();
constructor(init?: HeadersInit) { constructor(init?: HeadersInit) {
if (init) { if (arguments.length === 0 || init === undefined) {
this._fill(init); return;
} }
}
private _append(header: Header): void { if (init instanceof DenoHeaders) {
// TODO(qti3e) Check header based on the fetch spec. // init is the instance of Header
this._appendToHeaderList(header); init.forEach((value: string, name: string) => {
} this.headerMap.set(name, value);
});
private _appendToHeaderList(header: Header): void { } else if (Array.isArray(init)) {
const lowerCaseName = header.name.toLowerCase(); // init is a sequence
for (let i = 0; i < this.headerList.length; ++i) { init.forEach(item => {
if (this.headerList[i].name.toLowerCase() === lowerCaseName) { if (item.length !== 2) {
header.name = this.headerList[i].name;
}
}
this.headerList.push(header);
}
private _fill(init: HeadersInit): void {
if (Array.isArray(init)) {
for (let i = 0; i < init.length; ++i) {
const header = init[i];
if (header.length !== 2) {
throw new TypeError("Failed to construct 'Headers': Invalid value"); throw new TypeError("Failed to construct 'Headers': Invalid value");
} }
this._append({ const [name, value] = this.normalizeParams(item[0], item[1]);
name: header[0], const v = this.headerMap.get(name);
value: header[1] const str = v ? `${v}, ${value}` : value;
}); this.headerMap.set(name, str);
} });
} else if (Object.prototype.toString.call(init) === "[object Object]") {
// init is a object
const names = Object.keys(init);
names.forEach(name => {
const value = (init as Record<string, string>)[name];
const [newname, newvalue] = this.normalizeParams(name, value);
this.headerMap.set(newname, newvalue);
});
} else { } else {
for (const key in init) { throw new TypeError("Failed to construct 'Headers': Invalid value");
this._append({
name: key,
value: init[key]
});
}
} }
} }
private normalizeParams(name: string, value?: string): string[] {
name = String(name).toLowerCase();
value = String(value).trim();
return [name, value];
}
append(name: string, value: string): void { append(name: string, value: string): void {
this._appendToHeaderList({ name, value }); const [newname, newvalue] = this.normalizeParams(name, value);
const v = this.headerMap.get(newname);
const str = v ? `${v}, ${newvalue}` : newvalue;
this.headerMap.set(newname, str);
} }
delete(name: string): void { delete(name: string): void {
assert(false, "Implement me"); const [newname] = this.normalizeParams(name);
this.headerMap.delete(newname);
} }
get(name: string): string | null { get(name: string): string | null {
for (const header of this.headerList) { const [newname] = this.normalizeParams(name);
if (header.name.toLowerCase() === name.toLowerCase()) { const value = this.headerMap.get(newname);
return header.value; return value || null;
}
}
return null;
} }
has(name: string): boolean { has(name: string): boolean {
assert(false, "Implement me"); const [newname] = this.normalizeParams(name);
return false; return this.headerMap.has(newname);
} }
set(name: string, value: string): void { set(name: string, value: string): void {
assert(false, "Implement me"); const [newname, newvalue] = this.normalizeParams(name, value);
this.headerMap.set(newname, newvalue);
} }
forEach( forEach(
callbackfn: (value: string, key: string, parent: Headers) => void, callbackfn: (value: string, key: string, parent: Headers) => void,
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
thisArg?: any thisArg?: any
): void { ): void {
assert(false, "Implement me"); this.headerMap.forEach((value, name) => {
callbackfn(value, name, this);
});
} }
} }

View file

@ -43,3 +43,129 @@ testPerm({ net: true }, async function fetchBlob() {
assertEqual(blob.type, headers.get("Content-Type")); assertEqual(blob.type, headers.get("Content-Type"));
assertEqual(blob.size, Number(headers.get("Content-Length"))); assertEqual(blob.size, Number(headers.get("Content-Length")));
}); });
// Logic heavily copied from web-platform-tests, make
// sure pass mostly header basic test
/* tslint:disable-next-line:max-line-length */
// ref: https://github.com/web-platform-tests/wpt/blob/7c50c216081d6ea3c9afe553ee7b64534020a1b2/fetch/api/headers/headers-basic.html
/* tslint:disable:no-unused-expression */
test(function newHeaderTest() {
new Headers();
new Headers(undefined);
new Headers({});
try {
new Headers(null);
} catch (e) {
assertEqual(e.message, "Failed to construct 'Headers': Invalid value");
}
try {
const init = [["a", "b", "c"]];
new Headers(init);
} catch (e) {
assertEqual(e.message, "Failed to construct 'Headers': Invalid value");
}
});
const headerDict = {
"name1": "value1",
"name2": "value2",
"name3": "value3",
"name4": undefined,
"Content-Type": "value4"
};
const headerSeq = [];
for (const name in headerDict) {
headerSeq.push([name, headerDict[name]]);
}
test(function newHeaderWithSequence() {
const headers = new Headers(headerSeq);
for (const name in headerDict) {
assertEqual(headers.get(name), String(headerDict[name]));
}
assertEqual(headers.get("length"), null);
});
test(function newHeaderWithRecord() {
const headers = new Headers(headerDict);
for (const name in headerDict) {
assertEqual(headers.get(name), String(headerDict[name]));
}
});
test(function newHeaderWithHeadersInstance() {
const headers = new Headers(headerDict);
const headers2 = new Headers(headers);
for (const name in headerDict) {
assertEqual(headers2.get(name), String(headerDict[name]));
}
});
test(function headerAppendSuccess() {
const headers = new Headers();
for (const name in headerDict) {
headers.append(name, headerDict[name]);
assertEqual(headers.get(name), String(headerDict[name]));
}
});
test(function headerSetSuccess() {
const headers = new Headers();
for (const name in headerDict) {
headers.set(name, headerDict[name]);
assertEqual(headers.get(name), String(headerDict[name]));
}
});
test(function headerHasSuccess() {
const headers = new Headers(headerDict);
for (const name in headerDict) {
assert(headers.has(name), "headers has name " + name);
/* tslint:disable-next-line:max-line-length */
assert(!headers.has("nameNotInHeaders"), "headers do not have header: nameNotInHeaders");
}
});
test(function headerDeleteSuccess() {
const headers = new Headers(headerDict);
for (const name in headerDict) {
assert(headers.has(name), "headers have a header: " + name);
headers.delete(name);
assert(!headers.has(name), "headers do not have anymore a header: " + name);
}
});
test(function headerGetSuccess() {
const headers = new Headers(headerDict);
for (const name in headerDict) {
assertEqual(headers.get(name), String(headerDict[name]));
assertEqual(headers.get("nameNotInHeaders"), null);
}
});
const headerEntriesDict = {
"name1": "value1",
"Name2": "value2",
"name": "value3",
"content-Type": "value4",
"Content-Typ": "value5",
"Content-Types": "value6"
};
test(function headerForEachSuccess() {
const headers = new Headers(headerEntriesDict);
const keys = Object.keys(headerEntriesDict);
keys.forEach(key => {
const value = headerEntriesDict[key];
const newkey = key.toLowerCase();
headerEntriesDict[newkey] = value;
});
let callNum = 0;
headers.forEach((value, key, container) => {
assertEqual(headers, container);
assertEqual(value, headerEntriesDict[key]);
callNum++;
});
assertEqual(callNum, keys.length);
});