mirror of
https://github.com/actions/http-client.git
synced 2025-02-25 00:52:46 +00:00
json apis
This commit is contained in:
parent
ea44b6b1ad
commit
9827426984
@ -4,6 +4,13 @@ import * as fs from 'fs';
|
|||||||
|
|
||||||
let sampleFilePath: string = path.join(__dirname, 'testoutput.txt');
|
let sampleFilePath: string = path.join(__dirname, 'testoutput.txt');
|
||||||
|
|
||||||
|
interface HttpBinData {
|
||||||
|
url: string;
|
||||||
|
data: any;
|
||||||
|
json: any;
|
||||||
|
args?: any
|
||||||
|
}
|
||||||
|
|
||||||
describe('basics', () => {
|
describe('basics', () => {
|
||||||
let _http: httpm.HttpClient;
|
let _http: httpm.HttpClient;
|
||||||
|
|
||||||
@ -191,5 +198,45 @@ describe('basics', () => {
|
|||||||
expect(res.message.statusCode).toBe(404);
|
expect(res.message.statusCode).toBe(404);
|
||||||
let body: string = await res.readBody();
|
let body: string = await res.readBody();
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets a json object', async() => {
|
||||||
|
let jsonObj: httpm.ITypedResponse<HttpBinData> = await _http.getJson<HttpBinData>('https://httpbin.org/get');
|
||||||
|
expect(jsonObj.statusCode).toBe(200);
|
||||||
|
expect(jsonObj.result).toBeDefined();
|
||||||
|
expect(jsonObj.result.url).toBe('https://httpbin.org/get');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getting a non existent json object returns null', async() => {
|
||||||
|
let jsonObj: httpm.ITypedResponse<HttpBinData> = await _http.getJson<HttpBinData>('https://httpbin.org/status/404');
|
||||||
|
expect(jsonObj.statusCode).toBe(404);
|
||||||
|
expect(jsonObj.result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('posts a json object', async() => {
|
||||||
|
let res: any = { name: 'foo' };
|
||||||
|
let restRes: httpm.ITypedResponse<HttpBinData> = await _http.postJson<HttpBinData>('https://httpbin.org/post', res);
|
||||||
|
expect(restRes.statusCode).toBe(200);
|
||||||
|
expect(restRes.result).toBeDefined();
|
||||||
|
expect(restRes.result.url).toBe('https://httpbin.org/post');
|
||||||
|
expect(restRes.result.json.name).toBe('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('puts a json object', async() => {
|
||||||
|
let res: any = { name: 'foo' };
|
||||||
|
let restRes: httpm.ITypedResponse<HttpBinData> = await _http.putJson<HttpBinData>('https://httpbin.org/put', res);
|
||||||
|
expect(restRes.statusCode).toBe(200);
|
||||||
|
expect(restRes.result).toBeDefined();
|
||||||
|
expect(restRes.result.url).toBe('https://httpbin.org/put');
|
||||||
|
expect(restRes.result.json.name).toBe('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('patch a json object', async() => {
|
||||||
|
let res: any = { name: 'foo' };
|
||||||
|
let restRes: httpm.ITypedResponse<HttpBinData> = await _http.patchJson<HttpBinData>('https://httpbin.org/patch', res);
|
||||||
|
expect(restRes.statusCode).toBe(200);
|
||||||
|
expect(restRes.result).toBeDefined();
|
||||||
|
expect(restRes.result.url).toBe('https://httpbin.org/patch');
|
||||||
|
expect(restRes.result.json.name).toBe('foo');
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
113
index.ts
113
index.ts
@ -71,6 +71,12 @@ export class HttpClientResponse implements ifm.IHttpClientResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ITypedResponse<T> {
|
||||||
|
statusCode: number,
|
||||||
|
result: T | null,
|
||||||
|
headers: Object
|
||||||
|
}
|
||||||
|
|
||||||
export function isHttps(requestUrl: string) {
|
export function isHttps(requestUrl: string) {
|
||||||
let parsedUrl: url.Url = url.parse(requestUrl);
|
let parsedUrl: url.Url = url.parse(requestUrl);
|
||||||
return parsedUrl.protocol === 'https:';
|
return parsedUrl.protocol === 'https:';
|
||||||
@ -162,6 +168,33 @@ export class HttpClient {
|
|||||||
return this.request(verb, requestUrl, stream, additionalHeaders);
|
return this.request(verb, requestUrl, stream, additionalHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a typed object from an endpoint
|
||||||
|
* Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
|
||||||
|
*/
|
||||||
|
public async getJson<T>(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise<ITypedResponse<T>> {
|
||||||
|
let res: ifm.IHttpClientResponse = await this.get(requestUrl, additionalHeaders);
|
||||||
|
return this._processResponse<T>(res, this.requestOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async postJson<T>(requestUrl: string, obj:T, additionalHeaders?: ifm.IHeaders): Promise<ITypedResponse<T>> {
|
||||||
|
let data: string = JSON.stringify(obj, null, 2);
|
||||||
|
let res: ifm.IHttpClientResponse = await this.post(requestUrl, data, additionalHeaders);
|
||||||
|
return this._processResponse<T>(res, this.requestOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async putJson<T>(requestUrl: string, obj:T, additionalHeaders?: ifm.IHeaders): Promise<ITypedResponse<T>> {
|
||||||
|
let data: string = JSON.stringify(obj, null, 2);
|
||||||
|
let res: ifm.IHttpClientResponse = await this.put(requestUrl, data, additionalHeaders);
|
||||||
|
return this._processResponse<T>(res, this.requestOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async patchJson<T>(requestUrl: string, obj:T, additionalHeaders?: ifm.IHeaders): Promise<ITypedResponse<T>> {
|
||||||
|
let data: string = JSON.stringify(obj, null, 2);
|
||||||
|
let res: ifm.IHttpClientResponse = await this.patch(requestUrl, data, additionalHeaders);
|
||||||
|
return this._processResponse<T>(res, this.requestOptions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a raw http request.
|
* Makes a raw http request.
|
||||||
* All other methods such as get, post, patch, and request ultimately call this.
|
* All other methods such as get, post, patch, and request ultimately call this.
|
||||||
@ -358,7 +391,6 @@ export class HttpClient {
|
|||||||
info.options.port = info.parsedUrl.port ? parseInt(info.parsedUrl.port) : defaultPort;
|
info.options.port = info.parsedUrl.port ? parseInt(info.parsedUrl.port) : defaultPort;
|
||||||
info.options.path = (info.parsedUrl.pathname || '') + (info.parsedUrl.search || '');
|
info.options.path = (info.parsedUrl.pathname || '') + (info.parsedUrl.search || '');
|
||||||
info.options.method = method;
|
info.options.method = method;
|
||||||
|
|
||||||
info.options.headers = this._mergeHeaders(headers);
|
info.options.headers = this._mergeHeaders(headers);
|
||||||
if (this.userAgent != null) {
|
if (this.userAgent != null) {
|
||||||
info.options.headers["user-agent"] = this.userAgent;
|
info.options.headers["user-agent"] = this.userAgent;
|
||||||
@ -468,5 +500,82 @@ export class HttpClient {
|
|||||||
retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
|
retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
|
||||||
const ms: number = ExponentialBackoffTimeSlice*Math.pow(2, retryNumber);
|
const ms: number = ExponentialBackoffTimeSlice*Math.pow(2, retryNumber);
|
||||||
return new Promise(resolve => setTimeout(()=>resolve(), ms));
|
return new Promise(resolve => setTimeout(()=>resolve(), ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static dateTimeDeserializer(key: any, value: any): any {
|
||||||
|
if (typeof value === 'string'){
|
||||||
|
let a = new Date(value);
|
||||||
|
if (!isNaN(a.valueOf())) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _processResponse<T>(res: ifm.IHttpClientResponse, options: ifm.IRequestOptions): Promise<ITypedResponse<T>> {
|
||||||
|
return new Promise<ITypedResponse<T>>(async (resolve, reject) => {
|
||||||
|
const statusCode: number = res.message.statusCode;
|
||||||
|
|
||||||
|
const response: ITypedResponse<T> = {
|
||||||
|
statusCode: statusCode,
|
||||||
|
result: null,
|
||||||
|
headers: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// not found leads to null obj returned
|
||||||
|
if (statusCode == HttpCodes.NotFound) {
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj: any;
|
||||||
|
let contents: string;
|
||||||
|
|
||||||
|
// get the result from the body
|
||||||
|
try {
|
||||||
|
contents = await res.readBody();
|
||||||
|
if (contents && contents.length > 0) {
|
||||||
|
if (options && options.deserializeDates) {
|
||||||
|
obj = JSON.parse(contents, HttpClient.dateTimeDeserializer);
|
||||||
|
} else {
|
||||||
|
obj = JSON.parse(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.result = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.headers = res.message.headers;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
// Invalid resource (contents not json); leaving result obj null
|
||||||
|
}
|
||||||
|
|
||||||
|
// note that 3xx redirects are handled by the http layer.
|
||||||
|
if (statusCode > 299) {
|
||||||
|
let msg: string;
|
||||||
|
|
||||||
|
// if exception/error in body, attempt to get better error
|
||||||
|
if (obj && obj.message) {
|
||||||
|
msg = obj.message;
|
||||||
|
} else if (contents && contents.length > 0) {
|
||||||
|
// it may be the case that the exception is in the body message as string
|
||||||
|
msg = contents;
|
||||||
|
} else {
|
||||||
|
msg = "Failed request: (" + statusCode + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
let err: Error = new Error(msg);
|
||||||
|
|
||||||
|
// attach statusCode and body obj (if available) to the error object
|
||||||
|
err['statusCode'] = statusCode;
|
||||||
|
if (response.result) {
|
||||||
|
err['result'] = response.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ export interface IRequestOptions {
|
|||||||
maxRedirects?: number;
|
maxRedirects?: number;
|
||||||
maxSockets?: number;
|
maxSockets?: number;
|
||||||
keepAlive?: boolean;
|
keepAlive?: boolean;
|
||||||
|
deserializeDates?: boolean;
|
||||||
// Allows retries only on Read operations (since writes may not be idempotent)
|
// Allows retries only on Read operations (since writes may not be idempotent)
|
||||||
allowRetries?: boolean;
|
allowRetries?: boolean;
|
||||||
maxRetries?: number;
|
maxRetries?: number;
|
||||||
|
Loading…
Reference in New Issue
Block a user