Skip to content

Commit

Permalink
Merge pull request #239 from manolodewiner/bep-14_roundrobin
Browse files Browse the repository at this point in the history
Implementation of BEP-14
  • Loading branch information
Manolo committed Aug 31, 2018
2 parents 80bf01e + a99ccd5 commit 3c82ae4
Show file tree
Hide file tree
Showing 10 changed files with 440 additions and 87 deletions.
27 changes: 26 additions & 1 deletion docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,36 @@ To do so, you need to pass the **app_id and app_key**.

.. code-block:: js
let conn = new driver.Connection('https://test.bigchaindb.com/api/v1/', {
const conn = new driver.Connection('https://test.bigchaindb.com/api/v1/', {
app_id: 'Get one from testnet.bigchaindb.com',
app_key: 'Get one from testnet.bigchaindb.com'
})
A more complex connection can be created if the intention is to connect to
different nodes of a BigchainDB network.
The connection strategy will be the one specified in the BEP-14_

.. _BEP-14: https://github.com/bigchaindb/BEPs/tree/master/14#connection-strategy

.. code-block:: js
const conn = new driver.Connection([
'https://test.bigchaindb.com', // the first node does not use custom headers, only common headers
{endpoint: 'https://test.bigchaindb.com/api/v1/',
headers: {app_id: 'your_app_id',
app_key: 'your_app_key'}},
{endpoint: 'https://test2.bigchaindb.com/api/v1/',
headers: {app_id: 'your_app_id',
app_key: 'your_app_key',
extra_header: 'extra value'}},
{endpoint: 'https://test3.bigchaindb.com/api/v1/',
headers: {app_id: 'your_app_id',
app_key: 'your_app_key',
other_header: 'other value'}},
{endpoint: 'https://test4.bigchaindb.com/api/v1/',
headers: {custom_auth: 'custom token'}],
{'Content-Type': 'application/json'}, // this header is used by all nodes)
Cryptographic Identities Generation
-----------------------------------
Alice and Bob are represented by public/private key pairs. The private key is
Expand Down
82 changes: 61 additions & 21 deletions src/baseRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,60 @@
// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
// Code is Apache-2.0 and docs are CC-BY-4.0

import { Promise } from 'es6-promise'
import {
Promise
} from 'es6-promise'
import fetchPonyfill from 'fetch-ponyfill'
import { vsprintf } from 'sprintf-js'
import {
vsprintf
} from 'sprintf-js'

import formatText from './format_text'
import stringifyAsQueryParam from './stringify_as_query_param'

const fetch = fetchPonyfill(Promise)


/**
* @private
* Timeout function following https://github.com/github/fetch/issues/175#issuecomment-284787564
* @param {integer} obj Source object
* @param {Promise} filter Array of key names to select or function to invoke per iteration
* @return {Object} TimeoutError if the time was consumed, otherwise the Promise will be resolved
*/
function timeout(ms, promise) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const errorObject = {
message: 'TimeoutError'
}
reject(new Error(errorObject))
}, ms)
promise.then(resolve, reject)
})
}

/**
* @private
* @param {Promise} res Source object
* @return {Promise} Promise that will resolve with the response if its status was 2xx;
* otherwise rejects with the response
*/
function handleResponse(res) {
// If status is not a 2xx (based on Response.ok), assume it's an error
// See https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch
if (!(res && res.ok)) {
const errorObject = {
message: 'HTTP Error: Requested page not reachable',
status: `${res.status} ${res.statusText}`,
requestURI: res.url
}
throw errorObject
}
return res
}


/**
* @private
* imported from https://github.com/bigchaindb/js-utility-belt/
Expand All @@ -36,13 +80,17 @@ const fetch = fetchPonyfill(Promise)
* decamelized into snake case first.
* @param {*[]|Object} config.urlTemplateSpec Format spec to use to expand the url (see sprintf).
* @param {*} config.* All other options are passed through to fetch.
* @param {integer} requestTimeout Timeout for a single request
*
* @return {Promise} Promise that will resolve with the response if its status was 2xx;
* otherwise rejects with the response
* @return {Promise} If requestTimeout the timeout function will be called. Otherwise resolve the
* Promise with the handleResponse function
*/
export default function baseRequest(url, {
jsonBody, query, urlTemplateSpec, ...fetchConfig
} = {}) {
jsonBody,
query,
urlTemplateSpec,
...fetchConfig
} = {}, requestTimeout) {
let expandedUrl = url

if (urlTemplateSpec != null) {
Expand Down Expand Up @@ -73,19 +121,11 @@ export default function baseRequest(url, {
if (jsonBody != null) {
fetchConfig.body = JSON.stringify(jsonBody)
}

return fetch.fetch(expandedUrl, fetchConfig)
.then((res) => {
// If status is not a 2xx (based on Response.ok), assume it's an error
// See https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch
if (!(res && res.ok)) {
const errorObject = {
message: 'HTTP Error: Requested page not reachable',
status: `${res.status} ${res.statusText}`,
requestURI: res.url
}
throw errorObject
}
return res
})
if (requestTimeout) {
return timeout(requestTimeout, fetch.fetch(expandedUrl, fetchConfig))
.then(handleResponse)
} else {
return fetch.fetch(expandedUrl, fetchConfig)
.then(handleResponse)
}
}
71 changes: 51 additions & 20 deletions src/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,60 @@
// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
// Code is Apache-2.0 and docs are CC-BY-4.0

import request from './request'
import Transport from './transport'

const HEADER_BLACKLIST = ['content-type']
const DEFAULT_NODE = 'http://localhost:9984/api/v1/'
const DEFAULT_TIMEOUT = 20000 // The default value is 20 seconds

/**
* Base connection
*
* @param {String, Array} nodes Nodes for the connection. String possible to be backwards compatible
* with version before 4.1.0 version
* @param {Object} headers Common headers for every request
* @param {float} timeout Optional timeout in secs
*
*
*/

export default class Connection {
constructor(path, headers = {}) {
this.path = path
// This driver implements the BEP-14 https://github.com/bigchaindb/BEPs/tree/master/14
constructor(nodes, headers = {}, timeout = DEFAULT_TIMEOUT) {
// Copy object
this.headers = Object.assign({}, headers)

// Validate headers
Object.keys(headers).forEach(header => {
if (HEADER_BLACKLIST.includes(header.toLowerCase())) {
throw new Error(`Header ${header} is reserved and cannot be set.`)
}
})

this.normalizedNodes = []
if (!nodes) {
this.normalizedNodes.push(Connection.normalizeNode(DEFAULT_NODE, this.headers))
} else if (Array.isArray(nodes)) {
nodes.forEach(node => {
this.normalizedNodes.push(Connection.normalizeNode(node, this.headers))
})
} else {
this.normalizedNodes.push(Connection.normalizeNode(nodes, this.headers))
}

this.transport = new Transport(this.normalizedNodes, timeout)
}

static normalizeNode(node, headers) {
if (typeof node === 'string') {
return { 'endpoint': node, 'headers': headers }
} else {
const allHeaders = Object.assign({}, headers, node.headers)
return { 'endpoint': node.endpoint, 'headers': allHeaders }
}
}

getApiUrls(endpoint) {
return this.path + {
static getApiUrls(endpoint) {
return {
'blocks': 'blocks',
'blocksDetail': 'blocks/%(blockHeight)s',
'outputs': 'outputs',
Expand All @@ -38,16 +71,14 @@ export default class Connection {
}

_req(path, options = {}) {
// NOTE: `options.headers` could be undefined, but that's OK.
options.headers = Object.assign({}, options.headers, this.headers)
return request(path, options)
return this.transport.forwardRequest(path, options)
}

/**
* @param blockHeight
*/
getBlock(blockHeight) {
return this._req(this.getApiUrls('blocksDetail'), {
return this._req(Connection.getApiUrls('blocksDetail'), {
urlTemplateSpec: {
blockHeight
}
Expand All @@ -58,7 +89,7 @@ export default class Connection {
* @param transactionId
*/
getTransaction(transactionId) {
return this._req(this.getApiUrls('transactionsDetail'), {
return this._req(Connection.getApiUrls('transactionsDetail'), {
urlTemplateSpec: {
transactionId
}
Expand All @@ -70,7 +101,7 @@ export default class Connection {
* @param status
*/
listBlocks(transactionId) {
return this._req(this.getApiUrls('blocks'), {
return this._req(Connection.getApiUrls('blocks'), {
query: {
transaction_id: transactionId,
}
Expand All @@ -90,7 +121,7 @@ export default class Connection {
if (spent !== undefined) {
query.spent = spent.toString()
}
return this._req(this.getApiUrls('outputs'), {
return this._req(Connection.getApiUrls('outputs'), {
query
})
}
Expand All @@ -100,7 +131,7 @@ export default class Connection {
* @param operation
*/
listTransactions(assetId, operation) {
return this._req(this.getApiUrls('transactions'), {
return this._req(Connection.getApiUrls('transactions'), {
query: {
asset_id: assetId,
operation
Expand All @@ -112,7 +143,7 @@ export default class Connection {
* @param blockId
*/
listVotes(blockId) {
return this._req(this.getApiUrls('votes'), {
return this._req(Connection.getApiUrls('votes'), {
query: {
block_id: blockId
}
Expand All @@ -130,7 +161,7 @@ export default class Connection {
* @param transaction
*/
postTransactionSync(transaction) {
return this._req(this.getApiUrls('transactionsSync'), {
return this._req(Connection.getApiUrls('transactionsSync'), {
method: 'POST',
jsonBody: transaction
})
Expand All @@ -141,7 +172,7 @@ export default class Connection {
* @param transaction
*/
postTransactionAsync(transaction) {
return this._req(this.getApiUrls('transactionsAsync'), {
return this._req(Connection.getApiUrls('transactionsAsync'), {
method: 'POST',
jsonBody: transaction
})
Expand All @@ -152,7 +183,7 @@ export default class Connection {
* @param transaction
*/
postTransactionCommit(transaction) {
return this._req(this.getApiUrls('transactionsCommit'), {
return this._req(Connection.getApiUrls('transactionsCommit'), {
method: 'POST',
jsonBody: transaction
})
Expand All @@ -162,7 +193,7 @@ export default class Connection {
* @param search
*/
searchAssets(search) {
return this._req(this.getApiUrls('assets'), {
return this._req(Connection.getApiUrls('assets'), {
query: {
search
}
Expand All @@ -173,7 +204,7 @@ export default class Connection {
* @param search
*/
searchMetadata(search) {
return this._req(this.getApiUrls('metadata'), {
return this._req(Connection.getApiUrls('metadata'), {
query: {
search
}
Expand Down

0 comments on commit 3c82ae4

Please sign in to comment.