340 lines
8.7 KiB
Plaintext
340 lines
8.7 KiB
Plaintext
{{#platforms}}
|
|
{{#node}}
|
|
// TODO: evaluate if we can easily get rid of this library
|
|
import {{^supportsES6}}* as{{/supportsES6}} FormData from "form-data";
|
|
import { URLSearchParams } from 'url';
|
|
import * as http from 'http';
|
|
import * as https from 'https';
|
|
{{/node}}
|
|
{{/platforms}}
|
|
{{#platforms}}
|
|
{{^deno}}
|
|
import {{^supportsES6}}* as{{/supportsES6}} URLParse from "url-parse";
|
|
{{/deno}}
|
|
{{/platforms}}
|
|
import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}};
|
|
|
|
{{#platforms}}
|
|
{{^deno}}
|
|
{{#frameworks}}
|
|
{{#fetch-api}}
|
|
export * from './isomorphic-fetch';
|
|
{{/fetch-api}}
|
|
{{#jquery}}
|
|
export * from './jquery';
|
|
{{/jquery}}
|
|
{{/frameworks}}
|
|
{{/deno}}
|
|
{{/platforms}}
|
|
|
|
/**
|
|
* Represents an HTTP method.
|
|
*/
|
|
export enum HttpMethod {
|
|
GET = "GET",
|
|
HEAD = "HEAD",
|
|
POST = "POST",
|
|
PUT = "PUT",
|
|
DELETE = "DELETE",
|
|
CONNECT = "CONNECT",
|
|
OPTIONS = "OPTIONS",
|
|
TRACE = "TRACE",
|
|
PATCH = "PATCH"
|
|
}
|
|
|
|
/**
|
|
* Represents an HTTP file which will be transferred from or to a server.
|
|
*/
|
|
{{#platforms}}
|
|
{{#node}}
|
|
export type HttpFile = {
|
|
data: {{{fileContentDataType}}},
|
|
name: string
|
|
};
|
|
{{/node}}
|
|
{{^node}}
|
|
export type HttpFile = {{{fileContentDataType}}} & { readonly name: string };
|
|
{{/node}}
|
|
{{/platforms}}
|
|
|
|
{{#platforms}}
|
|
{{#deno}}
|
|
/**
|
|
* URLParse Wrapper for Deno
|
|
*/
|
|
class URLParse {
|
|
private url: URL;
|
|
|
|
constructor(address: string, _parser: boolean) {
|
|
this.url = new URL(address);
|
|
}
|
|
|
|
public set(_part: 'query', obj: {[key: string]: string | undefined}) {
|
|
for (const key in obj) {
|
|
const value = obj[key];
|
|
if (value) {
|
|
this.url.searchParams.set(key, value);
|
|
} else {
|
|
this.url.searchParams.set(key, "");
|
|
}
|
|
}
|
|
}
|
|
|
|
public get query() {
|
|
const obj: {[key: string]: string} = {};
|
|
for (const [key, value] of this.url.searchParams.entries()) {
|
|
obj[key] = value;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
public toString() {
|
|
return this.url.toString();
|
|
}
|
|
}
|
|
{{/deno}}
|
|
{{/platforms}}
|
|
|
|
export class HttpException extends Error {
|
|
public constructor(msg: string) {
|
|
super(msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents the body of an outgoing HTTP request.
|
|
*/
|
|
export type RequestBody = undefined | string | FormData | URLSearchParams;
|
|
|
|
/**
|
|
* Represents an HTTP request context
|
|
*/
|
|
export class RequestContext {
|
|
private headers: { [key: string]: string } = {};
|
|
private body: RequestBody = undefined;
|
|
private url: URLParse;
|
|
{{#platforms}}
|
|
{{#node}}
|
|
private agent: http.Agent | https.Agent | undefined = undefined;
|
|
{{/node}}
|
|
{{/platforms}}
|
|
|
|
/**
|
|
* Creates the request context using a http method and request resource url
|
|
*
|
|
* @param url url of the requested resource
|
|
* @param httpMethod http method
|
|
*/
|
|
public constructor(url: string, private httpMethod: HttpMethod) {
|
|
this.url = new URLParse(url, true);
|
|
}
|
|
|
|
/*
|
|
* Returns the url set in the constructor including the query string
|
|
*
|
|
*/
|
|
public getUrl(): string {
|
|
return this.url.toString();
|
|
}
|
|
|
|
/**
|
|
* Replaces the url set in the constructor with this url.
|
|
*
|
|
*/
|
|
public setUrl(url: string) {
|
|
this.url = new URLParse(url, true);
|
|
}
|
|
|
|
/**
|
|
* Sets the body of the http request either as a string or FormData
|
|
*
|
|
* Note that setting a body on a HTTP GET, HEAD, DELETE, CONNECT or TRACE
|
|
* request is discouraged.
|
|
* https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#rfc.section.7.3.1
|
|
*
|
|
* @param body the body of the request
|
|
*/
|
|
public setBody(body: RequestBody) {
|
|
this.body = body;
|
|
}
|
|
|
|
public getHttpMethod(): HttpMethod {
|
|
return this.httpMethod;
|
|
}
|
|
|
|
public getHeaders(): { [key: string]: string } {
|
|
return this.headers;
|
|
}
|
|
|
|
public getBody(): RequestBody {
|
|
return this.body;
|
|
}
|
|
|
|
public setQueryParam(name: string, value: string) {
|
|
let queryObj = this.url.query;
|
|
queryObj[name] = value;
|
|
this.url.set("query", queryObj);
|
|
}
|
|
|
|
/**
|
|
* Sets a cookie with the name and value. NO check for duplicate cookies is performed
|
|
*
|
|
*/
|
|
public addCookie(name: string, value: string): void {
|
|
if (!this.headers["Cookie"]) {
|
|
this.headers["Cookie"] = "";
|
|
}
|
|
this.headers["Cookie"] += name + "=" + value + "; ";
|
|
}
|
|
|
|
public setHeaderParam(key: string, value: string): void {
|
|
this.headers[key] = value;
|
|
}
|
|
{{#platforms}}
|
|
{{#node}}
|
|
|
|
public setAgent(agent: http.Agent | https.Agent) {
|
|
this.agent = agent;
|
|
}
|
|
|
|
public getAgent(): http.Agent | https.Agent | undefined {
|
|
return this.agent;
|
|
}
|
|
{{/node}}
|
|
{{/platforms}}
|
|
}
|
|
|
|
export interface ResponseBody {
|
|
text(): Promise<string>;
|
|
binary(): Promise<{{{fileContentDataType}}}>;
|
|
}
|
|
|
|
/**
|
|
* Helper class to generate a `ResponseBody` from binary data
|
|
*/
|
|
export class SelfDecodingBody implements ResponseBody {
|
|
constructor(private dataSource: Promise<{{{fileContentDataType}}}>) {}
|
|
|
|
binary(): Promise<{{{fileContentDataType}}}> {
|
|
return this.dataSource;
|
|
}
|
|
|
|
async text(): Promise<string> {
|
|
const data: {{{fileContentDataType}}} = await this.dataSource;
|
|
{{#platforms}}
|
|
{{#node}}
|
|
return data.toString();
|
|
{{/node}}
|
|
{{#browser}}
|
|
// @ts-ignore
|
|
if (data.text) {
|
|
// @ts-ignore
|
|
return data.text();
|
|
}
|
|
|
|
return new Promise<string>((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.addEventListener("load", () => resolve(reader.result as string));
|
|
reader.addEventListener("error", () => reject(reader.error));
|
|
reader.readAsText(data);
|
|
});
|
|
{{/browser}}
|
|
{{#deno}}
|
|
return data.text();
|
|
{{/deno}}
|
|
{{/platforms}}
|
|
}
|
|
}
|
|
|
|
export class ResponseContext {
|
|
public constructor(
|
|
public httpStatusCode: number,
|
|
public headers: { [key: string]: string },
|
|
public body: ResponseBody
|
|
) {}
|
|
|
|
/**
|
|
* Parse header value in the form `value; param1="value1"`
|
|
*
|
|
* E.g. for Content-Type or Content-Disposition
|
|
* Parameter names are converted to lower case
|
|
* The first parameter is returned with the key `""`
|
|
*/
|
|
public getParsedHeader(headerName: string): { [parameter: string]: string } {
|
|
const result: { [parameter: string]: string } = {};
|
|
if (!this.headers[headerName]) {
|
|
return result;
|
|
}
|
|
|
|
const parameters = this.headers[headerName].split(";");
|
|
for (const parameter of parameters) {
|
|
let [key, value] = parameter.split("=", 2);
|
|
key = key.toLowerCase().trim();
|
|
if (value === undefined) {
|
|
result[""] = key;
|
|
} else {
|
|
value = value.trim();
|
|
if (value.startsWith('"') && value.endsWith('"')) {
|
|
value = value.substring(1, value.length - 1);
|
|
}
|
|
result[key] = value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public async getBodyAsFile(): Promise<HttpFile> {
|
|
const data = await this.body.binary();
|
|
const fileName = this.getParsedHeader("content-disposition")["filename"] || "";
|
|
{{#platforms}}
|
|
{{#node}}
|
|
return { data, name: fileName };
|
|
{{/node}}
|
|
{{^node}}
|
|
const contentType = this.headers["content-type"] || "";
|
|
try {
|
|
return new File([data], fileName, { type: contentType });
|
|
} catch (error) {
|
|
/** Fallback for when the File constructor is not available */
|
|
return Object.assign(data, {
|
|
name: fileName,
|
|
type: contentType
|
|
});
|
|
}
|
|
{{/node}}
|
|
{{/platforms}}
|
|
}
|
|
|
|
/**
|
|
* Use a heuristic to get a body of unknown data structure.
|
|
* Return as string if possible, otherwise as binary.
|
|
*/
|
|
public getBodyAsAny(): Promise<string | {{{fileContentDataType}}} | undefined> {
|
|
try {
|
|
return this.body.text();
|
|
} catch {}
|
|
|
|
try {
|
|
return this.body.binary();
|
|
} catch {}
|
|
|
|
return Promise.resolve(undefined);
|
|
}
|
|
}
|
|
|
|
export interface HttpLibrary {
|
|
send(request: RequestContext): Observable<ResponseContext>;
|
|
}
|
|
|
|
export interface PromiseHttpLibrary {
|
|
send(request: RequestContext): Promise<ResponseContext>;
|
|
}
|
|
|
|
export function wrapHttpLibrary(promiseHttpLibrary: PromiseHttpLibrary): HttpLibrary {
|
|
return {
|
|
send(request: RequestContext): Observable<ResponseContext> {
|
|
return from(promiseHttpLibrary.send(request));
|
|
}
|
|
}
|
|
}
|