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:
parent
77e876388b
commit
b635553fd7
3 changed files with 195 additions and 54 deletions
|
@ -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
|
||||||
|
|
105
js/fetch.ts
105
js/fetch.ts
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
126
js/fetch_test.ts
126
js/fetch_test.ts
|
@ -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);
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue