feat(transports): add plain XML-RPC as last fallback (#65)
This commit is contained in:
parent
781ffa5574
commit
e65dd15edc
3
packages/xen-api/src/transports/_utils.js
Normal file
3
packages/xen-api/src/transports/_utils.js
Normal file
@ -0,0 +1,3 @@
|
||||
import makeError from 'make-error'
|
||||
|
||||
export const UnsupportedTransport = makeError('UnsupportedTransport')
|
@ -1,29 +1,36 @@
|
||||
import { InvalidJson } from 'json-rpc-protocol'
|
||||
|
||||
import jsonRpc from './json-rpc'
|
||||
import xmlRpc from './xml-rpc'
|
||||
import xmlRpcJson from './xml-rpc-json'
|
||||
import { UnsupportedTransport } from './_utils'
|
||||
|
||||
const factories = [ jsonRpc, xmlRpcJson, xmlRpc ]
|
||||
const { length } = factories
|
||||
|
||||
export default opts => {
|
||||
let call = (jsonRpcCall =>
|
||||
(method, args) => jsonRpcCall(method, args).then(
|
||||
result => {
|
||||
// JSON-RPC seems to be working, remove the test
|
||||
call = jsonRpcCall
|
||||
let i = 0
|
||||
|
||||
return result
|
||||
},
|
||||
error => {
|
||||
if (error instanceof InvalidJson) {
|
||||
// fallback to XML-RPC
|
||||
call = xmlRpc(opts)
|
||||
let call
|
||||
function create () {
|
||||
const current = factories[i++](opts)
|
||||
if (i < length) {
|
||||
const currentI = i
|
||||
call = (method, args) => current(method, args).catch(
|
||||
error => {
|
||||
if (error instanceof UnsupportedTransport) {
|
||||
if (currentI === i) { // not changed yet
|
||||
create()
|
||||
}
|
||||
return call(method, args)
|
||||
}
|
||||
|
||||
return call(method, args)
|
||||
throw error
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
)
|
||||
)(jsonRpc(opts))
|
||||
)
|
||||
} else {
|
||||
call = current
|
||||
}
|
||||
}
|
||||
create()
|
||||
|
||||
return (method, args) => call(method, args)
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import httpRequestPlus from 'http-request-plus'
|
||||
import { format, parse } from 'json-rpc-protocol'
|
||||
|
||||
import { UnsupportedTransport } from './_utils'
|
||||
|
||||
export default ({ allowUnauthorized, url }) => {
|
||||
return (method, args) => httpRequestPlus.post(url, {
|
||||
rejectUnauthorized: !allowUnauthorized,
|
||||
@ -10,12 +12,27 @@ export default ({ allowUnauthorized, url }) => {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
path: '/jsonrpc'
|
||||
}).readAll('utf8').then(text => {
|
||||
const response = parse(text)
|
||||
if (response.type === 'response') {
|
||||
return response.result
|
||||
}
|
||||
}).readAll('utf8').then(
|
||||
text => {
|
||||
let response
|
||||
try {
|
||||
response = parse(text)
|
||||
} catch (error) {
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
|
||||
throw response.error
|
||||
})
|
||||
if (response.type === 'response') {
|
||||
return response.result
|
||||
}
|
||||
|
||||
throw response.error
|
||||
},
|
||||
error => {
|
||||
if (error.response !== undefined) { // HTTP error
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
)
|
||||
}
|
||||
|
97
packages/xen-api/src/transports/xml-rpc-json.js
Normal file
97
packages/xen-api/src/transports/xml-rpc-json.js
Normal file
@ -0,0 +1,97 @@
|
||||
import { createClient, createSecureClient } from 'xmlrpc'
|
||||
import { promisify } from 'promise-toolbox'
|
||||
|
||||
import { UnsupportedTransport } from './_utils'
|
||||
|
||||
const logError = error => {
|
||||
if (error.res) {
|
||||
console.error(
|
||||
'XML-RPC Error: %s (response status %s)',
|
||||
error.message,
|
||||
error.res.statusCode
|
||||
)
|
||||
console.error('%s', error.body)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
const SPECIAL_CHARS = {
|
||||
'\r': '\\r',
|
||||
'\t': '\\t'
|
||||
}
|
||||
const SPECIAL_CHARS_RE = new RegExp(
|
||||
Object.keys(SPECIAL_CHARS).join('|'),
|
||||
'g'
|
||||
)
|
||||
|
||||
const parseResult = result => {
|
||||
const status = result.Status
|
||||
|
||||
// Return the plain result if it does not have a valid XAPI
|
||||
// format.
|
||||
if (status === undefined) {
|
||||
return result
|
||||
}
|
||||
|
||||
if (status !== 'Success') {
|
||||
throw result.ErrorDescription
|
||||
}
|
||||
|
||||
const value = result.Value
|
||||
|
||||
// XAPI returns an empty string (invalid JSON) for an empty
|
||||
// result.
|
||||
if (value === '') {
|
||||
return ''
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (error) {
|
||||
// XAPI JSON sometimes contains invalid characters.
|
||||
if (!(error instanceof SyntaxError)) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
let replaced = false
|
||||
const fixedValue = value.replace(SPECIAL_CHARS_RE, match => {
|
||||
replaced = true
|
||||
return SPECIAL_CHARS[match]
|
||||
})
|
||||
|
||||
if (replaced) {
|
||||
try {
|
||||
return JSON.parse(fixedValue)
|
||||
} catch (error) {
|
||||
if (!(error instanceof SyntaxError)) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
|
||||
export default ({
|
||||
allowUnauthorized,
|
||||
url: { hostname, path, port, protocol }
|
||||
}) => {
|
||||
const client = (
|
||||
protocol === 'https:'
|
||||
? createSecureClient
|
||||
: createClient
|
||||
)({
|
||||
host: hostname,
|
||||
path: '/json',
|
||||
port,
|
||||
rejectUnauthorized: !allowUnauthorized
|
||||
})
|
||||
const call = promisify(client.methodCall, client)
|
||||
|
||||
return (method, args) => call(method, args).then(
|
||||
parseResult,
|
||||
logError
|
||||
)
|
||||
}
|
@ -14,15 +14,6 @@ const logError = error => {
|
||||
throw error
|
||||
}
|
||||
|
||||
const SPECIAL_CHARS = {
|
||||
'\r': '\\r',
|
||||
'\t': '\\t'
|
||||
}
|
||||
const SPECIAL_CHARS_RE = new RegExp(
|
||||
Object.keys(SPECIAL_CHARS).join('|'),
|
||||
'g'
|
||||
)
|
||||
|
||||
const parseResult = result => {
|
||||
const status = result.Status
|
||||
|
||||
@ -36,32 +27,7 @@ const parseResult = result => {
|
||||
throw result.ErrorDescription
|
||||
}
|
||||
|
||||
const value = result.Value
|
||||
|
||||
// XAPI returns an empty string (invalid JSON) for an empty
|
||||
// result.
|
||||
if (value === '') {
|
||||
return ''
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (error) {
|
||||
// XAPI JSON sometimes contains invalid characters.
|
||||
if (error instanceof SyntaxError) {
|
||||
let replaced
|
||||
const fixedValue = value.replace(SPECIAL_CHARS_RE, match => {
|
||||
replaced = true
|
||||
return SPECIAL_CHARS[match]
|
||||
})
|
||||
|
||||
if (replaced) {
|
||||
return JSON.parse(fixedValue)
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
return result.Value
|
||||
}
|
||||
|
||||
export default ({
|
||||
@ -74,7 +40,6 @@ export default ({
|
||||
: createClient
|
||||
)({
|
||||
host: hostname,
|
||||
path: '/json',
|
||||
port,
|
||||
rejectUnauthorized: !allowUnauthorized
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user