870 lines
18 KiB
JavaScript
870 lines
18 KiB
JavaScript
/*!
|
|
* server.js - http server for bcoin
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var assert = require('assert');
|
|
var HTTPBase = require('./base');
|
|
var util = require('../utils/util');
|
|
var co = require('../utils/co');
|
|
var base58 = require('../utils/base58');
|
|
var Amount = require('../btc/amount');
|
|
var Bloom = require('../utils/bloom');
|
|
var TX = require('../primitives/tx');
|
|
var Outpoint = require('../primitives/outpoint');
|
|
var crypto = require('../crypto/crypto');
|
|
var Network = require('../protocol/network');
|
|
var Validator = require('../utils/validator');
|
|
var pkg = require('../pkg');
|
|
|
|
/**
|
|
* HTTPServer
|
|
* @alias module:http.Server
|
|
* @constructor
|
|
* @param {Object} options
|
|
* @param {Fullnode} options.node
|
|
* @see HTTPBase
|
|
* @emits HTTPServer#socket
|
|
*/
|
|
|
|
function HTTPServer(options) {
|
|
if (!(this instanceof HTTPServer))
|
|
return new HTTPServer(options);
|
|
|
|
options = new HTTPOptions(options);
|
|
|
|
HTTPBase.call(this, options);
|
|
|
|
this.options = options;
|
|
this.network = this.options.network;
|
|
this.logger = this.options.logger.context('http');
|
|
this.node = this.options.node;
|
|
|
|
this.chain = this.node.chain;
|
|
this.mempool = this.node.mempool;
|
|
this.pool = this.node.pool;
|
|
this.fees = this.node.fees;
|
|
this.miner = this.node.miner;
|
|
this.rpc = this.node.rpc;
|
|
|
|
this.init();
|
|
}
|
|
|
|
util.inherits(HTTPServer, HTTPBase);
|
|
|
|
/**
|
|
* Initialize routes.
|
|
* @private
|
|
*/
|
|
|
|
HTTPServer.prototype.init = function init() {
|
|
var self = this;
|
|
|
|
this.on('request', function(req, res) {
|
|
if (req.method === 'POST' && req.pathname === '/')
|
|
return;
|
|
|
|
self.logger.debug('Request for method=%s path=%s (%s).',
|
|
req.method, req.pathname, req.socket.remoteAddress);
|
|
});
|
|
|
|
this.on('listening', function(address) {
|
|
self.logger.info('Node HTTP server listening on %s (port=%d).',
|
|
address.address, address.port);
|
|
});
|
|
|
|
this.initRouter();
|
|
this.initSockets();
|
|
};
|
|
|
|
/**
|
|
* Initialize routes.
|
|
* @private
|
|
*/
|
|
|
|
HTTPServer.prototype.initRouter = function initRouter() {
|
|
this.use(this.cors());
|
|
|
|
if (!this.options.noAuth) {
|
|
this.use(this.basicAuth({
|
|
password: this.options.apiKey,
|
|
realm: 'node'
|
|
}));
|
|
}
|
|
|
|
this.use(this.bodyParser({
|
|
contentType: 'json'
|
|
}));
|
|
|
|
this.use(this.jsonRPC(this.rpc));
|
|
|
|
this.get('/', co(function* (req, res) {
|
|
var totalTX = this.mempool ? this.mempool.totalTX : 0;
|
|
var size = this.mempool ? this.mempool.getSize() : 0;
|
|
var addr = this.pool.hosts.getLocal();
|
|
|
|
if (!addr)
|
|
addr = this.pool.hosts.address;
|
|
|
|
res.send(200, {
|
|
version: pkg.version,
|
|
network: this.network.type,
|
|
chain: {
|
|
height: this.chain.height,
|
|
tip: this.chain.tip.rhash(),
|
|
progress: this.chain.getProgress()
|
|
},
|
|
pool: {
|
|
host: addr.host,
|
|
port: addr.port,
|
|
agent: this.pool.options.agent,
|
|
services: this.pool.options.services.toString(2),
|
|
outbound: this.pool.peers.outbound,
|
|
inbound: this.pool.peers.inbound
|
|
},
|
|
mempool: {
|
|
tx: totalTX,
|
|
size: size
|
|
},
|
|
time: {
|
|
uptime: this.node.uptime(),
|
|
system: util.now(),
|
|
adjusted: this.network.now(),
|
|
offset: this.network.time.offset
|
|
},
|
|
memory: util.memoryUsage()
|
|
});
|
|
}));
|
|
|
|
// UTXO by address
|
|
this.get('/coin/address/:address', co(function* (req, res) {
|
|
var valid = req.valid();
|
|
var address = valid.str('address');
|
|
var result = [];
|
|
var i, coins, coin;
|
|
|
|
enforce(address, 'Address is required.');
|
|
enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.');
|
|
|
|
coins = yield this.node.getCoinsByAddress(address);
|
|
|
|
for (i = 0; i < coins.length; i++) {
|
|
coin = coins[i];
|
|
result.push(coin.getJSON(this.network));
|
|
}
|
|
|
|
res.send(200, result);
|
|
}));
|
|
|
|
// UTXO by id
|
|
this.get('/coin/:hash/:index', co(function* (req, res) {
|
|
var valid = req.valid();
|
|
var hash = valid.hash('hash');
|
|
var index = valid.u32('index');
|
|
var coin;
|
|
|
|
enforce(hash, 'Hash is required.');
|
|
enforce(index != null, 'Index is required.');
|
|
enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.');
|
|
|
|
coin = yield this.node.getCoin(hash, index);
|
|
|
|
if (!coin) {
|
|
res.send(404);
|
|
return;
|
|
}
|
|
|
|
res.send(200, coin.getJSON(this.network));
|
|
}));
|
|
|
|
// Bulk read UTXOs
|
|
this.post('/coin/address', co(function* (req, res) {
|
|
var valid = req.valid();
|
|
var address = valid.array('addresses');
|
|
var result = [];
|
|
var i, coins, coin;
|
|
|
|
enforce(address, 'Address is required.');
|
|
enforce(!this.chain.options.spv, 'Cannot get coins in SPV mode.');
|
|
|
|
coins = yield this.node.getCoinsByAddress(address);
|
|
|
|
for (i = 0; i < coins.length; i++) {
|
|
coin = coins[i];
|
|
result.push(coin.getJSON(this.network));
|
|
}
|
|
|
|
res.send(200, result);
|
|
}));
|
|
|
|
// TX by hash
|
|
this.get('/tx/:hash', co(function* (req, res) {
|
|
var valid = req.valid();
|
|
var hash = valid.hash('hash');
|
|
var meta, view;
|
|
|
|
enforce(hash, 'Hash is required.');
|
|
enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.');
|
|
|
|
meta = yield this.node.getMeta(hash);
|
|
|
|
if (!meta) {
|
|
res.send(404);
|
|
return;
|
|
}
|
|
|
|
view = yield this.node.getMetaView(meta);
|
|
|
|
res.send(200, meta.getJSON(this.network, view));
|
|
}));
|
|
|
|
// TX by address
|
|
this.get('/tx/address/:address', co(function* (req, res) {
|
|
var valid = req.valid();
|
|
var address = valid.str('address');
|
|
var result = [];
|
|
var i, metas, meta, view;
|
|
|
|
enforce(address, 'Address is required.');
|
|
enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.');
|
|
|
|
metas = yield this.node.getMetaByAddress(address);
|
|
|
|
for (i = 0; i < metas.length; i++) {
|
|
meta = metas[i];
|
|
view = yield this.node.getMetaView(meta);
|
|
result.push(meta.getJSON(this.network, view));
|
|
}
|
|
|
|
res.send(200, result);
|
|
}));
|
|
|
|
// Bulk read TXs
|
|
this.post('/tx/address', co(function* (req, res) {
|
|
var valid = req.valid();
|
|
var address = valid.array('address');
|
|
var result = [];
|
|
var i, metas, meta, view;
|
|
|
|
enforce(address, 'Address is required.');
|
|
enforce(!this.chain.options.spv, 'Cannot get TX in SPV mode.');
|
|
|
|
metas = yield this.node.getMetaByAddress(address);
|
|
|
|
for (i = 0; i < metas.length; i++) {
|
|
meta = metas[i];
|
|
view = yield this.node.getMetaView(meta);
|
|
result.push(meta.getJSON(this.network, view));
|
|
}
|
|
|
|
res.send(200, result);
|
|
}));
|
|
|
|
// Block by hash/height
|
|
this.get('/block/:block', co(function* (req, res) {
|
|
var valid = req.valid();
|
|
var hash = valid.get('block');
|
|
var block, view, height;
|
|
|
|
enforce(typeof hash === 'string', 'Hash or height required.');
|
|
enforce(!this.chain.options.spv, 'Cannot get block in SPV mode.');
|
|
|
|
if (hash.length === 64)
|
|
hash = util.revHex(hash);
|
|
else
|
|
hash = +hash;
|
|
|
|
block = yield this.chain.db.getBlock(hash);
|
|
|
|
if (!block) {
|
|
res.send(404);
|
|
return;
|
|
}
|
|
|
|
view = yield this.chain.db.getBlockView(block);
|
|
|
|
if (!view) {
|
|
res.send(404);
|
|
return;
|
|
}
|
|
|
|
height = yield this.chain.db.getHeight(hash);
|
|
|
|
res.send(200, block.getJSON(this.network, view, height));
|
|
}));
|
|
|
|
// Mempool snapshot
|
|
this.get('/mempool', co(function* (req, res) {
|
|
var result = [];
|
|
var i, hash, hashes;
|
|
|
|
enforce(this.mempool, 'No mempool available.');
|
|
|
|
hashes = this.mempool.getSnapshot();
|
|
|
|
for (i = 0; i < hashes.length; i++) {
|
|
hash = hashes[i];
|
|
result.push(util.revHex(hash));
|
|
}
|
|
|
|
res.send(200, result);
|
|
}));
|
|
|
|
// Broadcast TX
|
|
this.post('/broadcast', co(function* (req, res) {
|
|
var valid = req.valid();
|
|
var tx = valid.buf('tx');
|
|
enforce(tx, 'TX is required.');
|
|
yield this.node.sendTX(tx);
|
|
res.send(200, { success: true });
|
|
}));
|
|
|
|
// Estimate fee
|
|
this.get('/fee', function(req, res) {
|
|
var valid = req.valid();
|
|
var blocks = valid.u32('blocks');
|
|
var fee;
|
|
|
|
if (!this.fees) {
|
|
res.send(200, { rate: Amount.btc(this.network.feeRate) });
|
|
return;
|
|
}
|
|
|
|
fee = this.fees.estimateFee(blocks);
|
|
|
|
res.send(200, { rate: Amount.btc(fee) });
|
|
});
|
|
|
|
// Reset chain
|
|
this.post('/reset', co(function* (req, res) {
|
|
var valid = req.valid();
|
|
var height = valid.u32('height');
|
|
|
|
enforce(height != null, 'Height is required.');
|
|
|
|
yield this.chain.reset(height);
|
|
|
|
res.send(200, { success: true });
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* Initialize websockets.
|
|
* @private
|
|
*/
|
|
|
|
HTTPServer.prototype.initSockets = function initSockets() {
|
|
var self = this;
|
|
|
|
if (!this.io)
|
|
return;
|
|
|
|
this.on('socket', function(socket) {
|
|
self.handleSocket(socket);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Handle new websocket.
|
|
* @private
|
|
* @param {WebSocket} socket
|
|
*/
|
|
|
|
HTTPServer.prototype.handleSocket = function handleSocket(socket) {
|
|
socket.hook('auth', function(args) {
|
|
var valid = new Validator([args]);
|
|
var hash = this.options.apiHash;
|
|
var key = valid.str(0);
|
|
|
|
if (socket.auth)
|
|
throw new Error('Already authed.');
|
|
|
|
if (!this.options.noAuth) {
|
|
if (!crypto.ccmp(hash256(key), hash))
|
|
throw new Error('Bad key.');
|
|
}
|
|
|
|
socket.auth = true;
|
|
|
|
this.logger.info('Successful auth from %s.', socket.remoteAddress);
|
|
this.handleAuth(socket);
|
|
|
|
return null;
|
|
});
|
|
|
|
socket.emit('version', {
|
|
version: pkg.version,
|
|
network: this.network.type
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Handle new auth'd websocket.
|
|
* @private
|
|
* @param {WebSocket} socket
|
|
*/
|
|
|
|
HTTPServer.prototype.handleAuth = function handleAuth(socket) {
|
|
socket.hook('watch chain', function(args) {
|
|
socket.join('chain');
|
|
return null;
|
|
});
|
|
|
|
socket.hook('unwatch chain', function(args) {
|
|
socket.leave('chain');
|
|
return null;
|
|
});
|
|
|
|
socket.hook('watch mempool', function(args) {
|
|
socket.join('mempool');
|
|
return null;
|
|
});
|
|
|
|
socket.hook('unwatch mempool', function(args) {
|
|
socket.leave('mempool');
|
|
return null;
|
|
});
|
|
|
|
socket.hook('set filter', function(args) {
|
|
var valid = new Validator([args]);
|
|
var data = valid.buf(0);
|
|
|
|
if (!data)
|
|
throw new Error('Invalid parameter.');
|
|
|
|
socket.filter = Bloom.fromRaw(data);
|
|
|
|
return null;
|
|
});
|
|
|
|
socket.hook('get tip', function(args) {
|
|
return this.chain.tip.toRaw();
|
|
});
|
|
|
|
socket.hook('get entry', co(function* (args) {
|
|
var valid = new Validator([args]);
|
|
var block = valid.numhash(0);
|
|
var entry;
|
|
|
|
if (block == null)
|
|
throw new Error('Invalid parameter.');
|
|
|
|
entry = yield this.chain.db.getEntry(block);
|
|
|
|
if (!(yield entry.isMainChain()))
|
|
entry = null;
|
|
|
|
if (!entry)
|
|
return null;
|
|
|
|
return entry.toRaw();
|
|
}));
|
|
|
|
socket.hook('add filter', function(args) {
|
|
var valid = new Validator([args]);
|
|
var chunks = valid.array(0);
|
|
var i, data;
|
|
|
|
if (!chunks)
|
|
throw new Error('Invalid parameter.');
|
|
|
|
if (!socket.filter)
|
|
throw new Error('No filter set.');
|
|
|
|
valid = new Validator([chunks]);
|
|
|
|
for (i = 0; i < chunks.length; i++) {
|
|
data = valid.buf(i);
|
|
|
|
if (!data)
|
|
throw new Error('Bad data chunk.');
|
|
|
|
this.filter.add(data);
|
|
|
|
if (this.node.spv)
|
|
this.pool.watch(data);
|
|
}
|
|
|
|
return null;
|
|
});
|
|
|
|
socket.hook('reset filter', function(args) {
|
|
socket.filter = null;
|
|
return null;
|
|
});
|
|
|
|
socket.hook('estimate fee', function(args) {
|
|
var valid = new Validator([args]);
|
|
var blocks = valid.u32(0);
|
|
var rate;
|
|
|
|
if (!this.fees) {
|
|
rate = this.network.feeRate;
|
|
rate = Amount.btc(rate);
|
|
return rate;
|
|
}
|
|
|
|
rate = this.fees.estimateFee(blocks);
|
|
rate = Amount.btc(rate);
|
|
|
|
return rate;
|
|
});
|
|
|
|
socket.hook('send', function(args) {
|
|
var valid = new Validator([args]);
|
|
var data = valid.buf(0);
|
|
var tx;
|
|
|
|
if (!data)
|
|
throw new Error('Invalid parameter.');
|
|
|
|
tx = TX.fromRaw(data);
|
|
|
|
this.node.send(tx);
|
|
|
|
return null;
|
|
});
|
|
|
|
socket.hook('rescan', function(args) {
|
|
var valid = new Validator([args]);
|
|
var start = valid.numhash(0);
|
|
|
|
if (start == null)
|
|
throw new Error('Invalid parameter.');
|
|
|
|
return this.scan(socket, start);
|
|
});
|
|
|
|
this.bindChain();
|
|
};
|
|
|
|
/**
|
|
* Bind to chain events.
|
|
* @private
|
|
*/
|
|
|
|
HTTPServer.prototype.bindChain = function bindChain() {
|
|
var self = this;
|
|
var pool = this.mempool || this.pool;
|
|
|
|
this.chain.on('connect', function(entry, block, view) {
|
|
var list = self.channel('chain');
|
|
var item, socket, raw, txs;
|
|
|
|
if (!list)
|
|
return;
|
|
|
|
raw = entry.toRaw();
|
|
|
|
self.to('chain', 'chain connect', raw);
|
|
|
|
for (item = list.head; item; item = item.next) {
|
|
socket = item.value;
|
|
txs = self.filterBlock(socket, block);
|
|
socket.emit('block connect', raw, txs);
|
|
}
|
|
});
|
|
|
|
this.chain.on('disconnect', function(entry, block, view) {
|
|
var list = self.channel('chain');
|
|
var raw;
|
|
|
|
if (!list)
|
|
return;
|
|
|
|
raw = entry.toRaw();
|
|
|
|
self.to('chain', 'chain disconnect', raw);
|
|
self.to('chain', 'block disconnect', raw);
|
|
});
|
|
|
|
this.chain.on('reset', function(tip) {
|
|
var list = self.channel('chain');
|
|
var raw;
|
|
|
|
if (!list)
|
|
return;
|
|
|
|
raw = tip.toRaw();
|
|
|
|
self.to('chain', 'chain reset', raw);
|
|
});
|
|
|
|
pool.on('tx', function(tx) {
|
|
var list = self.channel('mempool');
|
|
var item, socket, raw;
|
|
|
|
if (!list)
|
|
return;
|
|
|
|
raw = tx.toRaw();
|
|
|
|
for (item = list.head; item; item = item.next) {
|
|
socket = item.value;
|
|
|
|
if (!self.filterTX(socket, tx))
|
|
continue;
|
|
|
|
socket.emit('tx', raw);
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Filter block by socket.
|
|
* @private
|
|
* @param {WebSocket} socket
|
|
* @param {Block} block
|
|
* @returns {TX[]}
|
|
*/
|
|
|
|
HTTPServer.prototype.filterBlock = function filterBlock(socket, block) {
|
|
var txs = [];
|
|
var i, tx;
|
|
|
|
if (!socket.filter)
|
|
return txs;
|
|
|
|
for (i = 0; i < block.txs.length; i++) {
|
|
tx = block.txs[i];
|
|
if (this.filterTX(socket, tx))
|
|
txs.push(tx.toRaw());
|
|
}
|
|
|
|
return txs;
|
|
};
|
|
|
|
/**
|
|
* Filter transaction by socket.
|
|
* @private
|
|
* @param {WebSocket} socket
|
|
* @param {TX} tx
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
HTTPServer.prototype.filterTX = function filterTX(socket, tx) {
|
|
var found = false;
|
|
var i, hash, input, prevout, output;
|
|
|
|
if (!socket.filter)
|
|
return false;
|
|
|
|
for (i = 0; i < tx.outputs.length; i++) {
|
|
output = tx.outputs[i];
|
|
hash = output.getHash();
|
|
|
|
if (!hash)
|
|
continue;
|
|
|
|
if (socket.filter.test(hash)) {
|
|
prevout = Outpoint.fromTX(tx, i);
|
|
socket.filter.add(prevout.toRaw());
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
return true;
|
|
|
|
if (!tx.isCoinbase()) {
|
|
for (i = 0; i < tx.inputs.length; i++) {
|
|
input = tx.inputs[i];
|
|
prevout = input.prevout;
|
|
if (socket.filter.test(prevout.toRaw()))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Scan using a socket's filter.
|
|
* @private
|
|
* @param {WebSocket} socket
|
|
* @param {Hash} start
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
HTTPServer.prototype.scan = co(function* scan(socket, start) {
|
|
var scanner = this.scanner.bind(this, socket);
|
|
yield this.node.scan(start, socket.filter, scanner);
|
|
return null;
|
|
});
|
|
|
|
/**
|
|
* Handle rescan iteration.
|
|
* @private
|
|
* @param {WebSocket} socket
|
|
* @param {ChainEntry} entry
|
|
* @param {TX[]} txs
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
HTTPServer.prototype.scanner = function scanner(socket, entry, txs) {
|
|
var block = entry.toRaw();
|
|
var raw = [];
|
|
var i, tx;
|
|
|
|
for (i = 0; i < txs.length; i++) {
|
|
tx = txs[i];
|
|
raw.push(tx.toRaw());
|
|
}
|
|
|
|
socket.emit('block rescan', block, raw);
|
|
|
|
return Promise.resolve();
|
|
};
|
|
|
|
/**
|
|
* HTTPOptions
|
|
* @alias module:http.HTTPOptions
|
|
* @constructor
|
|
* @param {Object} options
|
|
*/
|
|
|
|
function HTTPOptions(options) {
|
|
if (!(this instanceof HTTPOptions))
|
|
return new HTTPOptions(options);
|
|
|
|
this.network = Network.primary;
|
|
this.logger = null;
|
|
this.node = null;
|
|
this.apiKey = base58.encode(crypto.randomBytes(20));
|
|
this.apiHash = hash256(this.apiKey);
|
|
this.noAuth = false;
|
|
|
|
this.prefix = null;
|
|
this.host = '127.0.0.1';
|
|
this.port = 8080;
|
|
this.ssl = false;
|
|
this.keyFile = null;
|
|
this.certFile = null;
|
|
|
|
this.fromOptions(options);
|
|
}
|
|
|
|
/**
|
|
* Inject properties from object.
|
|
* @private
|
|
* @param {Object} options
|
|
* @returns {HTTPOptions}
|
|
*/
|
|
|
|
HTTPOptions.prototype.fromOptions = function fromOptions(options) {
|
|
assert(options);
|
|
assert(options.node && typeof options.node === 'object',
|
|
'HTTP Server requires a Node.');
|
|
|
|
this.node = options.node;
|
|
this.network = options.node.network;
|
|
this.logger = options.node.logger;
|
|
|
|
this.port = this.network.rpcPort;
|
|
|
|
if (options.logger != null) {
|
|
assert(typeof options.logger === 'object');
|
|
this.logger = options.logger;
|
|
}
|
|
|
|
if (options.apiKey != null) {
|
|
assert(typeof options.apiKey === 'string',
|
|
'API key must be a string.');
|
|
assert(options.apiKey.length <= 200,
|
|
'API key must be under 200 bytes.');
|
|
this.apiKey = options.apiKey;
|
|
this.apiHash = hash256(this.apiKey);
|
|
}
|
|
|
|
if (options.noAuth != null) {
|
|
assert(typeof options.noAuth === 'boolean');
|
|
this.noAuth = options.noAuth;
|
|
}
|
|
|
|
if (options.prefix != null) {
|
|
assert(typeof options.prefix === 'string');
|
|
this.prefix = options.prefix;
|
|
this.keyFile = this.prefix + '/key.pem';
|
|
this.certFile = this.prefix + '/cert.pem';
|
|
}
|
|
|
|
if (options.host != null) {
|
|
assert(typeof options.host === 'string');
|
|
this.host = options.host;
|
|
}
|
|
|
|
if (options.port != null) {
|
|
assert(typeof options.port === 'number', 'Port must be a number.');
|
|
assert(options.port > 0 && options.port <= 0xffff);
|
|
this.port = options.port;
|
|
}
|
|
|
|
if (options.ssl != null) {
|
|
assert(typeof options.ssl === 'boolean');
|
|
this.ssl = options.ssl;
|
|
}
|
|
|
|
if (options.keyFile != null) {
|
|
assert(typeof options.keyFile === 'string');
|
|
this.keyFile = options.keyFile;
|
|
}
|
|
|
|
if (options.certFile != null) {
|
|
assert(typeof options.certFile === 'string');
|
|
this.certFile = options.certFile;
|
|
}
|
|
|
|
// Allow no-auth implicitly
|
|
// if we're listening locally.
|
|
if (!options.apiKey) {
|
|
if (this.host === '127.0.0.1' || this.host === '::1')
|
|
this.noAuth = true;
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Instantiate http options from object.
|
|
* @param {Object} options
|
|
* @returns {HTTPOptions}
|
|
*/
|
|
|
|
HTTPOptions.fromOptions = function fromOptions(options) {
|
|
return new HTTPOptions().fromOptions(options);
|
|
};
|
|
|
|
/*
|
|
* Helpers
|
|
*/
|
|
|
|
function hash256(data) {
|
|
if (typeof data !== 'string')
|
|
return new Buffer(0);
|
|
|
|
if (data.length > 200)
|
|
return new Buffer(0);
|
|
|
|
return crypto.hash256(new Buffer(data, 'utf8'));
|
|
}
|
|
|
|
function enforce(value, msg) {
|
|
var err;
|
|
|
|
if (!value) {
|
|
err = new Error(msg);
|
|
err.statusCode = 400;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = HTTPServer;
|