@travetto/net
Advanced tools
Comparing version 0.4.0 to 0.4.1
@@ -7,3 +7,3 @@ { | ||
"dependencies": { | ||
"@travetto/base": "^0.4.0" | ||
"@travetto/base": "^0.4.1" | ||
}, | ||
@@ -23,4 +23,4 @@ "description": "Network utilities of the travetto framework", | ||
}, | ||
"version": "0.4.0", | ||
"gitHead": "813fb4f842f149bcaa28c2e055e81fc86221eae1" | ||
"version": "0.4.1", | ||
"gitHead": "56dcc5bc1e70d9e61208be4128108a9c4f974f3d" | ||
} |
@@ -14,9 +14,91 @@ import * as http from 'http'; | ||
type ExecArgs = http.RequestOptions & { url: string }; | ||
type _ExecArgs = ExecArgs & { payload?: any }; | ||
export class HttpRequest { | ||
private static async _exec(client: HttpClient, opts: http.ClientRequestArgs, payload?: any): Promise<string>; | ||
private static async _exec(client: HttpClient, opts: http.ClientRequestArgs, payload: any, pipeTo: any): Promise<http.IncomingMessage>; | ||
private static async _exec(client: HttpClient, opts: http.ClientRequestArgs, payload?: any, pipeTo?: any): Promise<string | http.IncomingMessage> { | ||
return await new Promise<string | http.IncomingMessage>((resolve, reject) => { | ||
const req = client.request(opts, (msg: http.IncomingMessage) => { | ||
let body = ''; | ||
private static async _exec(opts: _ExecArgs, retry?: number): Promise<string>; | ||
private static async _exec(opts: _ExecArgs & { pipeTo: NodeJS.WritableStream }, retry?: number): Promise<http.IncomingMessage>; | ||
private static async _exec(inOpts: _ExecArgs & { pipeTo?: NodeJS.WritableStream }, retry = 0): Promise<string | http.IncomingMessage> { | ||
const { client, payload, pipeTo, opts } = this.requestOpts(inOpts); | ||
try { | ||
return await this.rawRequest(client, opts, payload, pipeTo); | ||
} catch (e) { | ||
if (typeof e === 'string') { | ||
try { | ||
e = JSON.parse(e); | ||
} catch { } | ||
} | ||
// Handle redirect | ||
if (e.status && e.status >= 300 && e.status < 400 && e.headers.location) { | ||
if (retry < 5) { | ||
return this._exec({ ...inOpts, url: e.headers.location }, retry + 1); | ||
} else { | ||
throw new Error('Maximum number of redirects attempted'); | ||
} | ||
} | ||
throw e; | ||
} | ||
} | ||
static requestOpts(inOpts: _ExecArgs & { pipeTo?: NodeJS.WritableStream }) { | ||
const { url: requestUrl, payload: inPayload, pipeTo, ...rest } = inOpts; | ||
const { hostname: host, port, pathname: path, username, password, searchParams, protocol } = new url.URL(requestUrl); | ||
const opts = { | ||
host, | ||
port, | ||
auth: (username && password) ? `${username}:${password}` : undefined, | ||
path, | ||
method: 'GET', | ||
headers: {}, | ||
...rest | ||
}; | ||
let payload = inPayload; | ||
const hasBody = (opts.method === 'POST' || opts.method === 'PUT'); | ||
const client = (protocol === 'https:' ? https : http) as HttpClient; | ||
if (payload) { | ||
if (hasBody) { | ||
payload = payload.toString(); | ||
opts.headers['Content-Length'] = Buffer.byteLength(payload as string); | ||
} else { | ||
const passedData = typeof payload === 'string' ? qs.parse(payload) : payload; | ||
for (const key of Object.keys(passedData)) { | ||
searchParams.set(key, passedData[key]); | ||
} | ||
} | ||
if (Array.from(searchParams.values()).length) { | ||
opts.path = `${opts.path || ''}?${searchParams.toString()}`; | ||
} | ||
} | ||
return { client, payload, pipeTo, opts }; | ||
} | ||
static configJSON(opts: _ExecArgs) { | ||
const out: _ExecArgs = { ...opts }; | ||
if (!out.headers) { | ||
out.headers = {}; | ||
} | ||
for (const k of ['Content-Type', 'Accept']) { | ||
if (!out.headers[k]) { | ||
out.headers[k] = 'application/json'; | ||
} | ||
} | ||
out.payload = opts.payload && (out.method === 'POST' || out.method === 'PUT') ? | ||
JSON.stringify(opts.payload) : opts.payload; | ||
return out; | ||
} | ||
static async rawRequest(client: HttpClient, requestOpts: http.ClientRequestArgs, payload?: any, pipeTo?: any) { | ||
return new Promise<string | http.IncomingMessage>((resolve, reject) => { | ||
const req = client.request(requestOpts, (msg: http.IncomingMessage) => { | ||
const body = Buffer.from('', 'utf8'); | ||
if (!pipeTo) { | ||
@@ -28,3 +110,3 @@ msg.setEncoding('utf8'); | ||
if ((msg.statusCode || 200) > 299 || !pipeTo) { | ||
body += chunk; | ||
body.write(chunk); | ||
} | ||
@@ -35,7 +117,8 @@ }); | ||
if ((msg.statusCode || 200) > 299) { | ||
reject({ message: body, status: msg.statusCode }); | ||
reject({ message: body.toString(), status: msg.statusCode, headers: msg.headers }); | ||
} else { | ||
resolve(pipeTo ? msg : body); | ||
resolve(pipeTo ? msg : body.toString()); | ||
} | ||
}); | ||
if (pipeTo) { | ||
@@ -51,4 +134,5 @@ msg.pipe(pipeTo); | ||
}); | ||
req.on('error', reject); | ||
if ((opts.method === 'PUT' || opts.method === 'POST') && payload !== undefined) { | ||
if ((requestOpts.method === 'PUT' || requestOpts.method === 'POST') && payload !== undefined) { | ||
req.write(payload); | ||
@@ -60,79 +144,13 @@ } | ||
static args(opts: http.RequestOptions & { url: string }, data?: any) { | ||
const { url: optsUrl, ...optsWithoutUrl } = opts; | ||
const { hostname: host, port, pathname: path, username, password, searchParams, protocol } = new url.URL(optsUrl); | ||
const auth = (username && password) ? `${username}:${password}` : undefined; | ||
const client = (protocol === 'https:' ? https : http) as HttpClient; | ||
const finalOpts = { | ||
host, port, | ||
auth, path, | ||
method: 'GET', | ||
headers: {}, | ||
payload: undefined as any, | ||
pipeTo: undefined as any, | ||
...optsWithoutUrl | ||
}; | ||
const hasBody = (finalOpts.method === 'POST' || finalOpts.method === 'PUT'); | ||
let payload: string | undefined = undefined; | ||
if (data) { | ||
if (hasBody) { | ||
payload = data.toString(); | ||
finalOpts.headers['Content-Length'] = Buffer.byteLength(payload as string); | ||
} else { | ||
const passedData = typeof data === 'string' ? qs.parse(data) : data; | ||
for (const key of Object.keys(passedData)) { | ||
searchParams.set(key, passedData[key]); | ||
} | ||
} | ||
} | ||
if (Array.from(searchParams.values()).length) { | ||
finalOpts.path = `${finalOpts.path || ''}?${searchParams.toString()}`; | ||
} | ||
return { opts: finalOpts, client, payload }; | ||
static async exec(opts: ExecArgs, payload?: any): Promise<string>; | ||
static async exec(opts: ExecArgs & { pipeTo: NodeJS.WritableStream }, payload?: any): Promise<http.IncomingMessage>; | ||
static async exec(opts: ExecArgs & { pipeTo?: NodeJS.WritableStream }, payload?: any): Promise<string | http.IncomingMessage> { | ||
return await this._exec({ ...opts, payload }); | ||
} | ||
static jsonArgs(opts: http.RequestOptions & { url: string }, data?: any) { | ||
if (!opts.headers) { | ||
opts.headers = {}; | ||
} | ||
for (const k of ['Content-Type', 'Accept']) { | ||
if (!opts.headers[k]) { | ||
opts.headers[k] = 'application/json'; | ||
} | ||
} | ||
const payload = data && (opts.method === 'POST' || opts.method === 'PUT') ? | ||
JSON.stringify(data) : data; | ||
static async execJSON<T, U = any>(opts: ExecArgs, payload?: U): Promise<T> { | ||
return this.args(opts, payload); | ||
const res = await this._exec(this.configJSON({ ...opts, payload })); | ||
return JSON.parse(res) as T; | ||
} | ||
static async exec(opts: http.RequestOptions & { url: string }, data?: any): Promise<string>; | ||
static async exec(opts: http.RequestOptions & { url: string, pipeTo: any }, data?: any): Promise<http.IncomingMessage>; | ||
static async exec(opts: http.RequestOptions & { url: string, pipeTo?: any }, data?: any): Promise<string | http.IncomingMessage> { | ||
const pipeTo = opts.pipeTo; | ||
delete opts.pipeTo; | ||
const { opts: finalOpts, client, payload } = this.args(opts, data); | ||
return this._exec(client, finalOpts, payload, pipeTo); | ||
} | ||
static async execJSON<T, U = any>(opts: http.RequestOptions & { url: string }, data?: U): Promise<T> { | ||
const { opts: finalOpts, client, payload } = this.jsonArgs(opts, data); | ||
try { | ||
const res = await this._exec(client, finalOpts, payload); | ||
return JSON.parse(res) as T; | ||
} catch (e) { | ||
if (typeof e === 'string') { | ||
e = JSON.parse(e); | ||
} | ||
throw e; | ||
} | ||
} | ||
} |
7634
144
Updated@travetto/base@^0.4.1