opennms
Version:
Client API for the OpenNMS network monitoring platform
281 lines (235 loc) • 8.45 kB
text/typescript
import axios from 'axios';
import { AxiosStatic, AxiosInstance, AxiosRequestConfig, getAdapter } from 'axios';
import cloneDeep from 'lodash/cloneDeep';
/** @hidden */
// eslint-disable-next-line
const URI = require('urijs');
import {AbstractHTTP} from './AbstractHTTP';
import {OnmsError} from '../api/OnmsError';
import {OnmsHTTPOptions} from '../api/OnmsHTTPOptions';
import {OnmsResult} from '../api/OnmsResult';
import {OnmsServer} from '../api/OnmsServer';
import {log} from '../api/Log';
/**
* Implementation of the [[IOnmsHTTP]] interface using Axios: https://github.com/mzabriskie/axios
* @category Rest
* @implements IOnmsHTTP
*/
export class AxiosHTTP extends AbstractHTTP {
/**
* The Axios implementation class we'll use for making ReST calls. This is necessary
* to make sure we end up with the correct backend (XMLHttpRequest or Node.js 'http')
* at runtime.
* @hidden
*/
private axiosImpl: AxiosStatic;
/**
* The Axios instance we'll use for making ReST calls. This will be reinitialized whenever
* the server configuration changes.
*/
private axiosObj?: AxiosInstance;
/**
* Construct an AxiosHTTP instance.
* @param server - The server to connect to.
* @param axiosImpl - The Axios implementation class to use.
* @param timeout - The default timeout for ReST connections.
*/
constructor(server?: OnmsServer, axiosImpl?: AxiosStatic, timeout = 10000) {
super(server, timeout);
this.axiosImpl = axiosImpl || axios;
}
/**
* Make an HTTP GET call using `axios.request({method:'get'})`.
*/
public get(url: string, options?: OnmsHTTPOptions) {
const realUrl = this.getServer(options).resolveURL(url);
const opts = this.getConfig(options);
const urlObj = new URI(realUrl);
urlObj.search(opts.params);
log.debug('GET ' + urlObj.toString());
opts.method = 'get';
opts.url = realUrl;
return this.getImpl(options).request(opts).then((response) => {
let type;
if (response.headers && response.headers['Content-Type']) {
type = response.headers['Content-Type'] as string;
}
return OnmsResult.ok(this.getData(response), undefined, response.status, type);
}).catch((err) => {
throw this.handleError(err, opts);
});
}
/**
* Make an HTTP HEAD call using `axios.request({method:'head'})`.
*/
public head(url: string, options?: OnmsHTTPOptions) {
const realUrl = this.getServer(options).resolveURL(url);
const opts = this.getConfig(options);
const urlObj = new URI(realUrl);
urlObj.search(opts.params);
log.debug('HEAD ' + urlObj.toString());
opts.method = 'head';
opts.url = realUrl;
return this.getImpl(options).request(opts).then((response) => {
let type;
if (response.headers && response.headers['Content-Type']) {
type = response.headers['Content-Type'] as string;
}
return OnmsResult.ok(this.getData(response), undefined, response.status, type);
}).catch((err) => {
throw this.handleError(err, opts);
});
}
/**
* Make an HTTP PUT call using `axios.request({method:'put'})`.
*/
public put(url: string, options?: OnmsHTTPOptions) {
const realUrl = this.getServer(options).resolveURL(url);
const opts = this.getConfig(options);
const urlObj = new URI(realUrl);
urlObj.search(opts.params);
log.debug('PUT ' + urlObj.toString());
opts.data = Object.assign({}, opts.params);
opts.method = 'put';
opts.url = realUrl;
return this.getImpl(options).request(opts).then((response) => {
let type;
if (response.headers && response.headers['Content-Type']) {
type = response.headers['Content-Type'] as string;
}
return OnmsResult.ok(this.getData(response), undefined, response.status, type);
}).catch((err) => {
throw this.handleError(err, opts);
});
}
/**
* Make an HTTP POST call using `axios.request({method:'post'})`.
*/
public post(url: string, options?: OnmsHTTPOptions) {
const realUrl = this.getServer(options).resolveURL(url);
const opts = this.getConfig(options);
const urlObj = new URI(realUrl);
urlObj.search(opts.params);
log.debug('POST ' + urlObj.toString());
opts.method = 'post';
opts.url = realUrl;
return this.getImpl(options).request(opts).then((response) => {
let type;
if (response.headers && response.headers['Content-Type']) {
type = response.headers['Content-Type'] as string;
}
return OnmsResult.ok(this.getData(response), undefined, response.status, type);
}).catch((err) => {
throw this.handleError(err, opts);
});
}
/**
* Make an HTTP DELETE call using `axios.request({method:'delete'})`.
*/
public httpDelete(url: string, options?: OnmsHTTPOptions) {
const realUrl = this.getServer(options).resolveURL(url);
const opts = this.getConfig(options);
const urlObj = new URI(realUrl);
urlObj.search(opts.params);
log.debug('DELETE ' + urlObj.toString());
opts.method = 'delete';
opts.url = realUrl;
return this.getImpl(options).request(opts).then((response) => {
let type;
if (response.headers && response.headers['Content-Type']) {
type = response.headers['Content-Type'] as string;
}
return OnmsResult.ok(this.getData(response), undefined, response.status, type);
}).catch((err) => {
throw this.handleError(err, opts);
});
}
/**
* Clear the current [[AxiosInstance]] so it is recreated on next request with the
* new server configuration.
*/
protected onSetServer() {
super.onSetServer();
this.axiosObj = undefined;
}
/**
* Internal method to turn [[OnmsHTTPOptions]] into an [[AxiosRequestConfig]] object.
* @hidden
*/
private getConfig(options?: OnmsHTTPOptions): AxiosRequestConfig {
const allOptions = this.getOptions(options);
const ret = {
transformResponse: [], // we do this so we can post-process only on success
} as AxiosRequestConfig;
if (allOptions.auth && allOptions.auth.username && allOptions.auth.password) {
ret.auth = {
password: allOptions.auth.password,
username: allOptions.auth.username,
};
this.axiosImpl.defaults.auth = cloneDeep(ret.auth);
}
if (allOptions.timeout) {
ret.timeout = allOptions.timeout;
}
if (allOptions.headers) {
ret.headers = cloneDeep(allOptions.headers);
} else {
ret.headers = {};
}
if (!ret.headers.Accept) {
ret.headers.Accept = 'application/json';
}
if (!ret.headers['Content-Type']) {
ret.headers['Content-Type'] = 'application/json;charset=utf-8';
}
const type = ret.headers.Accept;
ret.transformResponse = [];
if (type === 'application/json') {
ret.responseType = 'json';
} else if (type === 'text/plain') {
ret.responseType = 'text';
} else if (type === 'application/xml') {
ret.responseType = 'text';
} else {
throw new OnmsError('Unhandled "Accept" header: ' + type);
}
/*
* This tells Axios to turn multi-value parameters into key=value1&key=value2.
* See: https://github.com/axios/axios/issues/5058#issuecomment-1272229926
*/
ret.paramsSerializer = { indexes: null };
if (allOptions.parameters) {
ret.params = cloneDeep(allOptions.parameters);
}
if (allOptions.data) {
ret.data = cloneDeep(allOptions.data);
}
return ret;
}
/**
* Internal method for getting/constructing an Axios object on-demand,
* based on the current server configuration.
* @hidden
*/
private getImpl(options?: OnmsHTTPOptions) {
if (!this.axiosObj) {
const server = this.getServer(options);
if (!server) {
throw new OnmsError('You must set a server before attempting to make queries using Axios!');
}
const allOptions = this.getOptions(options);
const axiosOpts = {
baseURL: server.url,
timeout: allOptions.timeout,
withCredentials: true,
} as AxiosRequestConfig;
if (typeof XMLHttpRequest !== 'undefined') {
axiosOpts.adapter = getAdapter('xhr');
} else if (typeof process !== 'undefined') {
axiosOpts.adapter = getAdapter('http');
}
this.axiosObj = this.axiosImpl.create(axiosOpts);
}
return this.axiosObj;
}
}