angular-docs-cn/packages/common/http/src/headers.ts

259 lines
7.1 KiB
TypeScript
Executable File

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
interface Update {
name: string;
value?: string|string[];
op: 'a'|'s'|'d';
}
/**
* `HttpHeaders` class represents the header configuration options for an HTTP request.
* Instances should be assumed immutable with lazy parsing.
*
* @publicApi
*/
export class HttpHeaders {
/**
* Internal map of lowercase header names to values.
*/
// TODO(issue/24571): remove '!'.
private headers !: Map<string, string[]>;
/**
* Internal map of lowercased header names to the normalized
* form of the name (the form seen first).
*/
private normalizedNames: Map<string, string> = new Map();
/**
* Complete the lazy initialization of this object (needed before reading).
*/
// TODO(issue/24571): remove '!'.
private lazyInit !: HttpHeaders | Function | null;
/**
* Queued updates to be materialized the next initialization.
*/
private lazyUpdate: Update[]|null = null;
/** Constructs a new HTTP header object with the given values.*/
constructor(headers?: string|{[name: string]: string | string[]}) {
if (!headers) {
this.headers = new Map<string, string[]>();
} else if (typeof headers === 'string') {
this.lazyInit = () => {
this.headers = new Map<string, string[]>();
headers.split('\n').forEach(line => {
const index = line.indexOf(':');
if (index > 0) {
const name = line.slice(0, index);
const key = name.toLowerCase();
const value = line.slice(index + 1).trim();
this.maybeSetNormalizedName(name, key);
if (this.headers.has(key)) {
this.headers.get(key) !.push(value);
} else {
this.headers.set(key, [value]);
}
}
});
};
} else {
this.lazyInit = () => {
this.headers = new Map<string, string[]>();
Object.keys(headers).forEach(name => {
let values: string|string[] = headers[name];
const key = name.toLowerCase();
if (typeof values === 'string') {
values = [values];
}
if (values.length > 0) {
this.headers.set(key, values);
this.maybeSetNormalizedName(name, key);
}
});
};
}
}
/**
* Checks for existence of a header by a given name.
*
* @param name The header name to check for existence.
*
* @returns Whether the header exits.
*/
has(name: string): boolean {
this.init();
return this.headers.has(name.toLowerCase());
}
/**
* Returns the first header value that matches a given name.
*
* @param name The header name to retrieve.
*
* @returns A string if the header exists, null otherwise
*/
get(name: string): string|null {
this.init();
const values = this.headers.get(name.toLowerCase());
return values && values.length > 0 ? values[0] : null;
}
/**
* Returns the names of the headers.
*
* @returns A list of header names.
*/
keys(): string[] {
this.init();
return Array.from(this.normalizedNames.values());
}
/**
* Returns a list of header values for a given header name.
*
* @param name The header name from which to retrieve the values.
*
* @returns A string of values if the header exists, null otherwise.
*/
getAll(name: string): string[]|null {
this.init();
return this.headers.get(name.toLowerCase()) || null;
}
/**
* Appends a new header value to the existing set of
* header values.
*
* @param name The header name for which to append the values.
*
* @returns A clone of the HTTP header object with the value appended.
*/
append(name: string, value: string|string[]): HttpHeaders {
return this.clone({name, value, op: 'a'});
}
/**
* Sets a header value for a given name. If the header name already exists,
* its value is replaced with the given value.
*
* @param name The header name.
* @param value Provides the value to set or overide for a given name.
*
* @returns A clone of the HTTP header object with the newly set header value.
*/
set(name: string, value: string|string[]): HttpHeaders {
return this.clone({name, value, op: 's'});
}
/**
* Deletes all header values for a given name.
*
* @param name The header name.
* @param value The header values to delete for a given name.
*
* @returns A clone of the HTTP header object.
*/
delete (name: string, value?: string|string[]): HttpHeaders {
return this.clone({name, value, op: 'd'});
}
private maybeSetNormalizedName(name: string, lcName: string): void {
if (!this.normalizedNames.has(lcName)) {
this.normalizedNames.set(lcName, name);
}
}
private init(): void {
if (!!this.lazyInit) {
if (this.lazyInit instanceof HttpHeaders) {
this.copyFrom(this.lazyInit);
} else {
this.lazyInit();
}
this.lazyInit = null;
if (!!this.lazyUpdate) {
this.lazyUpdate.forEach(update => this.applyUpdate(update));
this.lazyUpdate = null;
}
}
}
private copyFrom(other: HttpHeaders) {
other.init();
Array.from(other.headers.keys()).forEach(key => {
this.headers.set(key, other.headers.get(key) !);
this.normalizedNames.set(key, other.normalizedNames.get(key) !);
});
}
private clone(update: Update): HttpHeaders {
const clone = new HttpHeaders();
clone.lazyInit =
(!!this.lazyInit && this.lazyInit instanceof HttpHeaders) ? this.lazyInit : this;
clone.lazyUpdate = (this.lazyUpdate || []).concat([update]);
return clone;
}
private applyUpdate(update: Update): void {
const key = update.name.toLowerCase();
switch (update.op) {
case 'a':
case 's':
let value = update.value !;
if (typeof value === 'string') {
value = [value];
}
if (value.length === 0) {
return;
}
this.maybeSetNormalizedName(update.name, key);
const base = (update.op === 'a' ? this.headers.get(key) : undefined) || [];
base.push(...value);
this.headers.set(key, base);
break;
case 'd':
const toDelete = update.value as string | undefined;
if (!toDelete) {
this.headers.delete(key);
this.normalizedNames.delete(key);
} else {
let existing = this.headers.get(key);
if (!existing) {
return;
}
existing = existing.filter(value => toDelete.indexOf(value) === -1);
if (existing.length === 0) {
this.headers.delete(key);
this.normalizedNames.delete(key);
} else {
this.headers.set(key, existing);
}
}
break;
}
}
/**
* @internal
*/
forEach(fn: (name: string, values: string[]) => void) {
this.init();
Array.from(this.normalizedNames.keys())
.forEach(key => fn(this.normalizedNames.get(key) !, this.headers.get(key) !));
}
}